.gitignore added
[dotfiles/.git] / .config / coc / extensions / node_modules / coc-prettier / node_modules / specificity / specificity.js
1 var SPECIFICITY = (function() {
2         var calculate,
3                 calculateSingle,
4                 compare;
5
6         // Calculate the specificity for a selector by dividing it into simple selectors and counting them
7         calculate = function(input) {
8                 var selectors,
9                         selector,
10                         i,
11                         len,
12                         results = [];
13
14                 // Separate input by commas
15                 selectors = input.split(',');
16
17                 for (i = 0, len = selectors.length; i < len; i += 1) {
18                         selector = selectors[i];
19                         if (selector.length > 0) {
20                                 results.push(calculateSingle(selector));
21                         }
22                 }
23
24                 return results;
25         };
26
27         /**
28          * Calculates the specificity of CSS selectors
29          * http://www.w3.org/TR/css3-selectors/#specificity
30          *
31          * Returns an object with the following properties:
32          *  - selector: the input
33          *  - specificity: e.g. 0,1,0,0
34          *  - parts: array with details about each part of the selector that counts towards the specificity
35          *  - specificityArray: e.g. [0, 1, 0, 0]
36          */
37         calculateSingle = function(input) {
38                 var selector = input,
39                         findMatch,
40                         typeCount = {
41                                 'a': 0,
42                                 'b': 0,
43                                 'c': 0
44                         },
45                         parts = [],
46                         // The following regular expressions assume that selectors matching the preceding regular expressions have been removed
47                         attributeRegex = /(\[[^\]]+\])/g,
48                         idRegex = /(#[^\#\s\+>~\.\[:]+)/g,
49                         classRegex = /(\.[^\s\+>~\.\[:]+)/g,
50                         pseudoElementRegex = /(::[^\s\+>~\.\[:]+|:first-line|:first-letter|:before|:after)/gi,
51                         // A regex for pseudo classes with brackets - :nth-child(), :nth-last-child(), :nth-of-type(), :nth-last-type(), :lang()
52                         pseudoClassWithBracketsRegex = /(:[\w-]+\([^\)]*\))/gi,
53                         // A regex for other pseudo classes, which don't have brackets
54                         pseudoClassRegex = /(:[^\s\+>~\.\[:]+)/g,
55                         elementRegex = /([^\s\+>~\.\[:]+)/g;
56
57                 // Find matches for a regular expression in a string and push their details to parts
58                 // Type is "a" for IDs, "b" for classes, attributes and pseudo-classes and "c" for elements and pseudo-elements
59                 findMatch = function(regex, type) {
60                         var matches, i, len, match, index, length;
61                         if (regex.test(selector)) {
62                                 matches = selector.match(regex);
63                                 for (i = 0, len = matches.length; i < len; i += 1) {
64                                         typeCount[type] += 1;
65                                         match = matches[i];
66                                         index = selector.indexOf(match);
67                                         length = match.length;
68                                         parts.push({
69                                                 selector: input.substr(index, length),
70                                                 type: type,
71                                                 index: index,
72                                                 length: length
73                                         });
74                                         // Replace this simple selector with whitespace so it won't be counted in further simple selectors
75                                         selector = selector.replace(match, Array(length + 1).join(' '));
76                                 }
77                         }
78                 };
79
80                 // Replace escaped characters with plain text, using the "A" character
81                 // https://www.w3.org/TR/CSS21/syndata.html#characters
82                 (function() {
83                         var replaceWithPlainText = function(regex) {
84                                         var matches, i, len, match;
85                                         if (regex.test(selector)) {
86                                                 matches = selector.match(regex);
87                                                 for (i = 0, len = matches.length; i < len; i += 1) {
88                                                         match = matches[i];
89                                                         selector = selector.replace(match, Array(match.length + 1).join('A'));
90                                                 }
91                                         }
92                                 },
93                                 // Matches a backslash followed by six hexadecimal digits followed by an optional single whitespace character
94                                 escapeHexadecimalRegex = /\\[0-9A-Fa-f]{6}\s?/g,
95                                 // Matches a backslash followed by fewer than six hexadecimal digits followed by a mandatory single whitespace character
96                                 escapeHexadecimalRegex2 = /\\[0-9A-Fa-f]{1,5}\s/g,
97                                 // Matches a backslash followed by any character
98                                 escapeSpecialCharacter = /\\./g;
99
100                         replaceWithPlainText(escapeHexadecimalRegex);
101                         replaceWithPlainText(escapeHexadecimalRegex2);
102                         replaceWithPlainText(escapeSpecialCharacter);
103                 }());
104
105                 // Remove the negation psuedo-class (:not) but leave its argument because specificity is calculated on its argument
106                 (function() {
107                         var regex = /:not\(([^\)]*)\)/g;
108                         if (regex.test(selector)) {
109                                 selector = selector.replace(regex, '     $1 ');
110                         }
111                 }());
112
113                 // Remove anything after a left brace in case a user has pasted in a rule, not just a selector
114                 (function() {
115                         var regex = /{[^]*/gm,
116                                 matches, i, len, match;
117                         if (regex.test(selector)) {
118                                 matches = selector.match(regex);
119                                 for (i = 0, len = matches.length; i < len; i += 1) {
120                                         match = matches[i];
121                                         selector = selector.replace(match, Array(match.length + 1).join(' '));
122                                 }
123                         }
124                 }());
125
126                 // Add attribute selectors to parts collection (type b)
127                 findMatch(attributeRegex, 'b');
128
129                 // Add ID selectors to parts collection (type a)
130                 findMatch(idRegex, 'a');
131
132                 // Add class selectors to parts collection (type b)
133                 findMatch(classRegex, 'b');
134
135                 // Add pseudo-element selectors to parts collection (type c)
136                 findMatch(pseudoElementRegex, 'c');
137
138                 // Add pseudo-class selectors to parts collection (type b)
139                 findMatch(pseudoClassWithBracketsRegex, 'b');
140                 findMatch(pseudoClassRegex, 'b');
141
142                 // Remove universal selector and separator characters
143                 selector = selector.replace(/[\*\s\+>~]/g, ' ');
144
145                 // Remove any stray dots or hashes which aren't attached to words
146                 // These may be present if the user is live-editing this selector
147                 selector = selector.replace(/[#\.]/g, ' ');
148
149                 // The only things left should be element selectors (type c)
150                 findMatch(elementRegex, 'c');
151
152                 // Order the parts in the order they appear in the original selector
153                 // This is neater for external apps to deal with
154                 parts.sort(function(a, b) {
155                         return a.index - b.index;
156                 });
157
158                 return {
159                         selector: input,
160                         specificity: '0,' + typeCount.a.toString() + ',' + typeCount.b.toString() + ',' + typeCount.c.toString(),
161                         specificityArray: [0, typeCount.a, typeCount.b, typeCount.c],
162                         parts: parts
163                 };
164         };
165
166         /**
167          * Compares two CSS selectors for specificity
168          * Alternatively you can replace one of the CSS selectors with a specificity array
169          *
170          *  - it returns -1 if a has a lower specificity than b
171          *  - it returns 1 if a has a higher specificity than b
172          *  - it returns 0 if a has the same specificity than b
173          */
174         compare = function(a, b) {
175                 var aSpecificity,
176                         bSpecificity,
177                         i;
178
179                 if (typeof a ==='string') {
180                         if (a.indexOf(',') !== -1) {
181                                 throw 'Invalid CSS selector';
182                         } else {
183                                 aSpecificity = calculateSingle(a)['specificityArray'];
184                         }
185                 } else if (Array.isArray(a)) {
186                         if (a.filter(function(e) { return (typeof e === 'number'); }).length !== 4) {
187                                 throw 'Invalid specificity array';
188                         } else {
189                                 aSpecificity = a;
190                         }
191                 } else {
192                         throw 'Invalid CSS selector or specificity array';
193                 }
194
195                 if (typeof b ==='string') {
196                         if (b.indexOf(',') !== -1) {
197                                 throw 'Invalid CSS selector';
198                         } else {
199                                 bSpecificity = calculateSingle(b)['specificityArray'];
200                         }
201                 } else if (Array.isArray(b)) {
202                         if (b.filter(function(e) { return (typeof e === 'number'); }).length !== 4) {
203                                 throw 'Invalid specificity array';
204                         } else {
205                                 bSpecificity = b;
206                         }
207                 } else {
208                         throw 'Invalid CSS selector or specificity array';
209                 }
210
211                 for (i = 0; i < 4; i += 1) {
212                         if (aSpecificity[i] < bSpecificity[i]) {
213                                 return -1;
214                         } else if (aSpecificity[i] > bSpecificity[i]) {
215                                 return 1;
216                         }
217                 }
218
219                 return 0;
220         };
221
222         return {
223                 calculate: calculate,
224                 compare: compare
225         };
226 }());
227
228 // Export for Node JS
229 if (typeof exports !== 'undefined') {
230         exports.calculate = SPECIFICITY.calculate;
231         exports.compare = SPECIFICITY.compare;
232 }