File: cssspecificity.js

package info (click to toggle)
chromium 138.0.7204.183-1
  • links: PTS, VCS
  • area: main
  • in suites: trixie
  • size: 6,071,908 kB
  • sloc: cpp: 34,937,088; ansic: 7,176,967; javascript: 4,110,704; python: 1,419,953; asm: 946,768; xml: 739,971; pascal: 187,324; sh: 89,623; perl: 88,663; objc: 79,944; sql: 50,304; cs: 41,786; fortran: 24,137; makefile: 21,806; php: 13,980; tcl: 13,166; yacc: 8,925; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (161 lines) | stat: -rw-r--r-- 5,893 bytes parent folder | download | duplicates (5)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
/**
 * @license
 * Copyright The Closure Library Authors.
 * SPDX-License-Identifier: Apache-2.0
 */

/** @fileoverview Calculator for specificity of CSS selectors. */

goog.module('goog.html.CssSpecificity');
goog.module.declareLegacyNamespace();


/**
 * Cached mapping from selectors to specificities.
 * @type {!Object<string, !Array<number>>}
 */
var specificityCache = {};

/**
 * Calculates the specificity of CSS selectors, using a global cache if
 * supported.
 * @see http://www.w3.org/TR/css3-selectors/#specificity
 * @see https://specificity.keegan.st/
 * @param {string} selector The CSS selector.
 * @return {!Array<number>} The CSS specificity.
 * @supported IE9+, other browsers.
 */
function getSpecificity(selector) {
  var specificity = specificityCache.hasOwnProperty(selector) ?
      specificityCache[selector] :
      null;
  if (specificity) {
    return specificity;
  }
  if (Object.keys(specificityCache).length > (1 << 16)) {
    // Limit the size of cache to (1 << 16) == 65536. Normally HTML pages don't
    // have such numbers of selectors.
    specificityCache = {};
  }
  specificity = calculateSpecificity(selector);
  specificityCache[selector] = specificity;
  return specificity;
}

/**
 * Find matches for a regular expression in the selector and increase count.
 * @param {string} selector The selector to match the regex with.
 * @param {!Array<number>} specificity The current specificity.
 * @param {!RegExp} regex The regular expression.
 * @param {number} typeIndex Index of type count.
 * @return {string}
 */
function replaceWithEmptyText(selector, specificity, regex, typeIndex) {
  return selector.replace(regex, function(match) {
    specificity[typeIndex] += 1;
    // Replace this simple selector with whitespace so it won't be counted
    // in further simple selectors.
    return Array(match.length + 1).join(' ');
  });
}

/**
 * Replace escaped characters with plain text, using the "A" character.
 * @see https://www.w3.org/TR/CSS21/syndata.html#characters
 * @param {string} selector
 * @param {!RegExp} regex
 * @return {string}
 */
function replaceWithPlainText(selector, regex) {
  return selector.replace(regex, function(match) {
    return Array(match.length + 1).join('A');
  });
}

/**
 * Calculates the specificity of CSS selectors
 * @see http://www.w3.org/TR/css3-selectors/#specificity
 * @see https://github.com/keeganstreet/specificity
 * @see https://specificity.keegan.st/
 * @param {string} selector
 * @return {!Array<number>} The CSS specificity.
 */
function calculateSpecificity(selector) {
  var specificity = [0, 0, 0, 0];

  // Cannot use RegExp literals for all regular expressions, IE does not accept
  // the syntax.

  // Matches a backslash followed by six hexadecimal digits followed by an
  // optional single whitespace character.
  var escapeHexadecimalRegex = new RegExp('\\\\[0-9A-Fa-f]{6}\\s?', 'g');
  // Matches a backslash followed by fewer than six hexadecimal digits
  // followed by a mandatory single whitespace character.
  var escapeHexadecimalRegex2 = new RegExp('\\\\[0-9A-Fa-f]{1,5}\\s', 'g');
  // Matches a backslash followed by any character
  var escapeSpecialCharacter = /\\./g;
  selector = replaceWithPlainText(selector, escapeHexadecimalRegex);
  selector = replaceWithPlainText(selector, escapeHexadecimalRegex2);
  selector = replaceWithPlainText(selector, escapeSpecialCharacter);

  // Remove the negation pseudo-class (:not) but leave its argument because
  // specificity is calculated on its argument.
  var pseudoClassWithNotRegex = new RegExp(':not\\(([^\\)]*)\\)', 'g');
  selector = selector.replace(pseudoClassWithNotRegex, '     $1 ');

  // Remove anything after a left brace in case a user has pasted in a rule,
  // not just a selector.
  var rulesRegex = new RegExp('{[^]*', 'gm');
  selector = selector.replace(rulesRegex, '');

  // The following regular expressions assume that selectors matching the
  // preceding regular expressions have been removed.

  // SPECIFICITY 2: Counts attribute selectors.
  var attributeRegex = new RegExp('(\\[[^\\]]+\\])', 'g');
  selector = replaceWithEmptyText(selector, specificity, attributeRegex, 2);

  // SPECIFICITY 1: Counts ID selectors.
  var idRegex = new RegExp('(#[^\\#\\s\\+>~\\.\\[:]+)', 'g');
  selector = replaceWithEmptyText(selector, specificity, idRegex, 1);

  // SPECIFICITY 2: Counts class selectors.
  var classRegex = new RegExp('(\\.[^\\s\\+>~\\.\\[:]+)', 'g');
  selector = replaceWithEmptyText(selector, specificity, classRegex, 2);

  // SPECIFICITY 3: Counts pseudo-element selectors.
  var pseudoElementRegex =
      /(::[^\s\+>~\.\[:]+|:first-line|:first-letter|:before|:after)/gi;
  selector = replaceWithEmptyText(selector, specificity, pseudoElementRegex, 3);

  // SPECIFICITY 2: Counts pseudo-class selectors.
  // A regex for pseudo classes with brackets. For example:
  //   :nth-child()
  //   :nth-last-child()
  //   :nth-of-type()
  //   :nth-last-type()
  //   :lang()
  var pseudoClassWithBracketsRegex = /(:[\w-]+\([^\)]*\))/gi;
  selector = replaceWithEmptyText(
      selector, specificity, pseudoClassWithBracketsRegex, 2);
  // A regex for other pseudo classes, which don't have brackets.
  var pseudoClassRegex = /(:[^\s\+>~\.\[:]+)/g;
  selector = replaceWithEmptyText(selector, specificity, pseudoClassRegex, 2);

  // 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, ' ');

  // SPECIFICITY 3: The only things left should be element selectors.
  var elementRegex = /([^\s\+>~\.\[:]+)/g;
  selector = replaceWithEmptyText(selector, specificity, elementRegex, 3);

  return specificity;
}

exports = {
  getSpecificity: getSpecificity
};