Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(language-service): re-implement auto-import patching in TypeScript plugin #3917

Merged
merged 3 commits into from
Mar 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 0 additions & 5 deletions extensions/vscode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -397,11 +397,6 @@
"default": true,
"description": "Show name casing in status bar."
},
"vue.complete.normalizeComponentImportName": {
"type": "boolean",
"default": true,
"description": "Normalize import name for auto import. (\"myCompVue\" -> \"MyComp\")"
},
"vue.autoInsert.parentheses": {
"type": "boolean",
"default": true,
Expand Down
4 changes: 2 additions & 2 deletions packages/language-service/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ import type { VueCompilerOptions } from './lib/types';
import { create as createEmmetServicePlugin } from 'volar-service-emmet';
import { create as createJsonServicePlugin } from 'volar-service-json';
import { create as createPugFormatServicePlugin } from 'volar-service-pug-beautify';
import { create as createTypeScriptServicePlugin } from 'volar-service-typescript';
import { create as createTypeScriptTwoslashQueriesServicePlugin } from 'volar-service-typescript-twoslash-queries';
import { create as createCssServicePlugin } from './lib/plugins/css';
import { create as createTypeScriptServicePlugin } from './lib/plugins/typescript';
import { create as createVueAutoDotValueServicePlugin } from './lib/plugins/vue-autoinsert-dotvalue';
import { create as createVueAutoWrapParenthesesServicePlugin } from './lib/plugins/vue-autoinsert-parentheses';
import { create as createVueAutoAddSpaceServicePlugin } from './lib/plugins/vue-autoinsert-space';
Expand All @@ -30,7 +30,7 @@ export function createVueServicePlugins(
getVueOptions: (env: ServiceEnvironment) => VueCompilerOptions,
): ServicePlugin[] {
return [
createTypeScriptServicePlugin(ts, getVueOptions),
createTypeScriptServicePlugin(ts),
createTypeScriptTwoslashQueriesServicePlugin(),
createCssServicePlugin(),
createPugFormatServicePlugin(),
Expand Down
179 changes: 0 additions & 179 deletions packages/language-service/lib/plugins/typescript.ts

This file was deleted.

16 changes: 13 additions & 3 deletions packages/language-service/lib/plugins/vue-autoinsert-dotvalue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,17 @@ import * as namedPipeClient from '@vue/typescript-plugin/lib/client';
import type * as ts from 'typescript';
import type * as vscode from 'vscode-languageserver-protocol';
import type { TextDocument } from 'vscode-languageserver-textdocument';
import { getAst } from './typescript';

const asts = new WeakMap<ts.IScriptSnapshot, ts.SourceFile>();

function getAst(ts: typeof import('typescript'), fileName: string, snapshot: ts.IScriptSnapshot, scriptKind?: ts.ScriptKind) {
let ast = asts.get(snapshot);
if (!ast) {
ast = ts.createSourceFile(fileName, snapshot.getText(0, snapshot.getLength()), ts.ScriptTarget.Latest, undefined, scriptKind);
asts.set(snapshot, ast);
}
return ast;
}

export function create(ts: typeof import('typescript')): ServicePlugin {
return {
Expand Down Expand Up @@ -44,7 +54,7 @@ export function create(ts: typeof import('typescript')): ServicePlugin {
if (script?.code !== code) {
return;
}
ast = getAst(fileName, script.code.snapshot, script.scriptKind);
ast = getAst(ts, fileName, script.code.snapshot, script.scriptKind);
let mapped = false;
for (const [_1, [_2, map]] of context.language.files.getMaps(code)) {
const sourceOffset = map.getSourceOffset(document.offsetAt(position));
Expand All @@ -59,7 +69,7 @@ export function create(ts: typeof import('typescript')): ServicePlugin {
}
}
else {
ast = getAst(fileName, file.snapshot);
ast = getAst(ts, fileName, file.snapshot);
}

if (isBlacklistNode(ts, ast, document.offsetAt(position), false))
Expand Down
2 changes: 1 addition & 1 deletion packages/language-service/tests/complete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const normalizeNewline = (text: string) => text.replace(/\r\n/g, '\n');

for (const dirName of testDirs) {

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

const dir = path.join(baseDir, dirName);
const inputFiles = readFiles(path.join(dir, 'input'));
Expand Down
58 changes: 57 additions & 1 deletion packages/typescript-plugin/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { projects } from './lib/utils';
import * as vue from '@vue/language-core';
import { startNamedPipeServer } from './lib/server';
import { _getComponentNames } from './lib/requests/componentInfos';
import { capitalize } from '@vue/shared';

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

const getCompletionsAtPosition = info.languageService.getCompletionsAtPosition;
const getCompletionEntryDetails = info.languageService.getCompletionEntryDetails;
const getCodeFixesAtPosition = info.languageService.getCodeFixesAtPosition;
const getEncodedSemanticClassifications = info.languageService.getEncodedSemanticClassifications;

info.languageService.getCompletionsAtPosition = (fileName, position, options) => {
const result = getCompletionsAtPosition(fileName, position, options);
if (result) {
result.entries = result.entries.filter(entry => entry.name.indexOf('__VLS_') === -1);
// filter __VLS_
result.entries = result.entries.filter(
entry => entry.name.indexOf('__VLS_') === -1
&& (!entry.labelDetails?.description || entry.labelDetails.description.indexOf('__VLS_') === -1)
);
// modify label
for (const item of result.entries) {
if (item.source) {
const originalName = item.name;
for (const ext of vueOptions.extensions) {
const suffix = capitalize(ext.substring('.'.length)); // .vue -> Vue
if (item.source.endsWith(ext) && item.name.endsWith(suffix)) {
item.name = item.name.slice(0, -suffix.length);
if (item.insertText) {
// #2286
item.insertText = item.insertText.replace(`${suffix}$1`, '$1');
}
if (item.data) {
// @ts-expect-error
item.data.__isComponentAutoImport = {
ext,
suffix,
originalName,
newName: item.insertText,
};
}
break;
}
}
}
}
}
return result;
};
info.languageService.getCompletionEntryDetails = (...args) => {
const details = getCompletionEntryDetails(...args);
// modify import statement
// @ts-expect-error
if (args[6]?.__isComponentAutoImport) {
// @ts-expect-error
const { ext, suffix, originalName, newName } = args[6]?.__isComponentAutoImport;
for (const codeAction of details?.codeActions ?? []) {
for (const change of codeAction.changes) {
for (const textChange of change.textChanges) {
textChange.newText = textChange.newText.replace('import ' + originalName + ' from ', 'import ' + newName + ' from ');
}
}
}
}
return details;
};
info.languageService.getCodeFixesAtPosition = (...args) => {
let result = getCodeFixesAtPosition(...args);
// filter __VLS_
result = result.filter(entry => entry.description.indexOf('__VLS_') === -1);
return result;
};
info.languageService.getEncodedSemanticClassifications = (fileName, span, format) => {
const result = getEncodedSemanticClassifications(fileName, span, format);
const file = files.get(fileName);
Expand Down