source: frontend/node_modules/postcss-modules-scope/src/index.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: 10.3 KB
Line 
1"use strict";
2
3const selectorParser = require("postcss-selector-parser");
4
5const hasOwnProperty = Object.prototype.hasOwnProperty;
6
7function isNestedRule(rule) {
8 if (!rule.parent || rule.parent.type === "root") {
9 return false;
10 }
11
12 if (rule.parent.type === "rule") {
13 return true;
14 }
15
16 return isNestedRule(rule.parent);
17}
18
19function getSingleLocalNamesForComposes(root, rule) {
20 if (isNestedRule(rule)) {
21 throw new Error(`composition is not allowed in nested rule \n\n${rule}`);
22 }
23
24 return root.nodes.map((node) => {
25 if (node.type !== "selector" || node.nodes.length !== 1) {
26 throw new Error(
27 `composition is only allowed when selector is single :local class name not in "${root}"`
28 );
29 }
30
31 node = node.nodes[0];
32
33 if (
34 node.type !== "pseudo" ||
35 node.value !== ":local" ||
36 node.nodes.length !== 1
37 ) {
38 throw new Error(
39 'composition is only allowed when selector is single :local class name not in "' +
40 root +
41 '", "' +
42 node +
43 '" is weird'
44 );
45 }
46
47 node = node.first;
48
49 if (node.type !== "selector" || node.length !== 1) {
50 throw new Error(
51 'composition is only allowed when selector is single :local class name not in "' +
52 root +
53 '", "' +
54 node +
55 '" is weird'
56 );
57 }
58
59 node = node.first;
60
61 if (node.type !== "class") {
62 // 'id' is not possible, because you can't compose ids
63 throw new Error(
64 'composition is only allowed when selector is single :local class name not in "' +
65 root +
66 '", "' +
67 node +
68 '" is weird'
69 );
70 }
71
72 return node.value;
73 });
74}
75
76const whitespace = "[\\x20\\t\\r\\n\\f]";
77const unescapeRegExp = new RegExp(
78 "\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)",
79 "ig"
80);
81
82function unescape(str) {
83 return str.replace(unescapeRegExp, (_, escaped, escapedWhitespace) => {
84 const high = "0x" + escaped - 0x10000;
85
86 // NaN means non-codepoint
87 // Workaround erroneous numeric interpretation of +"0x"
88 return high !== high || escapedWhitespace
89 ? escaped
90 : high < 0
91 ? // BMP codepoint
92 String.fromCharCode(high + 0x10000)
93 : // Supplemental Plane codepoint (surrogate pair)
94 String.fromCharCode((high >> 10) | 0xd800, (high & 0x3ff) | 0xdc00);
95 });
96}
97
98const plugin = (options = {}) => {
99 const generateScopedName =
100 (options && options.generateScopedName) || plugin.generateScopedName;
101 const generateExportEntry =
102 (options && options.generateExportEntry) || plugin.generateExportEntry;
103 const exportGlobals = options && options.exportGlobals;
104
105 return {
106 postcssPlugin: "postcss-modules-scope",
107 Once(root, { rule }) {
108 const exports = Object.create(null);
109
110 function exportScopedName(name, rawName, node) {
111 const scopedName = generateScopedName(
112 rawName ? rawName : name,
113 root.source.input.from,
114 root.source.input.css,
115 node
116 );
117 const exportEntry = generateExportEntry(
118 rawName ? rawName : name,
119 scopedName,
120 root.source.input.from,
121 root.source.input.css,
122 node
123 );
124 const { key, value } = exportEntry;
125
126 exports[key] = exports[key] || [];
127
128 if (exports[key].indexOf(value) < 0) {
129 exports[key].push(value);
130 }
131
132 return scopedName;
133 }
134
135 function localizeNode(node) {
136 switch (node.type) {
137 case "selector":
138 node.nodes = node.map((item) => localizeNode(item));
139 return node;
140 case "class":
141 return selectorParser.className({
142 value: exportScopedName(
143 node.value,
144 node.raws && node.raws.value ? node.raws.value : null,
145 node
146 ),
147 });
148 case "id": {
149 return selectorParser.id({
150 value: exportScopedName(
151 node.value,
152 node.raws && node.raws.value ? node.raws.value : null,
153 node
154 ),
155 });
156 }
157 case "attribute": {
158 if (node.attribute === "class" && node.operator === "=") {
159 return selectorParser.attribute({
160 attribute: node.attribute,
161 operator: node.operator,
162 quoteMark: "'",
163 value: exportScopedName(node.value, null, null),
164 });
165 }
166 }
167 }
168
169 throw new Error(
170 `${node.type} ("${node}") is not allowed in a :local block`
171 );
172 }
173
174 function traverseNode(node) {
175 switch (node.type) {
176 case "pseudo":
177 if (node.value === ":local") {
178 if (node.nodes.length !== 1) {
179 throw new Error('Unexpected comma (",") in :local block');
180 }
181
182 const selector = localizeNode(node.first);
183 // move the spaces that were around the pseudo selector to the first
184 // non-container node
185 selector.first.spaces = node.spaces;
186
187 const nextNode = node.next();
188
189 if (
190 nextNode &&
191 nextNode.type === "combinator" &&
192 nextNode.value === " " &&
193 /\\[A-F0-9]{1,6}$/.test(selector.last.value)
194 ) {
195 selector.last.spaces.after = " ";
196 }
197
198 node.replaceWith(selector);
199
200 return;
201 }
202 /* falls through */
203 case "root":
204 case "selector": {
205 node.each((item) => traverseNode(item));
206 break;
207 }
208 case "id":
209 case "class":
210 if (exportGlobals) {
211 exports[node.value] = [node.value];
212 }
213 break;
214 }
215 return node;
216 }
217
218 // Find any :import and remember imported names
219 const importedNames = {};
220
221 root.walkRules(/^:import\(.+\)$/, (rule) => {
222 rule.walkDecls((decl) => {
223 importedNames[decl.prop] = true;
224 });
225 });
226
227 // Find any :local selectors
228 root.walkRules((rule) => {
229 let parsedSelector = selectorParser().astSync(rule);
230
231 rule.selector = traverseNode(parsedSelector.clone()).toString();
232
233 rule.walkDecls(/^(composes|compose-with)$/i, (decl) => {
234 const localNames = getSingleLocalNamesForComposes(
235 parsedSelector,
236 decl.parent
237 );
238 const multiple = decl.value.split(",");
239
240 multiple.forEach((value) => {
241 const classes = value.trim().split(/\s+/);
242
243 classes.forEach((className) => {
244 const global = /^global\(([^)]+)\)$/.exec(className);
245
246 if (global) {
247 localNames.forEach((exportedName) => {
248 exports[exportedName].push(global[1]);
249 });
250 } else if (hasOwnProperty.call(importedNames, className)) {
251 localNames.forEach((exportedName) => {
252 exports[exportedName].push(className);
253 });
254 } else if (hasOwnProperty.call(exports, className)) {
255 localNames.forEach((exportedName) => {
256 exports[className].forEach((item) => {
257 exports[exportedName].push(item);
258 });
259 });
260 } else {
261 throw decl.error(
262 `referenced class name "${className}" in ${decl.prop} not found`
263 );
264 }
265 });
266 });
267
268 decl.remove();
269 });
270
271 // Find any :local values
272 rule.walkDecls((decl) => {
273 if (!/:local\s*\((.+?)\)/.test(decl.value)) {
274 return;
275 }
276
277 let tokens = decl.value.split(/(,|'[^']*'|"[^"]*")/);
278
279 tokens = tokens.map((token, idx) => {
280 if (idx === 0 || tokens[idx - 1] === ",") {
281 let result = token;
282
283 const localMatch = /:local\s*\((.+?)\)/.exec(token);
284
285 if (localMatch) {
286 const input = localMatch.input;
287 const matchPattern = localMatch[0];
288 const matchVal = localMatch[1];
289 const newVal = exportScopedName(matchVal);
290
291 result = input.replace(matchPattern, newVal);
292 } else {
293 return token;
294 }
295
296 return result;
297 } else {
298 return token;
299 }
300 });
301
302 decl.value = tokens.join("");
303 });
304 });
305
306 // Find any :local keyframes
307 root.walkAtRules(/keyframes$/i, (atRule) => {
308 const localMatch = /^\s*:local\s*\((.+?)\)\s*$/.exec(atRule.params);
309
310 if (!localMatch) {
311 return;
312 }
313
314 atRule.params = exportScopedName(localMatch[1]);
315 });
316
317 root.walkAtRules(/scope$/i, (atRule) => {
318 if (atRule.params) {
319 atRule.params = atRule.params
320 .split("to")
321 .map((item) => {
322 const selector = item.trim().slice(1, -1).trim();
323
324 const localMatch = /^\s*:local\s*\((.+?)\)\s*$/.exec(selector);
325
326 if (!localMatch) {
327 return `(${selector})`;
328 }
329
330 let parsedSelector = selectorParser().astSync(selector);
331
332 return `(${traverseNode(parsedSelector).toString()})`;
333 })
334 .join(" to ");
335 }
336 });
337
338 // If we found any :locals, insert an :export rule
339 const exportedNames = Object.keys(exports);
340
341 if (exportedNames.length > 0) {
342 const exportRule = rule({ selector: ":export" });
343
344 exportedNames.forEach((exportedName) =>
345 exportRule.append({
346 prop: exportedName,
347 value: exports[exportedName].join(" "),
348 raws: { before: "\n " },
349 })
350 );
351
352 root.append(exportRule);
353 }
354 },
355 };
356};
357
358plugin.postcss = true;
359
360plugin.generateScopedName = function (name, path) {
361 const sanitisedPath = path
362 .replace(/\.[^./\\]+$/, "")
363 .replace(/[\W_]+/g, "_")
364 .replace(/^_|_$/g, "");
365
366 return `_${sanitisedPath}__${name}`.trim();
367};
368
369plugin.generateExportEntry = function (name, scopedName) {
370 return {
371 key: unescape(name),
372 value: unescape(scopedName),
373 };
374};
375
376module.exports = plugin;
Note: See TracBrowser for help on using the repository browser.