Skip to content

Commit cf7b3a5

Browse files
committed
fix failure to add missing rule for named exports
1 parent b3bb43f commit cf7b3a5

2 files changed

Lines changed: 63 additions & 8 deletions

File tree

packages/ts-plugin/e2e/named-exports.test.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import dedent from 'dedent';
33
import ts from 'typescript';
44
import { describe, expect, test } from 'vitest';
5+
import { PROPERTY_DOES_NOT_EXIST_ERROR_CODES } from '../src/language-service/feature/code-fix.js';
56
import { createIFF } from './test-util/fixture.js';
67
import {
78
formatPath,
@@ -442,4 +443,51 @@ describe('supports code fixes', async () => {
442443
expect(normalizeCodeFixActions(res.body!)).toStrictEqual(normalizeCodeFixActions(expected));
443444
});
444445
});
446+
test('supports adding missing CSS rules', async () => {
447+
const iff = await createIFF({
448+
'a.tsx': dedent`
449+
import * as styles from './a.module.css';
450+
styles.a_1;
451+
`,
452+
'a.module.css': '',
453+
'tsconfig.json': dedent`
454+
{
455+
"cmkOptions": {
456+
"namedExports": true
457+
}
458+
}
459+
`,
460+
});
461+
const tsserver = launchTsserver();
462+
await tsserver.sendUpdateOpen({
463+
openFiles: [{ file: iff.paths['tsconfig.json'] }],
464+
});
465+
const res = await tsserver.sendGetCodeFixes({
466+
errorCodes: [PROPERTY_DOES_NOT_EXIST_ERROR_CODES[0]],
467+
file: iff.paths['a.tsx'],
468+
startLine: 2,
469+
startOffset: 8,
470+
endLine: 2,
471+
endOffset: 11,
472+
});
473+
expect(normalizeCodeFixActions(res.body!)).toStrictEqual(
474+
normalizeCodeFixActions([
475+
{
476+
fixName: 'fixMissingCSSRule',
477+
changes: [
478+
{
479+
fileName: formatPath(iff.paths['a.module.css']),
480+
textChanges: [
481+
{
482+
start: { line: 1, offset: 1 },
483+
end: { line: 1, offset: 1 },
484+
newText: '\n.a_1 {\n \n}',
485+
},
486+
],
487+
},
488+
],
489+
},
490+
]),
491+
);
492+
});
445493
});

packages/ts-plugin/src/language-service/feature/code-fix.ts

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import type { CMKConfig, Resolver } from '@css-modules-kit/core';
22
import { isComponentFileName, isCSSModuleFile } from '@css-modules-kit/core';
33
import type { Language } from '@volar/language-core';
44
import ts from 'typescript';
5-
import { isCSSModuleScript } from '../../language-plugin.js';
65
import { convertDefaultImportsToNamespaceImports, createPreferencesForCompletion } from '../../util.js';
76

87
// ref: https://github.com/microsoft/TypeScript/blob/220706eb0320ff46fad8bf80a5e99db624ee7dfb/src/compiler/diagnosticMessages.json
@@ -37,7 +36,7 @@ export function getCodeFixesAtPosition(
3736
if (isComponentFileName(fileName)) {
3837
// If a user is trying to use a non-existent token (e.g. `styles.nonExistToken`), provide a code fix to add the token.
3938
if (errorCodes.some((errorCode) => PROPERTY_DOES_NOT_EXIST_ERROR_CODES.includes(errorCode))) {
40-
const tokenConsumer = getTokenConsumerAtPosition(fileName, start, language, languageService, project);
39+
const tokenConsumer = getTokenConsumerAtPosition(fileName, start, languageService, project, config);
4140
if (tokenConsumer) {
4241
prior.push({
4342
fixName: 'fixMissingCSSRule',
@@ -85,9 +84,9 @@ interface TokenConsumer {
8584
function getTokenConsumerAtPosition(
8685
fileName: string,
8786
position: number,
88-
language: Language<string>,
8987
languageService: ts.LanguageService,
9088
project: ts.server.Project,
89+
config: CMKConfig,
9190
): TokenConsumer | undefined {
9291
const sourceFile = project.getSourceFile(project.projectService.toPath(fileName));
9392
if (!sourceFile) return undefined;
@@ -99,11 +98,19 @@ function getTokenConsumerAtPosition(
9998
// `expression` is the expression of the property access expression (e.g. `styles` in `styles.foo`).
10099
const expression = propertyAccessExpression.expression;
101100

102-
const definitions = languageService.getDefinitionAtPosition(fileName, expression.getStart());
103-
if (definitions && definitions[0]) {
104-
const script = language.scripts.get(definitions[0].fileName);
105-
if (isCSSModuleScript(script)) {
106-
return { tokenName: propertyAccessExpression.name.text, from: definitions[0].fileName };
101+
let [definition] = languageService.getDefinitionAtPosition(fileName, expression.getStart()) ?? [];
102+
if (!definition) return undefined;
103+
104+
// `definition` is may be `styles` definition in CSS Modules file.
105+
if (isCSSModuleFile(definition.fileName)) {
106+
return { tokenName: propertyAccessExpression.name.text, from: definition.fileName };
107+
} else if (config.namedExports) {
108+
// If namespaced import is used, it may be a definition in a component file
109+
// (e.g. the `styles` of `import * as styles from './a.module.css'`).
110+
// In that case, we need to call `getDefinitionAtPosition` again to get the definition in CSS module file.
111+
[definition] = languageService.getDefinitionAtPosition(definition.fileName, definition.textSpan.start) ?? [];
112+
if (definition && isCSSModuleFile(definition.fileName)) {
113+
return { tokenName: propertyAccessExpression.name.text, from: definition.fileName };
107114
}
108115
}
109116
return undefined;

0 commit comments

Comments
 (0)