diff --git a/packages/compiler-tsx/src/template/transforms/transformResolveComponent.ts b/packages/compiler-tsx/src/template/transforms/transformResolveComponent.ts index 5778f71f..e7dfaea1 100644 --- a/packages/compiler-tsx/src/template/transforms/transformResolveComponent.ts +++ b/packages/compiler-tsx/src/template/transforms/transformResolveComponent.ts @@ -95,9 +95,7 @@ export function createResolveComponentTransform( 'const ', id, ` = ${h('resolveComponent')}(${resolveComponentArgs}`, - isSimpleIdentifier(id) - ? `${ctx.internalIdentifierPrefix}_get_identifier_${id}()` - : 'null', + 'null', ', ', s(name), ', ', diff --git a/packages/typescript-plugin-vue/src/features/DefinitionService.ts b/packages/typescript-plugin-vue/src/features/DefinitionService.ts index a3633102..b7a801d8 100644 --- a/packages/typescript-plugin-vue/src/features/DefinitionService.ts +++ b/packages/typescript-plugin-vue/src/features/DefinitionService.ts @@ -1,10 +1,14 @@ import { createCache, debug } from '@vuedx/shared' +import { isComponentNode } from '@vuedx/template-ast-types' import { VueSFCDocument } from '@vuedx/vue-virtual-textdocument' import { inject, injectable } from 'inversify' import type { TSLanguageService, TypeScript } from '../contracts/TypeScript' import { FilesystemService } from '../services/FilesystemService' import { LoggerService } from '../services/LoggerService' -import { TemplateContextService } from '../services/TemplateContextService' +import { + TemplateContextKind, + TemplateContextService, +} from '../services/TemplateContextService' import { GeneratedPositionKind, TemplateDeclarationsService, @@ -69,12 +73,11 @@ export class DefinitionService ?.flatMap((definition) => this.processDefinitionInfo(definition)) } - @debug() public getDefinitionAndBoundSpan( fileName: string, position: number, ): TypeScript.DefinitionInfoAndBoundSpan | undefined { - return this.pick(fileName, position, { + return this.fs.pick(fileName, position, { script: (file) => { const generatedPosition = file.generatedOffsetAt(position) if (generatedPosition == null) return @@ -91,12 +94,46 @@ export class DefinitionService // TODO: check what kind of node at position. const context = this.templateContext.getContext(file, position) if (context == null || context.node == null) return - const result = this.ts.service.getDefinitionAndBoundSpan( - file.generatedFileName, - context.offsetInGenerated, - ) + if (context.kind === TemplateContextKind.Tag) { + if (isComponentNode(context.element)) { + const id = context.element.resolvedName ?? context.element.tag + const declaration = this.declarations + .getTemplateDeclaration(fileName) + .declarations.find((declaration) => declaration.id === id) + + if (declaration != null) { + const definition = this.ts.service.getTypeDefinitionAtPosition( + file.generatedFileName, + declaration.name.start, + ) + + if (definition != null) { + return { + textSpan: { + start: + context.template.loc.start.offset + + context.element.tagLoc.start.offset, + length: + context.element.tagLoc.end.offset - + context.element.tagLoc.start.offset, + }, + definitions: definition.flatMap((definition) => + this.processDefinitionInfo(definition), + ), + } + } + } + } + } - return this.processDefinitionInfoAndBoundSpan(file, position, result) + return this.processDefinitionInfoAndBoundSpan( + file, + position, + this.ts.service.getDefinitionAndBoundSpan( + file.generatedFileName, + context.offsetInGenerated, + ), + ) }, }) } @@ -259,18 +296,4 @@ export class DefinitionService definition.textSpan.start }:${definition.textSpan.length}` } - - private pick( - fileName: string, - position: number, - fns: Record R>, - ): R | undefined { - const file = this.fs.getVueFile(fileName) - if (file == null) return - const block = file.getBlockAt(position) - if (block == null) return - const fn = fns[block.type] - if (fn == null) return - return fn(file) - } } diff --git a/packages/typescript-plugin-vue/src/services/FilesystemService.ts b/packages/typescript-plugin-vue/src/services/FilesystemService.ts index d6fa9487..5effa860 100644 --- a/packages/typescript-plugin-vue/src/services/FilesystemService.ts +++ b/packages/typescript-plugin-vue/src/services/FilesystemService.ts @@ -334,4 +334,18 @@ export class FilesystemService implements Disposable { return fileTextChanges } + + public pick( + fileName: string, + position: number, + fns: Record R>, + ): R | undefined { + const file = this.getVueFile(fileName) + if (file == null) return + const block = file.getBlockAt(position) + if (block == null) return + const fn = fns[block.type] + if (fn == null) return + return fn(file) + } } diff --git a/packages/typescript-plugin-vue/src/services/TypescriptPluginService.ts b/packages/typescript-plugin-vue/src/services/TypescriptPluginService.ts index 5686cde9..470b7f2b 100644 --- a/packages/typescript-plugin-vue/src/services/TypescriptPluginService.ts +++ b/packages/typescript-plugin-vue/src/services/TypescriptPluginService.ts @@ -26,9 +26,7 @@ export class TypescriptPluginService implements Partial { //#region setup - private readonly logger = LoggerService.getLogger( - TypescriptPluginService.name, - ) + public readonly logger = LoggerService.getLogger(TypescriptPluginService.name) constructor( @inject(FilesystemService) @@ -132,8 +130,8 @@ export class TypescriptPluginService this.#isVueProject = true const fileNames = [...this.getScriptFileNames([...vue]), ...virtual] - this.logger.debug(`Project:`, project.getProjectName()) - this.logger.debug(`External files:`, fileNames) + // this.logger.debug(`Project:`, project.getProjectName()) + // this.logger.debug(`External files:`, fileNames) return fileNames } diff --git a/packages/typescript-plugin-vue/types/shared/components.d.ts b/packages/typescript-plugin-vue/types/shared/components.d.ts index 86c1ef8d..bf9ca6ff 100644 --- a/packages/typescript-plugin-vue/types/shared/components.d.ts +++ b/packages/typescript-plugin-vue/types/shared/components.d.ts @@ -9,6 +9,8 @@ declare module '@vue/runtime-dom' { export interface GlobalComponents {} } +// eslint-disable-next-line @typescript-eslint/prefer-ts-expect-error +// @ts-ignore declare module 'vue' { export interface GlobalComponents {} } @@ -40,7 +42,7 @@ export function resolveComponent< ? IntrinsicElements[B] : C extends keyof KnownKeys ? IntrinsicElements[C] - : A + : unknown : A type IsNotComponent = true extends IsStrictlyAny diff --git a/samples/vue3/typescript-diagnostics/src/test.vue b/samples/vue3/typescript-diagnostics/src/test.vue new file mode 100644 index 00000000..e69de29b diff --git a/test/specs/definition.spec.ts b/test/specs/definition.spec.ts index e832bf5b..65b85966 100644 --- a/test/specs/definition.spec.ts +++ b/test/specs/definition.spec.ts @@ -1,4 +1,4 @@ -import { first } from '@vuedx/shared' +import { first, trimIndent } from '@vuedx/shared' import { createEditorContext, getProjectPath } from '../support/helpers' import { TestServer } from '../support/TestServer' @@ -11,6 +11,8 @@ describe('definition', () => { afterAll(async () => await server.close()) + afterEach(async () => await ctx.closeAll()) + test('can go to import source in .vue', async () => { const editor = await ctx.open('src/test-completions-tag.vue') @@ -19,7 +21,7 @@ describe('definition', () => { expect(info.definitions).toHaveLength(1) expect(first(info.definitions).file).toBe(ctx.abs('src/fixture-attrs.vue')) }) - + test('can go to import source in .ts', async () => { const editor = await ctx.open('src/test-goto-definition.ts') @@ -28,4 +30,127 @@ describe('definition', () => { expect(info.definitions).toHaveLength(1) expect(first(info.definitions).file).toBe(ctx.abs('src/fixture-attrs.vue')) }) + + test('script + plain object', async () => { + const editor = await ctx.open('src/test.vue') + + await editor.type( + trimIndent(` + + + `), + ) + + await editor.setCursor({ line: 9, character: 4 }) + const info = await server.definitionAndBoundSpan(editor.fileAndLocation) + expect(info.definitions).toHaveLength(1) + expect(first(info.definitions).file).toBe(ctx.abs('src/fixture-attrs.vue')) + }) + + test('script + define component', async () => { + const editor = await ctx.open('src/test.vue') + + await editor.type( + trimIndent(` + + + `), + ) + + await editor.setCursor({ line: 10, character: 4 }) + const info = await server.definitionAndBoundSpan(editor.fileAndLocation) + expect(info.definitions).toHaveLength(1) + expect(first(info.definitions).file).toBe(ctx.abs('src/fixture-attrs.vue')) + }) + + // TODO: support local component + test.skip('script + define component + alias', async () => { + const editor = await ctx.open('src/test.vue') + + await editor.type( + trimIndent(` + + + `), + ) + + await editor.setCursor({ line: 10, character: 4 }) + const info = await server.definitionAndBoundSpan(editor.fileAndLocation) + expect(info.definitions).toHaveLength(1) + expect(first(info.definitions).file).toBe(ctx.abs('src/fixture-attrs.vue')) + }) + + test('script setup + import', async () => { + const editor = await ctx.open('src/test.vue') + + await editor.type( + trimIndent(` + + + `), + ) + + await editor.setCursor({ line: 4, character: 4 }) + const info = await server.definitionAndBoundSpan(editor.fileAndLocation) + expect(info.definitions).toHaveLength(1) + expect(first(info.definitions).file).toBe(ctx.abs('src/fixture-attrs.vue')) + }) + + test('script + script setup + import', async () => { + const editor = await ctx.open('src/test.vue') + + await editor.type( + trimIndent(` + + + + `), + ) + + await editor.setCursor({ line: 7, character: 4 }) + const info = await server.definitionAndBoundSpan(editor.fileAndLocation) + expect(info.definitions).toHaveLength(1) + expect(first(info.definitions).file).toBe(ctx.abs('src/fixture-attrs.vue')) + }) + + })