Socket
Socket
Sign inDemoInstall

analyze-css

Package Overview
Dependencies
3
Maintainers
1
Versions
188
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 0.12.12 to 0.12.13

125

bin/analyze-css.js

@@ -8,24 +8,36 @@ #!/usr/bin/env node

*/
'use strict';
"use strict";
const { program } = require('commander');
const { program } = require("commander");
var analyzer = require('./../lib/index'),
debug = require('debug')('analyze-css:bin'),
runner = require('./../lib/runner'),
runnerOpts = {};
var analyzer = require("./../lib/index"),
debug = require("debug")("analyze-css:bin"),
runner = require("./../lib/runner"),
runnerOpts = {};
// parse options
program
.version(analyzer.version)
.usage('--url <url> [options]')
// https://www.npmjs.com/package/commander#common-option-types-boolean-and-value
.option('--url <url>', 'Set URL of CSS to analyze')
.option('--file <file>', 'Set local CSS file to analyze')
.option('--ignore-ssl-errors', 'Ignores SSL errors, such as expired or self-signed certificate errors')
.option('-p, --pretty', 'Causes JSON with the results to be pretty-printed')
.option('-N, --no-offenders', 'Show only the metrics without the offenders part')
.option('--auth-user <user>', 'Sets the user name used for HTTP authentication')
.option('--auth-pass <pass>', 'Sets the password used for HTTP authentication')
.option('-x, --proxy <proxy>', 'Sets the HTTP proxy');
.version(analyzer.version)
.usage("--url <url> [options]")
// https://www.npmjs.com/package/commander#common-option-types-boolean-and-value
.option("--url <url>", "Set URL of CSS to analyze")
.option("--file <file>", "Set local CSS file to analyze")
.option(
"--ignore-ssl-errors",
"Ignores SSL errors, such as expired or self-signed certificate errors"
)
.option("-p, --pretty", "Causes JSON with the results to be pretty-printed")
.option(
"-N, --no-offenders",
"Show only the metrics without the offenders part"
)
.option(
"--auth-user <user>",
"Sets the user name used for HTTP authentication"
)
.option(
"--auth-pass <pass>",
"Sets the password used for HTTP authentication"
)
.option("-x, --proxy <proxy>", "Sets the HTTP proxy");

@@ -35,61 +47,68 @@ // parse it

debug('analyze-css v%s', analyzer.version);
debug("analyze-css v%s", analyzer.version);
// support stdin (issue #28)
if (process.argv.indexOf('-') > -1) {
runnerOpts.stdin = true;
if (process.argv.indexOf("-") > -1) {
runnerOpts.stdin = true;
}
// --url
else if (program.url) {
runnerOpts.url = program.url;
runnerOpts.url = program.url;
}
// --file
else if (program.file) {
runnerOpts.file = program.file;
runnerOpts.file = program.file;
}
// either --url or --file or - (stdin) needs to be provided
else {
console.log(program.helpInformation());
process.exit(analyzer.EXIT_NEED_OPTIONS);
console.log(program.helpInformation());
process.exitCode = analyzer.EXIT_NEED_OPTIONS;
return;
}
runnerOpts.ignoreSslErrors = program['ignore-ssl-errors'];
runnerOpts.ignoreSslErrors = program["ignore-ssl-errors"];
runnerOpts.noOffenders = program.offenders === false;
runnerOpts.authUser = program['auth-user'];
runnerOpts.authPass = program['auth-pass'];
runnerOpts.authUser = program["auth-user"];
runnerOpts.authPass = program["auth-pass"];
runnerOpts.proxy = program.proxy;
debug('opts: %j', runnerOpts);
debug("opts: %j", runnerOpts);
// run the analyzer
runner(runnerOpts, function(err, res) {
var output, exitCode;
runner(runnerOpts, function (err, res) {
var output, exitCode;
// emit an error and die
if (err) {
exitCode = err.code || 1;
debug('Exiting with exit code #%d', exitCode);
// emit an error and die
if (err) {
exitCode = err.code || 1;
debug("Exiting with exit code #%d", exitCode);
console.error(err.toString());
process.exit(exitCode);
}
console.error(err.toString());
process.exitCode = exitCode;
return;
}
// make offenders flat (and append position if possible - issue #25)
if (typeof res.offenders !== 'undefined') {
Object.keys(res.offenders).forEach(function(metricName) {
res.offenders[metricName] = res.offenders[metricName].map(function(offender) {
var position = offender.position && offender.position.start;
return offender.message + (position ? ' @ ' + position.line + ':' + position.column : '');
});
});
}
// make offenders flat (and append position if possible - issue #25)
if (typeof res.offenders !== "undefined") {
Object.keys(res.offenders).forEach(function (metricName) {
res.offenders[metricName] = res.offenders[metricName].map(function (
offender
) {
var position = offender.position && offender.position.start;
return (
offender.message +
(position ? " @ " + position.line + ":" + position.column : "")
);
});
});
}
// format the results
if (program.pretty === true) {
output = JSON.stringify(res, null, ' ');
} else {
output = JSON.stringify(res);
}
// format the results
if (program.pretty === true) {
output = JSON.stringify(res, null, " ");
} else {
output = JSON.stringify(res);
}
process.stdout.write(output);
process.stdout.write(output);
});
/**
* Push items and count them
*/
'use strict';
"use strict";
function collection() {
this.items = {};
this.items = {};
}
collection.prototype = {
push: function(item) {
if (typeof this.items[item] === 'undefined') {
this.items[item] = {
cnt: 1
};
} else {
this.items[item].cnt++;
}
},
push: function (item) {
if (typeof this.items[item] === "undefined") {
this.items[item] = {
cnt: 1,
};
} else {
this.items[item].cnt++;
}
},
sort: function() {
var newItems = {},
sortedKeys;
sort: function () {
var newItems = {},
sortedKeys;
// sort in descending order (by cnt)
sortedKeys = Object.keys(this.items).sort((function(a, b) {
return this.items[b].cnt - this.items[a].cnt;
}).bind(this));
// sort in descending order (by cnt)
sortedKeys = Object.keys(this.items).sort(
function (a, b) {
return this.items[b].cnt - this.items[a].cnt;
}.bind(this)
);
// build new items dictionary
sortedKeys.forEach(function(key) {
newItems[key] = this.items[key];
}, this);
// build new items dictionary
sortedKeys.forEach(function (key) {
newItems[key] = this.items[key];
}, this);
this.items = newItems;
return this;
},
this.items = newItems;
return this;
},
forEach: function(callback) {
Object.keys(this.items).forEach(function(key) {
callback(key, this.items[key].cnt);
}, this);
}
forEach: function (callback) {
Object.keys(this.items).forEach(function (key) {
callback(key, this.items[key].cnt);
}, this);
},
};
module.exports = collection;
/**
* analyze-css CommonJS module
*/
'use strict';
"use strict";
var cssParser = require('css').parse,
debug = require('debug')('analyze-css'),
fs = require('fs'),
path = require('path'),
preprocessors = new(require('./preprocessors'))(),
slickParse = require('slick').parse,
VERSION = require('./../package').version;
var cssParser = require("css").parse,
debug = require("debug")("analyze-css"),
fs = require("fs"),
path = require("path"),
preprocessors = new (require("./preprocessors"))(),
slickParse = require("slick").parse,
VERSION = require("./../package").version;
function analyzer(css, options, callback) {
var res;
var res;
// options can be omitted
if (typeof options === 'function') {
callback = options;
options = {};
}
// options can be omitted
if (typeof options === "function") {
callback = options;
options = {};
}
this.options = options;
debug('opts: %j', this.options);
this.options = options;
debug("opts: %j", this.options);
if (typeof css !== 'string') {
callback(this.error('css parameter passed is not a string!', analyzer.EXIT_CSS_PASSED_IS_NOT_STRING), null);
return;
}
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);
// preprocess the CSS (issue #3)
if (typeof options.preprocessor === "string") {
debug('Using "%s" preprocessor', options.preprocessor);
var preprocessor = preprocessors.get(options.preprocessor);
var preprocessor = preprocessors.get(options.preprocessor);
try {
css = preprocessor.process(css, options);
} catch (ex) {
throw new Error('Preprocessing failed: ' + ex);
}
try {
css = preprocessor.process(css, options);
} catch (ex) {
throw new Error("Preprocessing failed: " + ex);
}
debug('Preprocessing completed');
}
debug("Preprocessing completed");
}
res = this.analyze(css);
res = this.analyze(css);
// error handling
if (res !== true) {
callback(res, null);
return;
}
// error handling
if (res !== true) {
callback(res, null);
return;
}
// return the results
res = {
generator: 'analyze-css v' + VERSION,
metrics: this.metrics,
};
// 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;
}
// disable offenders output if requested (issue #64)
if (options.noOffenders !== true) {
res.offenders = this.offenders;
}
callback(null, res);
callback(null, res);
}

@@ -71,4 +77,4 @@

// @see https://github.com/macbre/phantomas/issues/664
analyzer.path = path.normalize(__dirname + '/..');
analyzer.pathBin = analyzer.path + '/bin/analyze-css.js';
analyzer.path = path.normalize(__dirname + "/..");
analyzer.pathBin = analyzer.path + "/bin/analyze-css.js";

@@ -84,262 +90,280 @@ // exit codes

analyzer.prototype = {
emitter: false,
tree: false,
emitter: false,
tree: false,
metrics: {},
offenders: {},
metrics: {},
offenders: {},
error: function(msg, code) {
var err = new Error(msg);
err.code = code;
error: function (msg, code) {
var err = new Error(msg);
err.code = code;
return err;
},
return err;
},
// emit given event
emit: function( /* eventName, arg1, arg2, ... */ ) {
//debug('Event %s emitted', arguments[0]);
this.emitter.emit.apply(this.emitter, arguments);
},
// 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);
},
// bind to a given event
on: function (ev, fn) {
this.emitter.on(ev, fn);
},
setMetric: function(name, value) {
value = value || 0;
setMetric: function (name, value) {
value = value || 0;
//debug('setMetric(%s) = %d', name, value);
this.metrics[name] = value;
},
//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;
// 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);
},
//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] = [];
}
addOffender: function (metricName, msg, position /* = undefined */) {
if (typeof this.offenders[metricName] === "undefined") {
this.offenders[metricName] = [];
}
this.offenders[metricName].push({
'message': msg,
'position': position || this.currentPosition
});
},
this.offenders[metricName].push({
message: msg,
position: position || this.currentPosition,
});
},
setCurrentPosition: function(position) {
this.currentPosition = position;
},
setCurrentPosition: function (position) {
this.currentPosition = position;
},
initRules: function() {
var debug = require('debug')('analyze-css:rules'),
re = /\.js$/,
rules = [];
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);
// 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, '');
});
// 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(', '));
debug("Rules to be loaded: %s", rules.join(", "));
rules.forEach(function(name) {
var rule = require('./../rules/' + name);
rule(this);
rules.forEach(function (name) {
var rule = require("./../rules/" + name);
rule(this);
debug('"%s" loaded: %s', name, rule.description || '-');
}, this);
},
debug('"%s" loaded: %s', name, rule.description || "-");
}, this);
},
parseCss: function(css) {
var debug = require('debug')('analyze-css:parser');
debug('Going to parse %s kB of CSS', (css.length / 1024).toFixed(2));
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);
}
if (css.trim() === "") {
return this.error("Empty CSS was provided", analyzer.EXIT_EMPTY_CSS);
}
this.tree = cssParser(css, {
// errors are listed in the parsingErrors property instead of being thrown (#84)
silent: true
});
this.tree = cssParser(css, {
// errors are listed in the parsingErrors property instead of being thrown (#84)
silent: true,
});
debug('CSS parsed');
return true;
},
debug("CSS parsed");
return true;
},
parseRules: function(rules) {
rules.forEach(function(rule, idx) {
debug('%j', rule);
parseRules: function (rules) {
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);
// 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);
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
if (rule.rules) {
this.parseRules(rule.rules);
}
// now run recursively to parse rules within the media query
if (rule.rules) {
this.parseRules(rule.rules);
}
this.emit('mediaEnd', rule.media, rule.rules);
break;
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;
}
// {
// "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);
this.emit("rule", rule);
// analyze each selector and declaration
rule.selectors.forEach(function(selector) {
var parsedSelector,
expressions = [],
i, len;
// 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];
// "#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);
}
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]);
}
// 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);
this.emit("selector", rule, selector, expressions);
expressions.forEach(function(expression) {
this.emit('expression', selector, expression);
}, this);
}, this);
expressions.forEach(function (expression) {
this.emit("expression", selector, expression);
}, this);
}, this);
rule.declarations.forEach(function(declaration) {
this.setCurrentPosition(declaration.position);
rule.declarations.forEach(function (declaration) {
this.setCurrentPosition(declaration.position);
switch (declaration.type) {
case 'declaration':
this.emit('declaration', rule, declaration.property, declaration.value);
break;
switch (declaration.type) {
case "declaration":
this.emit(
"declaration",
rule,
declaration.property,
declaration.value
);
break;
case 'comment':
this.emit('comment', declaration.comment);
break;
}
}, this);
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":"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":"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':
this.emit('import', rule.import);
break;
}
}, this);
},
// {"type":"import","import":"url('/css/styles.css')"}
case "import":
this.emit("import", rule.import);
break;
}
}, this);
},
run: function() {
var stylesheet = this.tree && this.tree.stylesheet,
rules = stylesheet && stylesheet.rules;
run: function () {
var stylesheet = this.tree && this.tree.stylesheet,
rules = stylesheet && stylesheet.rules;
this.emit('stylesheet', stylesheet);
this.emit("stylesheet", stylesheet);
// check for parsing errors (#84)
stylesheet.parsingErrors.forEach(function(err) {
debug('error: %j', err);
// 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
});
var pos = {
line: err.line,
column: err.column,
};
this.setCurrentPosition({
start: pos,
end: pos,
});
this.emit('error', err);
}, this);
this.emit("error", err);
}, this);
this.parseRules(rules);
},
this.parseRules(rules);
},
analyze: function(css) {
var res,
then = Date.now();
analyze: function (css) {
var res,
then = Date.now();
this.metrics = {};
this.offenders = {};
this.metrics = {};
this.offenders = {};
// load and init all rules
this.initRules();
// load and init all rules
this.initRules();
// parse CSS
res = this.parseCss(css);
// parse CSS
res = this.parseCss(css);
if (res !== true) {
return res;
}
if (res !== true) {
return res;
}
this.emit('css', css);
this.emit("css", css);
// now go through parsed CSS tree and emit events for rules
try {
this.run();
} catch (ex) {
return ex;
}
// now go through parsed CSS tree and emit events for rules
try {
this.run();
} catch (ex) {
return ex;
}
this.emit('report');
this.emit("report");
debug('Completed in %d ms', Date.now() - then);
return true;
}
debug("Completed in %d ms", Date.now() - then);
return true;
},
};
module.exports = analyzer;
/**
* A wrapper for preprocessors
*/
'use strict';
"use strict";
var debug = require('debug')('analyze-css:preprocessors'),
glob = require('glob');
var debug = require("debug")("analyze-css:preprocessors"),
glob = require("glob");
var preprocessors = function() {
var preprocessors = function () {};
};
preprocessors.prototype = {
get: function (name) {
return require(__dirname + "/preprocessors/" + name + ".js");
},
get: function(name) {
return require(__dirname + '/preprocessors/' + name + '.js');
},
// return instances of all available preprocessors
getAll: function () {
var files,
res = [];
// return instances of all available preprocessors
getAll: function() {
var files,
res = [];
files = glob.sync(__dirname + "/preprocessors/*.js");
debug("Initializing...");
files = glob.sync(__dirname + '/preprocessors/*.js');
debug('Initializing...');
if (Array.isArray(files)) {
files.forEach(function (file) {
res.push(require(file));
});
}
if (Array.isArray(files)) {
files.forEach(function(file) {
res.push(require(file));
});
}
return res;
},
return res;
},
// get name of matching preprocessor
findMatchingByFileName: function (fileName) {
var matching = false;
// get name of matching preprocessor
findMatchingByFileName: function(fileName) {
var matching = false;
this.getAll().forEach(function (preprocessor) {
if (preprocessor.matchesFileName(fileName)) {
matching = preprocessor.name;
this.getAll().forEach(function(preprocessor) {
if (preprocessor.matchesFileName(fileName)) {
matching = preprocessor.name;
debug('%s matches "%s" preprocessor', fileName, matching);
return false;
}
});
debug('%s matches "%s" preprocessor', fileName, matching);
return false;
}
});
return matching;
}
return matching;
},
};
module.exports = preprocessors;

@@ -6,54 +6,51 @@ /**

*/
'use strict';
"use strict";
var debug = require('debug')('analyze-css:preprocessors:sass');
var debug = require("debug")("analyze-css:preprocessors:sass");
module.exports = {
name: "sass",
matchesFileName: function (fileName) {
return /\.(scss|sass)$/.test(fileName);
},
process: function (css, options) {
var path = require("path"),
sass,
out;
name: 'sass',
matchesFileName: function(fileName) {
return /\.(scss|sass)$/.test(fileName);
},
process: function(css, options) {
var path = require('path'),
sass,
out;
// check the presense of the optional "node-sass" module (#118)
try {
sass = require("node-sass");
} catch (e) {
throw new Error(
"Can't process SASS/SCSS, please run 'npm install node-sass'"
);
}
// check the presense of the optional "node-sass" module (#118)
try {
sass = require('node-sass');
} catch (e) {
throw new Error("Can't process SASS/SCSS, please run 'npm install node-sass'");
}
var includeDir = options.file ? path.dirname(options.file) : undefined;
debug('Using "%s" include path', includeDir);
var includeDir = options.file ? path.dirname(options.file) : undefined;
debug('Using "%s" include path', includeDir);
try {
// 1: try to parse using SCSS syntax (i.e. with brackets)
debug("Parsing using SCSS syntax");
try {
// 1: try to parse using SCSS syntax (i.e. with brackets)
debug('Parsing using SCSS syntax');
out = sass.renderSync({
data: css,
indentedSyntax: false,
includePaths: [includeDir],
});
} catch (e) {
// 2: try to parse using SASS syntax (i.e. with indends) - issue #79
debug("Exception: %s", e.toString().trim());
debug("Parsing using SASS syntax as a fallback");
out = sass.renderSync({
data: css,
indentedSyntax: false,
includePaths: [
includeDir
]
});
} catch (e) {
// 2: try to parse using SASS syntax (i.e. with indends) - issue #79
debug('Exception: %s', e.toString().trim());
debug('Parsing using SASS syntax as a fallback');
out = sass.renderSync({
data: css,
indentedSyntax: true,
includePaths: [includeDir],
});
}
out = sass.renderSync({
data: css,
indentedSyntax: true,
includePaths: [
includeDir
]
});
}
return out.css.toString();
}
return out.css.toString();
},
};

@@ -6,10 +6,10 @@ /**

*/
'use strict';
"use strict";
var cli = require('cli'),
debug = require('debug')('analyze-css:runner'),
fs = require('fs'),
resolve = require('path').resolve,
analyzer = require('./index'),
preprocessors = new(require('./preprocessors'))();
var cli = require("cli"),
debug = require("debug")("analyze-css:runner"),
fs = require("fs"),
resolve = require("path").resolve,
analyzer = require("./index"),
preprocessors = new (require("./preprocessors"))();

@@ -20,13 +20,13 @@ /**

function getUserAgent() {
var format = require('util').format,
version = require('../package').version;
var format = require("util").format,
version = require("../package").version;
return format(
'analyze-css/%s (%s %s, %s %s)',
version,
process.release.name,
process.version,
process.platform,
process.arch
);
return format(
"analyze-css/%s (%s %s, %s %s)",
version,
process.release.name,
process.version,
process.platform,
process.arch
);
}

@@ -40,28 +40,33 @@

function request(requestOptions, callback) {
var debug = require('debug')('analyze-css:http'),
fetch = require('node-fetch');
var debug = require("debug")("analyze-css:http"),
fetch = require("node-fetch");
debug('GET %s', requestOptions.url);
debug('Options: %j', requestOptions);
debug("GET %s", requestOptions.url);
debug("Options: %j", requestOptions);
fetch(requestOptions.url, requestOptions).
then(function(resp) {
debug('HTTP %d %s', resp.status, resp.statusText);
debug('Headers: %j', resp.headers._headers);
fetch(requestOptions.url, requestOptions)
.then(function (resp) {
debug("HTTP %d %s", resp.status, resp.statusText);
debug("Headers: %j", resp.headers._headers);
if (!resp.ok) {
var err = new Error('HTTP request failed: ' + (err ? err.toString() : 'received HTTP ' + resp.status + ' ' + resp.statusText));
callback(err);
} else {
return resp.text(); // a promise
}
}).
then(function(body) {
debug('Received %d bytes of CSS', body.length);
callback(null, body);
}).
catch(function(err) {
debug(err);
callback(err);
});
if (!resp.ok) {
var err = new Error(
"HTTP request failed: " +
(err
? err.toString()
: "received HTTP " + resp.status + " " + resp.statusText)
);
callback(err);
} else {
return resp.text(); // a promise
}
})
.then(function (body) {
debug("Received %d bytes of CSS", body.length);
callback(null, body);
})
.catch(function (err) {
debug(err);
callback(err);
});
}

@@ -73,92 +78,102 @@

function runner(options, callback) {
// call CommonJS module
var analyzerOpts = {
'noOffenders': options.noOffenders,
'preprocessor': false,
};
// call CommonJS module
var analyzerOpts = {
noOffenders: options.noOffenders,
preprocessor: false,
};
function analyze(css) {
new analyzer(css, analyzerOpts, callback);
}
function analyze(css) {
new analyzer(css, analyzerOpts, callback);
}
if (options.url) {
debug('Fetching remote CSS file: %s', options.url);
if (options.url) {
debug("Fetching remote CSS file: %s", options.url);
// @see https://www.npmjs.com/package/node-fetch#options
var agentOptions = {},
requestOptions = {
url: options.url,
headers: {
'User-Agent': getUserAgent()
}
};
// @see https://www.npmjs.com/package/node-fetch#options
var agentOptions = {},
requestOptions = {
url: options.url,
headers: {
"User-Agent": getUserAgent(),
},
};
// handle options
// handle options
// @see https://github.com/bitinn/node-fetch/issues/15
// @see https://nodejs.org/api/https.html#https_https_request_options_callback
if (options.ignoreSslErrors) {
agentOptions.rejectUnauthorized = false;
}
// @see https://github.com/bitinn/node-fetch/issues/15
// @see https://nodejs.org/api/https.html#https_https_request_options_callback
if (options.ignoreSslErrors) {
agentOptions.rejectUnauthorized = false;
}
// @see https://gist.github.com/cojohn/1772154
if (options.authUser && options.authPass) {
requestOptions.headers.Authorization = "Basic " + new Buffer(options.authUser + ":" + options.authPass, "utf8").toString("base64");
}
// @see https://gist.github.com/cojohn/1772154
if (options.authUser && options.authPass) {
requestOptions.headers.Authorization =
"Basic " +
Buffer.from(options.authUser + ":" + options.authPass, "utf8").toString(
"base64"
);
}
// @see https://nodejs.org/api/http.html#http_class_http_agent
var client = require(/^https:/.test(options.url) ? 'https' : 'http');
requestOptions.agent = new client.Agent(agentOptions);
// @see https://nodejs.org/api/http.html#http_class_http_agent
var client = require(/^https:/.test(options.url) ? "https" : "http");
requestOptions.agent = new client.Agent(agentOptions);
// @see http://stackoverflow.com/a/5810547
options.proxy = options.proxy || process.env.HTTP_PROXY;
// @see http://stackoverflow.com/a/5810547
options.proxy = options.proxy || process.env.HTTP_PROXY;
if (options.proxy) {
debug('Using HTTP proxy: %s', options.proxy);
if (options.proxy) {
debug("Using HTTP proxy: %s", options.proxy);
requestOptions.agent = new(require('http-proxy-agent'))(options.proxy);
}
requestOptions.agent = new (require("http-proxy-agent"))(options.proxy);
}
request(requestOptions, function(err, css) {
if (err) {
err.code = analyzer.EXIT_URL_LOADING_FAILED;
request(requestOptions, function (err, css) {
if (err) {
err.code = analyzer.EXIT_URL_LOADING_FAILED;
debug(err);
callback(err);
} else {
analyze(css);
}
});
} else if (options.file) {
// resolve to the full path
options.file = resolve(process.cwd(), options.file);
debug('Loading local CSS file: %s', options.file);
debug(err);
callback(err);
} else {
analyze(css);
}
});
} else if (options.file) {
// resolve to the full path
options.file = resolve(process.cwd(), options.file);
debug("Loading local CSS file: %s", options.file);
fs.readFile(options.file, {
encoding: 'utf-8'
}, function(err, css) {
if (err) {
err = new Error('Loading CSS file failed: ' + err.toString());
err.code = analyzer.EXIT_FILE_LOADING_FAILED;
fs.readFile(
options.file,
{
encoding: "utf-8",
},
function (err, css) {
if (err) {
err = new Error("Loading CSS file failed: " + err.toString());
err.code = analyzer.EXIT_FILE_LOADING_FAILED;
debug(err);
callback(err);
} else {
// find the matching preprocessor and use it
if (analyzerOpts.preprocessor === false) {
analyzerOpts.preprocessor = preprocessors.findMatchingByFileName(options.file);
}
debug(err);
callback(err);
} else {
// find the matching preprocessor and use it
if (analyzerOpts.preprocessor === false) {
analyzerOpts.preprocessor = preprocessors.findMatchingByFileName(
options.file
);
}
// pass the name of the file being analyzed
analyzerOpts.file = options.file;
// pass the name of the file being analyzed
analyzerOpts.file = options.file;
analyze(css);
}
});
} else if (options.stdin) {
debug('Reading from stdin');
cli.withStdin(analyze);
}
analyze(css);
}
}
);
} else if (options.stdin) {
debug("Reading from stdin");
cli.withStdin(analyze);
}
}
module.exports = runner;
{
"name": "analyze-css",
"version": "0.12.12",
"version": "0.12.13",
"author": "Maciej Brencz <maciej.brencz@gmail.com> (https://github.com/macbre)",

@@ -24,7 +24,7 @@ "description": "CSS selectors complexity and performance analyzer",

"cli": "^1.0.1",
"commander": "^6.0.0",
"commander": "^7.0.0",
"css": "^3.0.0",
"css-shorthand-properties": "^1.1.1",
"debug": "^4.1.1",
"fast-stats": "0.0.5",
"fast-stats": "0.0.6",
"glob": "^7.1.6",

@@ -38,17 +38,23 @@ "http-proxy-agent": "^4.0.1",

"devDependencies": {
"autoprefixer-core": "^6.0.1",
"autoprefixer": "^10.2.4",
"browserslist": "^4.11.1",
"js-beautify": "^1.11.0",
"jshint": "^2.11.0",
"eslint": "^7.12.1",
"eslint-config-prettier": "8.0.0",
"eslint-plugin-node": "^11.1.0",
"mocha": "^8.0.1",
"nyc": "^15.1.0"
"nyc": "^15.1.0",
"postcss": "^8.2.6",
"prettier": "2.2.1"
},
"optionalDependencies": {
"node-sass": "5.0.0"
},
"bin": "./bin/analyze-css.js",
"preferGlobal": true,
"scripts": {
"test": "mocha -R spec && npm run lint",
"lint": "jshint --verbose bin/ lib/ rules/ test/",
"beautify": "js-beautify -r rules/*.js test/*.js test/rules/*.js bin/*.js lib/*.js lib/preprocessors/*.js",
"test": "mocha -R spec",
"lint": "eslint .",
"prettier": "npx prettier --write .",
"coverage": "nyc mocha -R spec",
"prefixes": "DEBUG=* node data/prefixes.js"
"prefixes": "npx browserslist@latest --update-db; DEBUG=* node data/prefixes.js"
},

@@ -55,0 +61,0 @@ "jshintConfig": {

@@ -16,5 +16,5 @@ analyze-css

```sh
$ npm install --global analyze-css
```
npm install --global analyze-css
```

@@ -27,7 +27,7 @@ ## Usage

```
analyze-css --file examples/elecena.css
```sh
$ analyze-css --file examples/elecena.css
analyze-css --url http://s3.macbre.net/analyze-css/propertyResets.css
analyze-css --url https://s3.macbre.net/analyze-css/propertyResets.css --ignore-ssl-errors
$ analyze-css --url http://s3.macbre.net/analyze-css/propertyResets.css
$ analyze-css --url https://s3.macbre.net/analyze-css/propertyResets.css --ignore-ssl-errors
```

@@ -37,5 +37,5 @@

```sh
$ echo ".foo {margin: 0 \!important}" | analyze-css -
```
echo ".foo {margin: 0 \!important}" | analyze-css -
```

@@ -71,3 +71,3 @@ This will emit JSON formatted results on ``stdout``. Use ``--pretty`` (or ``-p`` shortcut) option to make the output readable for human beings.

console.log(results); // example? see below
});```
});
```

@@ -79,5 +79,5 @@

```sh
$ npm i grunt-contrib-analyze-css
```
npm i grunt-contrib-analyze-css
```

@@ -192,10 +192,14 @@ It uses configurable threshold and compares the analyze-css result with it.

```sh
$ npm test && npm run-script lint
```
npm test && npm run-script lint
```
Turning on debug mode (i.e. verbose logging to stderr via [debug module](https://npmjs.org/package/debug)):
```sh
$ DEBUG=analyze-css* analyze-css ...
```
DEBUG=analyze-css* analyze-css ...
```
## Stargazers over time
[![Stargazers over time](https://starchart.cc/macbre/analyze-css.svg)](https://starchart.cc/macbre/analyze-css)

@@ -1,32 +0,39 @@

'use strict';
"use strict";
var format = require('util').format,
MAX_LENGTH = 4 * 1024;
var format = require("util").format,
MAX_LENGTH = 4 * 1024;
function rule(analyzer) {
// @see http://stackoverflow.com/a/11335500
var re = /data:.+\/(.+);base64,(.*)\)/;
analyzer.setMetric('base64Length');
// @see http://stackoverflow.com/a/11335500
var re = /data:.+\/(.+);base64,(.*)\)/;
analyzer.setMetric("base64Length");
analyzer.on('declaration', function(rule, property, value) {
var base64,
buf,
matches;
analyzer.on("declaration", function (rule, property, value) {
var base64, buf, matches;
if (re.test(value)) {
// parse data URI
matches = value.match(re);
base64 = matches[2];
buf = new Buffer(base64, 'base64');
if (re.test(value)) {
// parse data URI
matches = value.match(re);
base64 = matches[2];
buf = Buffer.from(base64, "base64");
analyzer.incrMetric('base64Length', base64.length);
analyzer.incrMetric("base64Length", base64.length);
if (base64.length > MAX_LENGTH) {
analyzer.addOffender('base64Length', format('%s { %s: ... } // base64: %s kB, raw: %s kB', rule.selectors.join(', '), property, (base64.length / 1024).toFixed(2), (buf.length / 1024).toFixed(2)));
}
}
});
if (base64.length > MAX_LENGTH) {
analyzer.addOffender(
"base64Length",
format(
"%s { %s: ... } // base64: %s kB, raw: %s kB",
rule.selectors.join(", "),
property,
(base64.length / 1024).toFixed(2),
(buf.length / 1024).toFixed(2)
)
);
}
}
});
}
rule.description = 'Reports on base64-encoded images';
rule.description = "Reports on base64-encoded images";
module.exports = rule;

@@ -1,59 +0,63 @@

'use strict';
"use strict";
function rule(analyzer) {
analyzer.setMetric('redundantBodySelectors');
analyzer.setMetric("redundantBodySelectors");
analyzer.on('selector', function(rule, selector, expressions) {
var noExpressions = expressions.length;
analyzer.on("selector", function (rule, selector, expressions) {
var noExpressions = expressions.length;
// check more complex selectors only
if (noExpressions < 2) {
return;
}
// check more complex selectors only
if (noExpressions < 2) {
return;
}
var firstTag = expressions[0].tag,
firstHasClass = !!expressions[0].classList,
isDescendantCombinator = (expressions[1].combinator === '>'),
isShortExpression = (noExpressions === 2),
isRedundant = true; // always expect the worst ;)
var firstTag = expressions[0].tag,
firstHasClass = !!expressions[0].classList,
isDescendantCombinator = expressions[1].combinator === ">",
isShortExpression = noExpressions === 2,
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');
// first, let's find the body tag selector in the expression
var bodyIndex = expressions
.map(function (item) {
return item.tag;
})
.indexOf("body");
// body selector not found - skip the rules that follow
if (bodyIndex < 0) {
return;
}
// body selector not found - skip the rules that follow
if (bodyIndex < 0) {
return;
}
// matches "html > body"
// matches "html.modal-popup-mode body" (issue #44)
if ((firstTag === 'html') && (bodyIndex === 1) && (isDescendantCombinator || isShortExpression)) {
isRedundant = false;
}
// matches "body > .bar" (issue #82)
else if ((bodyIndex === 0) && isDescendantCombinator) {
isRedundant = false;
}
// matches "body.foo ul li a"
else if ((bodyIndex === 0) && firstHasClass) {
isRedundant = false;
}
// matches ".has-modal > body" (issue #49)
else if (firstHasClass && (bodyIndex === 1) && isDescendantCombinator) {
isRedundant = false;
}
// matches "html > body"
// matches "html.modal-popup-mode body" (issue #44)
if (
firstTag === "html" &&
bodyIndex === 1 &&
(isDescendantCombinator || isShortExpression)
) {
isRedundant = false;
}
// matches "body > .bar" (issue #82)
else if (bodyIndex === 0 && isDescendantCombinator) {
isRedundant = false;
}
// matches "body.foo ul li a"
else if (bodyIndex === 0 && firstHasClass) {
isRedundant = false;
}
// matches ".has-modal > body" (issue #49)
else if (firstHasClass && bodyIndex === 1 && isDescendantCombinator) {
isRedundant = false;
}
// report he redundant body selector
if (isRedundant) {
analyzer.incrMetric('redundantBodySelectors');
analyzer.addOffender('redundantBodySelectors', selector);
}
});
// report he redundant body selector
if (isRedundant) {
analyzer.incrMetric("redundantBodySelectors");
analyzer.addOffender("redundantBodySelectors", selector);
}
});
}
rule.description = 'Reports redundant body selectors';
rule.description = "Reports redundant body selectors";
module.exports = rule;

@@ -1,75 +0,75 @@

'use strict';
"use strict";
function rule(analyzer) {
// definition of redundant child nodes selectors (see #51 for the initial idea):
// ul li
// ol li
// table tr
// table th
var redundantChildSelectors = {
'ul': ['li'],
'ol': ['li'],
'select': ['option'],
'table': ['tr', 'th'], // e.g. table can not be followed by any of tr / th
'tr': ['td', 'th'],
};
// definition of redundant child nodes selectors (see #51 for the initial idea):
// ul li
// ol li
// table tr
// table th
var redundantChildSelectors = {
ul: ["li"],
ol: ["li"],
select: ["option"],
table: ["tr", "th"], // e.g. table can not be followed by any of tr / th
tr: ["td", "th"],
};
analyzer.setMetric('redundantChildNodesSelectors');
analyzer.setMetric("redundantChildNodesSelectors");
analyzer.on('selector', function(rule, selector, expressions) {
var noExpressions = expressions.length;
analyzer.on("selector", function (rule, selector, expressions) {
var noExpressions = expressions.length;
// check more complex selectors only
if (noExpressions < 2) {
return;
}
// check more complex selectors only
if (noExpressions < 2) {
return;
}
// converts "ul#foo > li.test" selector into ['ul', 'li'] list
var selectorNodeNames = expressions.map(function(item) {
return item.tag;
});
// converts "ul#foo > li.test" selector into ['ul', 'li'] list
var selectorNodeNames = expressions.map(function (item) {
return item.tag;
});
Object.keys(redundantChildSelectors).forEach(function(nodeName) {
var nodeIndex = selectorNodeNames.indexOf(nodeName),
nextNode,
curExpression,
combinator,
redundantNodes = redundantChildSelectors[nodeName];
Object.keys(redundantChildSelectors).forEach(function (nodeName) {
var nodeIndex = selectorNodeNames.indexOf(nodeName),
nextNode,
curExpression,
combinator,
redundantNodes = redundantChildSelectors[nodeName];
if ((nodeIndex > -1) && (nodeIndex < noExpressions - 1)) {
// skip cases like the following: "article > ul li"
if (expressions[nodeIndex].combinator !== ' ') {
return;
}
if (nodeIndex > -1 && nodeIndex < noExpressions - 1) {
// skip cases like the following: "article > ul li"
if (expressions[nodeIndex].combinator !== " ") {
return;
}
// we've found the possible offender, get the next node in the selector
// and compare it against rules in redundantChildSelectors
nextNode = selectorNodeNames[nodeIndex + 1];
// we've found the possible offender, get the next node in the selector
// and compare it against rules in redundantChildSelectors
nextNode = selectorNodeNames[nodeIndex + 1];
if (redundantNodes.indexOf(nextNode) > -1) {
// skip selectors that match:
// - by attributes - foo[class*=bar]
// - by pseudo attributes - foo:lang(fo)
curExpression = expressions[nodeIndex];
if (redundantNodes.indexOf(nextNode) > -1) {
// skip selectors that match:
// - by attributes - foo[class*=bar]
// - by pseudo attributes - foo:lang(fo)
curExpression = expressions[nodeIndex];
if (curExpression.pseudos || curExpression.attributes) {
return;
}
if (curExpression.pseudos || curExpression.attributes) {
return;
}
// only the following combinator can match:
// ul li
// ul > li
combinator = expressions[nodeIndex + 1].combinator;
// only the following combinator can match:
// ul li
// ul > li
combinator = expressions[nodeIndex + 1].combinator;
if ((combinator === ' ') || (combinator === '>')) {
analyzer.incrMetric('redundantChildNodesSelectors');
analyzer.addOffender('redundantChildNodesSelectors', selector);
}
}
}
});
});
if (combinator === " " || combinator === ">") {
analyzer.incrMetric("redundantChildNodesSelectors");
analyzer.addOffender("redundantChildNodesSelectors", selector);
}
}
}
});
});
}
rule.description = 'Reports redundant child nodes selectors';
rule.description = "Reports redundant child nodes selectors";
module.exports = rule;

@@ -1,7 +0,7 @@

'use strict';
"use strict";
var collection = require('../lib/collection'),
debug = require('debug')('analyze-css:colors'),
format = require('util').format,
onecolor = require('onecolor');
var collection = require("../lib/collection"),
debug = require("debug")("analyze-css:colors"),
format = require("util").format,
onecolor = require("onecolor");

@@ -11,54 +11,54 @@ /**

*/
var regex = /(((rgba?|hsl)\([^\)]+\))|#(\w{3,6}))/g;
var regex = /(((rgba?|hsl)\([^)]+\))|#(\w{3,6}))/g;
function extractColors(value) {
var matches = value.match(regex);
return matches || false;
var matches = value.match(regex);
return matches || false;
}
function rule(analyzer) {
// store unique colors with the counter
var colors = new collection();
// store unique colors with the counter
var colors = new collection();
analyzer.setMetric('colors');
analyzer.setMetric("colors");
analyzer.on('declaration', function(rule, property, value) {
var extractedColors = extractColors(value);
analyzer.on("declaration", function (rule, property, value) {
var extractedColors = extractColors(value);
if (extractedColors === false) {
return;
}
if (extractedColors === false) {
return;
}
debug('%s: %s -> %j', property, value, extractedColors);
debug("%s: %s -> %j", property, value, extractedColors);
extractedColors.
map(function(item) {
var color = onecolor(item);
extractedColors
.map(function (item) {
var color = onecolor(item);
// handle parsing errors
if (color === false) {
return false;
}
// handle parsing errors
if (color === false) {
return false;
}
// return either rgba(0,0,0,0.25) or #000000
return (color.alpha() < 1.0) ? color.cssa() : color.hex();
}).
forEach(function(color) {
if (color !== false) {
colors.push(color);
}
});
});
// return either rgba(0,0,0,0.25) or #000000
return color.alpha() < 1.0 ? color.cssa() : color.hex();
})
.forEach(function (color) {
if (color !== false) {
colors.push(color);
}
});
});
analyzer.on('report', function() {
analyzer.setCurrentPosition(undefined);
analyzer.on("report", function () {
analyzer.setCurrentPosition(undefined);
colors.sort().forEach(function(color, cnt) {
analyzer.incrMetric('colors');
analyzer.addOffender('colors', format('%s (%d times)', color, cnt));
});
});
colors.sort().forEach(function (color, cnt) {
analyzer.incrMetric("colors");
analyzer.addOffender("colors", format("%s (%d times)", color, cnt));
});
});
}
rule.description = 'Reports number of unique colors used in CSS';
rule.description = "Reports number of unique colors used in CSS";
module.exports = rule;

@@ -65,0 +65,0 @@

@@ -1,22 +0,29 @@

'use strict';
"use strict";
var format = require('util').format,
MAX_LENGTH = 256;
var format = require("util").format,
MAX_LENGTH = 256;
function rule(analyzer) {
analyzer.setMetric('comments');
analyzer.setMetric('commentsLength');
analyzer.setMetric("comments");
analyzer.setMetric("commentsLength");
analyzer.on('comment', function(comment) {
analyzer.incrMetric('comments');
analyzer.incrMetric('commentsLength', comment.length);
analyzer.on("comment", function (comment) {
analyzer.incrMetric("comments");
analyzer.incrMetric("commentsLength", comment.length);
// report too long comments
if (comment.length > MAX_LENGTH) {
analyzer.addOffender('comments', format('"%s" is too long (%d characters)', comment.substr(0, 100), comment.length));
}
});
// report too long comments
if (comment.length > MAX_LENGTH) {
analyzer.addOffender(
"comments",
format(
'"%s" is too long (%d characters)',
comment.substr(0, 100),
comment.length
)
);
}
});
}
rule.description = 'Reports too long CSS comments';
rule.description = "Reports too long CSS comments";
module.exports = rule;

@@ -1,2 +0,2 @@

'use strict';
"use strict";

@@ -6,14 +6,14 @@ var COMPLEX_SELECTOR_THRESHOLD = 3;

function rule(analyzer) {
analyzer.setMetric('complexSelectors');
analyzer.setMetric("complexSelectors");
// #foo .bar ul li a
analyzer.on('selector', function(rule, selector, expressions) {
if (expressions.length > COMPLEX_SELECTOR_THRESHOLD) {
analyzer.incrMetric('complexSelectors');
analyzer.addOffender('complexSelectors', selector);
}
});
// #foo .bar ul li a
analyzer.on("selector", function (rule, selector, expressions) {
if (expressions.length > COMPLEX_SELECTOR_THRESHOLD) {
analyzer.incrMetric("complexSelectors");
analyzer.addOffender("complexSelectors", selector);
}
});
}
rule.description = 'Reports too complex CSS selectors';
rule.description = "Reports too complex CSS selectors";
module.exports = rule;

@@ -1,96 +0,112 @@

'use strict';
"use strict";
var collection = require('../lib/collection'),
debug = require('debug')('analyze-css:duplicated'),
format = require('util').format;
var collection = require("../lib/collection"),
debug = require("debug")("analyze-css:duplicated"),
format = require("util").format;
function rule(analyzer) {
var selectors = new collection(),
mediaQueryStack = [],
browserPrefixRegEx = /^-(moz|o|webkit|ms)-/;
var selectors = new collection(),
mediaQueryStack = [],
browserPrefixRegEx = /^-(moz|o|webkit|ms)-/;
analyzer.setMetric('duplicatedSelectors');
analyzer.setMetric('duplicatedProperties');
analyzer.setMetric("duplicatedSelectors");
analyzer.setMetric("duplicatedProperties");
// handle nested media queries
analyzer.on('media', function(query) {
mediaQueryStack.push(query);
debug('push: %j', mediaQueryStack);
});
// handle nested media queries
analyzer.on("media", function (query) {
mediaQueryStack.push(query);
debug("push: %j", mediaQueryStack);
});
analyzer.on('mediaEnd', function(query) {
mediaQueryStack.pop(query);
debug('pop: %j', mediaQueryStack);
});
analyzer.on("mediaEnd", function (query) {
mediaQueryStack.pop(query);
debug("pop: %j", mediaQueryStack);
});
// register each rule's selectors
analyzer.on('rule', function(rule) {
selectors.push(
// @media foo
(mediaQueryStack.length > 0 ? '@media ' + mediaQueryStack.join(' @media ') + ' ' : '') +
// #foo
rule.selectors.join(', ')
);
});
// register each rule's selectors
analyzer.on("rule", function (rule) {
selectors.push(
// @media foo
(mediaQueryStack.length > 0
? "@media " + mediaQueryStack.join(" @media ") + " "
: "") +
// #foo
rule.selectors.join(", ")
);
});
// find duplicated properties (issue #60)
analyzer.on('rule', function(rule) {
var propertiesHash = {};
// find duplicated properties (issue #60)
analyzer.on("rule", function (rule) {
var propertiesHash = {};
if (rule.declarations) {
rule.declarations.forEach(function(declaration) {
var propertyName;
if (rule.declarations) {
rule.declarations.forEach(function (declaration) {
var propertyName;
if (declaration.type === 'declaration') {
propertyName = declaration.property;
if (declaration.type === "declaration") {
propertyName = declaration.property;
// skip properties that require browser prefixes
// background-image:-moz-linear-gradient(...)
// background-image:-webkit-gradient(...)
if (browserPrefixRegEx.test(declaration.value) === true) {
return;
}
// skip properties that require browser prefixes
// background-image:-moz-linear-gradient(...)
// background-image:-webkit-gradient(...)
if (browserPrefixRegEx.test(declaration.value) === true) {
return;
}
// property was already used in the current selector - report it
if (propertiesHash[propertyName] === true) {
// report the position of the offending property
analyzer.setCurrentPosition(declaration.position);
// property was already used in the current selector - report it
if (propertiesHash[propertyName] === true) {
// report the position of the offending property
analyzer.setCurrentPosition(declaration.position);
analyzer.incrMetric('duplicatedProperties');
analyzer.addOffender('duplicatedProperties', format('%s {%s: %s}', rule.selectors.join(', '), declaration.property, declaration.value));
} else {
// mark given property as defined in the context of the current selector
propertiesHash[propertyName] = true;
}
}
});
}
});
analyzer.incrMetric("duplicatedProperties");
analyzer.addOffender(
"duplicatedProperties",
format(
"%s {%s: %s}",
rule.selectors.join(", "),
declaration.property,
declaration.value
)
);
} else {
// mark given property as defined in the context of the current selector
propertiesHash[propertyName] = true;
}
}
});
}
});
// special handling for @font-face (#52)
// include URL when detecting duplicates
analyzer.on('font-face', function(rule) {
rule.declarations.forEach(function(declaration) {
if (declaration.property === 'src') {
selectors.push('@font-face src: ' + declaration.value);
// special handling for @font-face (#52)
// include URL when detecting duplicates
analyzer.on("font-face", function (rule) {
rule.declarations.forEach(function (declaration) {
if (declaration.property === "src") {
selectors.push("@font-face src: " + declaration.value);
debug('special handling for @font-face, provided src: %s', declaration.value);
return false;
}
});
});
debug(
"special handling for @font-face, provided src: %s",
declaration.value
);
return false;
}
});
});
analyzer.on('report', function() {
analyzer.setCurrentPosition(undefined);
analyzer.on("report", function () {
analyzer.setCurrentPosition(undefined);
selectors.sort().forEach(function(selector, cnt) {
if (cnt > 1) {
analyzer.incrMetric('duplicatedSelectors');
analyzer.addOffender('duplicatedSelectors', format('%s (%d times)', selector, cnt));
}
});
});
selectors.sort().forEach(function (selector, cnt) {
if (cnt > 1) {
analyzer.incrMetric("duplicatedSelectors");
analyzer.addOffender(
"duplicatedSelectors",
format("%s (%d times)", selector, cnt)
);
}
});
});
}
rule.description = 'Reports duplicated CSS selectors and properties';
rule.description = "Reports duplicated CSS selectors and properties";
module.exports = rule;

@@ -1,19 +0,19 @@

'use strict';
"use strict";
function rule(analyzer) {
analyzer.setMetric('emptyRules');
analyzer.setMetric("emptyRules");
analyzer.on('rule', function(rule) {
var properties = rule.declarations.filter(function(item) {
return item.type === 'declaration';
});
analyzer.on("rule", function (rule) {
var properties = rule.declarations.filter(function (item) {
return item.type === "declaration";
});
if (properties.length === 0) {
analyzer.incrMetric('emptyRules');
analyzer.addOffender('emptyRules', rule.selectors.join(', '));
}
});
if (properties.length === 0) {
analyzer.incrMetric("emptyRules");
analyzer.addOffender("emptyRules", rule.selectors.join(", "));
}
});
}
rule.description = 'Total number of empty CSS rules';
rule.description = "Total number of empty CSS rules";
module.exports = rule;

@@ -1,19 +0,22 @@

'use strict';
"use strict";
var format = require('util').format;
var format = require("util").format;
function rule(analyzer) {
var re = /^expression/i;
var re = /^expression/i;
analyzer.setMetric('expressions');
analyzer.setMetric("expressions");
analyzer.on('declaration', function(rule, property, value) {
if (re.test(value)) {
analyzer.incrMetric('expressions');
analyzer.addOffender('expressions', format('%s {%s: %s}', rule.selectors.join(', '), property, value));
}
});
analyzer.on("declaration", function (rule, property, value) {
if (re.test(value)) {
analyzer.incrMetric("expressions");
analyzer.addOffender(
"expressions",
format("%s {%s: %s}", rule.selectors.join(", "), property, value)
);
}
});
}
rule.description = 'Reports CSS expressions';
rule.description = "Reports CSS expressions";
module.exports = rule;

@@ -1,4 +0,4 @@

'use strict';
"use strict";
var format = require('util').format;
var format = require("util").format;

@@ -11,33 +11,37 @@ /**

function rule(analyzer) {
var re = {
property: /^(\*|-ms-filter)/,
selector: /^(\* html|html\s?>\s?body) /,
value: /progid:DXImageTransform\.Microsoft|!ie$/
};
var re = {
property: /^(\*|-ms-filter)/,
selector: /^(\* html|html\s?>\s?body) /,
value: /progid:DXImageTransform\.Microsoft|!ie$/,
};
analyzer.setMetric('oldIEFixes');
analyzer.setMetric("oldIEFixes");
// * html // below IE7 fix
// html>body // IE6 excluded fix
// @see http://blogs.msdn.com/b/ie/archive/2005/09/02/460115.aspx
analyzer.on('selector', function(rule, selector) {
if (re.selector.test(selector)) {
analyzer.incrMetric('oldIEFixes');
analyzer.addOffender('oldIEFixes', selector);
}
});
// * html // below IE7 fix
// html>body // IE6 excluded fix
// @see http://blogs.msdn.com/b/ie/archive/2005/09/02/460115.aspx
analyzer.on("selector", function (rule, selector) {
if (re.selector.test(selector)) {
analyzer.incrMetric("oldIEFixes");
analyzer.addOffender("oldIEFixes", selector);
}
});
// *foo: bar // IE7 and below fix
// -ms-filter // IE9 and below specific property
// !ie // IE 7 and below equivalent of !important
// @see http://www.impressivewebs.com/ie7-ie8-css-hacks/
analyzer.on('declaration', function(rule, property, value) {
if (re.property.test(property) || re.value.test(value)) {
analyzer.incrMetric('oldIEFixes');
analyzer.addOffender('oldIEFixes', format('%s {%s: %s}', rule.selectors.join(', '), property, value));
}
});
// *foo: bar // IE7 and below fix
// -ms-filter // IE9 and below specific property
// !ie // IE 7 and below equivalent of !important
// @see http://www.impressivewebs.com/ie7-ie8-css-hacks/
analyzer.on("declaration", function (rule, property, value) {
if (re.property.test(property) || re.value.test(value)) {
analyzer.incrMetric("oldIEFixes");
analyzer.addOffender(
"oldIEFixes",
format("%s {%s: %s}", rule.selectors.join(", "), property, value)
);
}
});
}
rule.description = 'Reports fixes for old versions of Internet Explorer (IE9 and below)';
rule.description =
"Reports fixes for old versions of Internet Explorer (IE9 and below)";
module.exports = rule;

@@ -1,13 +0,13 @@

'use strict';
"use strict";
function rule(analyzer) {
analyzer.setMetric('imports');
analyzer.setMetric("imports");
analyzer.on('import', function(url) {
analyzer.incrMetric('imports');
analyzer.addOffender('imports', url);
});
analyzer.on("import", function (url) {
analyzer.incrMetric("imports");
analyzer.addOffender("imports", url);
});
}
rule.description = 'Number of @import rules';
rule.description = "Number of @import rules";
module.exports = rule;

@@ -1,17 +0,20 @@

'use strict';
"use strict";
var format = require('util').format;
var format = require("util").format;
function rule(analyzer) {
analyzer.setMetric('importants');
analyzer.setMetric("importants");
analyzer.on('declaration', function(rule, property, value) {
if (value.indexOf('!important') > -1) {
analyzer.incrMetric('importants');
analyzer.addOffender('importants', format('%s {%s: %s}', rule.selectors.join(', '), property, value));
}
});
analyzer.on("declaration", function (rule, property, value) {
if (value.indexOf("!important") > -1) {
analyzer.incrMetric("importants");
analyzer.addOffender(
"importants",
format("%s {%s: %s}", rule.selectors.join(", "), property, value)
);
}
});
}
rule.description = 'Number of properties with value forced by !important';
rule.description = "Number of properties with value forced by !important";
module.exports = rule;

@@ -1,10 +0,10 @@

'use strict';
"use strict";
function rule(analyzer) {
analyzer.on('css', function(css) {
analyzer.setMetric('length', css.length);
});
analyzer.on("css", function (css) {
analyzer.setMetric("length", css.length);
});
}
rule.description = 'Length of CSS file';
rule.description = "Length of CSS file";
module.exports = rule;

@@ -1,15 +0,18 @@

'use strict';
"use strict";
var format = require('util').format;
var format = require("util").format;
function rule(analyzer) {
analyzer.setMetric('mediaQueries');
analyzer.setMetric("mediaQueries");
analyzer.on('media', function(query, rules) {
analyzer.incrMetric('mediaQueries');
analyzer.addOffender('mediaQueries', format('@media %s (%d rules)', query, rules && rules.length || 0));
});
analyzer.on("media", function (query, rules) {
analyzer.incrMetric("mediaQueries");
analyzer.addOffender(
"mediaQueries",
format("@media %s (%d rules)", query, (rules && rules.length) || 0)
);
});
}
rule.description = 'Reports media queries';
rule.description = "Reports media queries";
module.exports = rule;

@@ -1,2 +0,2 @@

'use strict';
"use strict";

@@ -7,21 +7,21 @@ /**

function rule(analyzer) {
analyzer.setMetric('notMinified');
analyzer.setMetric("notMinified");
/**
* A simple CSS minification detector
*/
function isMinified(css) {
// analyze the first 1024 characters
css = css.trim().substring(0, 1024);
/**
* A simple CSS minification detector
*/
function isMinified(css) {
// analyze the first 1024 characters
css = css.trim().substring(0, 1024);
// there should be no newline in minified file
return /\n/.test(css) === false;
}
// there should be no newline in minified file
return /\n/.test(css) === false;
}
analyzer.on('css', function(css) {
analyzer.setMetric('notMinified', isMinified(css) ? 0 : 1);
});
analyzer.on("css", function (css) {
analyzer.setMetric("notMinified", isMinified(css) ? 0 : 1);
});
}
rule.description = 'Reports not minified CSS ';
rule.description = "Reports not minified CSS ";
module.exports = rule;

@@ -1,15 +0,18 @@

'use strict';
"use strict";
function rule(analyzer) {
analyzer.setMetric('multiClassesSelectors');
analyzer.setMetric("multiClassesSelectors");
analyzer.on('expression', function(selector, expression) {
if (expression.classList && expression.classList.length > 1) {
analyzer.incrMetric('multiClassesSelectors');
analyzer.addOffender('multiClassesSelectors', '.' + expression.classList.join('.'));
}
});
analyzer.on("expression", function (selector, expression) {
if (expression.classList && expression.classList.length > 1) {
analyzer.incrMetric("multiClassesSelectors");
analyzer.addOffender(
"multiClassesSelectors",
"." + expression.classList.join(".")
);
}
});
}
rule.description = 'Reports selectors with multiple classes';
rule.description = "Reports selectors with multiple classes";
module.exports = rule;

@@ -1,13 +0,13 @@

'use strict';
"use strict";
function rule(analyzer) {
analyzer.setMetric('parsingErrors');
analyzer.setMetric("parsingErrors");
analyzer.on('error', function(err) {
analyzer.incrMetric('parsingErrors');
analyzer.addOffender('parsingErrors', err.reason);
});
analyzer.on("error", function (err) {
analyzer.incrMetric("parsingErrors");
analyzer.addOffender("parsingErrors", err.reason);
});
}
rule.description = 'CSS parsing errors';
rule.description = "CSS parsing errors";
module.exports = rule;

@@ -1,25 +0,34 @@

'use strict';
"use strict";
var debug = require('debug')('analyze-css:prefixes'),
format = require('util').format;
var debug = require("debug")("analyze-css:prefixes"),
format = require("util").format;
function rule(analyzer) {
var data = require('./prefixes.json'),
prefixes = data.prefixes;
var data = require("./prefixes.json"),
prefixes = data.prefixes;
debug('Using data generated on %s', data.generated);
analyzer.setMetric('oldPropertyPrefixes');
debug("Using data generated on %s", data.generated);
analyzer.setMetric("oldPropertyPrefixes");
analyzer.on('declaration', function(rule, property, value) {
var prefixData = prefixes[property];
analyzer.on("declaration", function (rule, property, value) {
var prefixData = prefixes[property];
// prefix needs to be kept
if (prefixData && !prefixData.keep) {
analyzer.incrMetric('oldPropertyPrefixes');
analyzer.addOffender('oldPropertyPrefixes', format('%s { %s: %s } // %s', rule.selectors.join(', '), property, value, prefixData.msg));
}
});
// prefix needs to be kept
if (prefixData && !prefixData.keep) {
analyzer.incrMetric("oldPropertyPrefixes");
analyzer.addOffender(
"oldPropertyPrefixes",
format(
"%s { %s: %s } // %s",
rule.selectors.join(", "),
property,
value,
prefixData.msg
)
);
}
});
}
rule.description = 'Reports outdated vendor prefixes';
rule.description = "Reports outdated vendor prefixes";
module.exports = rule;

@@ -1,6 +0,5 @@

'use strict';
"use strict";
var debug = require('debug')('analyze-css:propertyResets'),
format = require('util').format,
shorthandProperties = require('css-shorthand-properties');
var format = require("util").format,
shorthandProperties = require("css-shorthand-properties");

@@ -13,49 +12,61 @@ /**

function rule(analyzer) {
var debug = require('debug');
var debug = require("debug");
analyzer.setMetric('propertyResets');
analyzer.setMetric("propertyResets");
analyzer.on('selector', function(rule, selector) {
var declarations = rule.declarations || [],
properties;
analyzer.on("selector", function (rule, selector) {
var declarations = rule.declarations || [],
properties;
// prepare the list of properties used in this selector
properties = declarations.
map(function(declaration) {
return (declaration.type === 'declaration') ? declaration.property : false;
}).
filter(function(item) {
return item !== false;
});
// prepare the list of properties used in this selector
properties = declarations
.map(function (declaration) {
return declaration.type === "declaration"
? declaration.property
: false;
})
.filter(function (item) {
return item !== false;
});
debug('%s: %j', selector, properties);
debug("%s: %j", selector, properties);
// iterate through all properties, expand shorthand properties and
// check if there's no expanded version of it earlier in the array
properties.forEach(function(property, idx) {
var expanded;
// iterate through all properties, expand shorthand properties and
// check if there's no expanded version of it earlier in the array
properties.forEach(function (property, idx) {
var expanded;
// skip if the current property is not the shorthand version
if (typeof shorthandProperties.shorthandProperties[property] === 'undefined') {
return;
}
// skip if the current property is not the shorthand version
if (
typeof shorthandProperties.shorthandProperties[property] === "undefined"
) {
return;
}
// property = 'margin'
// expanded = [ 'margin-top', 'margin-right', 'margin-bottom', 'margin-left' ]
expanded = shorthandProperties.expand(property);
debug('%s: %s', property, expanded.join(', '));
// property = 'margin'
// expanded = [ 'margin-top', 'margin-right', 'margin-bottom', 'margin-left' ]
expanded = shorthandProperties.expand(property);
debug("%s: %s", property, expanded.join(", "));
expanded.forEach(function(expandedProperty) {
var propertyPos = properties.indexOf(expandedProperty);
expanded.forEach(function (expandedProperty) {
var propertyPos = properties.indexOf(expandedProperty);
if (propertyPos > -1 && propertyPos < idx) {
analyzer.incrMetric('propertyResets');
analyzer.addOffender('propertyResets', format('%s: "%s" resets "%s" property set earlier', selector, property, expandedProperty));
}
});
});
});
if (propertyPos > -1 && propertyPos < idx) {
analyzer.incrMetric("propertyResets");
analyzer.addOffender(
"propertyResets",
format(
'%s: "%s" resets "%s" property set earlier',
selector,
property,
expandedProperty
)
);
}
});
});
});
}
rule.description = 'Reports accidental property resets';
rule.description = "Reports accidental property resets";
module.exports = rule;

@@ -1,27 +0,27 @@

'use strict';
"use strict";
function rule(analyzer) {
analyzer.setMetric('qualifiedSelectors');
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;
// @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;
if (
// tag#id
(hasId && hasTag) ||
// .class#id
(hasId && hasClass) ||
// tag.class
(hasClass && hasTag)
) {
analyzer.incrMetric('qualifiedSelectors');
analyzer.addOffender('qualifiedSelectors', selector);
}
});
if (
// tag#id
(hasId && hasTag) ||
// .class#id
(hasId && hasClass) ||
// tag.class
(hasClass && hasTag)
) {
analyzer.incrMetric("qualifiedSelectors");
analyzer.addOffender("qualifiedSelectors", selector);
}
});
}
rule.description = 'Reports qualified selectors';
rule.description = "Reports qualified selectors";
module.exports = rule;

@@ -1,55 +0,57 @@

'use strict';
"use strict";
var debug = require('debug')('analyze-css:specificity'),
specificity = require('specificity'),
stats = require('fast-stats').Stats;
var debug = require("debug")("analyze-css:specificity"),
specificity = require("specificity"),
stats = require("fast-stats").Stats;
function rule(analyzer) {
var types = ['Id', 'Class', 'Tag'],
typesLen = types.length,
values = [];
var types = ["Id", "Class", "Tag"],
values = [];
// prepare metrics and stacks for values
types.forEach(function(type) {
analyzer.setMetric('specificity' + type + 'Avg');
analyzer.setMetric('specificity' + type + 'Total');
// prepare metrics and stacks for values
types.forEach(function (type) {
analyzer.setMetric("specificity" + type + "Avg");
analyzer.setMetric("specificity" + type + "Total");
values.push(new stats());
});
values.push(new stats());
});
analyzer.on('selector', function(rule, selector, expressions) {
var selectorSpecificity = specificity.calculate(selector),
parts;
analyzer.on("selector", function (rule, selector) {
var selectorSpecificity = specificity.calculate(selector),
parts;
if (!selectorSpecificity) {
debug('not counted for %s!', selector);
return;
}
if (!selectorSpecificity) {
debug("not counted for %s!", selector);
return;
}
// parse the results
parts = selectorSpecificity[0].specificity.
split(',').
slice(1).
map(function(i) {
return parseInt(i, 10);
});
// parse the results
parts = selectorSpecificity[0].specificity
.split(",")
.slice(1)
.map(function (i) {
return parseInt(i, 10);
});
debug('%s: %s', selector, parts.join(''));
debug("%s: %s", selector, parts.join(""));
// add each piece to a separate stack
parts.forEach(function(val, idx) {
values[idx].push(val);
});
});
// add each piece to a separate stack
parts.forEach(function (val, idx) {
values[idx].push(val);
});
});
analyzer.on('report', function() {
debug('Gathering stats...');
analyzer.on("report", function () {
debug("Gathering stats...");
types.forEach(function(type, idx) {
analyzer.setMetric('specificity' + type + 'Avg', parseFloat(values[idx].amean().toFixed(2)));
analyzer.setMetric('specificity' + type + 'Total', values[idx].Σ());
});
types.forEach(function (type, idx) {
analyzer.setMetric(
"specificity" + type + "Avg",
parseFloat(values[idx].amean().toFixed(2))
);
analyzer.setMetric("specificity" + type + "Total", values[idx].Σ());
});
debug('Done');
});
debug("Done");
});
}

@@ -59,3 +61,3 @@

// @see http://css-tricks.com/specifics-on-css-specificity/
rule.description = 'Reports rules specificity';
rule.description = "Reports rules specificity";
module.exports = rule;

@@ -1,63 +0,63 @@

'use strict';
"use strict";
function rule(analyzer) {
var selectors = 0,
selectorsLength = 0;
var selectors = 0,
selectorsLength = 0;
analyzer.setMetric('selectors');
analyzer.setMetric('selectorLengthAvg');
analyzer.setMetric("selectors");
analyzer.setMetric("selectorLengthAvg");
analyzer.setMetric('selectorsByAttribute');
analyzer.setMetric('selectorsByClass');
analyzer.setMetric('selectorsById');
analyzer.setMetric('selectorsByPseudo');
analyzer.setMetric('selectorsByTag');
analyzer.setMetric("selectorsByAttribute");
analyzer.setMetric("selectorsByClass");
analyzer.setMetric("selectorsById");
analyzer.setMetric("selectorsByPseudo");
analyzer.setMetric("selectorsByTag");
analyzer.on('rule', function() {
analyzer.incrMetric('rules');
});
analyzer.on("rule", function () {
analyzer.incrMetric("rules");
});
analyzer.on('selector', function(rule, selector, expressions) {
selectors += 1;
selectorsLength += expressions.length;
});
analyzer.on("selector", function (rule, selector, expressions) {
selectors += 1;
selectorsLength += expressions.length;
});
analyzer.on('declaration', function() {
analyzer.incrMetric('declarations');
});
analyzer.on("declaration", function () {
analyzer.incrMetric("declarations");
});
analyzer.on('expression', function(selector, expression) {
// a[href]
if (expression.attributes) {
analyzer.incrMetric('selectorsByAttribute');
}
analyzer.on("expression", function (selector, expression) {
// a[href]
if (expression.attributes) {
analyzer.incrMetric("selectorsByAttribute");
}
// .bar
if (expression.classList) {
analyzer.incrMetric('selectorsByClass');
}
// .bar
if (expression.classList) {
analyzer.incrMetric("selectorsByClass");
}
// @foo
if (expression.id) {
analyzer.incrMetric('selectorsById');
}
// @foo
if (expression.id) {
analyzer.incrMetric("selectorsById");
}
// a:hover
if (expression.pseudos) {
analyzer.incrMetric('selectorsByPseudo');
}
// a:hover
if (expression.pseudos) {
analyzer.incrMetric("selectorsByPseudo");
}
// header
if (expression.tag && expression.tag !== '*') {
analyzer.incrMetric('selectorsByTag');
}
});
// header
if (expression.tag && expression.tag !== "*") {
analyzer.incrMetric("selectorsByTag");
}
});
analyzer.on('report', function() {
analyzer.setMetric('selectors', selectors);
analyzer.setMetric('selectorLengthAvg', selectorsLength / selectors);
});
analyzer.on("report", function () {
analyzer.setMetric("selectors", selectors);
analyzer.setMetric("selectorLengthAvg", selectorsLength / selectors);
});
}
rule.description = 'Emit CSS stats';
rule.description = "Emit CSS stats";
module.exports = rule;

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

SocketSocket SOC 2 Logo

Product

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

Packages

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc