Skip to content
Closed
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
1 change: 1 addition & 0 deletions examples/1-basic/generated/src/a.module.css.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ declare const styles = {
a_2: '' as readonly string,
a_3: '' as readonly string,
a_4: '' as readonly string,
'a-1': '' as readonly string,
...blockErrorType((await import('./b.module.css')).default),
c_1: (await import('./c.module.css')).default.c_1,
c_alias: (await import('./c.module.css')).default.c_2,
Expand Down
2 changes: 2 additions & 0 deletions examples/1-basic/src/a.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,7 @@
100% { transform: translateY(-100%); }
}

.a-1 { color: red; }

@import './b.module.css';
@value c_1, c_2 as c_alias from './c.module.css';
1 change: 1 addition & 0 deletions examples/1-basic/src/a.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ styles.a_4;
styles.b_1;
styles.b_2;
styles.c_1;
styles['a-1'];
styles.c_alias;
styles.unknown; // Expected TS2339 error

Expand Down
12 changes: 6 additions & 6 deletions packages/codegen/e2e-test/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ test('generates .d.ts', async () => {
"// @ts-nocheck
function blockErrorType<T>(val: T): [0] extends [(1 & T)] ? {} : T;
declare const styles = {
a1: '' as readonly string,
'a1': '' as readonly string,
...blockErrorType((await import('./b.module.css')).default),
...blockErrorType((await import('./unmatched.module.css')).default),
};
Expand All @@ -58,15 +58,15 @@ test('generates .d.ts', async () => {
expect(await iff.readFile('generated/src/b.module.css.d.ts')).toMatchInlineSnapshot(`
"// @ts-nocheck
declare const styles = {
b1: '' as readonly string,
'b1': '' as readonly string,
};
export default styles;
"
`);
expect(await iff.readFile('generated/src/c.module.css.d.ts')).toMatchInlineSnapshot(`
"// @ts-nocheck
declare const styles = {
c1: '' as readonly string,
'c1': '' as readonly string,
};
export default styles;
"
Expand Down Expand Up @@ -158,7 +158,7 @@ test('generates .d.ts with circular import', async () => {
"// @ts-nocheck
function blockErrorType<T>(val: T): [0] extends [(1 & T)] ? {} : T;
declare const styles = {
a1: '' as readonly string,
'a1': '' as readonly string,
...blockErrorType((await import('./b.module.css')).default),
};
export default styles;
Expand All @@ -168,7 +168,7 @@ test('generates .d.ts with circular import', async () => {
"// @ts-nocheck
function blockErrorType<T>(val: T): [0] extends [(1 & T)] ? {} : T;
declare const styles = {
b1: '' as readonly string,
'b1': '' as readonly string,
...blockErrorType((await import('./a.module.css')).default),
};
export default styles;
Expand All @@ -178,7 +178,7 @@ test('generates .d.ts with circular import', async () => {
"// @ts-nocheck
function blockErrorType<T>(val: T): [0] extends [(1 & T)] ? {} : T;
declare const styles = {
c1: '' as readonly string,
'c1': '' as readonly string,
...blockErrorType((await import('./c.module.css')).default),
};
export default styles;
Expand Down
8 changes: 4 additions & 4 deletions packages/codegen/src/project.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ describe('updateFile', () => {
expect(await iff.readFile('generated/src/a.module.css.d.ts')).toMatchInlineSnapshot(`
"// @ts-nocheck
declare const styles = {
a_1: '' as readonly string,
'a_1': '' as readonly string,
};
export default styles;
"
Expand Down Expand Up @@ -537,15 +537,15 @@ describe('emitDtsFiles', () => {
expect(await iff.readFile('generated/src/a.module.css.d.ts')).toMatchInlineSnapshot(`
"// @ts-nocheck
declare const styles = {
a1: '' as readonly string,
'a1': '' as readonly string,
};
export default styles;
"
`);
expect(await iff.readFile('generated/src/b.module.css.d.ts')).toMatchInlineSnapshot(`
"// @ts-nocheck
declare const styles = {
b1: '' as readonly string,
'b1': '' as readonly string,
};
export default styles;
"
Expand All @@ -562,7 +562,7 @@ describe('emitDtsFiles', () => {
expect(await iff.readFile('generated/src/a.module.css.d.ts')).toMatchInlineSnapshot(`
"// @ts-nocheck
declare const styles = {
a1: '' as readonly string,
'a1': '' as readonly string,
};
export default styles;
"
Expand Down
8 changes: 4 additions & 4 deletions packages/codegen/src/runner.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,15 @@ describe('runCMK', () => {
expect(await iff.readFile('generated/src/a.module.css.d.ts')).toMatchInlineSnapshot(`
"// @ts-nocheck
declare const styles = {
a_1: '' as readonly string,
'a_1': '' as readonly string,
};
export default styles;
"
`);
expect(await iff.readFile('generated/src/b.module.css.d.ts')).toMatchInlineSnapshot(`
"// @ts-nocheck
declare const styles = {
b_1: '' as readonly string,
'b_1': '' as readonly string,
};
export default styles;
"
Expand Down Expand Up @@ -133,15 +133,15 @@ describe('runCMKInWatchMode', () => {
expect(await iff.readFile('generated/src/a.module.css.d.ts')).toMatchInlineSnapshot(`
"// @ts-nocheck
declare const styles = {
a_1: '' as readonly string,
'a_1': '' as readonly string,
};
export default styles;
"
`);
expect(await iff.readFile('generated/src/b.module.css.d.ts')).toMatchInlineSnapshot(`
"// @ts-nocheck
declare const styles = {
b_1: '' as readonly string,
'b_1': '' as readonly string,
};
export default styles;
"
Expand Down
16 changes: 3 additions & 13 deletions packages/core/src/checker.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,16 +51,6 @@ describe('checkCSSModule', () => {
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": "css-modules-kit does not support invalid names as JavaScript identifiers.",
},
{
"category": "error",
"fileName": "<rootDir>/a.module.css",
Expand All @@ -69,7 +59,7 @@ describe('checkCSSModule', () => {
"column": 8,
"line": 2,
},
"text": "css-modules-kit does not support invalid names as JavaScript identifiers.",
"text": "css-modules-kit does not support invalid names as JavaScript identifiers when \`cmkOptions.namedExports\` is set to \`true\`.",
},
{
"category": "error",
Expand All @@ -79,7 +69,7 @@ describe('checkCSSModule', () => {
"column": 13,
"line": 2,
},
"text": "css-modules-kit does not support invalid names as JavaScript identifiers.",
"text": "css-modules-kit does not support invalid names as JavaScript identifiers when \`cmkOptions.namedExports\` is set to \`true\`.",
},
{
"category": "error",
Expand All @@ -89,7 +79,7 @@ describe('checkCSSModule', () => {
"column": 20,
"line": 2,
},
"text": "css-modules-kit does not support invalid names as JavaScript identifiers.",
"text": "css-modules-kit does not support invalid names as JavaScript identifiers when \`cmkOptions.namedExports\` is set to \`true\`.",
},
]
`);
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export function checkCSSModule(cssModule: CSSModule, args: CheckerArgs): Diagnos

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

function createInvalidNameAsJSIdentifiersDiagnostic(cssModule: CSSModule, loc: Location): Diagnostic {
return {
text: `css-modules-kit does not support invalid names as JavaScript identifiers.`,
text: `css-modules-kit does not support invalid names as JavaScript identifiers when \`cmkOptions.namedExports\` is set to \`true\`.`,
category: 'error',
file: { fileName: cssModule.fileName, text: cssModule.text },
start: { line: loc.start.line, column: loc.start.column },
Expand Down
7 changes: 4 additions & 3 deletions packages/core/src/dts-generator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ describe('generateDts', () => {
expect(generateDts(readAndParseCSSModule(iff.paths['test.module.css'])!, options).text).toMatchInlineSnapshot(`
"// @ts-nocheck
declare const styles = {
local1: '' as readonly string,
local2: '' as readonly string,
'local1': '' as readonly string,
'local2': '' as readonly string,
};
export default styles;
"
Expand Down Expand Up @@ -85,6 +85,7 @@ describe('generateDts', () => {
expect(generateDts(readAndParseCSSModule(iff.paths['test.module.css'])!, options).text).toMatchInlineSnapshot(`
"// @ts-nocheck
declare const styles = {
'a-1': '' as readonly string,
};
export default styles;
"
Expand Down Expand Up @@ -115,7 +116,7 @@ describe('generateDts', () => {
.toMatchInlineSnapshot(`
"// @ts-nocheck
declare const styles = {
default: '' as readonly string,
'default': '' as readonly string,
};
export default styles;
"
Expand Down
50 changes: 37 additions & 13 deletions packages/core/src/dts-generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ interface CodeMapping {
lengths: number[];
/** The generated offsets of the tokens in the *.d.ts file. */
generatedOffsets: number[];
/** The lengths of the tokens in the *.d.ts file. */
generatedLengths?: number[];
}

/** The map linking the two codes in *.d.ts */
Expand All @@ -36,14 +38,16 @@ interface GenerateDtsResult {
text: string;
mapping: CodeMapping;
linkedCodeMapping: LinkedCodeMapping;
/** Additional mappings used by ts-plugin (e.g. quoted string literal keys). */
extraMappings?: CodeMapping[];
}

/**
* Generate .d.ts from `CSSModule`.
*/
export function generateDts(cssModule: CSSModule, options: GenerateDtsOptions): GenerateDtsResult {
// Exclude invalid tokens
const localTokens = cssModule.localTokens.filter((token) => isValidName(token.name, options));
const localTokens = cssModule.localTokens.filter((token) => isValidName(token.name, options, false));
const tokenImporters = cssModule.tokenImporters
// Exclude invalid imported tokens
.map((tokenImporter) => {
Expand All @@ -52,8 +56,8 @@ export function generateDts(cssModule: CSSModule, options: GenerateDtsOptions):
...tokenImporter,
values: tokenImporter.values.filter(
(value) =>
isValidName(value.name, options) &&
(value.localName === undefined || isValidName(value.localName, options)),
isValidName(value.name, options, true) &&
(value.localName === undefined || isValidName(value.localName, options, true)),
),
};
} else {
Expand Down Expand Up @@ -111,7 +115,7 @@ function generateNamedExportsDts(
localTokens: Token[],
tokenImporters: TokenImporter[],
options: GenerateDtsOptions,
): { text: string; mapping: CodeMapping; linkedCodeMapping: LinkedCodeMapping } {
): GenerateDtsResult {
const mapping: CodeMapping = { sourceOffsets: [], lengths: [], generatedOffsets: [] };
const linkedCodeMapping: LinkedCodeMapping = {
sourceOffsets: [],
Expand Down Expand Up @@ -261,11 +265,9 @@ function generateNamedExportsDts(
}

/** Generate a d.ts file with a default export. */
function generateDefaultExportDts(
localTokens: Token[],
tokenImporters: TokenImporter[],
): { text: string; mapping: CodeMapping; linkedCodeMapping: LinkedCodeMapping } {
const mapping: CodeMapping = { sourceOffsets: [], lengths: [], generatedOffsets: [] };
function generateDefaultExportDts(localTokens: Token[], tokenImporters: TokenImporter[]): GenerateDtsResult {
const mapping: CodeMapping = { sourceOffsets: [], lengths: [], generatedOffsets: [], generatedLengths: [] };
const quotedMapping: CodeMapping = { sourceOffsets: [], lengths: [], generatedOffsets: [], generatedLengths: [] };
const linkedCodeMapping: LinkedCodeMapping = {
sourceOffsets: [],
lengths: [],
Expand Down Expand Up @@ -313,10 +315,25 @@ function generateDefaultExportDts(
*/

text += ` `;
const quoteStart = text.length;
text += `'`;
const keyStart = text.length;
// Map unquoted range in the primary mapping.
// This mapping is necessary when renaming.
// For rename, tsserver tends to return a span without quotes,
// while for go to definition, the span tends to include quotes.
// This is why we keep a dual mapping.
mapping.sourceOffsets.push(token.loc.start.offset);
mapping.generatedOffsets.push(text.length);
mapping.lengths.push(token.name.length);
text += `${token.name}: '' as readonly string,\n`;
mapping.generatedOffsets.push(keyStart);
mapping.generatedLengths!.push(token.name.length);
// Map quoted range separately to avoid overlapping ranges in a single mapping.
// This mapping is necessary for features like "go to definition".
quotedMapping.sourceOffsets.push(token.loc.start.offset);
quotedMapping.lengths.push(token.name.length);
quotedMapping.generatedOffsets.push(quoteStart);
quotedMapping.generatedLengths!.push(token.name.length + 2);
text += `${token.name}': '' as readonly string,\n`;
}
for (const tokenImporter of tokenImporters) {
if (tokenImporter.type === 'import') {
Expand Down Expand Up @@ -347,6 +364,7 @@ function generateDefaultExportDts(
mapping.sourceOffsets.push(tokenImporter.fromLoc.start.offset - 1);
mapping.lengths.push(tokenImporter.from.length + 2);
mapping.generatedOffsets.push(text.length);
mapping.generatedLengths!.push(tokenImporter.from.length + 2);
text += `'${tokenImporter.from}')).default),\n`;
} else {
/**
Expand Down Expand Up @@ -393,19 +411,22 @@ function generateDefaultExportDts(
mapping.sourceOffsets.push(localLoc.start.offset);
mapping.lengths.push(localName.length);
mapping.generatedOffsets.push(text.length);
mapping.generatedLengths!.push(localName.length);
linkedCodeMapping.sourceOffsets.push(text.length);
linkedCodeMapping.lengths.push(localName.length);
text += `${localName}: (await import(`;
if (i === 0) {
mapping.sourceOffsets.push(tokenImporter.fromLoc.start.offset - 1);
mapping.lengths.push(tokenImporter.from.length + 2);
mapping.generatedOffsets.push(text.length);
mapping.generatedLengths!.push(tokenImporter.from.length + 2);
}
text += `'${tokenImporter.from}')).default.`;
if ('localName' in value) {
mapping.sourceOffsets.push(value.loc.start.offset);
mapping.lengths.push(value.name.length);
mapping.generatedOffsets.push(text.length);
mapping.generatedLengths!.push(value.name.length);
}
linkedCodeMapping.generatedOffsets.push(text.length);
linkedCodeMapping.generatedLengths.push(value.name.length);
Expand All @@ -414,11 +435,14 @@ function generateDefaultExportDts(
}
}
text += `};\nexport default ${STYLES_EXPORT_NAME};\n`;
if (quotedMapping.sourceOffsets.length) {
return { text, mapping, linkedCodeMapping, extraMappings: [quotedMapping] };
}
return { text, mapping, linkedCodeMapping };
}

function isValidName(name: string, options: GenerateDtsOptions): boolean {
if (!isValidAsJSIdentifier(name)) return false;
function isValidName(name: string, options: GenerateDtsOptions, isTokenImport: boolean): boolean {
if ((options.namedExports || isTokenImport) && !isValidAsJSIdentifier(name)) return false;
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are branching to minimize the scope of impact.

if (name === '__proto__') return false;
if (options.namedExports && name === 'default') return false;
return true;
Expand Down
Loading