Skip to content

Commit 5dcab41

Browse files
authored
detect imported identifiers, improve sourcemaps, and filter completions by context (#299)
* detect imported identifiers * improve sourcemaps and completions * update snapshots
1 parent 2c1c288 commit 5dcab41

File tree

37 files changed

+1456
-487
lines changed

37 files changed

+1456
-487
lines changed

extensions/vscode-vue-language-features/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { VueVirtualDocumentProvider } from './scheme/vue'
1111
import { PluginCommunicationService } from './services/PluginCommunicationService'
1212
import { StyleLanguageProxy } from './services/StyleLanguageProxy'
1313
import { TemplateLanguageProxy } from './services/TemplateLanguageProxy'
14+
import { TriggerCompletionService } from './services/TriggerCompletionService'
1415
import { TwoSlashService } from './services/TwoSlashService'
1516
import { VirtualFileSwitcher } from './services/VirtualFileSwitcher'
1617

@@ -36,6 +37,7 @@ export async function activate(
3637
container.get(SelectVirtualFileCommand).install(),
3738
container.get(VirtualFileSwitcher).install(),
3839
container.get(TwoSlashService).install(),
40+
container.get(TriggerCompletionService).install(),
3941
new vscode.Disposable(() => container.unbindAll()),
4042
)
4143
const ts = vscode.extensions.getExtension(
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { injectable } from 'inversify'
2+
import vscode, { Disposable } from 'vscode'
3+
import { Installable } from '../utils/installable'
4+
5+
@injectable()
6+
export class TriggerCompletionService
7+
extends Installable
8+
implements vscode.CompletionItemProvider
9+
{
10+
public install(): Disposable {
11+
super.install()
12+
13+
return vscode.languages.registerCompletionItemProvider(
14+
{ language: 'vue' },
15+
this,
16+
...[':', '^'],
17+
)
18+
}
19+
20+
async provideCompletionItems(
21+
document: vscode.TextDocument,
22+
position: vscode.Position,
23+
_token: vscode.CancellationToken,
24+
context: vscode.CompletionContext,
25+
): Promise<vscode.CompletionItem[]> {
26+
if (context.triggerCharacter == null) return []
27+
// TODO: check if at directive node
28+
return await vscode.commands.executeCommand(
29+
'vscode.executeCompletionItemProvider',
30+
document.uri,
31+
position,
32+
)
33+
}
34+
}

packages/compiler-tsx/src/template/generate.ts

Lines changed: 59 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import {
2525
import {
2626
camelize,
2727
capitalize,
28+
getClassNameForTagName,
2829
invariant,
2930
last,
3031
pascalCase,
@@ -117,22 +118,6 @@ export function generate(
117118
): TransformedCode {
118119
ctx = createGenerateContext(options)
119120

120-
if (ctx.used.components.size > 0) {
121-
wrap(
122-
`${annotations.templateGlobals.start}\n`,
123-
`${annotations.templateGlobals.end}\n`,
124-
() => {
125-
ctx.used.components.forEach((component) => {
126-
if (isSimpleIdentifier(component)) {
127-
writeLine(
128-
`const ${ctx.internalIdentifierPrefix}_get_identifier_${component} = () => ${component};`,
129-
)
130-
}
131-
})
132-
},
133-
)
134-
}
135-
136121
genRootNode(root)
137122
genSlotTypes(root)
138123
genAttrTypes(root)
@@ -206,14 +191,25 @@ function genRootNode(node: RootNode): void {
206191
}
207192

208193
function genKnownIdentifierGetters(ids: string[]): void {
209-
const known = ids.filter((id) => ctx.identifiers.has(id))
210-
if (known.length === 0) return
194+
ids = Array.from(
195+
new Set([...ids, ...ctx.used.components, ...ctx.used.directives]),
196+
)
197+
if (!ids.some((id) => ctx.identifiers.has(id))) return
211198
wrap(
212199
annotations.templateGlobals.start,
213200
annotations.templateGlobals.end,
214201
() => {
215202
ctx.newLine()
216-
known.forEach((id) => {
203+
ids.forEach((id) => {
204+
const knownId = ctx.identifiers.get(id)
205+
if (knownId == null) return
206+
if (
207+
!['ref', 'maybeRef', 'externalMaybeRef', 'externalRef'].includes(
208+
knownId.kind,
209+
)
210+
)
211+
return
212+
217213
writeLine(
218214
`const ${
219215
ctx.internalIdentifierPrefix
@@ -287,10 +283,17 @@ function genGlobalDeclarations(node: Node): void {
287283
if (node.scope.globals.length === 0) return
288284
writeLine(annotations.templateGlobals.start)
289285
node.scope.globals.forEach((id) => {
290-
if (ctx.identifiers.has(id)) {
291-
writeLine(
292-
`let ${id} = ${ctx.internalIdentifierPrefix}_get_identifier_${id}();`,
293-
)
286+
const knownId = ctx.identifiers.get(id)
287+
if (knownId != null) {
288+
if (
289+
['ref', 'maybeRef', 'externalMaybeRef', 'externalRef'].includes(
290+
knownId.kind,
291+
)
292+
) {
293+
writeLine(
294+
`let ${id} = ${ctx.internalIdentifierPrefix}_get_identifier_${id}();`,
295+
)
296+
}
294297
} else {
295298
writeLine(`let ${id} = ${ctx.contextIdentifier}.${id}`)
296299
}
@@ -317,9 +320,12 @@ function genElementNode(node: ElementNode): void {
317320
ctx.write(`${annotations.tsxCompletions}`)
318321
})
319322
ctx.newLine()
323+
} else {
324+
return // tag is empty, when only "<" is present
320325
}
326+
321327
if (node.isSelfClosing) {
322-
ctx.write('/>')
328+
ctx.write('/>', node.endTagLoc)
323329
return // done
324330
}
325331
ctx.write('>').newLine()
@@ -390,15 +396,15 @@ function genComponentNode(node: ComponentNode): void {
390396

391397
genDirectiveChecks(node)
392398
ctx.write('<', node.loc)
393-
ctx.write(node.resolvedName ?? node.tag, node.tagLoc).newLine()
399+
ctx.write(node.resolvedName ?? node.tag, node.tagLoc, true).newLine()
394400
indent(() => {
395401
genProps(node)
396402
ctx.write(`${annotations.tsxCompletions}`)
397403
})
398404

399405
ctx.newLine()
400406
if (node.isSelfClosing) {
401-
writeLine('/>')
407+
ctx.write('/>', node.endTagLoc).newLine()
402408
return // done
403409
}
404410
writeLine('>')
@@ -563,16 +569,13 @@ function genProps(el: ElementNode | ComponentNode): void {
563569
ctx.newLine()
564570
} else if (prop.name === 'on') {
565571
if (prop.arg == null) {
566-
ctx.write('{...(')
567-
if (prop.exp != null) {
568-
genExpressionNode(prop.exp)
572+
if (prop.exp == null) {
573+
ctx.write('on', prop.loc, true)
569574
} else {
570-
ctx.write(
571-
annotations.missingExpression,
572-
createLoc(prop.loc, prop.loc.source.length),
573-
)
575+
ctx.write('{...(')
576+
genExpressionNode(prop.exp)
577+
ctx.write(')}')
574578
}
575-
ctx.write(')}')
576579
} else {
577580
invariant(isSimpleExpressionNode(prop.arg))
578581
const id = prop.arg.content
@@ -584,6 +587,15 @@ function genProps(el: ElementNode | ComponentNode): void {
584587
)
585588

586589
const genHandler = (): void => {
590+
if (isPlainElementNode(el)) {
591+
ctx.typeGuards.push(
592+
createCompoundExpression([
593+
`$event.currentTarget instanceof `,
594+
getClassNameForTagName(el.tag),
595+
]),
596+
)
597+
}
598+
587599
ctx.write(`${getRuntimeFn(ctx.typeIdentifier, 'first')}([`).newLine()
588600
indent(() => {
589601
all.forEach((directive) => {
@@ -599,6 +611,9 @@ function genProps(el: ElementNode | ComponentNode): void {
599611
})
600612
})
601613
ctx.write('])')
614+
if (isPlainElementNode(el)) {
615+
ctx.typeGuards.pop()
616+
}
602617
}
603618

604619
if (isStaticExpression(prop.arg)) {
@@ -644,12 +659,12 @@ function genProps(el: ElementNode | ComponentNode): void {
644659
type.value?.content === 'radio')
645660
) {
646661
isCheckbox = true
647-
ctx.write('checked', prop.nameLoc)
662+
ctx.write('checked', prop.nameLoc, true)
648663
} else {
649-
ctx.write('value', prop.nameLoc)
664+
ctx.write('value', prop.nameLoc, true)
650665
}
651666
} else {
652-
ctx.write('modelValue', prop.nameLoc)
667+
ctx.write('modelValue', prop.nameLoc, true)
653668
}
654669

655670
ctx.write('={')
@@ -674,7 +689,7 @@ function genProps(el: ElementNode | ComponentNode): void {
674689
}
675690
ctx.write('}')
676691
} else if (isStaticExpression(prop.arg)) {
677-
genExpressionNode(prop.arg)
692+
ctx.write(prop.arg.content, prop.arg.loc)
678693
ctx.write('={')
679694
genExp()
680695
ctx.write('}')
@@ -737,15 +752,14 @@ function genVBindDirective(
737752
ctx.write(': true')
738753
}
739754
ctx.write('})}')
755+
} else if (prop.exp == null) {
756+
ctx.write(' ', prop.loc)
740757
} else {
741758
ctx.write('{...(')
742759
if (prop.exp != null) {
743760
genExpressionNode(prop.exp)
744761
} else {
745-
ctx.write(
746-
annotations.missingExpression,
747-
createLoc(prop.loc, prop.loc.source.length),
748-
)
762+
ctx.write(' ', createLoc(prop.loc, prop.loc.source.length))
749763
}
750764
ctx.write(')}')
751765
}
@@ -756,12 +770,9 @@ function genTextNode(node: TextNode): void {
756770
}
757771

758772
function genInterpolationNode(node: InterpolationNode): void {
759-
ctx.write('{', node.loc)
773+
ctx.write(' {', node.loc)
760774
genExpressionNode(node.content)
761-
ctx.write(
762-
'}',
763-
createLoc(node.loc, node.content.loc.end.offset - node.loc.start.offset),
764-
)
775+
ctx.write('} ', sliceLoc(node.loc, -2))
765776
}
766777

767778
function genExpressionNode(node: ExpressionNode): void {
@@ -795,6 +806,7 @@ function genExpressionNodeAsFunction(node: ExpressionNode): void {
795806
node.content.includes('$event')
796807
? ctx.write('($event) => {').newLine()
797808
: ctx.write('() => {').newLine()
809+
genTypeGuards()
798810
genSimpleExpressionNode(node)
799811
ctx.newLine().write('}')
800812
}

packages/compiler-tsx/src/template/parse.ts

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
RootNode,
1010
transform,
1111
} from '@vue/compiler-core'
12-
import { last } from '@vuedx/shared'
12+
import { first, last } from '@vuedx/shared'
1313
import {
1414
createSimpleExpression,
1515
isDirectiveNode,
@@ -24,7 +24,7 @@ import './types/Node'
2424
const preprocess: NodeTransform = (node, context) => {
2525
if (isTextNode(node) && node.content.trim().startsWith('<')) {
2626
// Incomplete element tag
27-
context.replaceNode(createPlainElementNode(node.content, node.loc))
27+
context.replaceNode(createPlainElementNode(node.loc))
2828

2929
return
3030
}
@@ -42,14 +42,18 @@ const preprocess: NodeTransform = (node, context) => {
4242
node.props.forEach((prop, index) => {
4343
// remove empty modifiers
4444
if (isDirectiveNode(prop)) {
45-
const isShorthand = /^[:@.^]/.test(prop.loc.source)
46-
const nameEndOffset = isShorthand ? 1 : 2 + prop.name.length
45+
const nameEndOffset = prop.loc.source.startsWith('v-')
46+
? 2 + prop.name.length
47+
: 1
4748
let offset =
4849
prop.arg != null
4950
? prop.arg.loc.end.offset - prop.loc.start.offset
5051
: nameEndOffset
5152

5253
prop.nameLoc = sliceLoc(prop.loc, 0, nameEndOffset)
54+
if (prop.modifiers.length === 1 && first(prop.modifiers) === '') {
55+
prop.modifiers = []
56+
}
5357
prop.modifierLocs = prop.modifiers.map((modifier) => {
5458
try {
5559
offset += 1
@@ -78,11 +82,13 @@ const preprocess: NodeTransform = (node, context) => {
7882
false,
7983
createLoc(prop.loc, 1, prop.name.length - 1),
8084
)
81-
: createSimpleExpression(
85+
: prop.name.length > 1
86+
? createSimpleExpression(
8287
prop.name.slice(1),
8388
true,
8489
createLoc(prop.loc, 1, prop.name.length - 1),
85-
),
90+
)
91+
: undefined,
8692
loc: prop.loc,
8793
modifiers: [],
8894
modifierLocs: [],
@@ -105,6 +111,7 @@ const preprocess: NodeTransform = (node, context) => {
105111
node.tagLoc = createLoc(node.loc, 1, node.tag.length)
106112
if (node.isSelfClosing) {
107113
node.startTagLoc = node.loc
114+
node.endTagLoc = sliceLoc(node.loc, -2)
108115
} else {
109116
const startTagIndex = node.loc.source.indexOf(
110117
'>',
@@ -145,23 +152,22 @@ export function parse(template: string, options: ParserOptions): RootNode {
145152
return ast
146153
}
147154

148-
function createPlainElementNode(
149-
content: string,
150-
contentLoc: SourceLocation,
151-
): PlainElementNode {
152-
const source = content.trim()
155+
function createPlainElementNode(contentLoc: SourceLocation): PlainElementNode {
156+
const offset = contentLoc.source.indexOf('<')
157+
const loc = sliceLoc(contentLoc, offset)
158+
const tag = loc.source.slice(1).trim()
153159
return {
154160
type: 1 /* ELEMENT */,
155-
tag: source.slice(1),
161+
tag,
156162
tagType: 0 /* ELEMENT */,
157163
codegenNode: undefined,
158164
children: [],
159-
isSelfClosing: false,
160-
loc: contentLoc,
165+
isSelfClosing: tag.length > 0,
166+
loc,
161167
ns: 0,
162168
props: [],
163-
tagLoc: createLoc(contentLoc, 1, content.length - 1),
164-
startTagLoc: contentLoc,
169+
tagLoc: sliceLoc(loc, 1),
170+
startTagLoc: loc,
165171
endTagLoc: undefined,
166172
scope: new Scope(),
167173
}

0 commit comments

Comments
 (0)