Big News: Socket Selected for OpenAI's Cybersecurity Grant Program.Details
Socket
Book a DemoSign in
Socket

specificity

Package Overview
Dependencies
Maintainers
1
Versions
16
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

specificity - npm Package Compare versions

Comparing version
0.4.1
to
1.0.0-beta
+164
dist/cjs/calculate.js
"use strict";
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
if (ar || !(i in from)) {
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
ar[i] = from[i];
}
}
return to.concat(ar || Array.prototype.slice.call(from));
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.calculateWithDetails = exports.calculate = void 0;
var css_tree_1 = require("css-tree");
var sort_js_1 = require("./sort.js");
var universalSelectorName = "*";
var increment = function (node, _a, level) {
var _b;
var _c, _d, _e, _f, _g, _h, _j, _k;
var total = _a.total, contributingParts = _a.contributingParts;
return ({
total: __assign(__assign({}, total), (_b = {}, _b[level] = total[level] + 1, _b)),
contributingParts: __spreadArray(__spreadArray([], contributingParts, true), [
{
level: level,
start: {
line: (_d = (_c = node === null || node === void 0 ? void 0 : node.loc) === null || _c === void 0 ? void 0 : _c.start.line) !== null && _d !== void 0 ? _d : 1,
column: (_f = (_e = node === null || node === void 0 ? void 0 : node.loc) === null || _e === void 0 ? void 0 : _e.start.column) !== null && _f !== void 0 ? _f : 1,
},
end: {
line: (_h = (_g = node === null || node === void 0 ? void 0 : node.loc) === null || _g === void 0 ? void 0 : _g.end.line) !== null && _h !== void 0 ? _h : 1,
column: (_k = (_j = node === null || node === void 0 ? void 0 : node.loc) === null || _j === void 0 ? void 0 : _j.end.column) !== null && _k !== void 0 ? _k : 1,
},
},
], false),
});
};
var psuedoElementsWithDeprecatedOneColonNotation = [
"before",
"after",
"first-line",
"first-letter",
];
var getChildrenFromPseudoClassNode = function (node) {
if (node.children) {
var firstChild = node.children[0];
if (firstChild.type === "SelectorList") {
return firstChild.children;
}
else if (firstChild.type === "Nth" && firstChild.selector) {
return firstChild.selector.children;
}
else {
return node.children;
}
}
return [];
};
var handlePseudoClassSelector = function (node, accumulatingResult) {
var name = node.name.toLowerCase();
var children = getChildrenFromPseudoClassNode(node);
if (name === "not" || name === "is" || name === "has") {
// The specificity of an :is(), :not(), or :has() pseudo-class is replaced by the specificity of the most specific complex selector in its selector list argument
if (children.length > 0) {
return children
.map(function (childNode) { return traverse(childNode, accumulatingResult); })
.sort(function (a, b) { return (0, sort_js_1.compareDesc)(a.total, b.total); })[0];
}
}
else if (name === "nth-child" || name === "nth-last-child") {
// The specificity of an :nth-child() or :nth-last-child() selector is the specificity of the pseudo class itself (counting as one pseudo-class selector) plus the specificity of the most specific complex selector in its selector list argument (if any).
if (children.length > 0) {
var highestChildSpecificity = children
.map(function (childNode) { return traverse(childNode, increment(node, accumulatingResult, "B")); })
.sort(function (a, b) { return (0, sort_js_1.compareDesc)(a.total, b.total); })[0];
return highestChildSpecificity;
}
else {
return increment(node, accumulatingResult, "B");
}
}
else if (name === "where") {
// The specificity of a :where() pseudo-class is replaced by zero.
return accumulatingResult;
}
else if (name === "global" || name === "local") {
// The specificity for :global() and :local() is replaced by the specificity of the child selector because although they look like psuedo classes they are actually an identifier for CSS Modules
if (children.length > 0) {
return children.reduce(function (acc, childNode) { return traverse(childNode, acc); }, accumulatingResult);
}
}
else if (psuedoElementsWithDeprecatedOneColonNotation.includes(name)) {
// These pseudo-elements can look like pseudo-classes
// https://www.w3.org/TR/selectors-4/#pseudo-elements
return increment(node, accumulatingResult, "C");
}
else {
return increment(node, accumulatingResult, "B");
}
return accumulatingResult;
};
var traverse = function (node, accumulatingResult) {
var _a, _b, _c, _d;
if (accumulatingResult === void 0) { accumulatingResult = {
total: { A: 0, B: 0, C: 0 },
contributingParts: [],
}; }
if (node.type === "IdSelector") {
return increment(node, accumulatingResult, "A");
}
else if (node.type === "PseudoClassSelector") {
return handlePseudoClassSelector(node, accumulatingResult);
}
else if (node.type === "ClassSelector" ||
node.type === "AttributeSelector") {
return increment(node, accumulatingResult, "B");
}
else if (node.type === "TypeSelector") {
if (node.name !== universalSelectorName) {
return increment(node, accumulatingResult, "C");
}
}
else if (node.type === "PseudoElementSelector") {
return increment(node, accumulatingResult, "C");
}
else if (node.type === "Selector" || node.type === "SelectorList") {
return node.children.reduce(function (acc, childNode) { return traverse(childNode, acc); }, accumulatingResult);
}
else if (node.type === "Raw") {
return traverse((0, css_tree_1.toPlainObject)((0, css_tree_1.parse)(node.value, {
context: "selector",
positions: true,
line: (_b = (_a = node === null || node === void 0 ? void 0 : node.loc) === null || _a === void 0 ? void 0 : _a.end.line) !== null && _b !== void 0 ? _b : 1,
column: (_d = (_c = node === null || node === void 0 ? void 0 : node.loc) === null || _c === void 0 ? void 0 : _c.end.column) !== null && _d !== void 0 ? _d : 1,
})), accumulatingResult);
}
return accumulatingResult;
};
var calculate = function (selector) {
var ast = (0, css_tree_1.toPlainObject)((0, css_tree_1.parse)(selector, {
context: "selector",
}));
return traverse(ast).total;
};
exports.calculate = calculate;
var calculateWithDetails = function (selector) {
var ast = (0, css_tree_1.toPlainObject)((0, css_tree_1.parse)(selector, {
context: "selector",
positions: true,
}));
return traverse(ast);
};
exports.calculateWithDetails = calculateWithDetails;
//# sourceMappingURL=calculate.js.map
{"version":3,"file":"calculate.js","sourceRoot":"","sources":["../../src/calculate.ts"],"names":[],"mappings":"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"}
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.compareDesc = exports.compare = exports.calculateWithDetails = exports.calculate = void 0;
var calculate_js_1 = require("./calculate.js");
Object.defineProperty(exports, "calculate", { enumerable: true, get: function () { return calculate_js_1.calculate; } });
Object.defineProperty(exports, "calculateWithDetails", { enumerable: true, get: function () { return calculate_js_1.calculateWithDetails; } });
var sort_js_1 = require("./sort.js");
Object.defineProperty(exports, "compare", { enumerable: true, get: function () { return sort_js_1.compare; } });
Object.defineProperty(exports, "compareDesc", { enumerable: true, get: function () { return sort_js_1.compareDesc; } });
//# sourceMappingURL=index.js.map
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";;;AAAA,+CAAiE;AAAxD,yGAAA,SAAS,OAAA;AAAE,oHAAA,oBAAoB,OAAA;AACxC,qCAAiD;AAAxC,kGAAA,OAAO,OAAA;AAAE,sGAAA,WAAW,OAAA"}
{ "type": "commonjs" }
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.compareDesc = exports.compare = void 0;
var compare = function (a, b) {
if (a.A !== b.A) {
return a.A - b.A;
}
else if (a.B !== b.B) {
return a.B - b.B;
}
else {
return a.C - b.C;
}
};
exports.compare = compare;
var compareDesc = function (a, b) {
return (0, exports.compare)(b, a);
};
exports.compareDesc = compareDesc;
//# sourceMappingURL=sort.js.map
{"version":3,"file":"sort.js","sourceRoot":"","sources":["../../src/sort.ts"],"names":[],"mappings":";;;AAEO,IAAM,OAAO,GAAG,UAAC,CAAc,EAAE,CAAc;IACpD,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE;QACf,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;KAClB;SAAM,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE;QACtB,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;KAClB;SAAM;QACL,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;KAClB;AACH,CAAC,CAAC;AARW,QAAA,OAAO,WAQlB;AAEK,IAAM,WAAW,GAAG,UAAC,CAAc,EAAE,CAAc;IACxD,OAAA,IAAA,eAAO,EAAC,CAAC,EAAE,CAAC,CAAC;AAAb,CAAa,CAAC;AADH,QAAA,WAAW,eACR"}
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
//# sourceMappingURL=types.js.map
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":""}
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
if (ar || !(i in from)) {
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
ar[i] = from[i];
}
}
return to.concat(ar || Array.prototype.slice.call(from));
};
import { parse, toPlainObject, } from "css-tree";
import { compareDesc } from "./sort.js";
var universalSelectorName = "*";
var increment = function (node, _a, level) {
var _b;
var _c, _d, _e, _f, _g, _h, _j, _k;
var total = _a.total, contributingParts = _a.contributingParts;
return ({
total: __assign(__assign({}, total), (_b = {}, _b[level] = total[level] + 1, _b)),
contributingParts: __spreadArray(__spreadArray([], contributingParts, true), [
{
level: level,
start: {
line: (_d = (_c = node === null || node === void 0 ? void 0 : node.loc) === null || _c === void 0 ? void 0 : _c.start.line) !== null && _d !== void 0 ? _d : 1,
column: (_f = (_e = node === null || node === void 0 ? void 0 : node.loc) === null || _e === void 0 ? void 0 : _e.start.column) !== null && _f !== void 0 ? _f : 1,
},
end: {
line: (_h = (_g = node === null || node === void 0 ? void 0 : node.loc) === null || _g === void 0 ? void 0 : _g.end.line) !== null && _h !== void 0 ? _h : 1,
column: (_k = (_j = node === null || node === void 0 ? void 0 : node.loc) === null || _j === void 0 ? void 0 : _j.end.column) !== null && _k !== void 0 ? _k : 1,
},
},
], false),
});
};
var psuedoElementsWithDeprecatedOneColonNotation = [
"before",
"after",
"first-line",
"first-letter",
];
var getChildrenFromPseudoClassNode = function (node) {
if (node.children) {
var firstChild = node.children[0];
if (firstChild.type === "SelectorList") {
return firstChild.children;
}
else if (firstChild.type === "Nth" && firstChild.selector) {
return firstChild.selector.children;
}
else {
return node.children;
}
}
return [];
};
var handlePseudoClassSelector = function (node, accumulatingResult) {
var name = node.name.toLowerCase();
var children = getChildrenFromPseudoClassNode(node);
if (name === "not" || name === "is" || name === "has") {
// The specificity of an :is(), :not(), or :has() pseudo-class is replaced by the specificity of the most specific complex selector in its selector list argument
if (children.length > 0) {
return children
.map(function (childNode) { return traverse(childNode, accumulatingResult); })
.sort(function (a, b) { return compareDesc(a.total, b.total); })[0];
}
}
else if (name === "nth-child" || name === "nth-last-child") {
// The specificity of an :nth-child() or :nth-last-child() selector is the specificity of the pseudo class itself (counting as one pseudo-class selector) plus the specificity of the most specific complex selector in its selector list argument (if any).
if (children.length > 0) {
var highestChildSpecificity = children
.map(function (childNode) { return traverse(childNode, increment(node, accumulatingResult, "B")); })
.sort(function (a, b) { return compareDesc(a.total, b.total); })[0];
return highestChildSpecificity;
}
else {
return increment(node, accumulatingResult, "B");
}
}
else if (name === "where") {
// The specificity of a :where() pseudo-class is replaced by zero.
return accumulatingResult;
}
else if (name === "global" || name === "local") {
// The specificity for :global() and :local() is replaced by the specificity of the child selector because although they look like psuedo classes they are actually an identifier for CSS Modules
if (children.length > 0) {
return children.reduce(function (acc, childNode) { return traverse(childNode, acc); }, accumulatingResult);
}
}
else if (psuedoElementsWithDeprecatedOneColonNotation.includes(name)) {
// These pseudo-elements can look like pseudo-classes
// https://www.w3.org/TR/selectors-4/#pseudo-elements
return increment(node, accumulatingResult, "C");
}
else {
return increment(node, accumulatingResult, "B");
}
return accumulatingResult;
};
var traverse = function (node, accumulatingResult) {
var _a, _b, _c, _d;
if (accumulatingResult === void 0) { accumulatingResult = {
total: { A: 0, B: 0, C: 0 },
contributingParts: [],
}; }
if (node.type === "IdSelector") {
return increment(node, accumulatingResult, "A");
}
else if (node.type === "PseudoClassSelector") {
return handlePseudoClassSelector(node, accumulatingResult);
}
else if (node.type === "ClassSelector" ||
node.type === "AttributeSelector") {
return increment(node, accumulatingResult, "B");
}
else if (node.type === "TypeSelector") {
if (node.name !== universalSelectorName) {
return increment(node, accumulatingResult, "C");
}
}
else if (node.type === "PseudoElementSelector") {
return increment(node, accumulatingResult, "C");
}
else if (node.type === "Selector" || node.type === "SelectorList") {
return node.children.reduce(function (acc, childNode) { return traverse(childNode, acc); }, accumulatingResult);
}
else if (node.type === "Raw") {
return traverse(toPlainObject(parse(node.value, {
context: "selector",
positions: true,
line: (_b = (_a = node === null || node === void 0 ? void 0 : node.loc) === null || _a === void 0 ? void 0 : _a.end.line) !== null && _b !== void 0 ? _b : 1,
column: (_d = (_c = node === null || node === void 0 ? void 0 : node.loc) === null || _c === void 0 ? void 0 : _c.end.column) !== null && _d !== void 0 ? _d : 1,
})), accumulatingResult);
}
return accumulatingResult;
};
export var calculate = function (selector) {
var ast = toPlainObject(parse(selector, {
context: "selector",
}));
return traverse(ast).total;
};
export var calculateWithDetails = function (selector) {
var ast = toPlainObject(parse(selector, {
context: "selector",
positions: true,
}));
return traverse(ast);
};
//# sourceMappingURL=calculate.js.map
{"version":3,"file":"calculate.js","sourceRoot":"","sources":["../../src/calculate.ts"],"names":[],"mappings":"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"}
export { calculate, calculateWithDetails } from "./calculate.js";
export { compare, compareDesc } from "./sort.js";
//# sourceMappingURL=index.js.map
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AACjE,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC"}
export var compare = function (a, b) {
if (a.A !== b.A) {
return a.A - b.A;
}
else if (a.B !== b.B) {
return a.B - b.B;
}
else {
return a.C - b.C;
}
};
export var compareDesc = function (a, b) {
return compare(b, a);
};
//# sourceMappingURL=sort.js.map
{"version":3,"file":"sort.js","sourceRoot":"","sources":["../../src/sort.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,IAAM,OAAO,GAAG,UAAC,CAAc,EAAE,CAAc;IACpD,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE;QACf,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;KAClB;SAAM,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE;QACtB,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;KAClB;SAAM;QACL,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;KAClB;AACH,CAAC,CAAC;AAEF,MAAM,CAAC,IAAM,WAAW,GAAG,UAAC,CAAc,EAAE,CAAc;IACxD,OAAA,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC;AAAb,CAAa,CAAC"}
export {};
//# sourceMappingURL=types.js.map
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":""}
import { Result, Specificity } from "./types";
export declare const calculate: (selector: string) => Specificity;
export declare const calculateWithDetails: (selector: string) => Result;
//# sourceMappingURL=calculate.d.ts.map
{"version":3,"file":"calculate.d.ts","sourceRoot":"","sources":["../../src/calculate.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,MAAM,EAAE,WAAW,EAAS,MAAM,SAAS,CAAC;AA2IrD,eAAO,MAAM,SAAS,aAAc,MAAM,KAAG,WAO5C,CAAC;AAEF,eAAO,MAAM,oBAAoB,aAAc,MAAM,KAAG,MAQvD,CAAC"}
export { calculate, calculateWithDetails } from "./calculate.js";
export { compare, compareDesc } from "./sort.js";
//# sourceMappingURL=index.d.ts.map
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AACjE,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC"}
import { Specificity } from "./types";
export declare const compare: (a: Specificity, b: Specificity) => number;
export declare const compareDesc: (a: Specificity, b: Specificity) => number;
//# sourceMappingURL=sort.d.ts.map
{"version":3,"file":"sort.d.ts","sourceRoot":"","sources":["../../src/sort.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAEtC,eAAO,MAAM,OAAO,MAAO,WAAW,KAAK,WAAW,KAAG,MAQxD,CAAC;AAEF,eAAO,MAAM,WAAW,MAAO,WAAW,KAAK,WAAW,KAAG,MAC9C,CAAC"}
export type Level = "A" | "B" | "C";
export type Specificity = Record<Level, number>;
export type SpecificityContributingPart = {
level: Level;
start: {
line: number;
column: number;
};
end: {
line: number;
column: number;
};
};
export type Result = {
total: Specificity;
contributingParts: SpecificityContributingPart[];
};
//# sourceMappingURL=types.d.ts.map
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,KAAK,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;AAEpC,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;AAEhD,MAAM,MAAM,2BAA2B,GAAG;IACxC,KAAK,EAAE,KAAK,CAAC;IACb,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IACxC,GAAG,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;CACvC,CAAC;AAEF,MAAM,MAAM,MAAM,GAAG;IACnB,KAAK,EAAE,WAAW,CAAC;IACnB,iBAAiB,EAAE,2BAA2B,EAAE,CAAC;CAClD,CAAC"}
+30
-11
{
"name": "specificity",
"version": "0.4.1",
"version": "1.0.0-beta",
"description": "Calculate the specificity of a CSS selector",

@@ -22,17 +22,36 @@ "keywords": [

},
"main": "dist/specificity",
"module": "dist/specificity.mjs",
"bin": {
"specificity": "./bin/specificity"
"type": "module",
"module": "./dist/esm/index.js",
"main": "./dist/cjs/index.js",
"types": "./dist/types/index.d.ts",
"exports": {
".": {
"types": "./dist/types/index.d.ts",
"import": "./dist/esm/index.js",
"require": "./dist/cjs/index.js"
},
"./package.json": "./package.json"
},
"scripts": {
"prepare": "rollup --config",
"test": "mocha test/test.js --require esm"
"prebuild": "rm -rf ./dist",
"build": "tsc -b ./tsconfig.json ./tsconfig.cjs.json && cp package-cjs.json dist/cjs/package.json",
"test": "jest && node test/test-esm.mjs && node test/test-cjs.cjs"
},
"devDependencies": {
"esm": "^3.0.71",
"mocha": "^5.2.0",
"rollup": "^0.62.0"
"@jest/globals": "^29.5.0",
"@types/css-tree": "^2.3.1",
"jest": "^29.5.0",
"prettier": "^2.8.8",
"ts-jest": "^29.1.0",
"typescript": "^5.1.3"
},
"types": "specificity.d.ts"
"dependencies": {
"css-tree": "^2.3.1"
},
"files": [
"dist",
"package.json",
"README.md",
"LICENSE"
]
}
+48
-146
# Specificity Calculator
A JavaScript module for calculating and comparing the [specificity of CSS selectors](https://www.w3.org/TR/selectors-3/#specificity). The module is used on the [Specificity Calculator](https://specificity.keegan.st/) website.
A JavaScript module for calculating and comparing the [specificity of CSS selectors](https://www.w3.org/TR/selectors-4/#specificity). The module is used on the [Specificity Calculator](https://specificity.keegan.st/) website.
Specificity Calculator is built for CSS Selectors Level 3. Specificity Calculator isn’t a CSS validator. If you enter invalid selectors it will return incorrect results. For example, the [negation pseudo-class](https://www.w3.org/TR/selectors-3/#negation) may only take a simple selector as an argument. Using a psuedo-element or combinator as an argument for `:not()` is invalid CSS so Specificity Calculator will return incorrect results.
Note that version 1 is a complete re-write with support for CSS Selectors Level 4, and has a different API than earlier versions.
## Supported runtime environments
## calculate(selector)
The module is provided in two formats: an ECMAScript (ES) module in `dist/specificity.mjs`, and a Universal Module Definition (UMD) in `dist/specificity.js`. This enables support for the following runtime environments:
### Parameters
**Browser**
- `selector`: `string` - should be a valid CSS selector
* Directly loaded ES module
* ES module in a precompiled script (using a bundler like Webpack or Rollup)
* Global variable
### Returns
**Node.js**
A `Specificity` object with a type of `Record<"A" | "B" | "C", number>`.
* ES module
* CommonJS module
### Examples
### Browser usage as a directly loaded ES module
```html
<script type="module">
import { calculate } from './specificity/dist/specificity.mjs';
calculate('ul#nav li.active a');
</script>
```
### Browser usage as an ES module in a precompiled script
Bundlers like [Webpack and Rollup](https://github.com/rollup/rollup/wiki/pkg.module) import from the `module` field in `package.json`, which is set to the ES module artefact, `dist/specificity.mjs`.
```js
import { calculate } from 'specificity';
calculate("#id");
{
A: 1,
B: 0,
C: 0
}
calculate('ul#nav li.active a');
```
calculate(".classname");
{
A: 0,
B: 1,
C: 0
}
### Browser usage as a global variable
calculate("element");
{
A: 0,
B: 0,
C: 1
}
The UMD artefact, `dist/specificity.js`, sets a global variable, `SPECIFICITY`.
```html
<script src="./specificity/dist/specificity.js"></script>
<script>
SPECIFICITY.calculate('ul#nav li.active a');
</script>
```
### Node.js usage as an ES module
The `main` field in `package.json` has an extensionless value, `dist/specificity`. This allows Node.js to use either the ES module, in `dist/specificity.mjs`, or the CommonJS module, in `dist/specificity.js`.
When Node.js is run with the `--experimental-modules` [flag](https://nodejs.org/api/esm.html) or an [ES module loader](https://www.npmjs.com/package/esm), it will use the ES module artefact.
```js
import { calculate } from 'specificity';
calculate('ul#nav li.active a');
{
A: 1,
B: 1,
C: 3
}
```
### Node.js usage as a CommonJS module
## compare(a, b)
Otherwise, Node.js will use the UMD artefact, which contains a CommonJS module definition.
### Parameters
```js
const { calculate } = require('specificity');
- `a`: `Specificity` object with a type of `Record<"A" | "B" | "C", number>`
- `b`: `Specificity` object with a type of `Record<"A" | "B" | "C", number>`
calculate('ul#nav li.active a');
```
### Returns
## Calculate function
Returns a positive number if `a` has a higher specificity than `b`
Returns a negative number if `a` has a lower specificity than `b`
Returns `0` if `a` has the same specificity than `b`
The `calculate` function returns an array containing a result object for each selector input. Each result object has the following properties:
### Examples
* `selector`: the input
* `specificity`: the result as a string e.g. `0,1,0,0`
* `specificityArray`: the result as an array of numbers e.g. `[0, 1, 0, 0]`
* `parts`: array with details about each part of the selector that counts towards the specificity
## Example
```js
calculate('ul#nav li.active a');
/*
[
{
selector: 'ul#nav li.active a',
specificity: '0,1,1,3',
specificityArray: [0, 1, 1, 3],
parts: [
{ selector: 'ul', type: 'c', index: 0, length: 2 },
{ selector: '#nav', type: 'a', index: 2, length: 4 },
{ selector: 'li', type: 'c', index: 5, length: 2 },
{ selector: '.active', type: 'b', index: 8, length: 7 },
{ selector: 'a', type: 'c', index: 13, length: 1 }
]
}
"element",
".classname",
"#id",
]
*/
```
.map(calculate)
.sort(compare);
You can use comma separation to pass in multiple selectors:
```js
calculate('ul#nav li.active a, body.ie7 .col_3 h2 ~ h2');
/*
[
{
selector: 'ul#nav li.active a',
specificity: '0,1,1,3',
...
},
{
selector: 'body.ie7 .col_3 h2 ~ h2',
specificity: '0,0,2,3',
...
}
]
*/
[ { A: 0, B: 0, C: 1 }, { A: 0, B: 1, C: 0 }, { A: 1, B: 0, C: 0 } ]
```
## Comparing two selectors
## compareDesc(a, b)
Specificity Calculator also exports a `compare` function. This function accepts two CSS selectors or specificity arrays, `a` and `b`.
* It returns `-1` if `a` has a lower specificity than `b`
* It returns `1` if `a` has a higher specificity than `b`
* It returns `0` if `a` has the same specificity than `b`
```js
compare('div', '.active'); // -1
compare('#main', 'div'); // 1
compare('span', 'div'); // 0
compare('span', [0, 0, 0, 1]); // 0
compare('#main > div', [0, 1, 0, 1]); // 0
```
## Ordering an array of selectors by specificity
You can pass the `compare` function to `Array.prototype.sort` to sort an array of CSS selectors by specificity.
```js
import { compare } from 'specificity';
['#main', 'p', '.active'].sort(compare); // ['p', '.active', '#main']
```
## Command-line usage
Run `npm install specificity` to install the module locally, or `npm install -g specificity` for global installation. Run `specificity` without arguments to learn about its usage:
```bash
$ specificity
Usage: specificity <selector>
Computes specificity of a CSS selector.
```
Pass a selector as the first argument to get its specificity computed:
```bash
$ specificity "ul#nav li.active a"
0,1,1,3
```
## Testing
To install dependencies, run: `npm install`
Then to test, run: `npm test`
Same as `compare` but returns the opposite value, for use in sorting specificity objects with the highest specificity first.

Sorry, the diff of this file is not supported yet

(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(factory((global.SPECIFICITY = {})));
}(this, (function (exports) { 'use strict';
// Calculate the specificity for a selector by dividing it into simple selectors and counting them
var calculate = function(input) {
var selectors,
selector,
i,
len,
results = [];
// Separate input by commas
selectors = input.split(',');
for (i = 0, len = selectors.length; i < len; i += 1) {
selector = selectors[i];
if (selector.length > 0) {
results.push(calculateSingle(selector));
}
}
return results;
};
/**
* Calculates the specificity of CSS selectors
* http://www.w3.org/TR/css3-selectors/#specificity
*
* Returns an object with the following properties:
* - selector: the input
* - specificity: e.g. 0,1,0,0
* - parts: array with details about each part of the selector that counts towards the specificity
* - specificityArray: e.g. [0, 1, 0, 0]
*/
var calculateSingle = function(input) {
var selector = input,
findMatch,
typeCount = {
'a': 0,
'b': 0,
'c': 0
},
parts = [],
// The following regular expressions assume that selectors matching the preceding regular expressions have been removed
attributeRegex = /(\[[^\]]+\])/g,
idRegex = /(#[^\#\s\+>~\.\[:\)]+)/g,
classRegex = /(\.[^\s\+>~\.\[:\)]+)/g,
pseudoElementRegex = /(::[^\s\+>~\.\[:]+|:first-line|:first-letter|:before|:after)/gi,
// A regex for pseudo classes with brackets - :nth-child(), :nth-last-child(), :nth-of-type(), :nth-last-type(), :lang()
// The negation psuedo class (:not) is filtered out because specificity is calculated on its argument
// :global and :local are filtered out - they look like psuedo classes but are an identifier for CSS Modules
pseudoClassWithBracketsRegex = /(:(?!not|global|local)[\w-]+\([^\)]*\))/gi,
// A regex for other pseudo classes, which don't have brackets
pseudoClassRegex = /(:(?!not|global|local)[^\s\+>~\.\[:]+)/g,
elementRegex = /([^\s\+>~\.\[:]+)/g;
// Find matches for a regular expression in a string and push their details to parts
// Type is "a" for IDs, "b" for classes, attributes and pseudo-classes and "c" for elements and pseudo-elements
findMatch = function(regex, type) {
var matches, i, len, match, index, length;
if (regex.test(selector)) {
matches = selector.match(regex);
for (i = 0, len = matches.length; i < len; i += 1) {
typeCount[type] += 1;
match = matches[i];
index = selector.indexOf(match);
length = match.length;
parts.push({
selector: input.substr(index, length),
type: type,
index: index,
length: length
});
// Replace this simple selector with whitespace so it won't be counted in further simple selectors
selector = selector.replace(match, Array(length + 1).join(' '));
}
}
};
// Replace escaped characters with plain text, using the "A" character
// https://www.w3.org/TR/CSS21/syndata.html#characters
(function() {
var replaceWithPlainText = function(regex) {
var matches, i, len, match;
if (regex.test(selector)) {
matches = selector.match(regex);
for (i = 0, len = matches.length; i < len; i += 1) {
match = matches[i];
selector = selector.replace(match, Array(match.length + 1).join('A'));
}
}
},
// Matches a backslash followed by six hexadecimal digits followed by an optional single whitespace character
escapeHexadecimalRegex = /\\[0-9A-Fa-f]{6}\s?/g,
// Matches a backslash followed by fewer than six hexadecimal digits followed by a mandatory single whitespace character
escapeHexadecimalRegex2 = /\\[0-9A-Fa-f]{1,5}\s/g,
// Matches a backslash followed by any character
escapeSpecialCharacter = /\\./g;
replaceWithPlainText(escapeHexadecimalRegex);
replaceWithPlainText(escapeHexadecimalRegex2);
replaceWithPlainText(escapeSpecialCharacter);
}());
// Remove anything after a left brace in case a user has pasted in a rule, not just a selector
(function() {
var regex = /{[^]*/gm,
matches, i, len, match;
if (regex.test(selector)) {
matches = selector.match(regex);
for (i = 0, len = matches.length; i < len; i += 1) {
match = matches[i];
selector = selector.replace(match, Array(match.length + 1).join(' '));
}
}
}());
// Add attribute selectors to parts collection (type b)
findMatch(attributeRegex, 'b');
// Add ID selectors to parts collection (type a)
findMatch(idRegex, 'a');
// Add class selectors to parts collection (type b)
findMatch(classRegex, 'b');
// Add pseudo-element selectors to parts collection (type c)
findMatch(pseudoElementRegex, 'c');
// Add pseudo-class selectors to parts collection (type b)
findMatch(pseudoClassWithBracketsRegex, 'b');
findMatch(pseudoClassRegex, 'b');
// Remove universal selector and separator characters
selector = selector.replace(/[\*\s\+>~]/g, ' ');
// Remove any stray dots or hashes which aren't attached to words
// These may be present if the user is live-editing this selector
selector = selector.replace(/[#\.]/g, ' ');
// Remove the negation psuedo-class (:not) but leave its argument because specificity is calculated on its argument
// Remove non-standard :local and :global CSS Module identifiers because they do not effect the specificity
selector = selector.replace(/:not/g, ' ');
selector = selector.replace(/:local/g, ' ');
selector = selector.replace(/:global/g, ' ');
selector = selector.replace(/[\(\)]/g, ' ');
// The only things left should be element selectors (type c)
findMatch(elementRegex, 'c');
// Order the parts in the order they appear in the original selector
// This is neater for external apps to deal with
parts.sort(function(a, b) {
return a.index - b.index;
});
return {
selector: input,
specificity: '0,' + typeCount.a.toString() + ',' + typeCount.b.toString() + ',' + typeCount.c.toString(),
specificityArray: [0, typeCount.a, typeCount.b, typeCount.c],
parts: parts
};
};
/**
* Compares two CSS selectors for specificity
* Alternatively you can replace one of the CSS selectors with a specificity array
*
* - it returns -1 if a has a lower specificity than b
* - it returns 1 if a has a higher specificity than b
* - it returns 0 if a has the same specificity than b
*/
var compare = function(a, b) {
var aSpecificity,
bSpecificity,
i;
if (typeof a ==='string') {
if (a.indexOf(',') !== -1) {
throw 'Invalid CSS selector';
} else {
aSpecificity = calculateSingle(a)['specificityArray'];
}
} else if (Array.isArray(a)) {
if (a.filter(function(e) { return (typeof e === 'number'); }).length !== 4) {
throw 'Invalid specificity array';
} else {
aSpecificity = a;
}
} else {
throw 'Invalid CSS selector or specificity array';
}
if (typeof b ==='string') {
if (b.indexOf(',') !== -1) {
throw 'Invalid CSS selector';
} else {
bSpecificity = calculateSingle(b)['specificityArray'];
}
} else if (Array.isArray(b)) {
if (b.filter(function(e) { return (typeof e === 'number'); }).length !== 4) {
throw 'Invalid specificity array';
} else {
bSpecificity = b;
}
} else {
throw 'Invalid CSS selector or specificity array';
}
for (i = 0; i < 4; i += 1) {
if (aSpecificity[i] < bSpecificity[i]) {
return -1;
} else if (aSpecificity[i] > bSpecificity[i]) {
return 1;
}
}
return 0;
};
exports.calculate = calculate;
exports.compare = compare;
Object.defineProperty(exports, '__esModule', { value: true });
})));
// Calculate the specificity for a selector by dividing it into simple selectors and counting them
var calculate = function(input) {
var selectors,
selector,
i,
len,
results = [];
// Separate input by commas
selectors = input.split(',');
for (i = 0, len = selectors.length; i < len; i += 1) {
selector = selectors[i];
if (selector.length > 0) {
results.push(calculateSingle(selector));
}
}
return results;
};
/**
* Calculates the specificity of CSS selectors
* http://www.w3.org/TR/css3-selectors/#specificity
*
* Returns an object with the following properties:
* - selector: the input
* - specificity: e.g. 0,1,0,0
* - parts: array with details about each part of the selector that counts towards the specificity
* - specificityArray: e.g. [0, 1, 0, 0]
*/
var calculateSingle = function(input) {
var selector = input,
findMatch,
typeCount = {
'a': 0,
'b': 0,
'c': 0
},
parts = [],
// The following regular expressions assume that selectors matching the preceding regular expressions have been removed
attributeRegex = /(\[[^\]]+\])/g,
idRegex = /(#[^\#\s\+>~\.\[:\)]+)/g,
classRegex = /(\.[^\s\+>~\.\[:\)]+)/g,
pseudoElementRegex = /(::[^\s\+>~\.\[:]+|:first-line|:first-letter|:before|:after)/gi,
// A regex for pseudo classes with brackets - :nth-child(), :nth-last-child(), :nth-of-type(), :nth-last-type(), :lang()
// The negation psuedo class (:not) is filtered out because specificity is calculated on its argument
// :global and :local are filtered out - they look like psuedo classes but are an identifier for CSS Modules
pseudoClassWithBracketsRegex = /(:(?!not|global|local)[\w-]+\([^\)]*\))/gi,
// A regex for other pseudo classes, which don't have brackets
pseudoClassRegex = /(:(?!not|global|local)[^\s\+>~\.\[:]+)/g,
elementRegex = /([^\s\+>~\.\[:]+)/g;
// Find matches for a regular expression in a string and push their details to parts
// Type is "a" for IDs, "b" for classes, attributes and pseudo-classes and "c" for elements and pseudo-elements
findMatch = function(regex, type) {
var matches, i, len, match, index, length;
if (regex.test(selector)) {
matches = selector.match(regex);
for (i = 0, len = matches.length; i < len; i += 1) {
typeCount[type] += 1;
match = matches[i];
index = selector.indexOf(match);
length = match.length;
parts.push({
selector: input.substr(index, length),
type: type,
index: index,
length: length
});
// Replace this simple selector with whitespace so it won't be counted in further simple selectors
selector = selector.replace(match, Array(length + 1).join(' '));
}
}
};
// Replace escaped characters with plain text, using the "A" character
// https://www.w3.org/TR/CSS21/syndata.html#characters
(function() {
var replaceWithPlainText = function(regex) {
var matches, i, len, match;
if (regex.test(selector)) {
matches = selector.match(regex);
for (i = 0, len = matches.length; i < len; i += 1) {
match = matches[i];
selector = selector.replace(match, Array(match.length + 1).join('A'));
}
}
},
// Matches a backslash followed by six hexadecimal digits followed by an optional single whitespace character
escapeHexadecimalRegex = /\\[0-9A-Fa-f]{6}\s?/g,
// Matches a backslash followed by fewer than six hexadecimal digits followed by a mandatory single whitespace character
escapeHexadecimalRegex2 = /\\[0-9A-Fa-f]{1,5}\s/g,
// Matches a backslash followed by any character
escapeSpecialCharacter = /\\./g;
replaceWithPlainText(escapeHexadecimalRegex);
replaceWithPlainText(escapeHexadecimalRegex2);
replaceWithPlainText(escapeSpecialCharacter);
}());
// Remove anything after a left brace in case a user has pasted in a rule, not just a selector
(function() {
var regex = /{[^]*/gm,
matches, i, len, match;
if (regex.test(selector)) {
matches = selector.match(regex);
for (i = 0, len = matches.length; i < len; i += 1) {
match = matches[i];
selector = selector.replace(match, Array(match.length + 1).join(' '));
}
}
}());
// Add attribute selectors to parts collection (type b)
findMatch(attributeRegex, 'b');
// Add ID selectors to parts collection (type a)
findMatch(idRegex, 'a');
// Add class selectors to parts collection (type b)
findMatch(classRegex, 'b');
// Add pseudo-element selectors to parts collection (type c)
findMatch(pseudoElementRegex, 'c');
// Add pseudo-class selectors to parts collection (type b)
findMatch(pseudoClassWithBracketsRegex, 'b');
findMatch(pseudoClassRegex, 'b');
// Remove universal selector and separator characters
selector = selector.replace(/[\*\s\+>~]/g, ' ');
// Remove any stray dots or hashes which aren't attached to words
// These may be present if the user is live-editing this selector
selector = selector.replace(/[#\.]/g, ' ');
// Remove the negation psuedo-class (:not) but leave its argument because specificity is calculated on its argument
// Remove non-standard :local and :global CSS Module identifiers because they do not effect the specificity
selector = selector.replace(/:not/g, ' ');
selector = selector.replace(/:local/g, ' ');
selector = selector.replace(/:global/g, ' ');
selector = selector.replace(/[\(\)]/g, ' ');
// The only things left should be element selectors (type c)
findMatch(elementRegex, 'c');
// Order the parts in the order they appear in the original selector
// This is neater for external apps to deal with
parts.sort(function(a, b) {
return a.index - b.index;
});
return {
selector: input,
specificity: '0,' + typeCount.a.toString() + ',' + typeCount.b.toString() + ',' + typeCount.c.toString(),
specificityArray: [0, typeCount.a, typeCount.b, typeCount.c],
parts: parts
};
};
/**
* Compares two CSS selectors for specificity
* Alternatively you can replace one of the CSS selectors with a specificity array
*
* - it returns -1 if a has a lower specificity than b
* - it returns 1 if a has a higher specificity than b
* - it returns 0 if a has the same specificity than b
*/
var compare = function(a, b) {
var aSpecificity,
bSpecificity,
i;
if (typeof a ==='string') {
if (a.indexOf(',') !== -1) {
throw 'Invalid CSS selector';
} else {
aSpecificity = calculateSingle(a)['specificityArray'];
}
} else if (Array.isArray(a)) {
if (a.filter(function(e) { return (typeof e === 'number'); }).length !== 4) {
throw 'Invalid specificity array';
} else {
aSpecificity = a;
}
} else {
throw 'Invalid CSS selector or specificity array';
}
if (typeof b ==='string') {
if (b.indexOf(',') !== -1) {
throw 'Invalid CSS selector';
} else {
bSpecificity = calculateSingle(b)['specificityArray'];
}
} else if (Array.isArray(b)) {
if (b.filter(function(e) { return (typeof e === 'number'); }).length !== 4) {
throw 'Invalid specificity array';
} else {
bSpecificity = b;
}
} else {
throw 'Invalid CSS selector or specificity array';
}
for (i = 0; i < 4; i += 1) {
if (aSpecificity[i] < bSpecificity[i]) {
return -1;
} else if (aSpecificity[i] > bSpecificity[i]) {
return 1;
}
}
return 0;
};
export { calculate, compare };
const path = require('path');
export default [
{
input: 'specificity.js',
output: [
{
file: 'dist/specificity.mjs',
format: 'es',
},
{
file: 'dist/specificity.js',
format: 'umd',
name: 'SPECIFICITY',
},
],
},
];
/**
* Specificity arrays always have 4 numbers (integers) for quick comparison
* comparing from left to right, the next number only has to be checked if
* two numbers of the same index are equal.
*/
export type SpecificityArray = [number, number, number, number];
/**
* A result of parsing a selector into an array of parts.
* Calculating a specificity array is a matter of summing
* over all the parts and adding the values to the right
* bucket in a specificity array.
*
* @interface Part
*/
export interface Part {
selector: string;
type: 'a' | 'b' | 'c';
index: number;
length: number;
}
/**
* Returned by the calculate function. Represents the results
* of parsing and calculating the specificity of a selector.
*
* @interface Specificity
*/
export interface Specificity {
selector: string;
specificity: string;
specificityArray: SpecificityArray;
parts: Array<Part>;
}
/**
* Calculates the specificity for the given selector string.
* If the string contains a comma, each selector will be parsed
* separately.
*
* @returns A list of specificity objects one for each selector in the
* selector string.
*/
export function calculate(selector: string): Array<Specificity>;
/**
* Compares two selectors. If a string, the string cannot contain a comma.
*
* @returns A value less than 0 if selector a is less specific than selector b.
* A value more than 0 if selector a is more specific than selector b.
* 0 if the two selectors have the same specificity.
*/
export function compare(a: string | SpecificityArray, b: string | SpecificityArray): -1 | 0 | 1;
// Calculate the specificity for a selector by dividing it into simple selectors and counting them
var calculate = function(input) {
var selectors,
selector,
i,
len,
results = [];
// Separate input by commas
selectors = input.split(',');
for (i = 0, len = selectors.length; i < len; i += 1) {
selector = selectors[i];
if (selector.length > 0) {
results.push(calculateSingle(selector));
}
}
return results;
};
/**
* Calculates the specificity of CSS selectors
* http://www.w3.org/TR/css3-selectors/#specificity
*
* Returns an object with the following properties:
* - selector: the input
* - specificity: e.g. 0,1,0,0
* - parts: array with details about each part of the selector that counts towards the specificity
* - specificityArray: e.g. [0, 1, 0, 0]
*/
var calculateSingle = function(input) {
var selector = input,
findMatch,
typeCount = {
'a': 0,
'b': 0,
'c': 0
},
parts = [],
// The following regular expressions assume that selectors matching the preceding regular expressions have been removed
attributeRegex = /(\[[^\]]+\])/g,
idRegex = /(#[^\#\s\+>~\.\[:\)]+)/g,
classRegex = /(\.[^\s\+>~\.\[:\)]+)/g,
pseudoElementRegex = /(::[^\s\+>~\.\[:]+|:first-line|:first-letter|:before|:after)/gi,
// A regex for pseudo classes with brackets - :nth-child(), :nth-last-child(), :nth-of-type(), :nth-last-type(), :lang()
// The negation psuedo class (:not) is filtered out because specificity is calculated on its argument
// :global and :local are filtered out - they look like psuedo classes but are an identifier for CSS Modules
pseudoClassWithBracketsRegex = /(:(?!not|global|local)[\w-]+\([^\)]*\))/gi,
// A regex for other pseudo classes, which don't have brackets
pseudoClassRegex = /(:(?!not|global|local)[^\s\+>~\.\[:]+)/g,
elementRegex = /([^\s\+>~\.\[:]+)/g;
// Find matches for a regular expression in a string and push their details to parts
// Type is "a" for IDs, "b" for classes, attributes and pseudo-classes and "c" for elements and pseudo-elements
findMatch = function(regex, type) {
var matches, i, len, match, index, length;
if (regex.test(selector)) {
matches = selector.match(regex);
for (i = 0, len = matches.length; i < len; i += 1) {
typeCount[type] += 1;
match = matches[i];
index = selector.indexOf(match);
length = match.length;
parts.push({
selector: input.substr(index, length),
type: type,
index: index,
length: length
});
// Replace this simple selector with whitespace so it won't be counted in further simple selectors
selector = selector.replace(match, Array(length + 1).join(' '));
}
}
};
// Replace escaped characters with plain text, using the "A" character
// https://www.w3.org/TR/CSS21/syndata.html#characters
(function() {
var replaceWithPlainText = function(regex) {
var matches, i, len, match;
if (regex.test(selector)) {
matches = selector.match(regex);
for (i = 0, len = matches.length; i < len; i += 1) {
match = matches[i];
selector = selector.replace(match, Array(match.length + 1).join('A'));
}
}
},
// Matches a backslash followed by six hexadecimal digits followed by an optional single whitespace character
escapeHexadecimalRegex = /\\[0-9A-Fa-f]{6}\s?/g,
// Matches a backslash followed by fewer than six hexadecimal digits followed by a mandatory single whitespace character
escapeHexadecimalRegex2 = /\\[0-9A-Fa-f]{1,5}\s/g,
// Matches a backslash followed by any character
escapeSpecialCharacter = /\\./g;
replaceWithPlainText(escapeHexadecimalRegex);
replaceWithPlainText(escapeHexadecimalRegex2);
replaceWithPlainText(escapeSpecialCharacter);
}());
// Remove anything after a left brace in case a user has pasted in a rule, not just a selector
(function() {
var regex = /{[^]*/gm,
matches, i, len, match;
if (regex.test(selector)) {
matches = selector.match(regex);
for (i = 0, len = matches.length; i < len; i += 1) {
match = matches[i];
selector = selector.replace(match, Array(match.length + 1).join(' '));
}
}
}());
// Add attribute selectors to parts collection (type b)
findMatch(attributeRegex, 'b');
// Add ID selectors to parts collection (type a)
findMatch(idRegex, 'a');
// Add class selectors to parts collection (type b)
findMatch(classRegex, 'b');
// Add pseudo-element selectors to parts collection (type c)
findMatch(pseudoElementRegex, 'c');
// Add pseudo-class selectors to parts collection (type b)
findMatch(pseudoClassWithBracketsRegex, 'b');
findMatch(pseudoClassRegex, 'b');
// Remove universal selector and separator characters
selector = selector.replace(/[\*\s\+>~]/g, ' ');
// Remove any stray dots or hashes which aren't attached to words
// These may be present if the user is live-editing this selector
selector = selector.replace(/[#\.]/g, ' ');
// Remove the negation psuedo-class (:not) but leave its argument because specificity is calculated on its argument
// Remove non-standard :local and :global CSS Module identifiers because they do not effect the specificity
selector = selector.replace(/:not/g, ' ');
selector = selector.replace(/:local/g, ' ');
selector = selector.replace(/:global/g, ' ');
selector = selector.replace(/[\(\)]/g, ' ');
// The only things left should be element selectors (type c)
findMatch(elementRegex, 'c');
// Order the parts in the order they appear in the original selector
// This is neater for external apps to deal with
parts.sort(function(a, b) {
return a.index - b.index;
});
return {
selector: input,
specificity: '0,' + typeCount.a.toString() + ',' + typeCount.b.toString() + ',' + typeCount.c.toString(),
specificityArray: [0, typeCount.a, typeCount.b, typeCount.c],
parts: parts
};
};
/**
* Compares two CSS selectors for specificity
* Alternatively you can replace one of the CSS selectors with a specificity array
*
* - it returns -1 if a has a lower specificity than b
* - it returns 1 if a has a higher specificity than b
* - it returns 0 if a has the same specificity than b
*/
var compare = function(a, b) {
var aSpecificity,
bSpecificity,
i;
if (typeof a ==='string') {
if (a.indexOf(',') !== -1) {
throw 'Invalid CSS selector';
} else {
aSpecificity = calculateSingle(a)['specificityArray'];
}
} else if (Array.isArray(a)) {
if (a.filter(function(e) { return (typeof e === 'number'); }).length !== 4) {
throw 'Invalid specificity array';
} else {
aSpecificity = a;
}
} else {
throw 'Invalid CSS selector or specificity array';
}
if (typeof b ==='string') {
if (b.indexOf(',') !== -1) {
throw 'Invalid CSS selector';
} else {
bSpecificity = calculateSingle(b)['specificityArray'];
}
} else if (Array.isArray(b)) {
if (b.filter(function(e) { return (typeof e === 'number'); }).length !== 4) {
throw 'Invalid specificity array';
} else {
bSpecificity = b;
}
} else {
throw 'Invalid CSS selector or specificity array';
}
for (i = 0; i < 4; i += 1) {
if (aSpecificity[i] < bSpecificity[i]) {
return -1;
} else if (aSpecificity[i] > bSpecificity[i]) {
return 1;
}
}
return 0;
};
export {
calculate,
compare
};
import { describe, it } from 'mocha';
import assert from 'assert';
import { calculate, compare } from '../specificity';
describe('calculate', () => {
[
// http://css-tricks.com/specifics-on-css-specificity/
{ selector: 'ul#nav li.active a', expected: '0,1,1,3' },
{ selector: 'body.ie7 .col_3 h2 ~ h2', expected: '0,0,2,3' },
{ selector: '#footer *:not(nav) li', expected: '0,1,0,2' },
{ selector: 'ul > li ul li ol li:first-letter', expected: '0,0,0,7' },
// http://reference.sitepoint.com/css/specificity
{ selector: 'body#home div#warning p.message', expected: '0,2,1,3' },
{ selector: '* body#home>div#warning p.message', expected: '0,2,1,3' },
{ selector: '#home #warning p.message', expected: '0,2,1,1' },
{ selector: '#warning p.message', expected: '0,1,1,1' },
{ selector: '#warning p', expected: '0,1,0,1' },
{ selector: 'p.message', expected: '0,0,1,1' },
{ selector: 'p', expected: '0,0,0,1' },
// Test pseudo-element with uppertestCase letters
{ selector: 'li:bEfoRE', expected: '0,0,0,2' },
// Pseudo-class tests
{ selector: 'li:first-child+p', expected: '0,0,1,2'},
{ selector: 'li:nth-child(even)+p', expected: '0,0,1,2'},
{ selector: 'li:nth-child(2n+1)+p', expected: '0,0,1,2'},
{ selector: 'li:nth-child( 2n + 1 )+p', expected: '0,0,1,2'},
{ selector: 'li:nth-child(2n-1)+p', expected: '0,0,1,2'},
{ selector: 'li:nth-child(2n-1) p', expected: '0,0,1,2'},
{ selector: ':lang(nl-be)', expected: '0,0,1,0'},
// Tests with CSS escape sequences
// https://mathiasbynens.be/notes/css-escapes and https://mathiasbynens.be/demo/crazy-class
{ selector: '.\\3A -\\)', expected: '0,0,1,0' }, /* <p class=":-)"></p> */
{ selector: '.\\3A \\`\\(', expected: '0,0,1,0' }, /* <p class=":`("></p> */
{ selector: '.\\3A .\\`\\(', expected: '0,0,2,0' }, /* <p class=": `("></p> */
{ selector: '.\\31 a2b3c', expected: '0,0,1,0' }, /* <p class="1a2b3c"></p> */
{ selector: '.\\000031a2b3c', expected: '0,0,1,0' }, /* <p class="1a2b3c"></p> */
{ selector: '.\\000031 a2b3c', expected: '0,0,1,0' }, /* <p class="1a2b3c"></p> */
{ selector: '#\\#fake-id', expected: '0,1,0,0' }, /* <p id="#fake-id"></p> */
{ selector: '.\\#fake-id', expected: '0,0,1,0' }, /* <p class="#fake-id"></p> */
{ selector: '#\\<p\\>', expected: '0,1,0,0' }, /* <p id="<p>"></p> */
{ selector: '.\\#\\.\\#\\.\\#', expected: '0,0,1,0' }, /* <p class="#.#.#"></p> */
{ selector: '.foo\\.bar', expected: '0,0,1,0' }, /* <p class="foo.bar"></p> */
{ selector: '.\\:hover\\:active', expected: '0,0,1,0' }, /* <p class=":hover:active"></p> */
{ selector: '.\\3A hover\\3A active', expected: '0,0,1,0' }, /* <p class=":hover:active"></p> */
{ selector: '.\\000031 p', expected: '0,0,1,1' }, /* <p class="1"><p></p></p>" */
{ selector: '.\\3A \\`\\( .another', expected: '0,0,2,0' }, /* <p class=":`("><p class="another"></p></p> */
{ selector: '.\\--cool', expected: '0,0,1,0' }, /* <p class="--cool"></p> */
{ selector: '#home .\\[page\\]', expected: '0,1,1,0' }, /* <p id="home"><p class="[page]"></p></p> */
// Test repeated IDs
// https://github.com/keeganstreet/specificity/issues/29
{ selector: 'ul#nav#nav-main li.active a', expected: '0,2,1,3' },
// Test CSS Modules https://github.com/css-modules/css-modules
// Whilst they are not part of the CSS spec, this calculator can support them without breaking results for standard selectors
{ selector: '.root :global .text', expected: '0,0,2,0' },
{ selector: '.localA :global .global-b :local(.local-c) .global-d', expected: '0,0,4,0' },
{ selector: '.localA :global .global-b .global-c :local(.localD.localE) .global-d', expected: '0,0,6,0' },
{ selector: '.localA :global(.global-b) .local-b', expected: '0,0,3,0' },
{ selector: ':local(:nth-child(2n) .test)', expected: '0,0,2,0' },
].forEach(testCase => {
it(`calculate("${testCase.selector}")`, () => {
const result = calculate(testCase.selector);
assert.equal(result[0].specificity, testCase.expected);
});
});
});
describe('compare', () => {
[
{ a: 'div', b: 'span', expected: 0 },
{ a: '.active', b: ':focus', expected: 0 },
{ a: '#header', b: '#main', expected: 0 },
{ a: 'div', b: '.active', expected: -1 },
{ a: 'div', b: '#header', expected: -1 },
{ a: '.active', b: '#header', expected: -1 },
{ a: '.active', b: 'div', expected: 1 },
{ a: '#main', b: 'div', expected: 1 },
{ a: '#main', b: ':focus', expected: 1 },
{ a: 'div p', b: 'span a', expected: 0 },
{ a: '#main p .active', b: '#main span :focus', expected: 0 },
{ a: [0, 1, 1, 1], b: '#main span :focus', expected: 0 },
{ a: '#main p .active', b: [0, 1, 1, 1], expected: 0 },
{ a: ':focus', b: 'span a', expected: 1 },
{ a: '#main', b: 'span a:hover', expected: 1 },
{ a: 'ul > li > a > span:before', b: '.active', expected: -1 },
{ a: 'a.active:hover', b: '#main', expected: -1 },
].forEach(testCase => {
it(`compare("${testCase.a}", "${testCase.b}")`, () => {
const result = compare(testCase.a, testCase.b);
assert.equal(result, testCase.expected);
});
});
});
describe('sorting with compare', () => {
const a = 'div';
const b = 'p a';
const c = '.active';
const d = 'p.active';
const e = '.active:focus';
const f = '#main';
const original = [c, f, a, e, b, d];
const sorted = [a, b, c, d, e, f];
const result = original.sort(compare);
it('Array.sort(compare) should sort the array by specificity', () => {
assert.equal(result.join('|'), sorted.join('|'));
});
});