Socket
Socket
Sign inDemoInstall

analyze-css

Package Overview
Dependencies
Maintainers
1
Versions
193
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

analyze-css - npm Package Compare versions

Comparing version 1.1.2 to 2.0.0

lib/css-analyzer.d.ts

34

lib/collection.js

@@ -6,7 +6,16 @@ /**

function collection() {
/**
* @class
*/
function Collection() {
this.items = {};
}
collection.prototype = {
Collection.prototype = {
/**
* Pushes a given item to the collection and counts each occurrence
*
* @param {string} item
* @return {void}
*/
push: function (item) {

@@ -22,2 +31,7 @@ if (typeof this.items[item] === "undefined") {

/**
* Sorts collected items in desending order by their occurrences
*
* @return {Collection}
*/
sort: function () {

@@ -43,2 +57,10 @@ var newItems = {},

/**
* Runs provided callback for each item in the collection.
*
* Item and the count is provided to the callback.
*
* @param {forEachCallback} callback
*
*/
forEach: function (callback) {

@@ -51,2 +73,8 @@ Object.keys(this.items).forEach(function (key) {

module.exports = collection;
/**
* @callback forEachCallback
* @param {string} item
* @param {number} count
*/
module.exports = Collection;

415

lib/index.js

@@ -6,379 +6,88 @@ /**

var cssParser = require("css").parse,
debug = require("debug")("analyze-css"),
fs = require("fs"),
const debug = require("debug")("analyze-css"),
path = require("path"),
preprocessors = new (require("./preprocessors"))(),
slickParse = require("slick").parse,
VERSION = require("./../package").version;
function analyzer(css, options, callback) {
var res;
function error(msg, code) {
var err = new Error(msg);
err.code = code;
return err;
}
// Promise-based public endpoint
function analyze(css, options) {
// options can be omitted
if (typeof options === "function") {
callback = options;
options = {};
}
options = options || {};
this.options = options;
debug("opts: %j", this.options);
debug("opts: %j", options);
if (typeof css !== "string") {
callback(
this.error(
"css parameter passed is not a string!",
analyzer.EXIT_CSS_PASSED_IS_NOT_STRING
),
null
);
return;
}
// preprocess the CSS (issue #3)
if (typeof options.preprocessor === "string") {
debug('Using "%s" preprocessor', options.preprocessor);
var preprocessor = preprocessors.get(options.preprocessor);
try {
css = preprocessor.process(css, options);
} catch (ex) {
throw new Error("Preprocessing failed: " + ex);
return new Promise((resolve, reject) => {
if (typeof css !== "string") {
reject(
error(
"css parameter passed is not a string!",
analyze.EXIT_CSS_PASSED_IS_NOT_STRING
)
);
return;
}
debug("Preprocessing completed");
}
// preprocess the CSS (issue #3)
if (typeof options.preprocessor === "string") {
debug('Using "%s" preprocessor', options.preprocessor);
res = this.analyze(css);
var preprocessor = preprocessors.get(options.preprocessor);
// error handling
if (res !== true) {
callback(res, null);
return;
}
try {
css = preprocessor.process(css, options);
} catch (ex) {
throw new Error("Preprocessing failed: " + ex);
}
// return the results
res = {
generator: "analyze-css v" + VERSION,
metrics: this.metrics,
};
// disable offenders output if requested (issue #64)
if (options.noOffenders !== true) {
res.offenders = this.offenders;
}
callback(null, res);
}
analyzer.version = VERSION;
// @see https://github.com/macbre/phantomas/issues/664
analyzer.path = path.normalize(__dirname + "/..");
analyzer.pathBin = analyzer.path + "/bin/analyze-css.js";
// exit codes
analyzer.EXIT_NEED_OPTIONS = 2;
analyzer.EXIT_PARSING_FAILED = 251;
analyzer.EXIT_EMPTY_CSS = 252;
analyzer.EXIT_CSS_PASSED_IS_NOT_STRING = 253;
analyzer.EXIT_URL_LOADING_FAILED = 254;
analyzer.EXIT_FILE_LOADING_FAILED = 255;
analyzer.prototype = {
emitter: false,
tree: false,
metrics: {},
offenders: {},
error: function (msg, code) {
var err = new Error(msg);
err.code = code;
return err;
},
// emit given event
emit: function (/* eventName, arg1, arg2, ... */) {
//debug('Event %s emitted', arguments[0]);
this.emitter.emit.apply(this.emitter, arguments);
},
// bind to a given event
on: function (ev, fn) {
this.emitter.on(ev, fn);
},
setMetric: function (name, value) {
value = value || 0;
//debug('setMetric(%s) = %d', name, value);
this.metrics[name] = value;
},
// increements given metric by given number (default is one)
incrMetric: function (name, incr /* =1 */) {
var currVal = this.metrics[name] || 0;
incr = incr || 1;
//debug('incrMetric(%s) += %d', name, incr);
this.setMetric(name, currVal + incr);
},
addOffender: function (metricName, msg, position /* = undefined */) {
if (typeof this.offenders[metricName] === "undefined") {
this.offenders[metricName] = [];
debug("Preprocessing completed");
}
this.offenders[metricName].push({
message: msg,
position: position || this.currentPosition,
});
},
const CSSAnalyzer = require("./css-analyzer");
const instance = new CSSAnalyzer(options);
const res = instance.analyze(css);
setCurrentPosition: function (position) {
this.currentPosition = position;
},
initRules: function () {
var debug = require("debug")("analyze-css:rules"),
re = /\.js$/,
rules = [];
// init events emitter
this.emitter = new (require("events").EventEmitter)();
this.emitter.setMaxListeners(200);
// load all rules
rules = fs
.readdirSync(fs.realpathSync(__dirname + "/../rules/"))
// filter out all non *.js files
.filter(function (file) {
return re.test(file);
})
// remove file extensions to get just names
.map(function (file) {
return file.replace(re, "");
});
debug("Rules to be loaded: %s", rules.join(", "));
rules.forEach(function (name) {
var rule = require("./../rules/" + name);
rule(this);
debug('"%s" loaded: %s', name, rule.description);
}, this);
},
fixCss: function (css) {
// properly handle ; in @import URLs
// see https://github.com/macbre/analyze-css/pull/322
// see https://github.com/reworkcss/css/issues/137
return css.replace(/@import url([^)]+["'])/, (match) => {
return match.replace(/;/g, "%3B");
});
},
parseCss: function (css) {
var debug = require("debug")("analyze-css:parser");
debug("Going to parse %s kB of CSS", (css.length / 1024).toFixed(2));
if (css.trim() === "") {
return this.error("Empty CSS was provided", analyzer.EXIT_EMPTY_CSS);
// error handling
if (res instanceof Error) {
debug("Rejecting a promise with an error: " + res);
reject(res);
return;
}
css = this.fixCss(css);
// return the results
let result = {
generator: "analyze-css v" + VERSION,
metrics: instance.metrics,
};
this.tree = cssParser(css, {
// errors are listed in the parsingErrors property instead of being thrown (#84)
silent: true,
});
debug("CSS parsed");
return true;
},
parseRules: function (rules) {
const debug = require("debug")("analyze-css:parseRules");
rules.forEach(function (rule) {
debug("%j", rule);
// store the default current position
//
// it will be used when this.addOffender is called from within the rule
// it can be overridden by providing a "custom" position via a call to this.setCurrentPosition
this.setCurrentPosition(rule.position);
switch (rule.type) {
// {
// "type":"media"
// "media":"screen and (min-width: 1370px)",
// "rules":[{"type":"rule","selectors":["#foo"],"declarations":[]}]
// }
case "media":
this.emit("media", rule.media, rule.rules);
// now run recursively to parse rules within the media query
/* istanbul ignore else */
if (rule.rules) {
this.parseRules(rule.rules);
}
this.emit("mediaEnd", rule.media, rule.rules);
break;
// {
// "type":"rule",
// "selectors":[".ui-header .ui-btn-up-a",".ui-header .ui-btn-hover-a"],
// "declarations":[{"type":"declaration","property":"border","value":"0"},{"type":"declaration","property":"background","value":"none"}]
// }
case "rule":
if (!rule.selectors || !rule.declarations) {
return;
}
this.emit("rule", rule);
// analyze each selector and declaration
rule.selectors.forEach(function (selector) {
var parsedSelector,
expressions = [],
i,
len;
// "#features > div:first-child" will become two expressions:
// {"combinator":" ","tag":"*","id":"features"}
// {"combinator":">","tag":"div","pseudos":[{"key":"first-child","value":null}]}
parsedSelector = slickParse(selector)[0];
if (typeof parsedSelector === "undefined") {
var positionDump =
"Rule position start @ " +
rule.position.start.line +
":" +
rule.position.start.column +
", end @ " +
rule.position.end.line +
":" +
rule.position.end.column;
throw this.error(
'Unable to parse "' + selector + '" selector. ' + positionDump,
analyzer.EXIT_PARSING_FAILED
);
}
// convert object with keys to array with numeric index
for (i = 0, len = parsedSelector.length; i < len; i++) {
expressions.push(parsedSelector[i]);
}
this.emit("selector", rule, selector, expressions);
expressions.forEach(function (expression) {
this.emit("expression", selector, expression);
}, this);
}, this);
rule.declarations.forEach(function (declaration) {
this.setCurrentPosition(declaration.position);
switch (declaration.type) {
case "declaration":
this.emit(
"declaration",
rule,
declaration.property,
declaration.value
);
break;
case "comment":
this.emit("comment", declaration.comment);
break;
}
}, this);
break;
// {"type":"comment","comment":" Cached as static-css-r518-9b0f5ab4632defb55d67a1d672aa31bd120f4414 "}
case "comment":
this.emit("comment", rule.comment);
break;
// {"type":"font-face","declarations":[{"type":"declaration","property":"font-family","value":"myFont"...
case "font-face":
this.emit("font-face", rule);
break;
// {"type":"import","import":"url('/css/styles.css')"}
case "import":
// replace encoded semicolon back into ;
// https://github.com/macbre/analyze-css/pull/322
this.emit("import", rule.import.replace(/%3B/g, ";"));
break;
}
}, this);
},
run: function () {
var stylesheet = this.tree && this.tree.stylesheet,
rules = stylesheet && stylesheet.rules;
this.emit("stylesheet", stylesheet);
// check for parsing errors (#84)
stylesheet.parsingErrors.forEach(function (err) {
debug("error: %j", err);
var pos = {
line: err.line,
column: err.column,
};
this.setCurrentPosition({
start: pos,
end: pos,
});
this.emit("error", err);
}, this);
this.parseRules(rules);
},
analyze: function (css) {
var res,
then = Date.now();
this.metrics = {};
this.offenders = {};
// load and init all rules
this.initRules();
// parse CSS
res = this.parseCss(css);
if (res !== true) {
return res;
// disable offenders output if requested (issue #64)
if (options.noOffenders !== true) {
result.offenders = instance.offenders;
}
this.emit("css", css);
debug("Promise resolved");
resolve(result);
});
}
// now go through parsed CSS tree and emit events for rules
try {
this.run();
} catch (ex) {
return ex;
}
analyze.version = VERSION;
this.emit("report");
// @see https://github.com/macbre/phantomas/issues/664
analyze.path = path.normalize(__dirname + "/..");
analyze.pathBin = analyze.path + "/bin/analyze-css.js";
debug("Completed in %d ms", Date.now() - then);
return true;
},
};
// exit codes
analyze.EXIT_NEED_OPTIONS = 2;
analyze.EXIT_PARSING_FAILED = 251;
analyze.EXIT_EMPTY_CSS = 252;
analyze.EXIT_CSS_PASSED_IS_NOT_STRING = 253;
analyze.EXIT_URL_LOADING_FAILED = 254;
analyze.EXIT_FILE_LOADING_FAILED = 255;
module.exports = analyzer;
module.exports = analyze;

@@ -82,3 +82,5 @@ /**

function analyze(css) {
new analyzer(css, analyzerOpts, callback);
analyzer(css, analyzerOpts)
.then((res) => callback(null, res))
.catch((err) => callback(err, null));
}

@@ -85,0 +87,0 @@

{
"name": "analyze-css",
"version": "1.1.2",
"version": "2.0.0",
"author": "Maciej Brencz <maciej.brencz@gmail.com> (https://github.com/macbre)",
"description": "CSS selectors complexity and performance analyzer",
"main": "./lib/index.js",
"types": "./lib/index.d.ts",
"repository": {

@@ -20,3 +21,3 @@ "type": "git",

"engines": {
"node": ">=10.0"
"node": ">=14.0"
},

@@ -28,2 +29,3 @@ "dependencies": {

"css-shorthand-properties": "^1.1.1",
"css-what": "^5.0.1",
"debug": "^4.1.1",

@@ -35,8 +37,9 @@ "fast-stats": "0.0.6",

"onecolor": "^3.1.0",
"slick": "~1.12.1",
"specificity": "^0.4.1"
},
"devDependencies": {
"@types/css": "0.0.33",
"autoprefixer": "^10.2.4",
"browserslist": "^4.11.1",
"check-dts": "^0.5.5",
"eslint": "^7.12.1",

@@ -47,3 +50,3 @@ "eslint-config-prettier": "8.3.0",

"nyc": "^15.1.0",
"postcss": "^8.2.6",
"postcss": "^8.3.6",
"prettier": "2.3.2"

@@ -50,0 +53,0 @@ },

@@ -18,6 +18,12 @@ analyze-css

```sh
$ npm install --global analyze-css
```
npm install --global analyze-css
```
or to install from GitHub's repository:
```
npm install --global @macbre/analyze-css
```
## Usage

@@ -53,9 +59,13 @@

```
npm i --save analyze-css
```
```js
var analyzer = require('analyze-css');
const analyze = require('analyze-css');
new analyzer('.foo {margin: 0 !important}', function(err, results) {
console.error(err);
(async() => {
const results = await analyze('.foo {margin: 0 !important}');
console.log(results); // example? see below
});
})();
```

@@ -65,10 +75,10 @@

// options can be provided
var opts = {
const opts = {
'noOffenders': true
};
new analyzer('.foo {margin: 0 !important}', opts, function(err, results) {
console.error(err);
(async() => {
const results = await analyze('.foo {margin: 0 !important}', opts);
console.log(results); // example? see below
});
})();
```

@@ -75,0 +85,0 @@

@@ -6,2 +6,5 @@ "use strict";

/**
* @param { import("../lib/css-analyzer") } analyzer
*/
function rule(analyzer) {

@@ -8,0 +11,0 @@ // @see http://stackoverflow.com/a/11335500

"use strict";
/**
* @typedef { import("css-what").AttributeSelector[] } AttributeSelectors
*/
/**
* @param { AttributeSelectors } expressions
* @returns { number }
*/
function getBodyIndex(expressions) {
let idx = 0;
// body.foo h1 -> 0
// .foo body -> 1
// html.css body -> 1
for (let i = 0; i < expressions.length; i++) {
switch (expressions[i].type) {
case "tag":
if (expressions[i].name === "body") {
return idx;
}
break;
case "child":
case "descendant":
idx++;
}
}
return -1;
}
/**
* @param { AttributeSelectors } expressions
* @returns {boolean}
*/
function firstSelectorHasClass(expressions) {
// remove any non-class selectors
return expressions[0].type === "tag"
? // h1.foo
expressions[1].type === "attribute" && expressions[1].name === "class"
: // .foo
expressions[0].type === "attribute" && expressions[0].name === "class";
}
/**
* @param { AttributeSelectors } expressions
* @returns {number}
*/
function getDescendantCombinatorIndex(expressions) {
// body > .foo
// {"type":"child"}
return expressions
.filter((item) => {
return !["tag", "attribute", "pseudo"].includes(item.type);
})
.map((item) => {
return item.type;
})
.indexOf("child");
}
/**
* @param { import("../lib/css-analyzer") } analyzer
*/
function rule(analyzer) {
const debug = require("debug")("analyze-css:bodySelectors");
analyzer.setMetric("redundantBodySelectors");
analyzer.on("selector", function (rule, selector, expressions) {
var noExpressions = expressions.length;
analyzer.on("selector", function (_, selector, expressions) {
const noExpressions = expressions.length;

@@ -14,15 +80,29 @@ // check more complex selectors only

var firstTag = expressions[0].tag,
firstHasClass = !!expressions[0].classList,
isDescendantCombinator = expressions[1].combinator === ">",
isShortExpression = noExpressions === 2,
isRedundant = true; // always expect the worst ;)
const firstTag = expressions[0].type === "tag" && expressions[0].name;
const firstHasClass = firstSelectorHasClass(expressions);
const isDescendantCombinator =
getDescendantCombinatorIndex(expressions) === 0;
// there only a single descendant / child selector
// e.g. "body > foo" or "html h1"
const isShortExpression =
expressions.filter((item) => {
return ["child", "descendant"].includes(item.type);
}).length === 1;
let isRedundant = true; // always expect the worst ;)
// first, let's find the body tag selector in the expression
var bodyIndex = expressions
.map(function (item) {
return item.tag;
})
.indexOf("body");
const bodyIndex = getBodyIndex(expressions);
debug("selector: %s %j", selector, {
firstTag,
firstHasClass,
isDescendantCombinator,
isShortExpression,
bodyIndex,
});
// body selector not found - skip the rules that follow

@@ -34,3 +114,11 @@ if (bodyIndex < 0) {

// matches "html > body"
// {"type":"tag","name":"html","namespace":null}
// {"type":"child"}
// {"type":"tag","name":"body","namespace":null}
//
// matches "html.modal-popup-mode body" (issue #44)
// {"type":"tag","name":"html","namespace":null}
// {"type":"attribute","name":"class","action":"element","value":"modal-popup-mode","namespace":null,"ignoreCase":false}
// {"type":"descendant"}
// {"type":"tag","name":"body","namespace":null}
if (

@@ -58,2 +146,4 @@ firstTag === "html" &&

if (isRedundant) {
debug("selector %s - is redundant", selector);
analyzer.incrMetric("redundantBodySelectors");

@@ -60,0 +150,0 @@ analyzer.addOffender("redundantBodySelectors", selector);

"use strict";
/**
* @param { import("css-what").Selector[] } expressions
* @returns { number }
*/
function getExpressionsLength(expressions) {
// body -> 1
// ul li -> 2
// ol:lang(or) li -> 2
// .class + foo a -> 3
return (
expressions.filter((item) => {
return ["child", "descendant", "adjacent"].includes(item.type);
}).length + 1
);
}
/**
* Report redundant child selectors, e.g.:
*
* ul li
* ul > li
* table > tr
* tr td
*
* @param { import("../lib/css-analyzer") } analyzer
*/
function rule(analyzer) {

@@ -9,3 +35,3 @@ // definition of redundant child nodes selectors (see #51 for the initial idea):

// table th
var redundantChildSelectors = {
const redundantChildSelectors = {
ul: ["li"],

@@ -20,53 +46,73 @@ ol: ["li"],

analyzer.on("selector", function (rule, selector, expressions) {
var noExpressions = expressions.length;
analyzer.on("selector", (_, selector, expressions) => {
// there only a single descendant / child selector
// e.g. "body > foo" or "html h1"
//
// check more complex selectors only
if (noExpressions < 2) {
if (getExpressionsLength(expressions) < 3) {
return;
}
// converts "ul#foo > li.test" selector into ['ul', 'li'] list
var selectorNodeNames = expressions.map(function (item) {
return item.tag;
});
Object.keys(redundantChildSelectors).forEach((tagName) => {
// find the tagName in our selector
const tagInSelectorIndex = expressions
.map((expr) => expr.type == "tag" && expr.name)
.indexOf(tagName);
Object.keys(redundantChildSelectors).forEach(function (nodeName) {
var nodeIndex = selectorNodeNames.indexOf(nodeName),
nextNode,
curExpression,
combinator,
redundantNodes = redundantChildSelectors[nodeName];
// tag not found in the selector
if (tagInSelectorIndex < 0) {
return;
}
if (nodeIndex > -1 && nodeIndex < noExpressions - 1) {
// skip cases like the following: "article > ul li"
if (expressions[nodeIndex].combinator !== " ") {
return;
}
// converts "ul#foo > li.test" selector into [{tag: 'ul'}, {combinator:'child'}, {tag: 'li'}] list
const selectorNodeNames = expressions
.filter((expr) =>
[
"tag",
"descendant" /* */,
"child" /* > */,
"adjacent" /* + */,
].includes(expr.type)
)
.map((expr) =>
expr.name ? { tag: expr.name } : { combinator: expr.type }
);
// we've found the possible offender, get the next node in the selector
// and compare it against rules in redundantChildSelectors
nextNode = selectorNodeNames[nodeIndex + 1];
// console.log(selector, expressions, selectorNodeNames);
if (redundantNodes.indexOf(nextNode) > -1) {
// skip selectors that match:
// - by attributes - foo[class*=bar]
// - by pseudo attributes - foo:lang(fo)
curExpression = expressions[nodeIndex];
const tagIndex = selectorNodeNames
.map((item) => item.tag)
.indexOf(tagName);
if (curExpression.pseudos || curExpression.attributes) {
return;
}
const nextTagInSelector = selectorNodeNames[tagIndex + 2]?.tag;
const nextCombinator = selectorNodeNames[tagIndex + 1]?.combinator;
const previousCombinator = selectorNodeNames[tagIndex - 1]?.combinator;
// only the following combinator can match:
// ul li
// ul > li
combinator = expressions[nodeIndex + 1].combinator;
// our tag is not followed by the tag listed in redundantChildSelectors
const followedByRedundantTag =
redundantChildSelectors[tagName].includes(nextTagInSelector);
if (!followedByRedundantTag) {
return;
}
if (combinator === " " || combinator === ">") {
analyzer.incrMetric("redundantChildNodesSelectors");
analyzer.addOffender("redundantChildNodesSelectors", selector);
}
}
// ignore cases like "article > ul li"
if (previousCombinator === "child") {
return;
}
// console.log(
// tagName, {selector, expressions}, selectorNodeNames,
// {tagIndex, prreviousTagInSelector, previousCombinator, nextTagInSelector, nextCombinator, followedByRedundantTag}
// );
// only the following combinator can match:
// ul li
// ul > li
if (
followedByRedundantTag &&
["descendant", "child"].includes(nextCombinator)
) {
analyzer.incrMetric("redundantChildNodesSelectors");
analyzer.addOffender("redundantChildNodesSelectors", selector);
}
});

@@ -73,0 +119,0 @@ });

@@ -18,2 +18,5 @@ "use strict";

/**
* @param { import("../lib/css-analyzer") } analyzer
*/
function rule(analyzer) {

@@ -20,0 +23,0 @@ // store unique colors with the counter

"use strict";
var format = require("util").format,
const format = require("util").format,
MAX_LENGTH = 256;
/**
* @param { import("../lib/css-analyzer") } analyzer
*/
function rule(analyzer) {

@@ -7,0 +10,0 @@ analyzer.setMetric("comments");

@@ -5,2 +5,5 @@ "use strict";

/**
* @param { import("../lib/css-analyzer") } analyzer
*/
function rule(analyzer) {

@@ -7,0 +10,0 @@ analyzer.setMetric("complexSelectors");

"use strict";
var collection = require("../lib/collection"),
const Collection = require("../lib/collection"),
debug = require("debug")("analyze-css:duplicated"),
format = require("util").format;
/**
* @param { import("../lib/css-analyzer") } analyzer
*/
function rule(analyzer) {
var selectors = new collection(),
var selectors = new Collection(),
mediaQueryStack = [],

@@ -100,3 +103,3 @@ browserPrefixRegEx = /^-(moz|o|webkit|ms)-/;

selectors.sort().forEach(function (selector, cnt) {
selectors.sort().forEach((selector, cnt) => {
if (cnt > 1) {

@@ -103,0 +106,0 @@ analyzer.incrMetric("duplicatedSelectors");

"use strict";
/**
* @param { import("../lib/css-analyzer") } analyzer
*/
function rule(analyzer) {

@@ -4,0 +7,0 @@ analyzer.setMetric("emptyRules");

@@ -5,2 +5,5 @@ "use strict";

/**
* @param { import("../lib/css-analyzer") } analyzer
*/
function rule(analyzer) {

@@ -7,0 +10,0 @@ var re = /^expression/i;

@@ -9,2 +9,3 @@ "use strict";

* @see http://browserhacks.com/
* @param { import("../lib/css-analyzer") } analyzer
*/

@@ -11,0 +12,0 @@ function rule(analyzer) {

"use strict";
/**
* @param { import("../lib/css-analyzer") } analyzer
*/
function rule(analyzer) {

@@ -4,0 +7,0 @@ analyzer.setMetric("imports");

"use strict";
var format = require("util").format;
const format = require("util").format;
/**
* @param { import("../lib/css-analyzer") } analyzer
*/
function rule(analyzer) {

@@ -6,0 +9,0 @@ analyzer.setMetric("importants");

"use strict";
/**
* @param { import("../lib/css-analyzer") } analyzer
*/
function rule(analyzer) {

@@ -4,0 +7,0 @@ analyzer.on("css", function (css) {

@@ -5,2 +5,5 @@ "use strict";

/**
* @param { import("../lib/css-analyzer") } analyzer
*/
function rule(analyzer) {

@@ -7,0 +10,0 @@ analyzer.setMetric("mediaQueries");

@@ -5,2 +5,4 @@ "use strict";

* Detect not minified CSS
*
* @param { import("../lib/css-analyzer") } analyzer
*/

@@ -12,2 +14,5 @@ function rule(analyzer) {

* A simple CSS minification detector
*
* @param {string} css
* @return {boolean}
*/

@@ -22,3 +27,3 @@ function isMinified(css) {

analyzer.on("css", function (css) {
analyzer.on("css", (css) => {
analyzer.setMetric("notMinified", isMinified(css) ? 0 : 1);

@@ -25,0 +30,0 @@ });

"use strict";
/**
* @param { import("../lib/css-analyzer") } analyzer
*/
function rule(analyzer) {
analyzer.setMetric("multiClassesSelectors");
analyzer.on("expression", function (selector, expression) {
if (expression.classList && expression.classList.length > 1) {
analyzer.on("selector", (_, selector, expressions) => {
const expressionsWithClass = expressions.filter(
(expr) => expr.name === "class"
);
// console.log(selector, expressions, {expressionsWithClass});
if (expressionsWithClass.length > 1) {
analyzer.incrMetric("multiClassesSelectors");
analyzer.addOffender(
"multiClassesSelectors",
"." + expression.classList.join(".")
"." + expressionsWithClass.map((expr) => expr.value).join(".")
);

@@ -13,0 +22,0 @@ }

"use strict";
/**
* @param { import("../lib/css-analyzer") } analyzer
*/
function rule(analyzer) {
analyzer.setMetric("parsingErrors");
analyzer.on("error", function (err) {
analyzer.on("error", (err) => {
analyzer.incrMetric("parsingErrors");
analyzer.addOffender("parsingErrors", err.reason);
analyzer.addOffender("parsingErrors", err.message);
});

@@ -10,0 +13,0 @@ }

@@ -6,4 +6,7 @@ "use strict";

/**
* @param { import("../lib/css-analyzer") } analyzer
*/
function rule(analyzer) {
var data = require("./prefixes.json"),
var data = require(__dirname + "/prefixes.json"),
prefixes = data.prefixes;

@@ -10,0 +13,0 @@

@@ -10,2 +10,3 @@ "use strict";

* @see http://css-tricks.com/accidental-css-resets/
* @param { import("../lib/css-analyzer") } analyzer
*/

@@ -12,0 +13,0 @@ function rule(analyzer) {

"use strict";
/**
* @param { import("../lib/css-analyzer") } analyzer
*/
function rule(analyzer) {

@@ -7,7 +10,9 @@ analyzer.setMetric("qualifiedSelectors");

// @see https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Writing_efficient_CSS
analyzer.on("expression", function (selector, expression) {
var hasId = expression.id,
hasTag = expression.tag && expression.tag !== "*",
hasClass = expression.classList;
analyzer.on("selector", (_, selector, expressions) => {
var hasId = expressions.some((expr) => expr.name === "id"),
hasTag = expressions.some((expr) => expr.type === "tag"),
hasClass = expressions.some((expr) => expr.name === "class");
// console.log(selector, expressions, {hasId, hasTag, hasClass});
if (

@@ -14,0 +19,0 @@ // tag#id

@@ -7,2 +7,5 @@ "use strict";

/**
* @param { import("../lib/css-analyzer") } analyzer
*/
function rule(analyzer) {

@@ -25,3 +28,3 @@ var types = ["Id", "Class", "Tag"],

/* istanbul ignore if */
if (!selectorSpecificity) {
if (!selectorSpecificity || !selectorSpecificity[0]) {
debug("not counted for %s!", selector);

@@ -28,0 +31,0 @@ return;

"use strict";
/**
* @param { import("../lib/css-analyzer") } analyzer
*/
function rule(analyzer) {
var selectors = 0,
let selectors = 0,
selectorsLength = 0;

@@ -16,18 +19,23 @@

analyzer.on("rule", function () {
analyzer.on("rule", () => {
analyzer.incrMetric("rules");
});
analyzer.on("selector", function (rule, selector, expressions) {
analyzer.on("selector", (_, __, expressions) => {
selectors += 1;
selectorsLength += expressions.length;
selectorsLength +=
expressions.filter((item) => {
return ["child", "descendant"].includes(item.type);
}).length + 1;
});
analyzer.on("declaration", function () {
analyzer.on("declaration", () => {
analyzer.incrMetric("declarations");
});
analyzer.on("expression", function (selector, expression) {
analyzer.on("expression", (selector, expression) => {
// console.log(selector, expression);
// a[href]
if (expression.attributes) {
if (["exists"].includes(expression.action)) {
analyzer.incrMetric("selectorsByAttribute");

@@ -37,8 +45,8 @@ }

// .bar
if (expression.classList) {
if (expression.name === "class") {
analyzer.incrMetric("selectorsByClass");
}
// @foo
if (expression.id) {
// #foo
if (expression.name === "id") {
analyzer.incrMetric("selectorsById");

@@ -48,3 +56,3 @@ }

// a:hover
if (expression.pseudos) {
if (expression.type === "pseudo") {
analyzer.incrMetric("selectorsByPseudo");

@@ -54,3 +62,3 @@ }

// header
if (expression.tag && expression.tag !== "*") {
if (expression.type === "tag") {
analyzer.incrMetric("selectorsByTag");

@@ -60,3 +68,3 @@ }

analyzer.on("report", function () {
analyzer.on("report", () => {
analyzer.setMetric("selectors", selectors);

@@ -63,0 +71,0 @@ analyzer.setMetric("selectorLengthAvg", selectorsLength / selectors);

Sorry, the diff of this file is too big to display

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc