diff --git a/packages/language-core/lib/plugins.ts b/packages/language-core/lib/plugins.ts index 4c7d4ea3c0..29414161fb 100644 --- a/packages/language-core/lib/plugins.ts +++ b/packages/language-core/lib/plugins.ts @@ -22,6 +22,7 @@ export function createPlugins(pluginContext: Parameters[0]) { useMdFilePlugin, useHtmlFilePlugin, vueRootTagsPlugin, + vueTsx, vueScriptJsPlugin, vueStyleCss, vueTemplateHtmlPlugin, @@ -31,7 +32,6 @@ export function createPlugins(pluginContext: Parameters[0]) { vueSfcCustomBlocks, vueSfcScriptsFormat, vueSfcTemplate, - vueTsx, ...pluginContext.vueCompilerOptions.plugins, ]; diff --git a/packages/language-service/index.ts b/packages/language-service/index.ts index 6fc96f34d9..d3848d5b90 100644 --- a/packages/language-service/index.ts +++ b/packages/language-service/index.ts @@ -51,7 +51,6 @@ export function createVueLanguageServicePlugins( createVueGlobalTypesErrorPlugin(), createVueScopedClassLinksPlugin(), createVueSfcPlugin(), - createVueSuggestDefineAssignmentPlugin(), createVueTemplateRefLinksPlugin(), createEmmetPlugin({ mappedLanguages: { @@ -61,6 +60,7 @@ export function createVueLanguageServicePlugins( }), // TS related plugins + createVueSuggestDefineAssignmentPlugin(ts), createTypeScriptDocCommentTemplatePlugin(ts), createTypeScriptSyntacticPlugin(ts), createVueInlayHintsPlugin(ts), diff --git a/packages/language-service/lib/plugins/vue-autoinsert-dotvalue.ts b/packages/language-service/lib/plugins/vue-autoinsert-dotvalue.ts index c86b23c441..f485c1a0f4 100644 --- a/packages/language-service/lib/plugins/vue-autoinsert-dotvalue.ts +++ b/packages/language-service/lib/plugins/vue-autoinsert-dotvalue.ts @@ -55,7 +55,7 @@ export function create( if (sourceOffset < startTagEnd || sourceOffset > endTagStart) { continue; } - if (isBlacklistNode(ts, ast, sourceOffset - startTagEnd, false)) { + if (shouldSkip(ts, ast, sourceOffset - startTagEnd, false)) { return; } } @@ -86,7 +86,7 @@ function isCharacterTyping(document: TextDocument, change: { text: string; range return charReg.test(lastCharacter) && !charReg.test(nextCharacter); } -function isBlacklistNode(ts: typeof import('typescript'), node: ts.Node, pos: number, allowAccessDotValue: boolean) { +function shouldSkip(ts: typeof import('typescript'), node: ts.Node, pos: number, allowAccessDotValue: boolean) { if (ts.isVariableDeclaration(node) && pos >= node.name.getFullStart() && pos <= node.name.getEnd()) { return true; } @@ -123,47 +123,48 @@ function isBlacklistNode(ts: typeof import('typescript'), node: ts.Node, pos: nu ts.isCallExpression(node) && ts.isIdentifier(node.expression) && isWatchOrUseFunction(node.expression.text) - && isTopLevelArgOrArrayTopLevelItemItem(node) + && isTopLevelArgOrArrayTopLevelItemItem(ts, node, pos) ) { return true; } else { - let _isBlacklistNode = false; + let _shouldSkip = false; node.forEachChild(node => { - if (_isBlacklistNode) { + if (_shouldSkip) { return; } if (pos >= node.getFullStart() && pos <= node.getEnd()) { - if (isBlacklistNode(ts, node, pos, allowAccessDotValue)) { - _isBlacklistNode = true; + if (shouldSkip(ts, node, pos, allowAccessDotValue)) { + _shouldSkip = true; } } }); - return _isBlacklistNode; + return _shouldSkip; } +} - function isWatchOrUseFunction(fnName: string) { - return fnName === 'watch' - || fnName === 'unref' - || fnName === 'triggerRef' - || fnName === 'isRef' - || hyphenateAttr(fnName).startsWith('use-'); - } - function isTopLevelArgOrArrayTopLevelItemItem(node: ts.CallExpression) { - for (const arg of node.arguments) { - if (pos >= arg.getFullStart() && pos <= arg.getEnd()) { - if (ts.isIdentifier(arg)) { - return true; - } - if (ts.isArrayLiteralExpression(arg)) { - for (const el of arg.elements) { - if (pos >= el.getFullStart() && pos <= el.getEnd()) { - return ts.isIdentifier(el); - } +function isWatchOrUseFunction(fnName: string) { + return fnName === 'watch' + || fnName === 'unref' + || fnName === 'triggerRef' + || fnName === 'isRef' + || hyphenateAttr(fnName).startsWith('use-'); +} + +function isTopLevelArgOrArrayTopLevelItemItem(ts: typeof import('typescript'), node: ts.CallExpression, pos: number) { + for (const arg of node.arguments) { + if (pos >= arg.getFullStart() && pos <= arg.getEnd()) { + if (ts.isIdentifier(arg)) { + return true; + } + if (ts.isArrayLiteralExpression(arg)) { + for (const el of arg.elements) { + if (pos >= el.getFullStart() && pos <= el.getEnd()) { + return ts.isIdentifier(el); } } - return false; } + return false; } } } diff --git a/packages/language-service/lib/plugins/vue-suggest-define-assignment.ts b/packages/language-service/lib/plugins/vue-suggest-define-assignment.ts index 121ae5370e..47d37e6775 100644 --- a/packages/language-service/lib/plugins/vue-suggest-define-assignment.ts +++ b/packages/language-service/lib/plugins/vue-suggest-define-assignment.ts @@ -1,8 +1,12 @@ -import type { CompletionItem, CompletionItemKind, LanguageServicePlugin } from '@volar/language-service'; +import type { CompletionItem, CompletionItemKind, LanguageServicePlugin, TextDocument } from '@volar/language-service'; import { type TextRange, tsCodegen } from '@vue/language-core'; +import type * as ts from 'typescript'; +import { URI } from 'vscode-uri'; import { resolveEmbeddedCode } from '../utils'; -export function create(): LanguageServicePlugin { +const documentToSourceFile = new WeakMap(); + +export function create(ts: typeof import('typescript')): LanguageServicePlugin { return { name: 'vue-suggest-define-assignment', capabilities: { @@ -11,7 +15,7 @@ export function create(): LanguageServicePlugin { create(context) { return { isAdditionalCompletion: true, - async provideCompletionItems(document) { + async provideCompletionItems(document, position) { const info = resolveEmbeddedCode(context, document.uri); if (!info?.code.id.startsWith('script_')) { return; @@ -30,6 +34,11 @@ export function create(): LanguageServicePlugin { return; } + const sourceFile = getSourceFile(ts, document); + if (shouldSkip(ts, sourceFile, document.offsetAt(position))) { + return; + } + const result: CompletionItem[] = []; const mappings = [...context.language.maps.forEach(info.code)]; @@ -93,3 +102,39 @@ export function create(): LanguageServicePlugin { }, }; } + +function shouldSkip(ts: typeof import('typescript'), node: ts.Node, pos: number) { + if (ts.isStringLiteral(node) && pos >= node.getFullStart() && pos <= node.getEnd()) { + return true; + } + else if (ts.isTemplateLiteral(node) && pos >= node.getFullStart() && pos <= node.getEnd()) { + return true; + } + else { + let _shouldSkip = false; + node.forEachChild(node => { + if (_shouldSkip) { + return; + } + if (pos >= node.getFullStart() && pos <= node.getEnd()) { + if (shouldSkip(ts, node, pos)) { + _shouldSkip = true; + } + } + }); + return _shouldSkip; + } +} + +function getSourceFile(ts: typeof import('typescript'), document: TextDocument): ts.SourceFile { + let sourceFile = documentToSourceFile.get(document); + if (!sourceFile) { + sourceFile = ts.createSourceFile( + URI.parse(document.uri).path, + document.getText(), + ts.ScriptTarget.Latest, + ); + documentToSourceFile.set(document, sourceFile); + } + return sourceFile; +}