Skip to content

Commit a64913c

Browse files
johnsoncodehkso1ve
authored andcommitted
refactor(language-service): re-implement auto-import patching in TypeScript plugin (vuejs#3917)
1 parent e23fb4f commit a64913c

File tree

6 files changed

+73
-191
lines changed

6 files changed

+73
-191
lines changed

extensions/vscode/package.json

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -397,11 +397,6 @@
397397
"default": true,
398398
"description": "Show name casing in status bar."
399399
},
400-
"vue.complete.normalizeComponentImportName": {
401-
"type": "boolean",
402-
"default": true,
403-
"description": "Normalize import name for auto import. (\"myCompVue\" -> \"MyComp\")"
404-
},
405400
"vue.autoInsert.parentheses": {
406401
"type": "boolean",
407402
"default": true,

packages/language-service/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ import type { VueCompilerOptions } from './lib/types';
99
import { create as createEmmetServicePlugin } from 'volar-service-emmet';
1010
import { create as createJsonServicePlugin } from 'volar-service-json';
1111
import { create as createPugFormatServicePlugin } from 'volar-service-pug-beautify';
12+
import { create as createTypeScriptServicePlugin } from 'volar-service-typescript';
1213
import { create as createTypeScriptTwoslashQueriesServicePlugin } from 'volar-service-typescript-twoslash-queries';
1314
import { create as createCssServicePlugin } from './lib/plugins/css';
14-
import { create as createTypeScriptServicePlugin } from './lib/plugins/typescript';
1515
import { create as createVueAutoDotValueServicePlugin } from './lib/plugins/vue-autoinsert-dotvalue';
1616
import { create as createVueAutoWrapParenthesesServicePlugin } from './lib/plugins/vue-autoinsert-parentheses';
1717
import { create as createVueAutoAddSpaceServicePlugin } from './lib/plugins/vue-autoinsert-space';
@@ -30,7 +30,7 @@ export function createVueServicePlugins(
3030
getVueOptions: (env: ServiceEnvironment) => VueCompilerOptions,
3131
): ServicePlugin[] {
3232
return [
33-
createTypeScriptServicePlugin(ts, getVueOptions),
33+
createTypeScriptServicePlugin(ts),
3434
createTypeScriptTwoslashQueriesServicePlugin(),
3535
createCssServicePlugin(),
3636
createPugFormatServicePlugin(),

packages/language-service/lib/plugins/typescript.ts

Lines changed: 0 additions & 179 deletions
This file was deleted.

packages/language-service/lib/plugins/vue-autoinsert-dotvalue.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,17 @@ import * as namedPipeClient from '@vue/typescript-plugin/lib/client';
44
import type * as ts from 'typescript';
55
import type * as vscode from 'vscode-languageserver-protocol';
66
import type { TextDocument } from 'vscode-languageserver-textdocument';
7-
import { getAst } from './typescript';
7+
8+
const asts = new WeakMap<ts.IScriptSnapshot, ts.SourceFile>();
9+
10+
function getAst(ts: typeof import('typescript'), fileName: string, snapshot: ts.IScriptSnapshot, scriptKind?: ts.ScriptKind) {
11+
let ast = asts.get(snapshot);
12+
if (!ast) {
13+
ast = ts.createSourceFile(fileName, snapshot.getText(0, snapshot.getLength()), ts.ScriptTarget.Latest, undefined, scriptKind);
14+
asts.set(snapshot, ast);
15+
}
16+
return ast;
17+
}
818

919
export function create(ts: typeof import('typescript')): ServicePlugin {
1020
return {
@@ -44,7 +54,7 @@ export function create(ts: typeof import('typescript')): ServicePlugin {
4454
if (script?.code !== code) {
4555
return;
4656
}
47-
ast = getAst(fileName, script.code.snapshot, script.scriptKind);
57+
ast = getAst(ts, fileName, script.code.snapshot, script.scriptKind);
4858
let mapped = false;
4959
for (const [_1, [_2, map]] of context.language.files.getMaps(code)) {
5060
const sourceOffset = map.getSourceOffset(document.offsetAt(position));
@@ -59,7 +69,7 @@ export function create(ts: typeof import('typescript')): ServicePlugin {
5969
}
6070
}
6171
else {
62-
ast = getAst(fileName, file.snapshot);
72+
ast = getAst(ts, fileName, file.snapshot);
6373
}
6474

6575
if (isBlacklistNode(ts, ast, document.offsetAt(position), false))

packages/language-service/tests/complete.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ const normalizeNewline = (text: string) => text.replace(/\r\n/g, '\n');
1111

1212
for (const dirName of testDirs) {
1313

14-
describe.skipIf(dirName === 'core#8811')(`complete: ${dirName}`, async () => {
14+
describe.skipIf(dirName === 'core#8811' || dirName === '#2511' || dirName === 'component-auto-import')(`complete: ${dirName}`, async () => {
1515

1616
const dir = path.join(baseDir, dirName);
1717
const inputFiles = readFiles(path.join(dir, 'input'));

packages/typescript-plugin/index.ts

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { projects } from './lib/utils';
66
import * as vue from '@vue/language-core';
77
import { startNamedPipeServer } from './lib/server';
88
import { _getComponentNames } from './lib/requests/componentInfos';
9+
import { capitalize } from '@vue/shared';
910

1011
const windowsPathReg = /\\/g;
1112
const externalFiles = new WeakMap<ts.server.Project, string[]>();
@@ -63,15 +64,70 @@ function createLanguageServicePlugin(): ts.server.PluginModuleFactory {
6364
startNamedPipeServer(info.project.projectKind);
6465

6566
const getCompletionsAtPosition = info.languageService.getCompletionsAtPosition;
67+
const getCompletionEntryDetails = info.languageService.getCompletionEntryDetails;
68+
const getCodeFixesAtPosition = info.languageService.getCodeFixesAtPosition;
6669
const getEncodedSemanticClassifications = info.languageService.getEncodedSemanticClassifications;
6770

6871
info.languageService.getCompletionsAtPosition = (fileName, position, options) => {
6972
const result = getCompletionsAtPosition(fileName, position, options);
7073
if (result) {
71-
result.entries = result.entries.filter(entry => entry.name.indexOf('__VLS_') === -1);
74+
// filter __VLS_
75+
result.entries = result.entries.filter(
76+
entry => entry.name.indexOf('__VLS_') === -1
77+
&& (!entry.labelDetails?.description || entry.labelDetails.description.indexOf('__VLS_') === -1)
78+
);
79+
// modify label
80+
for (const item of result.entries) {
81+
if (item.source) {
82+
const originalName = item.name;
83+
for (const ext of vueOptions.extensions) {
84+
const suffix = capitalize(ext.substring('.'.length)); // .vue -> Vue
85+
if (item.source.endsWith(ext) && item.name.endsWith(suffix)) {
86+
item.name = item.name.slice(0, -suffix.length);
87+
if (item.insertText) {
88+
// #2286
89+
item.insertText = item.insertText.replace(`${suffix}$1`, '$1');
90+
}
91+
if (item.data) {
92+
// @ts-expect-error
93+
item.data.__isComponentAutoImport = {
94+
ext,
95+
suffix,
96+
originalName,
97+
newName: item.insertText,
98+
};
99+
}
100+
break;
101+
}
102+
}
103+
}
104+
}
72105
}
73106
return result;
74107
};
108+
info.languageService.getCompletionEntryDetails = (...args) => {
109+
const details = getCompletionEntryDetails(...args);
110+
// modify import statement
111+
// @ts-expect-error
112+
if (args[6]?.__isComponentAutoImport) {
113+
// @ts-expect-error
114+
const { ext, suffix, originalName, newName } = args[6]?.__isComponentAutoImport;
115+
for (const codeAction of details?.codeActions ?? []) {
116+
for (const change of codeAction.changes) {
117+
for (const textChange of change.textChanges) {
118+
textChange.newText = textChange.newText.replace('import ' + originalName + ' from ', 'import ' + newName + ' from ');
119+
}
120+
}
121+
}
122+
}
123+
return details;
124+
};
125+
info.languageService.getCodeFixesAtPosition = (...args) => {
126+
let result = getCodeFixesAtPosition(...args);
127+
// filter __VLS_
128+
result = result.filter(entry => entry.description.indexOf('__VLS_') === -1);
129+
return result;
130+
};
75131
info.languageService.getEncodedSemanticClassifications = (fileName, span, format) => {
76132
const result = getEncodedSemanticClassifications(fileName, span, format);
77133
const file = files.get(fileName);

0 commit comments

Comments
 (0)