Skip to content

Commit 9819482

Browse files
committed
default and __proto__ is not allowed as names
1 parent 15dcba8 commit 9819482

7 files changed

Lines changed: 275 additions & 9 deletions

File tree

packages/codegen/src/runner.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ export async function runCMK(project: string, clean: boolean, logger: Logger): P
110110
const exportBuilder = createExportBuilder({ getCSSModule, matchesPattern, resolver });
111111
const semanticDiagnostics: Diagnostic[] = [];
112112
for (const { cssModule } of parseResults) {
113-
const diagnostics = checkCSSModule(cssModule, exportBuilder, matchesPattern, resolver, getCSSModule);
113+
const diagnostics = checkCSSModule(cssModule, config, exportBuilder, matchesPattern, resolver, getCSSModule);
114114
semanticDiagnostics.push(...diagnostics);
115115
}
116116

packages/core/src/checker.test.ts

Lines changed: 167 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { createExportBuilder } from './export-builder.js';
44
import { resolve } from './path.js';
55
import { createResolver } from './resolver.js';
66
import { fakeCSSModule } from './test/css-module.js';
7+
import { fakeConfig } from './test/faker.js';
78
import {
89
fakeAtImportTokenImporter,
910
fakeAtValueTokenImporter,
@@ -15,14 +16,15 @@ import type { CSSModule, Location } from './type.js';
1516
const resolver = createResolver({}, undefined);
1617

1718
function prepareCheckerArgs<const T extends CSSModule[]>(cssModules: T) {
19+
const config = fakeConfig();
1820
const getCSSModule = (path: string) => cssModules.find((m) => resolve(m.fileName) === resolve(path));
1921
const matchesPattern = (path: string) => path.endsWith('.module.css');
2022
const exportBuilder = createExportBuilder({
2123
getCSSModule,
2224
matchesPattern,
2325
resolver,
2426
});
25-
return { cssModules, exportBuilder, matchesPattern, resolver, getCSSModule };
27+
return { cssModules, config, exportBuilder, matchesPattern, resolver, getCSSModule };
2628
}
2729

2830
function fakeLoc({ column }: { column: number }): Location {
@@ -61,6 +63,7 @@ describe('checkCSSModule', () => {
6163
]);
6264
const diagnostics = checkCSSModule(
6365
args.cssModules[0],
66+
args.config,
6467
args.exportBuilder,
6568
args.matchesPattern,
6669
args.resolver,
@@ -123,6 +126,165 @@ describe('checkCSSModule', () => {
123126
]
124127
`);
125128
});
129+
test('report diagnostics for "__proto__" name', () => {
130+
const args = prepareCheckerArgs([
131+
fakeCSSModule({
132+
fileName: '/a.module.css',
133+
localTokens: [fakeToken({ name: '__proto__', loc: fakeLoc({ column: 1 }) })],
134+
tokenImporters: [
135+
fakeAtValueTokenImporter({
136+
from: './b.module.css',
137+
fromLoc: fakeLoc({ column: 2 }),
138+
values: [
139+
fakeAtValueTokenImporterValue({ name: '__proto__', loc: fakeLoc({ column: 3 }) }),
140+
fakeAtValueTokenImporterValue({
141+
name: 'valid',
142+
loc: fakeLoc({ column: 4 }),
143+
localName: '__proto__',
144+
localLoc: fakeLoc({ column: 5 }),
145+
}),
146+
],
147+
}),
148+
],
149+
}),
150+
fakeCSSModule({
151+
fileName: '/b.module.css',
152+
localTokens: [fakeToken({ name: '__proto__' }), fakeToken({ name: 'valid' })],
153+
}),
154+
]);
155+
156+
const diagnostics = checkCSSModule(
157+
args.cssModules[0],
158+
args.config,
159+
args.exportBuilder,
160+
args.matchesPattern,
161+
args.resolver,
162+
args.getCSSModule,
163+
);
164+
expect(diagnostics).toMatchInlineSnapshot(`
165+
[
166+
{
167+
"category": "error",
168+
"file": {
169+
"fileName": "/a.module.css",
170+
"text": "",
171+
},
172+
"length": 0,
173+
"start": {
174+
"column": 1,
175+
"line": 1,
176+
},
177+
"text": "\`__proto__\` is not allowed as names.",
178+
},
179+
{
180+
"category": "error",
181+
"file": {
182+
"fileName": "/a.module.css",
183+
"text": "",
184+
},
185+
"length": 0,
186+
"start": {
187+
"column": 3,
188+
"line": 1,
189+
},
190+
"text": "\`__proto__\` is not allowed as names.",
191+
},
192+
{
193+
"category": "error",
194+
"file": {
195+
"fileName": "/a.module.css",
196+
"text": "",
197+
},
198+
"length": 0,
199+
"start": {
200+
"column": 5,
201+
"line": 1,
202+
},
203+
"text": "\`__proto__\` is not allowed as names.",
204+
},
205+
]
206+
`);
207+
});
208+
test('report diagnostics for "default" name when namedExports is true', () => {
209+
const args = prepareCheckerArgs([
210+
fakeCSSModule({
211+
fileName: '/a.module.css',
212+
localTokens: [fakeToken({ name: 'default', loc: fakeLoc({ column: 1 }) })],
213+
tokenImporters: [
214+
fakeAtValueTokenImporter({
215+
from: './b.module.css',
216+
fromLoc: fakeLoc({ column: 2 }),
217+
values: [
218+
fakeAtValueTokenImporterValue({ name: 'default', loc: fakeLoc({ column: 3 }) }),
219+
fakeAtValueTokenImporterValue({
220+
name: 'valid',
221+
loc: fakeLoc({ column: 4 }),
222+
localName: 'default',
223+
localLoc: fakeLoc({ column: 5 }),
224+
}),
225+
],
226+
}),
227+
],
228+
}),
229+
fakeCSSModule({
230+
fileName: '/b.module.css',
231+
localTokens: [fakeToken({ name: 'default' }), fakeToken({ name: 'valid' })],
232+
}),
233+
]);
234+
args.config.namedExports = true;
235+
236+
const diagnostics = checkCSSModule(
237+
args.cssModules[0],
238+
args.config,
239+
args.exportBuilder,
240+
args.matchesPattern,
241+
args.resolver,
242+
args.getCSSModule,
243+
);
244+
expect(diagnostics).toMatchInlineSnapshot(`
245+
[
246+
{
247+
"category": "error",
248+
"file": {
249+
"fileName": "/a.module.css",
250+
"text": "",
251+
},
252+
"length": 0,
253+
"start": {
254+
"column": 1,
255+
"line": 1,
256+
},
257+
"text": "\`default\` is not allowed as names when \`cmkOptions.namedExports\` is set to \`true\`.",
258+
},
259+
{
260+
"category": "error",
261+
"file": {
262+
"fileName": "/a.module.css",
263+
"text": "",
264+
},
265+
"length": 0,
266+
"start": {
267+
"column": 3,
268+
"line": 1,
269+
},
270+
"text": "\`default\` is not allowed as names when \`cmkOptions.namedExports\` is set to \`true\`.",
271+
},
272+
{
273+
"category": "error",
274+
"file": {
275+
"fileName": "/a.module.css",
276+
"text": "",
277+
},
278+
"length": 0,
279+
"start": {
280+
"column": 5,
281+
"line": 1,
282+
},
283+
"text": "\`default\` is not allowed as names when \`cmkOptions.namedExports\` is set to \`true\`.",
284+
},
285+
]
286+
`);
287+
});
126288
test('report diagnostics for non-existing module', () => {
127289
const args = prepareCheckerArgs([
128290
fakeCSSModule({
@@ -139,6 +301,7 @@ describe('checkCSSModule', () => {
139301
]);
140302
const diagnostics = checkCSSModule(
141303
args.cssModules[0],
304+
args.config,
142305
args.exportBuilder,
143306
args.matchesPattern,
144307
args.resolver,
@@ -197,6 +360,7 @@ describe('checkCSSModule', () => {
197360
]);
198361
const diagnostics = checkCSSModule(
199362
args.cssModules[0],
363+
args.config,
200364
args.exportBuilder,
201365
args.matchesPattern,
202366
args.resolver,
@@ -229,6 +393,7 @@ describe('checkCSSModule', () => {
229393
]);
230394
const diagnostics = checkCSSModule(
231395
args.cssModules[0],
396+
args.config,
232397
args.exportBuilder,
233398
args.matchesPattern,
234399
() => undefined, // Simulate unresolvable module
@@ -251,6 +416,7 @@ describe('checkCSSModule', () => {
251416
]);
252417
const diagnostics = checkCSSModule(
253418
args.cssModules[0],
419+
args.config,
254420
args.exportBuilder,
255421
(path: string) => path === '/a.module.css', // Only match the current module
256422
args.resolver,

packages/core/src/checker.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { CMKConfig } from './config.js';
12
import type {
23
AtValueTokenImporter,
34
AtValueTokenImporterValue,
@@ -11,8 +12,10 @@ import type {
1112
} from './type.js';
1213
import { isValidAsJSIdentifier } from './util.js';
1314

15+
// eslint-disable-next-line max-params, complexity
1416
export function checkCSSModule(
1517
cssModule: CSSModule,
18+
config: CMKConfig,
1619
exportBuilder: ExportBuilder,
1720
matchesPattern: MatchesPattern,
1821
resolver: Resolver,
@@ -21,9 +24,16 @@ export function checkCSSModule(
2124
const diagnostics: Diagnostic[] = [];
2225

2326
for (const token of cssModule.localTokens) {
27+
// Reject special names as they may break .d.ts files
2428
if (!isValidAsJSIdentifier(token.name)) {
2529
diagnostics.push(createInvalidNameAsJSIdentifiersDiagnostic(cssModule, token.loc));
2630
}
31+
if (token.name === '__proto__') {
32+
diagnostics.push(createProtoIsNotAllowedDiagnostic(cssModule, token.loc));
33+
}
34+
if (config.namedExports && token.name === 'default') {
35+
diagnostics.push(createDefaultIsNotAllowedDiagnostic(cssModule, token.loc));
36+
}
2737
}
2838

2939
for (const tokenImporter of cssModule.tokenImporters) {
@@ -47,6 +57,20 @@ export function checkCSSModule(
4757
if (value.localName && !isValidAsJSIdentifier(value.localName)) {
4858
diagnostics.push(createInvalidNameAsJSIdentifiersDiagnostic(cssModule, value.localLoc!));
4959
}
60+
if (value.name === '__proto__') {
61+
diagnostics.push(createProtoIsNotAllowedDiagnostic(cssModule, value.loc));
62+
}
63+
if (value.localName === '__proto__') {
64+
diagnostics.push(createProtoIsNotAllowedDiagnostic(cssModule, value.localLoc!));
65+
}
66+
if (config.namedExports) {
67+
if (value.name === 'default') {
68+
diagnostics.push(createDefaultIsNotAllowedDiagnostic(cssModule, value.loc));
69+
}
70+
if (value.localName === 'default') {
71+
diagnostics.push(createDefaultIsNotAllowedDiagnostic(cssModule, value.localLoc!));
72+
}
73+
}
5074
}
5175
}
5276
}
@@ -86,3 +110,23 @@ function createInvalidNameAsJSIdentifiersDiagnostic(cssModule: CSSModule, loc: L
86110
length: loc.end.offset - loc.start.offset,
87111
};
88112
}
113+
114+
function createProtoIsNotAllowedDiagnostic(cssModule: CSSModule, loc: Location): Diagnostic {
115+
return {
116+
text: `\`__proto__\` is not allowed as names.`,
117+
category: 'error',
118+
file: { fileName: cssModule.fileName, text: cssModule.text },
119+
start: { line: loc.start.line, column: loc.start.column },
120+
length: loc.end.offset - loc.start.offset,
121+
};
122+
}
123+
124+
function createDefaultIsNotAllowedDiagnostic(cssModule: CSSModule, loc: Location): Diagnostic {
125+
return {
126+
text: `\`default\` is not allowed as names when \`cmkOptions.namedExports\` is set to \`true\`.`,
127+
category: 'error',
128+
file: { fileName: cssModule.fileName, text: cssModule.text },
129+
start: { line: loc.start.line, column: loc.start.column },
130+
length: loc.end.offset - loc.start.offset,
131+
};
132+
}

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

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,53 @@ describe('createDts', () => {
241241
"
242242
`);
243243
});
244+
test('does not create types for `__proto__`', () => {
245+
expect(
246+
createDts(
247+
fakeCSSModule({
248+
localTokens: [fakeToken({ name: '__proto__', loc: fakeLoc(0) })],
249+
}),
250+
host,
251+
options,
252+
).text,
253+
).toMatchInlineSnapshot(`
254+
"// @ts-nocheck
255+
declare const styles = {
256+
};
257+
export default styles;
258+
"
259+
`);
260+
});
261+
test('does not create types for `default` when `namedExports` is true', () => {
262+
expect(
263+
createDts(
264+
fakeCSSModule({
265+
localTokens: [fakeToken({ name: 'default', loc: fakeLoc(0) })],
266+
}),
267+
host,
268+
{ ...options, namedExports: true },
269+
).text,
270+
).toMatchInlineSnapshot(`
271+
"// @ts-nocheck
272+
"
273+
`);
274+
expect(
275+
createDts(
276+
fakeCSSModule({
277+
localTokens: [fakeToken({ name: 'default', loc: fakeLoc(0) })],
278+
}),
279+
host,
280+
{ ...options, namedExports: false },
281+
).text,
282+
).toMatchInlineSnapshot(`
283+
"// @ts-nocheck
284+
declare const styles = {
285+
default: '' as readonly string,
286+
};
287+
export default styles;
288+
"
289+
`);
290+
});
244291
test('creates d.ts file with named exports', () => {
245292
expect(
246293
createDts(

0 commit comments

Comments
 (0)