Skip to content

Commit f868dcb

Browse files
committed
feat: change to allow non-identifiers when selecting default export
1 parent a1afb57 commit f868dcb

10 files changed

Lines changed: 59 additions & 56 deletions

File tree

examples/1-basic/generated/src/a.module.css.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ declare const styles = {
66
a_2: '' as readonly string,
77
a_3: '' as readonly string,
88
a_4: '' as readonly string,
9+
'a-1': '' as readonly string,
910
...blockErrorType((await import('./b.module.css')).default),
1011
c_1: (await import('./c.module.css')).default.c_1,
1112
c_alias: (await import('./c.module.css')).default.c_2,

examples/1-basic/src/a.module.css

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,7 @@
77
100% { transform: translateY(-100%); }
88
}
99

10+
.a-1 { color: red; }
11+
1012
@import './b.module.css';
1113
@value c_1, c_2 as c_alias from './c.module.css';

examples/1-basic/src/a.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ styles.a_4;
77
styles.b_1;
88
styles.b_2;
99
styles.c_1;
10+
styles['a-1'];
1011
styles.c_alias;
1112
styles.unknown; // Expected TS2339 error
1213

packages/core/src/checker.test.ts

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -51,16 +51,6 @@ describe('checkCSSModule', () => {
5151
const diagnostics = check(readAndParseCSSModule(iff.paths['a.module.css'])!);
5252
expect(formatDiagnostics(diagnostics, iff.rootDir)).toMatchInlineSnapshot(`
5353
[
54-
{
55-
"category": "error",
56-
"fileName": "<rootDir>/a.module.css",
57-
"length": 3,
58-
"start": {
59-
"column": 2,
60-
"line": 1,
61-
},
62-
"text": "css-modules-kit does not support invalid names as JavaScript identifiers.",
63-
},
6454
{
6555
"category": "error",
6656
"fileName": "<rootDir>/a.module.css",
@@ -69,7 +59,7 @@ describe('checkCSSModule', () => {
6959
"column": 8,
7060
"line": 2,
7161
},
72-
"text": "css-modules-kit does not support invalid names as JavaScript identifiers.",
62+
"text": "css-modules-kit does not support invalid names as JavaScript identifiers when \`cmkOptions.namedExports\` is set to \`true\`.",
7363
},
7464
{
7565
"category": "error",
@@ -79,7 +69,7 @@ describe('checkCSSModule', () => {
7969
"column": 13,
8070
"line": 2,
8171
},
82-
"text": "css-modules-kit does not support invalid names as JavaScript identifiers.",
72+
"text": "css-modules-kit does not support invalid names as JavaScript identifiers when \`cmkOptions.namedExports\` is set to \`true\`.",
8373
},
8474
{
8575
"category": "error",
@@ -89,7 +79,7 @@ describe('checkCSSModule', () => {
8979
"column": 20,
9080
"line": 2,
9181
},
92-
"text": "css-modules-kit does not support invalid names as JavaScript identifiers.",
82+
"text": "css-modules-kit does not support invalid names as JavaScript identifiers when \`cmkOptions.namedExports\` is set to \`true\`.",
9383
},
9484
]
9585
`);

packages/core/src/checker.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export function checkCSSModule(cssModule: CSSModule, args: CheckerArgs): Diagnos
2626

2727
for (const token of cssModule.localTokens) {
2828
// Reject special names as they may break .d.ts files
29-
if (!isValidAsJSIdentifier(token.name)) {
29+
if (config.namedExports && !isValidAsJSIdentifier(token.name)) {
3030
diagnostics.push(createInvalidNameAsJSIdentifiersDiagnostic(cssModule, token.loc));
3131
}
3232
if (token.name === '__proto__') {
@@ -106,7 +106,7 @@ function createModuleHasNoExportedTokenDiagnostic(
106106

107107
function createInvalidNameAsJSIdentifiersDiagnostic(cssModule: CSSModule, loc: Location): Diagnostic {
108108
return {
109-
text: `css-modules-kit does not support invalid names as JavaScript identifiers.`,
109+
text: `css-modules-kit does not support invalid names as JavaScript identifiers when \`cmkOptions.namedExports\` is set to \`true\`.`,
110110
category: 'error',
111111
file: { fileName: cssModule.fileName, text: cssModule.text },
112112
start: { line: loc.start.line, column: loc.start.column },

packages/core/src/dts-generator.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ describe('generateDts', () => {
8585
expect(generateDts(readAndParseCSSModule(iff.paths['test.module.css'])!, options).text).toMatchInlineSnapshot(`
8686
"// @ts-nocheck
8787
declare const styles = {
88+
'a-1': '' as readonly string,
8889
};
8990
export default styles;
9091
"

packages/core/src/dts-generator.ts

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ interface CodeMapping {
1717
lengths: number[];
1818
/** The generated offsets of the tokens in the *.d.ts file. */
1919
generatedOffsets: number[];
20+
/** The lengths of the tokens in the *.d.ts file. */
21+
generatedLengths?: number[];
2022
}
2123

2224
/** The map linking the two codes in *.d.ts */
@@ -43,7 +45,7 @@ interface GenerateDtsResult {
4345
*/
4446
export function generateDts(cssModule: CSSModule, options: GenerateDtsOptions): GenerateDtsResult {
4547
// Exclude invalid tokens
46-
const localTokens = cssModule.localTokens.filter((token) => isValidName(token.name, options));
48+
const localTokens = cssModule.localTokens.filter((token) => isValidName(token.name, options, false));
4749
const tokenImporters = cssModule.tokenImporters
4850
// Exclude invalid imported tokens
4951
.map((tokenImporter) => {
@@ -52,8 +54,8 @@ export function generateDts(cssModule: CSSModule, options: GenerateDtsOptions):
5254
...tokenImporter,
5355
values: tokenImporter.values.filter(
5456
(value) =>
55-
isValidName(value.name, options) &&
56-
(value.localName === undefined || isValidName(value.localName, options)),
57+
isValidName(value.name, options, true) &&
58+
(value.localName === undefined || isValidName(value.localName, options, true)),
5759
),
5860
};
5961
} else {
@@ -265,7 +267,7 @@ function generateDefaultExportDts(
265267
localTokens: Token[],
266268
tokenImporters: TokenImporter[],
267269
): { text: string; mapping: CodeMapping; linkedCodeMapping: LinkedCodeMapping } {
268-
const mapping: CodeMapping = { sourceOffsets: [], lengths: [], generatedOffsets: [] };
270+
const mapping: CodeMapping = { sourceOffsets: [], lengths: [], generatedOffsets: [], generatedLengths: [] };
269271
const linkedCodeMapping: LinkedCodeMapping = {
270272
sourceOffsets: [],
271273
lengths: [],
@@ -310,13 +312,31 @@ function generateDefaultExportDts(
310312
* | ^ mapping.generatedOffsets[1]
311313
* |
312314
* 4 | };
315+
*
316+
* invalid as js identifier:
317+
* a.module.css:
318+
* 1 | .a-123 { color: red; }
319+
* | ^ mapping.sourceOffsets[0]
320+
*
321+
* a.module.css.d.ts:
322+
* 1 | declare const styles = {
323+
* 2 | 'a-123': '' as readonly string,
324+
* | ^ mapping.generatedOffsets[0]
325+
* 3 | };
313326
*/
314327

315328
text += ` `;
316329
mapping.sourceOffsets.push(token.loc.start.offset);
317330
mapping.generatedOffsets.push(text.length);
318331
mapping.lengths.push(token.name.length);
319-
text += `${token.name}: '' as readonly string,\n`;
332+
if (isValidAsJSIdentifier(token.name)) {
333+
mapping.generatedLengths!.push(token.name.length);
334+
text += `${token.name}: '' as readonly string,\n`;
335+
} else {
336+
// Include quotes in the mapping for invalid JS identifiers
337+
mapping.generatedLengths!.push(token.name.length + 2);
338+
text += `'${token.name}': '' as readonly string,\n`;
339+
}
320340
}
321341
for (const tokenImporter of tokenImporters) {
322342
if (tokenImporter.type === 'import') {
@@ -347,6 +367,7 @@ function generateDefaultExportDts(
347367
mapping.sourceOffsets.push(tokenImporter.fromLoc.start.offset - 1);
348368
mapping.lengths.push(tokenImporter.from.length + 2);
349369
mapping.generatedOffsets.push(text.length);
370+
mapping.generatedLengths!.push(tokenImporter.from.length + 2);
350371
text += `'${tokenImporter.from}')).default),\n`;
351372
} else {
352373
/**
@@ -393,19 +414,22 @@ function generateDefaultExportDts(
393414
mapping.sourceOffsets.push(localLoc.start.offset);
394415
mapping.lengths.push(localName.length);
395416
mapping.generatedOffsets.push(text.length);
417+
mapping.generatedLengths!.push(localName.length);
396418
linkedCodeMapping.sourceOffsets.push(text.length);
397419
linkedCodeMapping.lengths.push(localName.length);
398420
text += `${localName}: (await import(`;
399421
if (i === 0) {
400422
mapping.sourceOffsets.push(tokenImporter.fromLoc.start.offset - 1);
401423
mapping.lengths.push(tokenImporter.from.length + 2);
402424
mapping.generatedOffsets.push(text.length);
425+
mapping.generatedLengths!.push(tokenImporter.from.length + 2);
403426
}
404427
text += `'${tokenImporter.from}')).default.`;
405428
if ('localName' in value) {
406429
mapping.sourceOffsets.push(value.loc.start.offset);
407430
mapping.lengths.push(value.name.length);
408431
mapping.generatedOffsets.push(text.length);
432+
mapping.generatedLengths!.push(value.name.length);
409433
}
410434
linkedCodeMapping.generatedOffsets.push(text.length);
411435
linkedCodeMapping.generatedLengths.push(value.name.length);
@@ -417,8 +441,8 @@ function generateDefaultExportDts(
417441
return { text, mapping, linkedCodeMapping };
418442
}
419443

420-
function isValidName(name: string, options: GenerateDtsOptions): boolean {
421-
if (!isValidAsJSIdentifier(name)) return false;
444+
function isValidName(name: string, options: GenerateDtsOptions, isTokenImport: boolean): boolean {
445+
if ((options.namedExports || isTokenImport) && !isValidAsJSIdentifier(name)) return false;
422446
if (name === '__proto__') return false;
423447
if (options.namedExports && name === 'default') return false;
424448
return true;

packages/ts-plugin/e2e-test/feature/go-to-definition.test.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ describe('Go to Definition', async () => {
1414
styles.b_1;
1515
styles.c_1;
1616
styles.c_alias;
17+
styles['d-1'];
1718
`,
1819
'a.module.css': dedent`
1920
@import './b.module.css';
@@ -23,6 +24,7 @@ describe('Go to Definition', async () => {
2324
.a_2 { color: red; }
2425
@value a_3: red;
2526
@import url(./b.module.css);
27+
.d-1 { color: red; }
2628
`,
2729
'b.module.css': dedent`
2830
.b_1 { color: red; }
@@ -300,6 +302,19 @@ describe('Go to Definition', async () => {
300302
{ file: formatPath(iff.paths['b.module.css']), start: { line: 1, offset: 1 }, end: { line: 1, offset: 1 } },
301303
],
302304
},
305+
{
306+
name: 'd-1 in index.ts',
307+
file: iff.paths['index.ts'],
308+
line: 8,
309+
offset: 8,
310+
expected: [
311+
{
312+
file: formatPath(iff.paths['a.module.css']),
313+
start: { line: 8, offset: 2 },
314+
end: { line: 8, offset: 5 },
315+
},
316+
],
317+
},
303318
])('Go to Definition for $name', async ({ file, line, offset, expected }) => {
304319
const res = await tsserver.sendDefinitionAndBoundSpan({
305320
file,

packages/ts-plugin/e2e-test/feature/semantic-diagnostics.test.ts

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ test('Semantic Diagnostics', async () => {
88
const iff = await createIFF({
99
'index.ts': dedent`
1010
import styles from './a.module.css';
11-
type Expected = { a_1: string, a_2: string, b_1: string, c_1: string, c_alias: string, c_3: string };
11+
type Expected = { a_1: string, a_2: string, b_1: string, c_1: string, c_alias: string, c_3: string, 'a-3': string };
1212
const t1: Expected = styles;
1313
const t2: typeof styles = t1;
1414
styles.unknown;
@@ -55,7 +55,7 @@ test('Semantic Diagnostics', async () => {
5555
"line": 5,
5656
"offset": 8,
5757
},
58-
"text": "Property 'unknown' does not exist on type '{ c_1: string; c_alias: string; c_3: any; b_1: string; a_1: string; a_2: string; }'.",
58+
"text": "Property 'unknown' does not exist on type '{ c_1: string; c_alias: string; c_3: any; b_1: string; a_1: string; a_2: string; 'a-3': string; }'.",
5959
},
6060
]
6161
`);
@@ -65,20 +65,6 @@ test('Semantic Diagnostics', async () => {
6565
});
6666
expect(res2.body).toMatchInlineSnapshot(`
6767
[
68-
{
69-
"category": "error",
70-
"code": 0,
71-
"end": {
72-
"line": 5,
73-
"offset": 5,
74-
},
75-
"source": "css-modules-kit",
76-
"start": {
77-
"line": 5,
78-
"offset": 2,
79-
},
80-
"text": "css-modules-kit does not support invalid names as JavaScript identifiers.",
81-
},
8268
{
8369
"category": "error",
8470
"code": 0,

packages/ts-plugin/e2e-test/file-operation.test.ts

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -137,24 +137,7 @@ test('updating file', async () => {
137137
const res2 = await tsserver.sendSemanticDiagnosticsSync({
138138
file: iff.paths['a.module.css'],
139139
});
140-
expect(res2.body).toMatchInlineSnapshot(`
141-
[
142-
{
143-
"category": "error",
144-
"code": 0,
145-
"end": {
146-
"line": 2,
147-
"offset": 5,
148-
},
149-
"source": "css-modules-kit",
150-
"start": {
151-
"line": 2,
152-
"offset": 2,
153-
},
154-
"text": "css-modules-kit does not support invalid names as JavaScript identifiers.",
155-
},
156-
]
157-
`);
140+
expect(res2.body).toMatchInlineSnapshot(`[]`);
158141

159142
// The diagnostics of files importing a.module.css are updated.
160143
const res3 = await tsserver.sendSemanticDiagnosticsSync({

0 commit comments

Comments
 (0)