UNPKG

analyze-css

Version:

CSS selectors complexity and performance analyzer

117 lines (99 loc) 3.33 kB
"use strict"; 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(), mediaQueryStack = [], browserPrefixRegEx = /^-(moz|o|webkit|ms)-/; analyzer.setMetric("duplicatedSelectors"); analyzer.setMetric("duplicatedProperties"); // 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); }); // 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 = {}; /* istanbul ignore else */ if (rule.declarations) { rule.declarations.forEach(function (declaration) { var propertyName; 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; } // 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; } } }); } }); // 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; } }); }); analyzer.on("report", function () { analyzer.setCurrentPosition(undefined); selectors.sort().forEach((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"; module.exports = rule;