css-conflict-inspector
Advanced tools
Comparing version 0.1.0 to 0.1.1
@@ -1,22 +0,2 @@ | ||
export interface CssInspectorOptions { | ||
universalPenalty?: number; | ||
elementPenalty?: number; | ||
customElementPenalty?: number; | ||
simpleClassPenalty?: number; | ||
simplerClassPenalty?: number; | ||
simplestClassPenalty?: number; | ||
idPenalty?: number; | ||
attributePenalty?: number; | ||
} | ||
export interface CssConflict { | ||
penalty: number; | ||
message: string; | ||
} | ||
export declare function analyzeCss(content: string, options?: CssInspectorOptions): { | ||
selectors: string[]; | ||
conflicts: CssConflict[]; | ||
warnings: import("lightningcss").Warning[]; | ||
dependencies: void | import("lightningcss").Dependency[]; | ||
totalPenalty: number; | ||
score: number; | ||
}; | ||
export * from './analyze'; | ||
export type * from './types'; |
285
lib/index.js
"use strict"; | ||
var __assign = (this && this.__assign) || function () { | ||
__assign = Object.assign || function(t) { | ||
for (var s, i = 1, n = arguments.length; i < n; i++) { | ||
s = arguments[i]; | ||
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) | ||
t[p] = s[p]; | ||
} | ||
return t; | ||
}; | ||
return __assign.apply(this, arguments); | ||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
var desc = Object.getOwnPropertyDescriptor(m, k); | ||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { | ||
desc = { enumerable: true, get: function() { return m[k]; } }; | ||
} | ||
Object.defineProperty(o, k2, desc); | ||
}) : (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
o[k2] = m[k]; | ||
})); | ||
var __exportStar = (this && this.__exportStar) || function(m, exports) { | ||
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.analyzeCss = void 0; | ||
var lightningcss_1 = require("lightningcss"); | ||
function getPenalty(options, name, defaultValue) { | ||
if (name in options) { | ||
var value = options[name]; | ||
if (typeof value === 'number') { | ||
return value; | ||
} | ||
} | ||
return defaultValue; | ||
} | ||
function inspect(selectors, violations, options, scale) { | ||
if (scale === void 0) { scale = 1; } | ||
selectors.forEach(function (sel) { | ||
switch (sel.type) { | ||
case 'combinator': { | ||
// e.g., ">" | ||
switch (sel.value) { | ||
case 'descendant': | ||
scale *= 0.4; | ||
break; | ||
case 'child': | ||
scale *= 0.2; | ||
break; | ||
case 'next-sibling': | ||
case 'later-sibling': | ||
scale *= 0.1; | ||
break; | ||
} | ||
break; | ||
} | ||
case 'universal': { | ||
// e.g., "*" | ||
var penalty = getPenalty(options, 'universalPenalty', 50) * scale; | ||
if (penalty) { | ||
violations.push({ | ||
message: 'Detected use of an universal selector ("*")', | ||
penalty: penalty, | ||
}); | ||
} | ||
break; | ||
} | ||
case 'type': { | ||
// e.g., "p" | ||
var elementPenalty = getPenalty(options, 'elementPenalty', 20) * scale; | ||
var customElementPenalty = getPenalty(options, 'customElementPenalty', 10) * scale; | ||
var isCustomElement = sel.name.includes('-'); | ||
if (isCustomElement && customElementPenalty) { | ||
violations.push({ | ||
message: "Detected use of a type selector (custom element \"".concat(sel.name, "\")"), | ||
penalty: customElementPenalty, | ||
}); | ||
} | ||
else if (!isCustomElement && elementPenalty) { | ||
violations.push({ | ||
message: "Detected use of a type selector (element \"".concat(sel.name, "\")"), | ||
penalty: elementPenalty, | ||
}); | ||
} | ||
break; | ||
} | ||
case 'class': { | ||
// e.g., ".foo" | ||
var numHyphens = sel.name.replace(/[^\_\-]/g, '').length; | ||
var simplePenalty = getPenalty(options, 'simpleClassPenalty', 5) * scale; | ||
var simplerPenalty = getPenalty(options, 'simplerClassPenalty', 3) * scale; | ||
var simplestPenalty = getPenalty(options, 'simplestClassPenalty', 2) * scale; | ||
if (numHyphens < 1 && sel.name.length < 8 && simplePenalty) { | ||
violations.push({ | ||
message: "Detected use of a simple class selector (\"".concat(sel.name, "\")"), | ||
penalty: simplePenalty, | ||
}); | ||
} | ||
else if (numHyphens < 1 && sel.name.length < 20 && simplerPenalty) { | ||
violations.push({ | ||
message: "Detected use of an almost simple class selector (\"".concat(sel.name, "\")"), | ||
penalty: simplerPenalty, | ||
}); | ||
} | ||
else if (numHyphens < 2 && sel.name.length < 10 && simplestPenalty) { | ||
violations.push({ | ||
message: "Detected use of an almost simple class selector (\"".concat(sel.name, "\")"), | ||
penalty: simplestPenalty, | ||
}); | ||
} | ||
else { | ||
scale = 0; | ||
} | ||
break; | ||
} | ||
case 'id': { | ||
// e.g., "#foo" | ||
var penalty = getPenalty(options, 'idPenalty', 0) * scale; | ||
if (penalty) { | ||
violations.push({ | ||
message: "Detected use of an ID selector (\"".concat(sel.name, "\")"), | ||
penalty: penalty, | ||
}); | ||
} | ||
break; | ||
} | ||
case 'attribute': { | ||
// e.g., "hidden" | ||
var penalty = getPenalty(options, 'attributePenalty', 0) * scale; | ||
if (penalty) { | ||
violations.push({ | ||
message: "Detected use of an attribute selector (\"".concat(sel.name, "\")"), | ||
penalty: penalty, | ||
}); | ||
} | ||
break; | ||
} | ||
case 'pseudo-class': { | ||
// e.g., ":where" | ||
if (sel.kind === 'not') { | ||
inspect(sel.selectors, violations, options, 0.5); | ||
} | ||
else if (sel.kind === 'where') { | ||
inspect(sel.selectors, violations, options, 0.5); | ||
} | ||
else if (sel.kind === 'has') { | ||
inspect(sel.selectors, violations, options, 0.2); | ||
} | ||
else if (sel.kind === 'is') { | ||
inspect(sel.selectors, violations, options, 0.1); | ||
} | ||
else { | ||
// | ||
} | ||
break; | ||
} | ||
case 'pseudo-element': { | ||
break; | ||
} | ||
default: { | ||
if (Array.isArray(sel)) { | ||
inspect(sel, violations, options); | ||
} | ||
else { | ||
console.log('Got unknown type', sel.type, sel); | ||
} | ||
break; | ||
} | ||
} | ||
}); | ||
} | ||
function serializeOperation(op) { | ||
if (op) { | ||
var mapping = { | ||
equal: '=', | ||
includes: '~=', | ||
'dash-match': '|=', | ||
prefix: '^=', | ||
substring: '*=', | ||
suffix: '$=', | ||
}; | ||
var o = mapping[op.operator]; | ||
return "".concat(o).concat(op.value); | ||
} | ||
return ''; | ||
} | ||
function serializeNamespace(ns) { | ||
if (ns) { | ||
if (ns.type === 'specific') { | ||
return "".concat(ns.prefix, "\\:"); | ||
} | ||
else if (ns.type === 'any') { | ||
return '*\\:'; | ||
} | ||
} | ||
return ''; | ||
} | ||
function stringify(selectors) { | ||
return selectors | ||
.map(function (sel) { | ||
switch (sel.type) { | ||
case 'combinator': | ||
// e.g., ">" | ||
switch (sel.value) { | ||
case 'descendant': | ||
return ' '; | ||
case 'child': | ||
return '>'; | ||
case 'next-sibling': | ||
return '+'; | ||
case 'later-sibling': | ||
return '~'; | ||
} | ||
return ''; | ||
case 'universal': | ||
// e.g., "*" | ||
return '*'; | ||
case 'type': | ||
// e.g., "p" | ||
return sel.name; | ||
case 'class': | ||
// e.g., ".foo" | ||
return ".".concat(sel.name); | ||
case 'id': | ||
// e.g., "#foo" | ||
return "#".concat(sel.name); | ||
case 'attribute': | ||
// e.g., "hidden" | ||
return "[".concat(serializeNamespace(sel.namespace)).concat(sel.name).concat(serializeOperation(sel.operation), "]"); | ||
case 'pseudo-class': | ||
// e.g., ":where" | ||
if (Array.isArray(sel.selectors)) { | ||
var inner = stringify(sel.selectors); | ||
return ":".concat(sel.kind, "(").concat(inner, ")"); | ||
} | ||
return ":".concat(sel.kind); | ||
case 'pseudo-element': | ||
return "::".concat(sel.kind); | ||
default: { | ||
if (Array.isArray(sel)) { | ||
return stringify(sel); | ||
} | ||
return ''; | ||
} | ||
} | ||
}) | ||
.join(''); | ||
} | ||
function analyzeCss(content, options) { | ||
if (options === void 0) { options = {}; } | ||
var conflicts = []; | ||
var selectors = []; | ||
var result = (0, lightningcss_1.transform)({ | ||
code: Buffer.from(content, 'utf8'), | ||
filename: 'style.css', | ||
analyzeDependencies: true, | ||
errorRecovery: true, | ||
visitor: { | ||
Selector: function (selector) { | ||
var violations = []; | ||
inspect(selector, violations, options); | ||
var conflict = violations | ||
.filter(function (v) { return v.penalty; }) | ||
.reduce(function (p, c) { | ||
if (p && p.penalty < c.penalty) { | ||
return __assign(__assign({}, c), { penalty: p.penalty }); | ||
} | ||
return c; | ||
}, undefined); | ||
if (conflict) { | ||
conflicts.push(conflict); | ||
} | ||
selectors.push(stringify(selector)); | ||
}, | ||
}, | ||
}); | ||
return { | ||
selectors: selectors, | ||
conflicts: conflicts, | ||
warnings: result.warnings, | ||
dependencies: result.dependencies, | ||
totalPenalty: Math.ceil(conflicts.reduce(function (p, c) { return p + c.penalty; }, 0)), | ||
score: 100 - Math.ceil(conflicts.reduce(function (p, c) { return Math.max(p, c.penalty); }, 0)), | ||
}; | ||
} | ||
exports.analyzeCss = analyzeCss; | ||
__exportStar(require("./analyze"), exports); | ||
//# sourceMappingURL=index.js.map |
{ | ||
"name": "css-conflict-inspector", | ||
"version": "0.1.0", | ||
"version": "0.1.1", | ||
"description": "Find potential conflict in your CSS files - to avoid surprises when used in the context of Micro Frontends.", | ||
@@ -15,2 +15,7 @@ "main": "lib/index.js", | ||
}, | ||
"files": [ | ||
"esm", | ||
"lib", | ||
"src" | ||
], | ||
"repository": { | ||
@@ -17,0 +22,0 @@ "type": "git", |
297
src/index.ts
@@ -1,295 +0,2 @@ | ||
import { transform, Selector, AttrOperation, NamespaceConstraint } from 'lightningcss'; | ||
export interface CssInspectorOptions { | ||
universalPenalty?: number; | ||
elementPenalty?: number; | ||
customElementPenalty?: number; | ||
simpleClassPenalty?: number; | ||
simplerClassPenalty?: number; | ||
simplestClassPenalty?: number; | ||
idPenalty?: number; | ||
attributePenalty?: number; | ||
} | ||
export interface CssConflict { | ||
penalty: number; | ||
message: string; | ||
} | ||
function getPenalty(options: CssInspectorOptions, name: keyof CssInspectorOptions, defaultValue: number) { | ||
if (name in options) { | ||
const value = options[name]; | ||
if (typeof value === 'number') { | ||
return value; | ||
} | ||
} | ||
return defaultValue; | ||
} | ||
function inspect(selectors: Selector | Array<Selector>, violations: Array<CssConflict>, options: CssInspectorOptions, scale = 1) { | ||
selectors.forEach((sel) => { | ||
switch (sel.type) { | ||
case 'combinator': { | ||
// e.g., ">" | ||
switch (sel.value) { | ||
case 'descendant': | ||
scale *= 0.4; | ||
break; | ||
case 'child': | ||
scale *= 0.2; | ||
break; | ||
case 'next-sibling': | ||
case 'later-sibling': | ||
scale *= 0.1; | ||
break; | ||
} | ||
break; | ||
} | ||
case 'universal': { | ||
// e.g., "*" | ||
const penalty = getPenalty(options, 'universalPenalty', 50) * scale; | ||
if (penalty) { | ||
violations.push({ | ||
message: 'Detected use of an universal selector ("*")', | ||
penalty, | ||
}); | ||
} | ||
break; | ||
} | ||
case 'type': { | ||
// e.g., "p" | ||
const elementPenalty = getPenalty(options, 'elementPenalty', 20) * scale; | ||
const customElementPenalty = getPenalty(options, 'customElementPenalty', 10) * scale; | ||
const isCustomElement = sel.name.includes('-'); | ||
if (isCustomElement && customElementPenalty) { | ||
violations.push({ | ||
message: `Detected use of a type selector (custom element "${sel.name}")`, | ||
penalty: customElementPenalty, | ||
}); | ||
} else if (!isCustomElement && elementPenalty) { | ||
violations.push({ | ||
message: `Detected use of a type selector (element "${sel.name}")`, | ||
penalty: elementPenalty, | ||
}); | ||
} | ||
break; | ||
} | ||
case 'class': { | ||
// e.g., ".foo" | ||
const numHyphens = sel.name.replace(/[^\_\-]/g, '').length; | ||
const simplePenalty = getPenalty(options, 'simpleClassPenalty', 5) * scale; | ||
const simplerPenalty = getPenalty(options, 'simplerClassPenalty', 3) * scale; | ||
const simplestPenalty = getPenalty(options, 'simplestClassPenalty', 2) * scale; | ||
if (numHyphens < 1 && sel.name.length < 8 && simplePenalty) { | ||
violations.push({ | ||
message: `Detected use of a simple class selector ("${sel.name}")`, | ||
penalty: simplePenalty, | ||
}); | ||
} else if (numHyphens < 1 && sel.name.length < 20 && simplerPenalty) { | ||
violations.push({ | ||
message: `Detected use of an almost simple class selector ("${sel.name}")`, | ||
penalty: simplerPenalty, | ||
}); | ||
} else if (numHyphens < 2 && sel.name.length < 10 && simplestPenalty) { | ||
violations.push({ | ||
message: `Detected use of an almost simple class selector ("${sel.name}")`, | ||
penalty: simplestPenalty, | ||
}); | ||
} else { | ||
scale = 0; | ||
} | ||
break; | ||
} | ||
case 'id': { | ||
// e.g., "#foo" | ||
const penalty = getPenalty(options, 'idPenalty', 0) * scale; | ||
if (penalty) { | ||
violations.push({ | ||
message: `Detected use of an ID selector ("${sel.name}")`, | ||
penalty, | ||
}); | ||
} | ||
break; | ||
} | ||
case 'attribute': { | ||
// e.g., "hidden" | ||
const penalty = getPenalty(options, 'attributePenalty', 0) * scale; | ||
if (penalty) { | ||
violations.push({ | ||
message: `Detected use of an attribute selector ("${sel.name}")`, | ||
penalty, | ||
}); | ||
} | ||
break; | ||
} | ||
case 'pseudo-class': { | ||
// e.g., ":where" | ||
if (sel.kind === 'not') { | ||
inspect(sel.selectors, violations, options, 0.5); | ||
} else if (sel.kind === 'where') { | ||
inspect(sel.selectors, violations, options, 0.5); | ||
} else if (sel.kind === 'has') { | ||
inspect(sel.selectors, violations, options, 0.2); | ||
} else if (sel.kind === 'is') { | ||
inspect(sel.selectors, violations, options, 0.1); | ||
} else { | ||
// | ||
} | ||
break; | ||
} | ||
case 'pseudo-element': { | ||
break; | ||
} | ||
default: { | ||
if (Array.isArray(sel)) { | ||
inspect(sel, violations, options); | ||
} else { | ||
console.log('Got unknown type', sel.type, sel); | ||
} | ||
break; | ||
} | ||
} | ||
}); | ||
} | ||
function serializeOperation(op: AttrOperation | undefined) { | ||
if (op) { | ||
const mapping = { | ||
equal: '=', | ||
includes: '~=', | ||
'dash-match': '|=', | ||
prefix: '^=', | ||
substring: '*=', | ||
suffix: '$=', | ||
}; | ||
const o = mapping[op.operator]; | ||
return `${o}${op.value}`; | ||
} | ||
return ''; | ||
} | ||
function serializeNamespace(ns: NamespaceConstraint | undefined) { | ||
if (ns) { | ||
if (ns.type === 'specific') { | ||
return `${ns.prefix}\\:`; | ||
} else if (ns.type === 'any') { | ||
return '*\\:'; | ||
} | ||
} | ||
return ''; | ||
} | ||
function stringify(selectors: Selector | Array<Selector>) { | ||
return selectors | ||
.map((sel) => { | ||
switch (sel.type) { | ||
case 'combinator': | ||
// e.g., ">" | ||
switch (sel.value) { | ||
case 'descendant': | ||
return ' '; | ||
case 'child': | ||
return '>'; | ||
case 'next-sibling': | ||
return '+'; | ||
case 'later-sibling': | ||
return '~'; | ||
} | ||
return ''; | ||
case 'universal': | ||
// e.g., "*" | ||
return '*'; | ||
case 'type': | ||
// e.g., "p" | ||
return sel.name; | ||
case 'class': | ||
// e.g., ".foo" | ||
return `.${sel.name}`; | ||
case 'id': | ||
// e.g., "#foo" | ||
return `#${sel.name}`; | ||
case 'attribute': | ||
// e.g., "hidden" | ||
return `[${serializeNamespace(sel.namespace)}${sel.name}${serializeOperation(sel.operation)}]`; | ||
case 'pseudo-class': | ||
// e.g., ":where" | ||
if (Array.isArray(sel.selectors)) { | ||
const inner = stringify(sel.selectors); | ||
return `:${sel.kind}(${inner})`; | ||
} | ||
return `:${sel.kind}`; | ||
case 'pseudo-element': | ||
return `::${sel.kind}`; | ||
default: { | ||
if (Array.isArray(sel)) { | ||
return stringify(sel); | ||
} | ||
return ''; | ||
} | ||
} | ||
}) | ||
.join(''); | ||
} | ||
export function analyzeCss(content: string, options: CssInspectorOptions = {}) { | ||
const conflicts: Array<CssConflict> = []; | ||
const selectors: Array<string> = []; | ||
const result = transform({ | ||
code: Buffer.from(content, 'utf8'), | ||
filename: 'style.css', | ||
analyzeDependencies: true, | ||
errorRecovery: true, | ||
visitor: { | ||
Selector(selector) { | ||
const violations: Array<CssConflict> = []; | ||
inspect(selector, violations, options); | ||
const conflict = violations | ||
.filter((v) => v.penalty) | ||
.reduce((p, c) => { | ||
if (p && p.penalty < c.penalty) { | ||
return { | ||
...c, | ||
penalty: p.penalty, | ||
}; | ||
} | ||
return c; | ||
}, undefined as CssConflict | undefined); | ||
if (conflict) { | ||
conflicts.push(conflict); | ||
} | ||
selectors.push(stringify(selector)); | ||
}, | ||
}, | ||
}); | ||
return { | ||
selectors, | ||
conflicts, | ||
warnings: result.warnings, | ||
dependencies: result.dependencies, | ||
totalPenalty: Math.ceil(conflicts.reduce((p, c) => p + c.penalty, 0)), | ||
score: 100 - Math.ceil(conflicts.reduce((p, c) => Math.max(p, c.penalty), 0)), | ||
}; | ||
} | ||
export * from './analyze'; | ||
export type * from './types'; |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
51125
38
921
1