Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/allow-non-js-ident-named-exports.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@css-modules-kit/core': minor
'@css-modules-kit/ts-plugin': minor
---

feat(core, ts-plugin): support non-JavaScript identifier token in named export
15 changes: 9 additions & 6 deletions examples/2-named-exports/generated/src/a.module.css.d.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
// @ts-nocheck
export var a_1: string;
export var a_2: string;
export var a_2: string;
export var a_3: string;
var _token_0: string;
export { _token_0 as 'a_1' };
var _token_1: string;
var _token_1: string;
export { _token_1 as 'a_2' };
var _token_2: string;
export { _token_2 as 'a_3' };
export * from './b.module.css';
export {
c_1,
c_2 as c_alias,
'c_1' as 'c_1',
'c_2' as 'c_alias',
} from './c.module.css';
3 changes: 2 additions & 1 deletion examples/2-named-exports/generated/src/b.module.css.d.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
// @ts-nocheck
export var b_1: string;
var _token_0: string;
export { _token_0 as 'b_1' };
6 changes: 4 additions & 2 deletions examples/2-named-exports/generated/src/c.module.css.d.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// @ts-nocheck
export var c_1: string;
export var c_2: string;
var _token_0: string;
export { _token_0 as 'c_1' };
var _token_1: string;
export { _token_1 as 'c_2' };
77 changes: 2 additions & 75 deletions packages/core/src/checker.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,79 +36,6 @@ function prepareChecker(args?: Partial<CheckerArgs>): Checker {
}

describe('checkCSSModule', () => {
test('do not report diagnostics for invalid name as js identifier when namedExports is false', async () => {
const iff = await createIFF({
'a.module.css': dedent`
.a-1 { color: red; }
@value b-1, b-2 as a-2 from './b.module.css';
`,
'b.module.css': dedent`
@value b-1: red;
@value b-2: red;
`,
});
const check = prepareChecker();
const diagnostics = check(readAndParseCSSModule(iff.paths['a.module.css'])!);
expect(formatDiagnostics(diagnostics, iff.rootDir)).toMatchInlineSnapshot(`[]`);
});
test('report diagnostics for invalid name as js identifier when namedExports is true', async () => {
const iff = await createIFF({
'a.module.css': dedent`
.a-1 { color: red; }
@value b-1, b-2 as a-2 from './b.module.css';
`,
'b.module.css': dedent`
@value b-1: red;
@value b-2: red;
`,
});
const check = prepareChecker({ config: fakeConfig({ namedExports: true }) });
const diagnostics = check(readAndParseCSSModule(iff.paths['a.module.css'])!);
expect(formatDiagnostics(diagnostics, iff.rootDir)).toMatchInlineSnapshot(`
[
{
"category": "error",
"fileName": "<rootDir>/a.module.css",
"length": 3,
"start": {
"column": 2,
"line": 1,
},
"text": "Token names must be valid JavaScript identifiers when \`cmkOptions.namedExports\` is set to \`true\`.",
},
{
"category": "error",
"fileName": "<rootDir>/a.module.css",
"length": 3,
"start": {
"column": 8,
"line": 2,
},
"text": "Token names must be valid JavaScript identifiers when \`cmkOptions.namedExports\` is set to \`true\`.",
},
{
"category": "error",
"fileName": "<rootDir>/a.module.css",
"length": 3,
"start": {
"column": 13,
"line": 2,
},
"text": "Token names must be valid JavaScript identifiers when \`cmkOptions.namedExports\` is set to \`true\`.",
},
{
"category": "error",
"fileName": "<rootDir>/a.module.css",
"length": 3,
"start": {
"column": 20,
"line": 2,
},
"text": "Token names must be valid JavaScript identifiers when \`cmkOptions.namedExports\` is set to \`true\`.",
},
]
`);
});
test('report diagnostics for "__proto__" name', async () => {
const iff = await createIFF({
'a.module.css': dedent`
Expand Down Expand Up @@ -205,7 +132,7 @@ describe('checkCSSModule', () => {
]
`);
});
test('report diagnostics for backslash in name when namedExports is false', async () => {
test('report diagnostics for backslash in name', async () => {
// NOTE: The backslash is valid syntax in class selectors, but it is invalid syntax in `@value`.
// Therefore, it is sufficient for diagnostics to be reported only for class selectors.
const iff = await createIFF({
Expand All @@ -225,7 +152,7 @@ describe('checkCSSModule', () => {
"column": 2,
"line": 1,
},
"text": "Backslash (\\) is not allowed in names when \`cmkOptions.namedExports\` is set to \`false\`.",
"text": "Backslash (\\) is not allowed in names.",
},
]
`);
Expand Down
5 changes: 1 addition & 4 deletions packages/core/src/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,17 +68,14 @@ export function checkCSSModule(cssModule: CSSModule, args: CheckerArgs): Diagnos
function createTokenNameDiagnostic(cssModule: CSSModule, loc: Location, violation: TokenNameViolation): Diagnostic {
let text: string;
switch (violation) {
case 'invalid-js-identifier':
text = `Token names must be valid JavaScript identifiers when \`cmkOptions.namedExports\` is set to \`true\`.`;
break;
case 'proto-not-allowed':
text = `\`__proto__\` is not allowed as names.`;
break;
case 'default-not-allowed':
text = `\`default\` is not allowed as names when \`cmkOptions.namedExports\` is set to \`true\`.`;
break;
case 'backslash-not-allowed':
text = `Backslash (\\) is not allowed in names when \`cmkOptions.namedExports\` is set to \`false\`.`;
text = `Backslash (\\) is not allowed in names.`;
break;
default:
throw new Error('unreachable: unknown TokenNameViolation');
Expand Down
27 changes: 16 additions & 11 deletions packages/core/src/dts-generator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,22 +95,26 @@ describe('generateDts', () => {
'test.module.css': dedent`
.local1 { color: red; }
.local2 { color: red; }
.local2 { color: red; }
@import './a.module.css';
@value imported1, imported2 as aliasedImported2 from './b.module.css';
`,
});
expect(generateDts(readAndParseCSSModule(iff.paths['test.module.css'])!, { ...options, namedExports: true }).text)
.toMatchInlineSnapshot(`
"// @ts-nocheck
export var local1: string;
export var local2: string;
export * from './a.module.css';
export {
imported1,
imported2 as aliasedImported2,
} from './b.module.css';
"
`);
"// @ts-nocheck
var _token_0: string;
export { _token_0 as 'local1' };
var _token_1: string;
var _token_1: string;
export { _token_1 as 'local2' };
export * from './a.module.css';
export {
'imported1' as 'imported1',
'imported2' as 'aliasedImported2',
} from './b.module.css';
"
`);
});
test('exports styles as default when `namedExports` and `forTsPlugin` are true, but `prioritizeNamedImports` is false', async () => {
const iff = await createIFF({
Expand All @@ -125,7 +129,8 @@ describe('generateDts', () => {
}).text,
).toMatchInlineSnapshot(`
"// @ts-nocheck
export var local1: string;
var _token_0: string;
export { _token_0 as 'local1' };
declare const styles: {};
export default styles;
"
Expand Down
Loading