Skip to content

Commit

Permalink
detect imported identifiers, improve sourcemaps, and filter completio…
Browse files Browse the repository at this point in the history
…ns by context (#299)

* detect imported identifiers

* improve sourcemaps and completions

* update snapshots
  • Loading branch information
znck authored Oct 16, 2022
1 parent 2c1c288 commit 5dcab41
Show file tree
Hide file tree
Showing 37 changed files with 1,456 additions and 487 deletions.
2 changes: 2 additions & 0 deletions extensions/vscode-vue-language-features/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { VueVirtualDocumentProvider } from './scheme/vue'
import { PluginCommunicationService } from './services/PluginCommunicationService'
import { StyleLanguageProxy } from './services/StyleLanguageProxy'
import { TemplateLanguageProxy } from './services/TemplateLanguageProxy'
import { TriggerCompletionService } from './services/TriggerCompletionService'
import { TwoSlashService } from './services/TwoSlashService'
import { VirtualFileSwitcher } from './services/VirtualFileSwitcher'

Expand All @@ -36,6 +37,7 @@ export async function activate(
container.get(SelectVirtualFileCommand).install(),
container.get(VirtualFileSwitcher).install(),
container.get(TwoSlashService).install(),
container.get(TriggerCompletionService).install(),
new vscode.Disposable(() => container.unbindAll()),
)
const ts = vscode.extensions.getExtension(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { injectable } from 'inversify'
import vscode, { Disposable } from 'vscode'
import { Installable } from '../utils/installable'

@injectable()
export class TriggerCompletionService
extends Installable
implements vscode.CompletionItemProvider
{
public install(): Disposable {
super.install()

return vscode.languages.registerCompletionItemProvider(
{ language: 'vue' },
this,
...[':', '^'],
)
}

async provideCompletionItems(
document: vscode.TextDocument,
position: vscode.Position,
_token: vscode.CancellationToken,
context: vscode.CompletionContext,
): Promise<vscode.CompletionItem[]> {
if (context.triggerCharacter == null) return []
// TODO: check if at directive node
return await vscode.commands.executeCommand(
'vscode.executeCompletionItemProvider',
document.uri,
position,
)
}
}
106 changes: 59 additions & 47 deletions packages/compiler-tsx/src/template/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
import {
camelize,
capitalize,
getClassNameForTagName,
invariant,
last,
pascalCase,
Expand Down Expand Up @@ -117,22 +118,6 @@ export function generate(
): TransformedCode {
ctx = createGenerateContext(options)

if (ctx.used.components.size > 0) {
wrap(
`${annotations.templateGlobals.start}\n`,
`${annotations.templateGlobals.end}\n`,
() => {
ctx.used.components.forEach((component) => {
if (isSimpleIdentifier(component)) {
writeLine(
`const ${ctx.internalIdentifierPrefix}_get_identifier_${component} = () => ${component};`,
)
}
})
},
)
}

genRootNode(root)
genSlotTypes(root)
genAttrTypes(root)
Expand Down Expand Up @@ -206,14 +191,25 @@ function genRootNode(node: RootNode): void {
}

function genKnownIdentifierGetters(ids: string[]): void {
const known = ids.filter((id) => ctx.identifiers.has(id))
if (known.length === 0) return
ids = Array.from(
new Set([...ids, ...ctx.used.components, ...ctx.used.directives]),
)
if (!ids.some((id) => ctx.identifiers.has(id))) return
wrap(
annotations.templateGlobals.start,
annotations.templateGlobals.end,
() => {
ctx.newLine()
known.forEach((id) => {
ids.forEach((id) => {
const knownId = ctx.identifiers.get(id)
if (knownId == null) return
if (
!['ref', 'maybeRef', 'externalMaybeRef', 'externalRef'].includes(
knownId.kind,
)
)
return

writeLine(
`const ${
ctx.internalIdentifierPrefix
Expand Down Expand Up @@ -287,10 +283,17 @@ function genGlobalDeclarations(node: Node): void {
if (node.scope.globals.length === 0) return
writeLine(annotations.templateGlobals.start)
node.scope.globals.forEach((id) => {
if (ctx.identifiers.has(id)) {
writeLine(
`let ${id} = ${ctx.internalIdentifierPrefix}_get_identifier_${id}();`,
)
const knownId = ctx.identifiers.get(id)
if (knownId != null) {
if (
['ref', 'maybeRef', 'externalMaybeRef', 'externalRef'].includes(
knownId.kind,
)
) {
writeLine(
`let ${id} = ${ctx.internalIdentifierPrefix}_get_identifier_${id}();`,
)
}
} else {
writeLine(`let ${id} = ${ctx.contextIdentifier}.${id}`)
}
Expand All @@ -317,9 +320,12 @@ function genElementNode(node: ElementNode): void {
ctx.write(`${annotations.tsxCompletions}`)
})
ctx.newLine()
} else {
return // tag is empty, when only "<" is present
}

if (node.isSelfClosing) {
ctx.write('/>')
ctx.write('/>', node.endTagLoc)
return // done
}
ctx.write('>').newLine()
Expand Down Expand Up @@ -390,15 +396,15 @@ function genComponentNode(node: ComponentNode): void {

genDirectiveChecks(node)
ctx.write('<', node.loc)
ctx.write(node.resolvedName ?? node.tag, node.tagLoc).newLine()
ctx.write(node.resolvedName ?? node.tag, node.tagLoc, true).newLine()
indent(() => {
genProps(node)
ctx.write(`${annotations.tsxCompletions}`)
})

ctx.newLine()
if (node.isSelfClosing) {
writeLine('/>')
ctx.write('/>', node.endTagLoc).newLine()
return // done
}
writeLine('>')
Expand Down Expand Up @@ -563,16 +569,13 @@ function genProps(el: ElementNode | ComponentNode): void {
ctx.newLine()
} else if (prop.name === 'on') {
if (prop.arg == null) {
ctx.write('{...(')
if (prop.exp != null) {
genExpressionNode(prop.exp)
if (prop.exp == null) {
ctx.write('on', prop.loc, true)
} else {
ctx.write(
annotations.missingExpression,
createLoc(prop.loc, prop.loc.source.length),
)
ctx.write('{...(')
genExpressionNode(prop.exp)
ctx.write(')}')
}
ctx.write(')}')
} else {
invariant(isSimpleExpressionNode(prop.arg))
const id = prop.arg.content
Expand All @@ -584,6 +587,15 @@ function genProps(el: ElementNode | ComponentNode): void {
)

const genHandler = (): void => {
if (isPlainElementNode(el)) {
ctx.typeGuards.push(
createCompoundExpression([
`$event.currentTarget instanceof `,
getClassNameForTagName(el.tag),
]),
)
}

ctx.write(`${getRuntimeFn(ctx.typeIdentifier, 'first')}([`).newLine()
indent(() => {
all.forEach((directive) => {
Expand All @@ -599,6 +611,9 @@ function genProps(el: ElementNode | ComponentNode): void {
})
})
ctx.write('])')
if (isPlainElementNode(el)) {
ctx.typeGuards.pop()
}
}

if (isStaticExpression(prop.arg)) {
Expand Down Expand Up @@ -644,12 +659,12 @@ function genProps(el: ElementNode | ComponentNode): void {
type.value?.content === 'radio')
) {
isCheckbox = true
ctx.write('checked', prop.nameLoc)
ctx.write('checked', prop.nameLoc, true)
} else {
ctx.write('value', prop.nameLoc)
ctx.write('value', prop.nameLoc, true)
}
} else {
ctx.write('modelValue', prop.nameLoc)
ctx.write('modelValue', prop.nameLoc, true)
}

ctx.write('={')
Expand All @@ -674,7 +689,7 @@ function genProps(el: ElementNode | ComponentNode): void {
}
ctx.write('}')
} else if (isStaticExpression(prop.arg)) {
genExpressionNode(prop.arg)
ctx.write(prop.arg.content, prop.arg.loc)
ctx.write('={')
genExp()
ctx.write('}')
Expand Down Expand Up @@ -737,15 +752,14 @@ function genVBindDirective(
ctx.write(': true')
}
ctx.write('})}')
} else if (prop.exp == null) {
ctx.write(' ', prop.loc)
} else {
ctx.write('{...(')
if (prop.exp != null) {
genExpressionNode(prop.exp)
} else {
ctx.write(
annotations.missingExpression,
createLoc(prop.loc, prop.loc.source.length),
)
ctx.write(' ', createLoc(prop.loc, prop.loc.source.length))
}
ctx.write(')}')
}
Expand All @@ -756,12 +770,9 @@ function genTextNode(node: TextNode): void {
}

function genInterpolationNode(node: InterpolationNode): void {
ctx.write('{', node.loc)
ctx.write(' {', node.loc)
genExpressionNode(node.content)
ctx.write(
'}',
createLoc(node.loc, node.content.loc.end.offset - node.loc.start.offset),
)
ctx.write('} ', sliceLoc(node.loc, -2))
}

function genExpressionNode(node: ExpressionNode): void {
Expand Down Expand Up @@ -795,6 +806,7 @@ function genExpressionNodeAsFunction(node: ExpressionNode): void {
node.content.includes('$event')
? ctx.write('($event) => {').newLine()
: ctx.write('() => {').newLine()
genTypeGuards()
genSimpleExpressionNode(node)
ctx.newLine().write('}')
}
Expand Down
38 changes: 22 additions & 16 deletions packages/compiler-tsx/src/template/parse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
RootNode,
transform,
} from '@vue/compiler-core'
import { last } from '@vuedx/shared'
import { first, last } from '@vuedx/shared'
import {
createSimpleExpression,
isDirectiveNode,
Expand All @@ -24,7 +24,7 @@ import './types/Node'
const preprocess: NodeTransform = (node, context) => {
if (isTextNode(node) && node.content.trim().startsWith('<')) {
// Incomplete element tag
context.replaceNode(createPlainElementNode(node.content, node.loc))
context.replaceNode(createPlainElementNode(node.loc))

return
}
Expand All @@ -42,14 +42,18 @@ const preprocess: NodeTransform = (node, context) => {
node.props.forEach((prop, index) => {
// remove empty modifiers
if (isDirectiveNode(prop)) {
const isShorthand = /^[:@.^]/.test(prop.loc.source)
const nameEndOffset = isShorthand ? 1 : 2 + prop.name.length
const nameEndOffset = prop.loc.source.startsWith('v-')
? 2 + prop.name.length
: 1
let offset =
prop.arg != null
? prop.arg.loc.end.offset - prop.loc.start.offset
: nameEndOffset

prop.nameLoc = sliceLoc(prop.loc, 0, nameEndOffset)
if (prop.modifiers.length === 1 && first(prop.modifiers) === '') {
prop.modifiers = []
}
prop.modifierLocs = prop.modifiers.map((modifier) => {
try {
offset += 1
Expand Down Expand Up @@ -78,11 +82,13 @@ const preprocess: NodeTransform = (node, context) => {
false,
createLoc(prop.loc, 1, prop.name.length - 1),
)
: createSimpleExpression(
: prop.name.length > 1
? createSimpleExpression(
prop.name.slice(1),
true,
createLoc(prop.loc, 1, prop.name.length - 1),
),
)
: undefined,
loc: prop.loc,
modifiers: [],
modifierLocs: [],
Expand All @@ -105,6 +111,7 @@ const preprocess: NodeTransform = (node, context) => {
node.tagLoc = createLoc(node.loc, 1, node.tag.length)
if (node.isSelfClosing) {
node.startTagLoc = node.loc
node.endTagLoc = sliceLoc(node.loc, -2)
} else {
const startTagIndex = node.loc.source.indexOf(
'>',
Expand Down Expand Up @@ -145,23 +152,22 @@ export function parse(template: string, options: ParserOptions): RootNode {
return ast
}

function createPlainElementNode(
content: string,
contentLoc: SourceLocation,
): PlainElementNode {
const source = content.trim()
function createPlainElementNode(contentLoc: SourceLocation): PlainElementNode {
const offset = contentLoc.source.indexOf('<')
const loc = sliceLoc(contentLoc, offset)
const tag = loc.source.slice(1).trim()
return {
type: 1 /* ELEMENT */,
tag: source.slice(1),
tag,
tagType: 0 /* ELEMENT */,
codegenNode: undefined,
children: [],
isSelfClosing: false,
loc: contentLoc,
isSelfClosing: tag.length > 0,
loc,
ns: 0,
props: [],
tagLoc: createLoc(contentLoc, 1, content.length - 1),
startTagLoc: contentLoc,
tagLoc: sliceLoc(loc, 1),
startTagLoc: loc,
endTagLoc: undefined,
scope: new Scope(),
}
Expand Down
Loading

0 comments on commit 5dcab41

Please sign in to comment.