Skip to content

Commit

Permalink
Fix resolve component definition (#309)
Browse files Browse the repository at this point in the history
  • Loading branch information
znck authored Nov 4, 2022
1 parent 0e6253e commit dc4ec40
Show file tree
Hide file tree
Showing 7 changed files with 193 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -95,9 +95,7 @@ export function createResolveComponentTransform(
'const ',
id,
` = ${h('resolveComponent')}(${resolveComponentArgs}`,
isSimpleIdentifier(id)
? `${ctx.internalIdentifierPrefix}_get_identifier_${id}()`
: 'null',
'null',
', ',
s(name),
', ',
Expand Down
67 changes: 45 additions & 22 deletions packages/typescript-plugin-vue/src/features/DefinitionService.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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
Expand All @@ -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,
),
)
},
})
}
Expand Down Expand Up @@ -259,18 +296,4 @@ export class DefinitionService
definition.textSpan.start
}:${definition.textSpan.length}`
}

private pick<R>(
fileName: string,
position: number,
fns: Record<string, (file: VueSFCDocument) => 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)
}
}
14 changes: 14 additions & 0 deletions packages/typescript-plugin-vue/src/services/FilesystemService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -334,4 +334,18 @@ export class FilesystemService implements Disposable {

return fileTextChanges
}

public pick<R>(
fileName: string,
position: number,
fns: Record<string, (file: VueSFCDocument) => 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)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,7 @@ export class TypescriptPluginService
implements Partial<ExtendedTSLanguageService>
{
//#region setup
private readonly logger = LoggerService.getLogger(
TypescriptPluginService.name,
)
public readonly logger = LoggerService.getLogger(TypescriptPluginService.name)

constructor(
@inject(FilesystemService)
Expand Down Expand Up @@ -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
}
Expand Down
4 changes: 3 additions & 1 deletion packages/typescript-plugin-vue/types/shared/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {}
}
Expand Down Expand Up @@ -40,7 +42,7 @@ export function resolveComponent<
? IntrinsicElements[B]
: C extends keyof KnownKeys<IntrinsicElements>
? IntrinsicElements[C]
: A
: unknown
: A

type IsNotComponent<T> = true extends IsStrictlyAny<T>
Expand Down
Empty file.
129 changes: 127 additions & 2 deletions test/specs/definition.spec.ts
Original file line number Diff line number Diff line change
@@ -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'

Expand All @@ -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')

Expand All @@ -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')

Expand All @@ -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(`
<script>
import FixtureAttrs from './fixture-attrs.vue'
export default {
components: {
FixtureAttrs,
},
}
</script>
<template>
<FixtureAttrs />
</template>
`),
)

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(`
<script>
import FixtureAttrs from './fixture-attrs.vue'
import { defineComponent } from 'vue'
export default defineComponent({
components: {
FixtureAttrs,
},
})
</script>
<template>
<FixtureAttrs />
</template>
`),
)

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(`
<script>
import Foo from './fixture-attrs.vue'
import { defineComponent } from 'vue'
export default defineComponent({
components: {
FixtureAttrs: Foo,
},
})
</script>
<template>
<FixtureAttrs />
</template>
`),
)

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(`
<script setup>
import FixtureAttrs from './fixture-attrs.vue'
</script>
<template>
<FixtureAttrs />
</template>
`),
)

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(`
<script>
import FixtureAttrs from './fixture-attrs.vue'
</script>
<script setup>
const a = FixtureAttrs
</script>
<template>
<FixtureAttrs />
</template>
`),
)

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'))
})


})

0 comments on commit dc4ec40

Please sign in to comment.