Skip to content

Commit cbba4fc

Browse files
committed
refactor completion/code-fix handler
1 parent 9aeeb86 commit cbba4fc

6 files changed

Lines changed: 39 additions & 82 deletions

File tree

packages/core/src/file.ts

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@ import { join, parse } from './path.js';
33
import type { MatchesPattern } from './type.js';
44

55
export const CSS_MODULE_EXTENSION = '.module.css';
6-
export const CSS_MODULE_DTS_EXTENSION = `.module.css.d.ts`;
7-
export const ARBITRARY_CSS_MODULE_DTS_EXTENSION = '.module.d.css.ts';
86
const COMPONENT_EXTENSIONS = ['.tsx', '.jsx'];
97

108
export function isCSSModuleFile(fileName: string): boolean {
@@ -16,14 +14,6 @@ export function getCssModuleFileName(tsFileName: string): string {
1614
return join(dir, `${name}${CSS_MODULE_EXTENSION}`);
1715
}
1816

19-
export function isCSSModulesDtsFile(fileName: string, arbitraryExtensions: boolean): boolean {
20-
if (arbitraryExtensions) {
21-
return fileName.endsWith(ARBITRARY_CSS_MODULE_DTS_EXTENSION);
22-
} else {
23-
return fileName.endsWith(CSS_MODULE_DTS_EXTENSION);
24-
}
25-
}
26-
2717
export function isComponentFileName(fileName: string): boolean {
2818
// NOTE: Do not check whether it is an upper camel case or not, since lower camel case (e.g. `page.tsx`) is used in Next.js.
2919
return COMPONENT_EXTENSIONS.some((ext) => fileName.endsWith(ext));

packages/core/src/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ export { createResolver } from './resolver.js';
2626
export {
2727
CSS_MODULE_EXTENSION,
2828
getCssModuleFileName,
29-
isCSSModulesDtsFile,
3029
isComponentFileName,
3130
isCSSModuleFile,
3231
findComponentFile,

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

Lines changed: 2 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import type { CMKConfig, Resolver } from '@css-modules-kit/core';
2-
import { isComponentFileName, isCSSModuleFile, STYLES_EXPORT_NAME } from '@css-modules-kit/core';
2+
import { isComponentFileName } from '@css-modules-kit/core';
33
import type { Language } from '@volar/language-core';
44
import ts from 'typescript';
55
import { isCSSModuleScript } from '../../language-plugin.js';
6+
import { convertDefaultImportsToNamespaceImports } from '../../util.js';
67

78
// ref: https://github.com/microsoft/TypeScript/blob/220706eb0320ff46fad8bf80a5e99db624ee7dfb/src/compiler/diagnosticMessages.json#L2051-L2054
89
export const PROPERTY_DOES_NOT_EXIST_ERROR_CODE = 2339;
@@ -42,35 +43,6 @@ export function getCodeFixesAtPosition(
4243
};
4344
}
4445

45-
function convertDefaultImportsToNamespaceImports(
46-
codeFixes: ts.CodeFixAction[],
47-
fileName: string,
48-
resolver: Resolver,
49-
): void {
50-
// Convert default imports to namespace imports for CSS modules.
51-
// For example, convert `import styles from './styles.module.css'` to `import * as styles from './styles.module.css'`.
52-
for (const codeFix of codeFixes) {
53-
if (codeFix.fixName === 'import') {
54-
// Check if the code fix is to add an import for a CSS module.
55-
const match = codeFix.description.match(/^Add import from "(.*)"$/u);
56-
if (!match) continue;
57-
const specifier = match[1]!;
58-
const resolved = resolver(specifier, { request: fileName });
59-
if (!resolved || !isCSSModuleFile(resolved)) continue;
60-
61-
// If the specifier is a CSS module, convert the import to a namespace import.
62-
for (const change of codeFix.changes) {
63-
for (const textChange of change.textChanges) {
64-
textChange.newText = textChange.newText.replace(
65-
`import ${STYLES_EXPORT_NAME} from`,
66-
`import * as ${STYLES_EXPORT_NAME} from`,
67-
);
68-
}
69-
}
70-
}
71-
}
72-
}
73-
7446
interface TokenConsumer {
7547
/** The token name (e.g. `foo` in `styles.foo`) */
7648
tokenName: string;

packages/ts-plugin/src/language-service/feature/completion.ts

Lines changed: 6 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,7 @@
1-
import type { CMKConfig } from '@css-modules-kit/core';
2-
import {
3-
getCssModuleFileName,
4-
isComponentFileName,
5-
isCSSModuleFile,
6-
isCSSModulesDtsFile,
7-
STYLES_EXPORT_NAME,
8-
} from '@css-modules-kit/core';
1+
import type { CMKConfig, Resolver } from '@css-modules-kit/core';
2+
import { getCssModuleFileName, isComponentFileName, isCSSModuleFile, STYLES_EXPORT_NAME } from '@css-modules-kit/core';
93
import ts from 'typescript';
4+
import { convertDefaultImportsToNamespaceImports } from '../../util.js';
105

116
export function getCompletionsAtPosition(
127
languageService: ts.LanguageService,
@@ -89,6 +84,7 @@ function isClassNamePropEntry(entry: ts.CompletionEntry) {
8984

9085
export function getCompletionEntryDetails(
9186
languageService: ts.LanguageService,
87+
resolver: Resolver,
9288
config: CMKConfig,
9389
): ts.LanguageService['getCompletionEntryDetails'] {
9490
// eslint-disable-next-line max-params
@@ -104,39 +100,9 @@ export function getCompletionEntryDetails(
104100
);
105101
if (!details) return undefined;
106102

107-
if (config.namedExports && isDefaultExportedStylesEntryDetails(entryName, data, config)) {
108-
for (const codeAction of details.codeActions ?? []) {
109-
for (const change of codeAction.changes) {
110-
// `import styles from` => `import * as styles from`
111-
for (const textChange of change.textChanges) {
112-
if (textChange.newText.startsWith(`import ${STYLES_EXPORT_NAME} from`)) {
113-
textChange.newText = textChange.newText.replace(
114-
`import ${STYLES_EXPORT_NAME} from`,
115-
`import * as ${STYLES_EXPORT_NAME} from`,
116-
);
117-
}
118-
}
119-
}
120-
}
103+
if (config.namedExports && details.codeActions) {
104+
convertDefaultImportsToNamespaceImports(details.codeActions, fileName, resolver);
121105
}
122106
return details;
123107
};
124108
}
125-
126-
/**
127-
* Check if the completion entry details are the default exported `styles` entry.
128-
*/
129-
function isDefaultExportedStylesEntryDetails(
130-
entryName: string,
131-
data: ts.CompletionEntryData | undefined,
132-
config: CMKConfig,
133-
): boolean {
134-
return (
135-
entryName === STYLES_EXPORT_NAME &&
136-
!!data &&
137-
// eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison
138-
data.exportName === ts.InternalSymbolName.Default &&
139-
data.fileName !== undefined &&
140-
isCSSModulesDtsFile(data.fileName, config.arbitraryExtensions)
141-
);
142-
}

packages/ts-plugin/src/language-service/proxy.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ export function proxyLanguageService(
4848
proxy.getApplicableRefactors = getApplicableRefactors(languageService, project);
4949
proxy.getEditsForRefactor = getEditsForRefactor(languageService);
5050
proxy.getCompletionsAtPosition = getCompletionsAtPosition(languageService, config);
51-
proxy.getCompletionEntryDetails = getCompletionEntryDetails(languageService, config);
51+
proxy.getCompletionEntryDetails = getCompletionEntryDetails(languageService, resolver, config);
5252
proxy.getCodeFixesAtPosition = getCodeFixesAtPosition(language, languageService, project, resolver, config);
5353

5454
return proxy;

packages/ts-plugin/src/util.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import type { Resolver } from '@css-modules-kit/core';
2+
import { isCSSModuleFile, STYLES_EXPORT_NAME } from '@css-modules-kit/core';
13
import ts from 'typescript';
24

35
/** The error code used by tsserver to display the css-modules-kit error in the editor. */
@@ -17,3 +19,31 @@ export function convertErrorCategory(category: 'error' | 'warning' | 'suggestion
1719
throw new Error(`Unknown category: ${String(category)}`);
1820
}
1921
}
22+
23+
export function convertDefaultImportsToNamespaceImports(
24+
codeFixes: ts.CodeFixAction[] | ts.CodeAction[],
25+
fileName: string,
26+
resolver: Resolver,
27+
): void {
28+
// Convert default imports to namespace imports for CSS modules.
29+
// For example, convert `import styles from './styles.module.css'` to `import * as styles from './styles.module.css'`.
30+
for (const codeFix of codeFixes) {
31+
if ('fixName' in codeFix && codeFix.fixName !== 'import') continue;
32+
// Check if the code fix is to add an import for a CSS module.
33+
const match = codeFix.description.match(/^Add import from "(.*)"$/u);
34+
if (!match) continue;
35+
const specifier = match[1]!;
36+
const resolved = resolver(specifier, { request: fileName });
37+
if (!resolved || !isCSSModuleFile(resolved)) continue;
38+
39+
// If the specifier is a CSS module, convert the import to a namespace import.
40+
for (const change of codeFix.changes) {
41+
for (const textChange of change.textChanges) {
42+
textChange.newText = textChange.newText.replace(
43+
`import ${STYLES_EXPORT_NAME} from`,
44+
`import * as ${STYLES_EXPORT_NAME} from`,
45+
);
46+
}
47+
}
48+
}
49+
}

0 commit comments

Comments
 (0)