@css-modules-kit/core 0.8.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/checker.d.ts.map +1 -1
- package/dist/checker.js +37 -54
- package/dist/checker.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +0 -1
- package/dist/config.js.map +1 -1
- package/dist/dts-generator.d.ts +3 -0
- package/dist/dts-generator.d.ts.map +1 -1
- package/dist/dts-generator.js +205 -72
- package/dist/dts-generator.js.map +1 -1
- package/dist/parser/rule-parser.d.ts.map +1 -1
- package/dist/parser/rule-parser.js +13 -3
- package/dist/parser/rule-parser.js.map +1 -1
- package/dist/resolver.d.ts.map +1 -1
- package/dist/resolver.js +3 -4
- package/dist/resolver.js.map +1 -1
- package/dist/util.d.ts +12 -1
- package/dist/util.d.ts.map +1 -1
- package/dist/util.js +26 -8
- package/dist/util.js.map +1 -1
- package/package.json +1 -1
- package/src/checker.ts +38 -57
- package/src/config.ts +0 -1
- package/src/dts-generator.ts +221 -70
- package/src/parser/rule-parser.ts +13 -3
- package/src/resolver.ts +3 -4
- package/src/typing/typescript.d.ts +1 -1
- package/src/util.ts +32 -8
package/src/dts-generator.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { CSSModule, Token, TokenImporter } from './type.js';
|
|
2
|
-
import {
|
|
2
|
+
import type { ValidateTokenNameOptions } from './util.js';
|
|
3
|
+
import { isURLSpecifier, validateTokenName } from './util.js';
|
|
3
4
|
|
|
4
5
|
export const STYLES_EXPORT_NAME = 'styles';
|
|
5
6
|
|
|
@@ -17,6 +18,8 @@ interface CodeMapping {
|
|
|
17
18
|
lengths: number[];
|
|
18
19
|
/** The generated offsets of the tokens in the *.d.ts file. */
|
|
19
20
|
generatedOffsets: number[];
|
|
21
|
+
/** The lengths of the tokens in the *.d.ts file. If not provided, it is assumed to be the same as `lengths`. */
|
|
22
|
+
generatedLengths?: number[];
|
|
20
23
|
}
|
|
21
24
|
|
|
22
25
|
/** The map linking the two codes in *.d.ts */
|
|
@@ -35,6 +38,7 @@ interface LinkedCodeMapping extends CodeMapping {
|
|
|
35
38
|
interface GenerateDtsResult {
|
|
36
39
|
text: string;
|
|
37
40
|
mapping: CodeMapping;
|
|
41
|
+
secondaryMapping?: CodeMapping;
|
|
38
42
|
linkedCodeMapping: LinkedCodeMapping;
|
|
39
43
|
}
|
|
40
44
|
|
|
@@ -43,7 +47,7 @@ interface GenerateDtsResult {
|
|
|
43
47
|
*/
|
|
44
48
|
export function generateDts(cssModule: CSSModule, options: GenerateDtsOptions): GenerateDtsResult {
|
|
45
49
|
// Exclude invalid tokens
|
|
46
|
-
const localTokens = cssModule.localTokens.filter((token) =>
|
|
50
|
+
const localTokens = cssModule.localTokens.filter((token) => isValidTokenName(token.name, options));
|
|
47
51
|
const tokenImporters = cssModule.tokenImporters
|
|
48
52
|
// Exclude invalid imported tokens
|
|
49
53
|
.map((tokenImporter) => {
|
|
@@ -52,8 +56,8 @@ export function generateDts(cssModule: CSSModule, options: GenerateDtsOptions):
|
|
|
52
56
|
...tokenImporter,
|
|
53
57
|
values: tokenImporter.values.filter(
|
|
54
58
|
(value) =>
|
|
55
|
-
|
|
56
|
-
(value.localName === undefined ||
|
|
59
|
+
isValidTokenName(value.name, options) &&
|
|
60
|
+
(value.localName === undefined || isValidTokenName(value.localName, options)),
|
|
57
61
|
),
|
|
58
62
|
};
|
|
59
63
|
} else {
|
|
@@ -106,28 +110,7 @@ export function generateDts(cssModule: CSSModule, options: GenerateDtsOptions):
|
|
|
106
110
|
}
|
|
107
111
|
}
|
|
108
112
|
|
|
109
|
-
/**
|
|
110
|
-
* Generate a d.ts file with named exports.
|
|
111
|
-
* @example
|
|
112
|
-
* If the CSS module file is:
|
|
113
|
-
* ```css
|
|
114
|
-
* @import './a.module.css';
|
|
115
|
-
* @value local1: string;
|
|
116
|
-
* @value imported1, imported2 as aliasedImported2 from './b.module.css';
|
|
117
|
-
* .local2 { color: red }
|
|
118
|
-
* ```
|
|
119
|
-
* The d.ts file would be:
|
|
120
|
-
* ```ts
|
|
121
|
-
* // @ts-nocheck
|
|
122
|
-
* export var local1: string;
|
|
123
|
-
* export var local2: string;
|
|
124
|
-
* export * from './a.module.css';
|
|
125
|
-
* export {
|
|
126
|
-
* imported1,
|
|
127
|
-
* imported2 as aliasedImported2,
|
|
128
|
-
* } from './b.module.css';
|
|
129
|
-
* ```
|
|
130
|
-
*/
|
|
113
|
+
/** Generate a d.ts file with named exports. */
|
|
131
114
|
function generateNamedExportsDts(
|
|
132
115
|
localTokens: Token[],
|
|
133
116
|
tokenImporters: TokenImporter[],
|
|
@@ -149,6 +132,25 @@ function generateNamedExportsDts(
|
|
|
149
132
|
let text = `// @ts-nocheck\n`;
|
|
150
133
|
|
|
151
134
|
for (const token of localTokens) {
|
|
135
|
+
/**
|
|
136
|
+
* The mapping is created as follows:
|
|
137
|
+
* a.module.css:
|
|
138
|
+
* 1 | .a_1 { color: red; }
|
|
139
|
+
* | ^ mapping.sourceOffsets[0]
|
|
140
|
+
* |
|
|
141
|
+
* 2 | .a_2 { color: blue; }
|
|
142
|
+
* | ^ mapping.sourceOffsets[1]
|
|
143
|
+
* |
|
|
144
|
+
*
|
|
145
|
+
* a.module.css.d.ts:
|
|
146
|
+
* 1 | // @ts-nocheck
|
|
147
|
+
* 2 | export var a_1: string;
|
|
148
|
+
* | ^ mapping.generatedOffsets[0]
|
|
149
|
+
* |
|
|
150
|
+
* 3 | export var a_2: string;
|
|
151
|
+
* | ^ mapping.generatedOffsets[1]
|
|
152
|
+
*/
|
|
153
|
+
|
|
152
154
|
text += `export var `;
|
|
153
155
|
mapping.sourceOffsets.push(token.loc.start.offset);
|
|
154
156
|
mapping.generatedOffsets.push(text.length);
|
|
@@ -157,12 +159,71 @@ function generateNamedExportsDts(
|
|
|
157
159
|
}
|
|
158
160
|
for (const tokenImporter of tokenImporters) {
|
|
159
161
|
if (tokenImporter.type === 'import') {
|
|
162
|
+
/**
|
|
163
|
+
* The mapping is created as follows:
|
|
164
|
+
* a.module.css:
|
|
165
|
+
* 1 | @import './b.module.css';
|
|
166
|
+
* | ^ mapping.sourceOffsets[0]
|
|
167
|
+
* |
|
|
168
|
+
* 2 | @import './c.module.css';
|
|
169
|
+
* | ^ mapping.sourceOffsets[1]
|
|
170
|
+
* |
|
|
171
|
+
*
|
|
172
|
+
* a.module.css.d.ts:
|
|
173
|
+
* 1 | // @ts-nocheck
|
|
174
|
+
* 2 | export * from './b.module.css';
|
|
175
|
+
* | ^ mapping.generatedOffsets[0]
|
|
176
|
+
* |
|
|
177
|
+
* 3 | export * from './c.module.css';
|
|
178
|
+
* | ^ mapping.generatedOffsets[1]
|
|
179
|
+
*
|
|
180
|
+
* NOTE: Not only the specifier but also the surrounding quotes are included in the mapping.
|
|
181
|
+
*/
|
|
182
|
+
|
|
160
183
|
text += `export * from `;
|
|
161
184
|
mapping.sourceOffsets.push(tokenImporter.fromLoc.start.offset - 1);
|
|
162
185
|
mapping.lengths.push(tokenImporter.from.length + 2);
|
|
163
186
|
mapping.generatedOffsets.push(text.length);
|
|
164
187
|
text += `'${tokenImporter.from}';\n`;
|
|
165
188
|
} else {
|
|
189
|
+
/**
|
|
190
|
+
* The mapping is created as follows:
|
|
191
|
+
* a.module.css:
|
|
192
|
+
* 1 | @value b_1, b_2 from './b.module.css';
|
|
193
|
+
* | ^ ^ ^ mapping.sourceOffsets[2]
|
|
194
|
+
* | ^ ^ mapping.sourceOffsets[1]
|
|
195
|
+
* | ^ mapping.sourceOffsets[0]
|
|
196
|
+
* |
|
|
197
|
+
* 2 | @value c_1 as aliased_c_1 from './c.module.css';
|
|
198
|
+
* | ^ ^ ^ mapping.sourceOffsets[5]
|
|
199
|
+
* | ^ ^ mapping.sourceOffsets[4]
|
|
200
|
+
* | ^ mapping.sourceOffsets[3]
|
|
201
|
+
* |
|
|
202
|
+
*
|
|
203
|
+
* a.module.css.d.ts:
|
|
204
|
+
* 1 | // @ts-nocheck
|
|
205
|
+
* 2 | export {
|
|
206
|
+
* 3 | b_1,
|
|
207
|
+
* | ^ mapping.generatedOffsets[0]
|
|
208
|
+
* |
|
|
209
|
+
* 4 | b_2,
|
|
210
|
+
* | ^ mapping.generatedOffsets[1]
|
|
211
|
+
* |
|
|
212
|
+
* 5 | } from './b.module.css';
|
|
213
|
+
* | ^ mapping.generatedOffsets[2]
|
|
214
|
+
* |
|
|
215
|
+
* 6 | export {
|
|
216
|
+
* 7 | c_1 as aliased_c_1,
|
|
217
|
+
* | ^ ^ mapping.generatedOffsets[4], linkedCodeMapping.sourceOffsets[0]
|
|
218
|
+
* | ^ mapping.generatedOffsets[3], linkedCodeMapping.generatedOffsets[0]
|
|
219
|
+
* |
|
|
220
|
+
* 8 | } from './c.module.css';
|
|
221
|
+
* | ^ mapping.generatedOffsets[5]
|
|
222
|
+
*
|
|
223
|
+
* NOTE: Not only the specifier but also the surrounding quotes are included in the mapping.
|
|
224
|
+
* NOTE: linkedCodeMapping is only generated for tokens that have a `localName` (i.e., aliased tokens).
|
|
225
|
+
*/
|
|
226
|
+
|
|
166
227
|
text += `export {\n`;
|
|
167
228
|
// eslint-disable-next-line no-loop-func
|
|
168
229
|
tokenImporter.values.forEach((value) => {
|
|
@@ -203,34 +264,28 @@ function generateNamedExportsDts(
|
|
|
203
264
|
return { text, mapping, linkedCodeMapping };
|
|
204
265
|
}
|
|
205
266
|
|
|
206
|
-
/**
|
|
207
|
-
* Generate a d.ts file with a default export.
|
|
208
|
-
* @example
|
|
209
|
-
* If the CSS module file is:
|
|
210
|
-
* ```css
|
|
211
|
-
* @import './a.module.css';
|
|
212
|
-
* @value local1: string;
|
|
213
|
-
* @value imported1, imported2 as aliasedImported2 from './b.module.css';
|
|
214
|
-
* .local2 { color: red }
|
|
215
|
-
* ```
|
|
216
|
-
* The d.ts file would be:
|
|
217
|
-
* ```ts
|
|
218
|
-
* // @ts-nocheck
|
|
219
|
-
* const styles = {
|
|
220
|
-
* local1: '' as readonly string,
|
|
221
|
-
* local2: '' as readonly string,
|
|
222
|
-
* ...(await import('./a.module.css')).default,
|
|
223
|
-
* imported1: (await import('./b.module.css')).default.imported1,
|
|
224
|
-
* aliasedImported2: (await import('./b.module.css')).default.imported2,
|
|
225
|
-
* };
|
|
226
|
-
* export default styles;
|
|
227
|
-
* ```
|
|
228
|
-
*/
|
|
267
|
+
/** Generate a d.ts file with a default export. */
|
|
229
268
|
function generateDefaultExportDts(
|
|
230
269
|
localTokens: Token[],
|
|
231
270
|
tokenImporters: TokenImporter[],
|
|
232
|
-
): {
|
|
271
|
+
): {
|
|
272
|
+
text: string;
|
|
273
|
+
mapping: CodeMapping;
|
|
274
|
+
secondaryMapping: CodeMapping;
|
|
275
|
+
linkedCodeMapping: LinkedCodeMapping;
|
|
276
|
+
} {
|
|
233
277
|
const mapping: CodeMapping = { sourceOffsets: [], lengths: [], generatedOffsets: [] };
|
|
278
|
+
/**
|
|
279
|
+
* In "Go to Definition", mapping only the inner part of the quotes does not work.
|
|
280
|
+
* Therefore, we also generate a mapping that includes the quotes.
|
|
281
|
+
* For more details, see https://github.com/mizdra/volar-single-quote-span-problem.
|
|
282
|
+
*/
|
|
283
|
+
const secondaryMapping: CodeMapping & { generatedLengths: number[] } = {
|
|
284
|
+
sourceOffsets: [],
|
|
285
|
+
lengths: [],
|
|
286
|
+
generatedOffsets: [],
|
|
287
|
+
generatedLengths: [],
|
|
288
|
+
};
|
|
234
289
|
const linkedCodeMapping: LinkedCodeMapping = {
|
|
235
290
|
sourceOffsets: [],
|
|
236
291
|
lengths: [],
|
|
@@ -256,54 +311,150 @@ function generateDefaultExportDts(
|
|
|
256
311
|
|
|
257
312
|
text += `declare const ${STYLES_EXPORT_NAME} = {\n`;
|
|
258
313
|
for (const token of localTokens) {
|
|
259
|
-
|
|
314
|
+
/**
|
|
315
|
+
* The mapping is created as follows:
|
|
316
|
+
* a.module.css:
|
|
317
|
+
* 1 | .a_1 { color: red; }
|
|
318
|
+
* | ^ mapping.sourceOffsets[0], secondaryMapping.sourceOffsets[0]
|
|
319
|
+
* |
|
|
320
|
+
* 2 | .a_2 { color: blue; }
|
|
321
|
+
* | ^ mapping.sourceOffsets[1], secondaryMapping.sourceOffsets[1]
|
|
322
|
+
* |
|
|
323
|
+
*
|
|
324
|
+
* a.module.css.d.ts:
|
|
325
|
+
* 1 | declare const styles = {
|
|
326
|
+
* 2 | 'a_1': '' as readonly string,
|
|
327
|
+
* | ^^ mapping.generatedOffsets[0]
|
|
328
|
+
* | ^ secondaryMapping.generatedOffsets[0]
|
|
329
|
+
* |
|
|
330
|
+
* 3 | 'a_2': '' as readonly string,
|
|
331
|
+
* | ^^ mapping.generatedOffsets[1]
|
|
332
|
+
* | ^ secondaryMapping.generatedOffsets[1]
|
|
333
|
+
* |
|
|
334
|
+
* 4 | };
|
|
335
|
+
*/
|
|
336
|
+
|
|
337
|
+
text += ` '`;
|
|
260
338
|
mapping.sourceOffsets.push(token.loc.start.offset);
|
|
261
|
-
mapping.generatedOffsets.push(text.length);
|
|
262
339
|
mapping.lengths.push(token.name.length);
|
|
263
|
-
text
|
|
340
|
+
mapping.generatedOffsets.push(text.length);
|
|
341
|
+
secondaryMapping.sourceOffsets.push(token.loc.start.offset);
|
|
342
|
+
secondaryMapping.lengths.push(token.name.length);
|
|
343
|
+
secondaryMapping.generatedOffsets.push(text.length - 1);
|
|
344
|
+
secondaryMapping.generatedLengths.push(token.name.length + 2);
|
|
345
|
+
text += `${token.name}': '' as readonly string,\n`;
|
|
264
346
|
}
|
|
265
347
|
for (const tokenImporter of tokenImporters) {
|
|
266
348
|
if (tokenImporter.type === 'import') {
|
|
349
|
+
/**
|
|
350
|
+
* The mapping is created as follows:
|
|
351
|
+
* a.module.css:
|
|
352
|
+
* 1 | @import './b.module.css';
|
|
353
|
+
* | ^ mapping.sourceOffsets[0]
|
|
354
|
+
* |
|
|
355
|
+
* 2 | @import './c.module.css';
|
|
356
|
+
* | ^ mapping.sourceOffsets[1]
|
|
357
|
+
* |
|
|
358
|
+
*
|
|
359
|
+
* a.module.css.d.ts:
|
|
360
|
+
* 1 | declare const styles = {
|
|
361
|
+
* 2 | ...blockErrorType((await import('./b.module.css')).default),
|
|
362
|
+
* | ^ mapping.generatedOffsets[0]
|
|
363
|
+
* |
|
|
364
|
+
* 3 | ...blockErrorType((await import('./c.module.css')).default),
|
|
365
|
+
* | ^ mapping.generatedOffsets[1]
|
|
366
|
+
* |
|
|
367
|
+
* 4 | };
|
|
368
|
+
*
|
|
369
|
+
* NOTE: Not only the specifier but also the surrounding quotes are included in the mapping.
|
|
370
|
+
*/
|
|
371
|
+
|
|
267
372
|
text += ` ...blockErrorType((await import(`;
|
|
268
373
|
mapping.sourceOffsets.push(tokenImporter.fromLoc.start.offset - 1);
|
|
269
374
|
mapping.lengths.push(tokenImporter.from.length + 2);
|
|
270
375
|
mapping.generatedOffsets.push(text.length);
|
|
271
376
|
text += `'${tokenImporter.from}')).default),\n`;
|
|
272
377
|
} else {
|
|
378
|
+
/**
|
|
379
|
+
* The mapping is created as follows:
|
|
380
|
+
* a.module.css:
|
|
381
|
+
* 1 | @value b_1, b_2 from './b.module.css';
|
|
382
|
+
* | ^ ^ ^ mapping.sourceOffsets[1]
|
|
383
|
+
* | ^ ^ mapping.sourceOffsets[2], secondaryMapping.sourceOffsets[1]
|
|
384
|
+
* | ^ mapping.sourceOffsets[0], secondaryMapping.sourceOffsets[0]
|
|
385
|
+
* |
|
|
386
|
+
* 2 | @value c_1 as aliased_c_1 from './c.module.css';
|
|
387
|
+
* | ^ ^ ^ mapping.sourceOffsets[4]
|
|
388
|
+
* | ^ ^ mapping.sourceOffsets[3], secondaryMapping.sourceOffsets[2]
|
|
389
|
+
* | ^ mapping.sourceOffsets[5], secondaryMapping.sourceOffsets[3]
|
|
390
|
+
* |
|
|
391
|
+
*
|
|
392
|
+
* a.module.css.d.ts:
|
|
393
|
+
* 1 | declare const styles = {
|
|
394
|
+
* 2 | 'b_1': (await import('./b.module.css')).default['b_1'],
|
|
395
|
+
* | ^^ ^ ^ linkedCodeMapping.generatedOffsets[0]
|
|
396
|
+
* | ^^ ^ mapping.generatedOffsets[1]
|
|
397
|
+
* | ^^ mapping.generatedOffsets[0]
|
|
398
|
+
* | ^ secondaryMapping.generatedOffsets[0], linkedCodeMapping.sourceOffsets[0]
|
|
399
|
+
* |
|
|
400
|
+
* 3 | 'b_2': (await import('./b.module.css')).default['b_2'],
|
|
401
|
+
* | ^^ ^ linkedCodeMapping.generatedOffsets[1]
|
|
402
|
+
* | ^^ mapping.generatedOffsets[2]
|
|
403
|
+
* | ^ secondaryMapping.generatedOffsets[1], linkedCodeMapping.sourceOffsets[1]
|
|
404
|
+
* |
|
|
405
|
+
* 4 | 'aliased_c_1': (await import('./c.module.css')).default['c_1'],
|
|
406
|
+
* | ^^ ^ ^^ mapping.generatedOffsets[5]
|
|
407
|
+
* | ^^ ^ ^ secondaryMapping.generatedOffsets[3], linkedCodeMapping.generatedOffsets[2]
|
|
408
|
+
* | ^^ ^ mapping.generatedOffsets[4]
|
|
409
|
+
* | ^^ mapping.generatedOffsets[3]
|
|
410
|
+
* | ^ secondaryMapping.generatedOffsets[2], linkedCodeMapping.sourceOffsets[2]
|
|
411
|
+
* |
|
|
412
|
+
* 5 | };
|
|
413
|
+
*
|
|
414
|
+
* NOTE: Not only the specifier but also the surrounding quotes are included in the mapping.
|
|
415
|
+
*/
|
|
416
|
+
|
|
273
417
|
// eslint-disable-next-line no-loop-func
|
|
274
418
|
tokenImporter.values.forEach((value, i) => {
|
|
275
419
|
const localName = value.localName ?? value.name;
|
|
276
420
|
const localLoc = value.localLoc ?? value.loc;
|
|
277
421
|
|
|
278
|
-
text += ` `;
|
|
422
|
+
text += ` '`;
|
|
279
423
|
mapping.sourceOffsets.push(localLoc.start.offset);
|
|
280
424
|
mapping.lengths.push(localName.length);
|
|
281
425
|
mapping.generatedOffsets.push(text.length);
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
text
|
|
426
|
+
secondaryMapping.sourceOffsets.push(localLoc.start.offset);
|
|
427
|
+
secondaryMapping.lengths.push(localName.length);
|
|
428
|
+
secondaryMapping.generatedOffsets.push(text.length - 1);
|
|
429
|
+
secondaryMapping.generatedLengths.push(localName.length + 2);
|
|
430
|
+
linkedCodeMapping.sourceOffsets.push(text.length - 1);
|
|
431
|
+
linkedCodeMapping.lengths.push(localName.length + 2);
|
|
432
|
+
text += `${localName}': (await import(`;
|
|
285
433
|
if (i === 0) {
|
|
286
434
|
mapping.sourceOffsets.push(tokenImporter.fromLoc.start.offset - 1);
|
|
287
435
|
mapping.lengths.push(tokenImporter.from.length + 2);
|
|
288
436
|
mapping.generatedOffsets.push(text.length);
|
|
289
437
|
}
|
|
290
|
-
text += `'${tokenImporter.from}')).default
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
438
|
+
text += `'${tokenImporter.from}')).default['`;
|
|
439
|
+
if ('localName' in value) {
|
|
440
|
+
mapping.sourceOffsets.push(value.loc.start.offset);
|
|
441
|
+
mapping.lengths.push(value.name.length);
|
|
442
|
+
mapping.generatedOffsets.push(text.length);
|
|
443
|
+
secondaryMapping.sourceOffsets.push(value.loc.start.offset);
|
|
444
|
+
secondaryMapping.lengths.push(value.name.length);
|
|
445
|
+
secondaryMapping.generatedOffsets.push(text.length - 1);
|
|
446
|
+
secondaryMapping.generatedLengths.push(value.name.length + 2);
|
|
447
|
+
}
|
|
448
|
+
linkedCodeMapping.generatedOffsets.push(text.length - 1);
|
|
449
|
+
linkedCodeMapping.generatedLengths.push(value.name.length + 2);
|
|
450
|
+
text += `${value.name}'],\n`;
|
|
297
451
|
});
|
|
298
452
|
}
|
|
299
453
|
}
|
|
300
454
|
text += `};\nexport default ${STYLES_EXPORT_NAME};\n`;
|
|
301
|
-
return { text, mapping, linkedCodeMapping };
|
|
455
|
+
return { text, mapping, linkedCodeMapping, secondaryMapping };
|
|
302
456
|
}
|
|
303
457
|
|
|
304
|
-
function
|
|
305
|
-
|
|
306
|
-
if (name === '__proto__') return false;
|
|
307
|
-
if (options.namedExports && name === 'default') return false;
|
|
308
|
-
return true;
|
|
458
|
+
function isValidTokenName(name: string, options: ValidateTokenNameOptions): boolean {
|
|
459
|
+
return validateTokenName(name, options) === undefined;
|
|
309
460
|
}
|
|
@@ -119,14 +119,24 @@ export function parseRule(rule: Rule): ParseRuleResult {
|
|
|
119
119
|
column: rule.source!.start!.column + className.source!.start!.column,
|
|
120
120
|
offset: rule.source!.start!.offset + className.sourceIndex + 1,
|
|
121
121
|
};
|
|
122
|
+
/**
|
|
123
|
+
* When there is a selector like `.\31 backslash`, `className.value` becomes `"1backslash"`.
|
|
124
|
+
* In other words, it is the string after escape sequences have been interpreted.
|
|
125
|
+
* However, here we need the raw string as written in the CSS source code.
|
|
126
|
+
* So we use `className.toString()`.
|
|
127
|
+
*
|
|
128
|
+
* The return value of `className.toString()` may contain leading dots and spaces like `" .1backslash"`.
|
|
129
|
+
* Therefore, we remove the leading spaces and dot with a regular expression.
|
|
130
|
+
*/
|
|
131
|
+
const rawClassName = className.toString().replace(/^\s*\./u, '');
|
|
122
132
|
const end = {
|
|
123
133
|
// The end line is always the same as the start line, as a class selector cannot break in the middle.
|
|
124
134
|
line: start.line,
|
|
125
|
-
column: start.column +
|
|
126
|
-
offset: start.offset +
|
|
135
|
+
column: start.column + rawClassName.length,
|
|
136
|
+
offset: start.offset + rawClassName.length,
|
|
127
137
|
};
|
|
128
138
|
return {
|
|
129
|
-
name:
|
|
139
|
+
name: rawClassName,
|
|
130
140
|
loc: { start, end },
|
|
131
141
|
declarationLoc: { start: rule.source!.start!, end: rule.positionBy({ index: rule.toString().length }) },
|
|
132
142
|
};
|
package/src/resolver.ts
CHANGED
|
@@ -12,8 +12,8 @@ export function createResolver(
|
|
|
12
12
|
const host: ts.ModuleResolutionHost = {
|
|
13
13
|
...ts.sys,
|
|
14
14
|
fileExists: (fileName) => {
|
|
15
|
-
if (fileName.endsWith('.
|
|
16
|
-
return ts.sys.fileExists(fileName.replace(/\.
|
|
15
|
+
if (fileName.endsWith('.d.css.ts')) {
|
|
16
|
+
return ts.sys.fileExists(fileName.replace(/\.d\.css\.ts$/u, '.css'));
|
|
17
17
|
}
|
|
18
18
|
return ts.sys.fileExists(fileName);
|
|
19
19
|
},
|
|
@@ -26,8 +26,7 @@ export function createResolver(
|
|
|
26
26
|
moduleResolutionCache,
|
|
27
27
|
);
|
|
28
28
|
if (resolvedModule) {
|
|
29
|
-
|
|
30
|
-
return resolvedModule.resolvedFileName.replace(/\.module\.d\.css\.ts$/u, '.module.css');
|
|
29
|
+
return resolvedModule.resolvedFileName.replace(/\.d\.css\.ts$/u, '.css');
|
|
31
30
|
}
|
|
32
31
|
return undefined;
|
|
33
32
|
};
|
package/src/util.ts
CHANGED
|
@@ -4,24 +4,48 @@ export function isPosixRelativePath(path: string): boolean {
|
|
|
4
4
|
|
|
5
5
|
const JS_IDENTIFIER_PATTERN = /^[$_\p{ID_Start}][$\u200c\u200d\p{ID_Continue}]*$/u;
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
/** The type of token name violation. */
|
|
8
|
+
export type TokenNameViolation =
|
|
9
|
+
| 'invalid-js-identifier' // Invalid as a JavaScript identifier
|
|
10
|
+
| 'proto-not-allowed' // `__proto__` is not allowed
|
|
11
|
+
| 'default-not-allowed' // `default` is not allowed when namedExports is true
|
|
12
|
+
| 'backslash-not-allowed'; // Backslash (`\`) is not allowed
|
|
13
|
+
|
|
14
|
+
export interface ValidateTokenNameOptions {
|
|
15
|
+
namedExports: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Validates a token name and returns the violation if any.
|
|
20
|
+
* @param name The token name to validate.
|
|
21
|
+
* @param options The validation options.
|
|
22
|
+
* @returns The violation, or `undefined` if the name is valid.
|
|
23
|
+
*/
|
|
24
|
+
export function validateTokenName(name: string, options: ValidateTokenNameOptions): TokenNameViolation | undefined {
|
|
25
|
+
if (name === '__proto__') return 'proto-not-allowed';
|
|
26
|
+
if (options.namedExports) {
|
|
27
|
+
if (name === 'default') return 'default-not-allowed';
|
|
28
|
+
if (!JS_IDENTIFIER_PATTERN.test(name)) return 'invalid-js-identifier';
|
|
29
|
+
} else {
|
|
30
|
+
if (name.includes('\\')) return 'backslash-not-allowed';
|
|
31
|
+
}
|
|
32
|
+
return undefined;
|
|
9
33
|
}
|
|
10
34
|
|
|
11
35
|
/**
|
|
12
36
|
* The syntax pattern for consuming tokens imported from CSS Module.
|
|
13
|
-
* @example `styles.foo`
|
|
37
|
+
* @example `styles.foo`, `styles['foo']`, `styles["foo"]`
|
|
14
38
|
*/
|
|
15
|
-
// TODO(#125): Support `styles['foo']` and `styles["foo"]`
|
|
16
39
|
// MEMO: The `xxxStyles.foo` format is not supported, because the css module file for current component file is usually imported with `styles`.
|
|
17
40
|
// It is sufficient to support only the `styles.foo` format.
|
|
18
|
-
const TOKEN_CONSUMER_PATTERN =
|
|
41
|
+
const TOKEN_CONSUMER_PATTERN =
|
|
42
|
+
/styles(?:\.([$_\p{ID_Start}][$\u200c\u200d\p{ID_Continue}]*)|\['([^']*?)'\]|\["([^"]*?)"\])/gu;
|
|
19
43
|
|
|
20
44
|
export function findUsedTokenNames(componentText: string): Set<string> {
|
|
21
45
|
const usedClassNames = new Set<string>();
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
usedClassNames.add(
|
|
46
|
+
for (const match of componentText.matchAll(TOKEN_CONSUMER_PATTERN)) {
|
|
47
|
+
const name = match[1] ?? match[2] ?? match[3];
|
|
48
|
+
if (name) usedClassNames.add(name);
|
|
25
49
|
}
|
|
26
50
|
return usedClassNames;
|
|
27
51
|
}
|