source: frontend/node_modules/postcss-merge-rules/src/lib/ensureCompatibility.js

Last change on this file was 9af201e, checked in by MBK <marija.karapandzova@…>, 12 days ago

Fix frontend appearance

  • Property mode set to 100644
File size: 6.4 KB
Line 
1'use strict';
2const { isSupported } = require('caniuse-api');
3const selectorParser = require('postcss-selector-parser');
4
5const simpleSelectorRe = /^#?[-._a-z0-9 ]+$/i;
6
7const cssSel2 = 'css-sel2';
8const cssSel3 = 'css-sel3';
9const cssGencontent = 'css-gencontent';
10const cssFirstLetter = 'css-first-letter';
11const cssFirstLine = 'css-first-line';
12const cssInOutOfRange = 'css-in-out-of-range';
13const formValidation = 'form-validation';
14
15const vendorPrefix =
16 /-(ah|apple|atsc|epub|hp|khtml|moz|ms|o|rim|ro|tc|wap|webkit|xv)-/;
17
18const level2Sel = new Set(['=', '~=', '|=']);
19const level3Sel = new Set(['^=', '$=', '*=']);
20
21/**
22 * @param {string} selector
23 * @return {RegExpMatchArray | null}
24 */
25function filterPrefixes(selector) {
26 return selector.match(vendorPrefix);
27}
28
29/**
30 * Internet Explorer use :-ms-input-placeholder.
31 * Microsoft Edge use ::-ms-input-placeholder.
32 *
33 * @type {(selector: string) => number}
34 */
35const findMsInputPlaceholder = (selector) =>
36 ~selector.search(/-ms-input-placeholder/i);
37
38/**
39 * @param {string[]} selectorsA
40 * @param {string[]} selectorsB
41 * @return {boolean}
42 */
43function sameVendor(selectorsA, selectorsB) {
44 /** @type {(selectors: string[]) => string} */
45 let same = (selectors) => selectors.map(filterPrefixes).join();
46 /** @type {(selectors: string[]) => string | undefined} */
47 let findMsVendor = (selectors) => selectors.find(findMsInputPlaceholder);
48 return (
49 same(selectorsA) === same(selectorsB) &&
50 !(findMsVendor(selectorsA) && findMsVendor(selectorsB))
51 );
52}
53
54/**
55 * @param {string} selector
56 * @return {boolean}
57 */
58function noVendor(selector) {
59 return !vendorPrefix.test(selector);
60}
61
62const pseudoElements = {
63 ':active': cssSel2,
64 ':after': cssGencontent,
65 ':any-link': 'css-any-link',
66 ':before': cssGencontent,
67 ':checked': cssSel3,
68 ':default': 'css-default-pseudo',
69 ':dir': 'css-dir-pseudo',
70 ':disabled': cssSel3,
71 ':empty': cssSel3,
72 ':enabled': cssSel3,
73 ':first-child': cssSel2,
74 ':first-letter': cssFirstLetter,
75 ':first-line': cssFirstLine,
76 ':first-of-type': cssSel3,
77 ':focus': cssSel2,
78 ':focus-within': 'css-focus-within',
79 ':focus-visible': 'css-focus-visible',
80 ':has': 'css-has',
81 ':hover': cssSel2,
82 ':in-range': cssInOutOfRange,
83 ':indeterminate': 'css-indeterminate-pseudo',
84 ':invalid': formValidation,
85 ':is': 'css-matches-pseudo',
86 ':lang': cssSel2,
87 ':last-child': cssSel3,
88 ':last-of-type': cssSel3,
89 ':link': cssSel2,
90 ':matches': 'css-matches-pseudo',
91 ':not': cssSel3,
92 ':nth-child': cssSel3,
93 ':nth-last-child': cssSel3,
94 ':nth-last-of-type': cssSel3,
95 ':nth-of-type': cssSel3,
96 ':only-child': cssSel3,
97 ':only-of-type': cssSel3,
98 ':optional': 'css-optional-pseudo',
99 ':out-of-range': cssInOutOfRange,
100 ':placeholder-shown': 'css-placeholder-shown',
101 ':required': formValidation,
102 ':root': cssSel3,
103 ':target': cssSel3,
104 '::after': cssGencontent,
105 '::backdrop': 'dialog',
106 '::before': cssGencontent,
107 '::first-letter': cssFirstLetter,
108 '::first-line': cssFirstLine,
109 '::marker': 'css-marker-pseudo',
110 '::placeholder': 'css-placeholder',
111 '::selection': 'css-selection',
112 ':valid': formValidation,
113 ':visited': cssSel2,
114};
115
116/**
117 * @param {string} selector
118 * @return {boolean}
119 */
120function isCssMixin(selector) {
121 return selector[selector.length - 1] === ':';
122}
123
124/**
125 * @param {string} selector
126 * @return {boolean}
127 */
128function isHostPseudoClass(selector) {
129 return selector.includes(':host');
130}
131
132const isSupportedCache = new Map();
133
134// Move to util in future
135/**
136 * @param {string} feature
137 * @param {string[] | undefined} browsers
138 */
139function isSupportedCached(feature, browsers) {
140 const key = JSON.stringify({ feature, browsers });
141 let result = isSupportedCache.get(key);
142
143 if (!result) {
144 result = isSupported(feature, /** @type {string[]} */ (browsers));
145 isSupportedCache.set(key, result);
146 }
147
148 return result;
149}
150
151/**
152 * @param {string[]} selectors
153 * @param{string[]=} browsers
154 * @param{Map<string,boolean>=} compatibilityCache
155 * @return {boolean}
156 */
157function ensureCompatibility(selectors, browsers, compatibilityCache) {
158 // Should not merge mixins
159 if (selectors.some(isCssMixin)) {
160 return false;
161 }
162
163 // Should not merge :host selector https://github.com/angular/angular-cli/issues/18672
164 if (selectors.some(isHostPseudoClass)) {
165 return false;
166 }
167 return selectors.every((selector) => {
168 if (simpleSelectorRe.test(selector)) {
169 return true;
170 }
171 if (compatibilityCache && compatibilityCache.has(selector)) {
172 return compatibilityCache.get(selector);
173 }
174 let compatible = true;
175 selectorParser((ast) => {
176 ast.walk((node) => {
177 const { type, value } = node;
178 if (type === 'pseudo') {
179 const entry =
180 pseudoElements[/** @type {keyof pseudoElements} */ (value)];
181 if (!entry && noVendor(value)) {
182 compatible = false;
183 }
184 if (entry && compatible) {
185 compatible = isSupportedCached(entry, browsers);
186 }
187 }
188 if (type === 'combinator') {
189 if (value.includes('~')) {
190 compatible = isSupportedCached(cssSel3, browsers);
191 }
192 if (value.includes('>') || value.includes('+')) {
193 compatible = isSupportedCached(cssSel2, browsers);
194 }
195 }
196 if (type === 'attribute' && node.attribute) {
197 // [foo]
198 if (!node.operator) {
199 compatible = isSupportedCached(cssSel2, browsers);
200 }
201 if (value) {
202 // [foo="bar"], [foo~="bar"], [foo|="bar"]
203 if (level2Sel.has(/** @type {string} */ (node.operator))) {
204 compatible = isSupportedCached(cssSel2, browsers);
205 }
206 // [foo^="bar"], [foo$="bar"], [foo*="bar"]
207 if (level3Sel.has(/** @type {string} */ (node.operator))) {
208 compatible = isSupportedCached(cssSel3, browsers);
209 }
210 }
211
212 // [foo="bar" i]
213 if (node.insensitive) {
214 compatible = isSupportedCached('css-case-insensitive', browsers);
215 }
216 }
217 if (!compatible) {
218 // If this node was not compatible,
219 // break out early from walking the rest
220 return false;
221 }
222 });
223 }).processSync(selector);
224 if (compatibilityCache) {
225 compatibilityCache.set(selector, compatible);
226 }
227 return compatible;
228 });
229}
230
231module.exports = { sameVendor, noVendor, pseudoElements, ensureCompatibility };
Note: See TracBrowser for help on using the repository browser.