source: trip-planner-front/node_modules/postcss-modules-scope/src/index.js@ b738035

Last change on this file since b738035 was 6a3a178, checked in by Ema <ema_spirova@…>, 3 years ago

initial commit

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