common-intellisense
Version:
983 lines (915 loc) • 30.4 kB
text/typescript
import fsp from 'node:fs/promises'
import * as vscode from 'vscode'
import { parse } from '@vue/compiler-sfc'
import type { SFCTemplateBlock } from '@vue/compiler-sfc'
import { parse as tsParser } from '@typescript-eslint/typescript-estree'
import { createRange, getActiveText, getActiveTextEditor, getActiveTextEditorLanguageId, getCurrentFileUrl, getLocale, getOffsetFromPosition, getPosition, isInPosition, registerCodeLensProvider } from '@vscode-use/utils'
import { traverse } from '@babel/types'
import type { VineCompilerHooks, VineDiagnostic, VineFileCtx } from '@vue-vine/compiler'
import {
compileVineTypeScriptFile,
createCompilerCtx,
} from '@vue-vine/compiler'
import { isVine, isVue, toCamel } from './ui/utils'
import type { PropsConfig, PropsConfigItem } from './ui/utils'
const { parse: svelteParser } = require('svelte/compiler')
// 引入vue-parser只在template中才处理一些逻辑
let isInTemplate = false
export function parser(code: string, position: vscode.Position) {
const entry = getCurrentFileUrl()
if (!entry)
return
const isVine = entry.endsWith('.vine.ts')
if (isVine) {
return parserVine(code, position)
}
else {
const suffix = entry.slice(entry.lastIndexOf('.') + 1)
if (!suffix)
return
isInTemplate = false
if (suffix === 'vue') {
const result = transformVue(code, position)
if (!result)
return
if (!result.refs?.length || !result.template)
return result
const refsMap = findRefs(result.template, result.refs)
return Object.assign(result, { refsMap })
}
if (/ts|js|jsx|tsx/.test(suffix))
return parserJSX(code, position)
if (suffix === 'svelte')
return parserSvelte(code, position)
}
return true
}
export function transformVue(code: string, position: vscode.Position, offset = 0) {
const {
descriptor: { template, script, scriptSetup },
errors,
} = parse(code)
if (errors.length)
return
const _script = script || scriptSetup
if (!template) {
if (_script?.lang === 'tsx') {
const r = parserJSX(_script.content, position)
r.loc = _script.loc
return r
}
return
}
if (_script && isInPosition(_script.loc, position, offset)) {
const content = _script.content!
const refs: (string | [string, string])[] = []
for (const match of content.matchAll(/(const|let|var)\s+([\w$]+)\s*=\s*(ref|useTemplateRef)[^()]*\(([^)]*)\)/g)) {
if (match[3] === 'useTemplateRef') {
refs.push([match[2], match[4].slice(1, -1)])
}
else if (match) {
refs.push(match[2])
}
}
return {
type: 'script',
refs,
template,
}
}
if (!isInPosition(template.loc, position, offset))
return
// 在template中
const { ast } = template
const r = dfs(ast.children, template, position, offset)
if (r) {
r.loc = _script?.loc
return r
}
return r
}
export function transformVine(vineFileCtx: VineFileCtx, position: vscode.Position) {
const targetInPositionNode = vineFileCtx.vineCompFns.find(item => item.fnDeclNode.loc ? isInPosition(item.fnDeclNode.loc, position) : false)
if (!targetInPositionNode)
return
const { templateAst, fnDeclNode, templateStringNode } = targetInPositionNode
const children = templateAst?.children
if (!children)
return
const parent = fnDeclNode
const result = dfs(children, parent, position, templateStringNode?.quasi.quasis[0].start || 0)
const refsMap = findRef(children, {})
if (result)
return Object.assign(result, refsMap)
return {
type: 'script',
refsMap,
}
}
function dfs(children: any, parent: any, position: vscode.Position, offset = 0) {
for (const child of children) {
const { loc, tag, props, children } = child
if (!isInPosition(loc, position, offset))
continue
if (tag) {
const isTag = isInPosition({
start: loc.start,
end: {
line: loc.start.line,
column: loc.start.column + tag.length,
},
}, position, offset)
if (isTag) {
return {
tag,
props,
type: 'tag',
isInTemplate: true,
parent: {
tag: parent.tag ? parent.tag : 'template',
props: parent.props || [],
},
template: parent,
}
}
}
if (props && props.length) {
for (const prop of props) {
if (isInPosition(prop.loc, position, offset)) {
if (!isInAttribute(child, position, offset))
return false
if ((prop.name === 'bind' || prop.name === 'on') && prop.exp && isInPosition(prop.exp.loc, position)) {
return {
tag,
propName: prop.exp?.content !== undefined,
props,
type: 'props',
isInTemplate: true,
isValue: prop.exp?.content !== undefined,
parent: {
tag: parent.tag ? parent.tag : 'template',
props: parent.props || [],
},
isDynamic: prop.name === 'bind',
isEvent: prop.name === 'on',
template: parent,
}
}
else {
let propName = prop.name
if (prop.arg && isInPosition(prop.arg.loc, position))
propName = prop.arg.content
else if (prop.exp && isInPosition(prop.exp.loc, position))
propName = prop.exp.content
return {
tag,
propName,
props,
type: 'props',
isInTemplate: true,
isValue: prop.value?.content !== undefined,
parent: {
tag: parent.tag ? parent.tag : 'template',
props: parent.props || [],
},
template: parent,
}
}
}
}
}
if (children && children.length) {
const result = dfs(children, child, position, offset) as any
if (result)
return result
}
if (tag) {
if (!isInAttribute(child, position, offset))
return false
return {
type: 'props',
tag,
props,
isInTemplate: true,
parent: {
tag: parent.tag ? parent.tag : 'template',
props: parent.props || [],
},
template: parent,
}
}
if (child.type === 2 || child.content?.type === 2) {
return {
type: 'text',
isInTemplate: true,
props,
parent: {
tag: parent.tag ? parent.tag : 'template',
props: parent.props || [],
},
template: parent,
}
}
return
}
}
export function getReactRefsMap() {
const ast = tsParser(getActiveText()!, { jsx: true, loc: true })
const children = ast.body
return findJsxRefs(children)
}
export function parserJSX(code: string, position: vscode.Position) {
try {
const ast = tsParser(code, { jsx: true, loc: true })
const children = ast.body
const result = jsxDfs(children, null, position)
const map = findJsxRefs(children)
if (result)
return Object.assign(result, map)
return {
type: 'script',
...map,
}
}
catch (error) {
console.error(error)
}
}
function jsxDfs(children: any, parent: any, position: vscode.Position) {
for (const child of children) {
let { loc, type, openingElement, body: children, argument, declarations, init } = child
child.name = openingElement?.name.name
if (!loc)
loc = convertPositionToLoc(child)
if (!isInPosition(loc, position))
continue
if (!openingElement && child.attributes) {
openingElement = {
name: {
name: child.name,
},
attributes: child.attributes,
}
}
if (openingElement && openingElement.attributes.length) {
for (const prop of openingElement.attributes) {
if (!prop.loc)
prop.loc = convertPositionToLoc(prop)
if (isInPosition(prop.loc, position)) {
return {
tag: openingElement.name.type === 'JSXMemberExpression'
? `${openingElement.name.object.name}.${openingElement.name.property.name}`
: openingElement.name.name,
propName: typeof prop.name === 'string' ? prop.type === 'EventHandler' ? 'on' : prop.name : prop.name.name,
props: openingElement.attributes,
propType: prop.type,
type: 'props',
isInTemplate,
isValue: prop.value
? Array.isArray(prop.value)
? prop.value[0]?.raw !== undefined
: prop.value.type === 'JSXExpressionContainer'
? prop.value?.expression !== undefined
: prop.value?.value !== undefined
: false,
parent,
isEvent: prop.type === 'EventHandler' || (prop.type === 'JSXAttribute' && prop.name.name.startsWith('on')),
}
}
}
}
if (type === 'JSXElement' || type === 'Element' || (type === 'ReturnStatement' && (argument.type === 'JSXElement' || argument.type === 'JSXFragment')))
isInTemplate = true
if (child.children) { children = child.children }
else if (type === 'ExportNamedDeclaration') { children = child.declaration }
else if (type === 'ObjectExpression') { children = child.properties }
else if (type === 'Property' && child.value.type === 'FunctionExpression') { children = child.value.body.body }
else if (type === 'ExportDefaultDeclaration') {
if (child.declaration.type === 'FunctionDeclaration')
children = child.declaration.body.body
else
children = child.declaration.arguments
}
else if (type === 'JSXExpressionContainer' || type === 'ChainExpression') {
if (child.expression.type === 'CallExpression') { children = child.expression.arguments }
else if (child.expression.type === 'ConditionalExpression') {
children = [
child.expression.alternate,
child.expression.consequent,
].filter(Boolean)
}
else if (child.expression.type === 'LogicalExpression') {
children = [
child.expression.left,
child.expression.right,
]
}
else { children = child.expression }
}
else if (type === 'TemplateLiteral') {
children = child.expressions
}
else if (type === 'ConditionalExpression') {
children = [
child.alternate,
child.consequent,
].filter(Boolean)
}
else if (type === 'ArrowFunctionExpression') {
children = child.body
}
else if (type === 'VariableDeclaration') { children = declarations }
else if (type === 'VariableDeclarator') { children = init }
else if (type === 'ReturnStatement') { children = argument }
else if (type === 'JSXElement') { children = child.children }
else if (type === 'ExportNamedDeclaration') { children = child.declaration.body }
else if (type === 'CallExpression') {
children = child.arguments
}
if (children && !Array.isArray(children))
children = [children]
if (children && children.length) {
const p = child.type === 'JSXElement' ? { name: openingElement.name.name, props: openingElement.attributes } : null
const result = jsxDfs(children, p, position) as any
if (result)
return result
}
if (type === 'JSXElement' || type === 'Element' || type === 'InlineComponent') {
const target = openingElement.attributes.find((item: any) => isInPosition(item.loc, position) || item.value === null)
if (!openingElement) {
openingElement = {
name: {
name: child.name,
},
attributes: child.attributes,
}
}
if (target) {
return {
type: 'props',
tag: openingElement.name.type === 'JSXMemberExpression'
? `${openingElement.name.object.name}.${openingElement.name.property.name}`
: openingElement.name.name,
props: openingElement.attributes,
propName: target.value
? typeof target.name === 'string'
? target.type === 'EventHandler'
? 'on'
: target.name
: target.name.name
: '',
propType: target.type,
isInTemplate,
parent,
}
}
const isTag = isInPosition({
start: loc.start,
end: {
line: loc.start.line,
column: loc.start.column + child.name.length,
},
}, position)
return {
type: isTag ? 'tag' : 'props',
tag: openingElement.name.type === 'JSXMemberExpression'
? `${openingElement.name.object.name}.${openingElement.name.property.name}`
: openingElement.name.name,
props: openingElement.attributes,
isInTemplate,
parent,
}
}
if (type === 'JSXText' || type === 'Text') {
return {
isInTemplate,
type: 'text',
props: openingElement?.attributes,
parent,
}
}
return
}
}
function findJsxRefs(childrens: any, map: any = {}, refs: any = []) {
for (const child of childrens) {
let { type, openingElement, body: children, argument, declarations, init, id, expression } = child
if (child.children) {
children = child.children
}
else if (type === 'VariableDeclaration') {
children = declarations
}
else if (type === 'VariableDeclarator') {
children = init
if (init.callee && init.callee.name === 'useRef') {
refs.push(id.name)
continue
}
}
else if (type === 'ExpressionStatement') {
children = expression.arguments
}
else if (type === 'ReturnStatement') {
children = argument
}
else if (type === 'JSXElement') {
children = child.children
}
else if (type === 'ExportDefaultDeclaration') {
if (child.declaration.type === 'FunctionDeclaration') {
children = child.declaration.body.body
}
}
else if (!children) {
continue
}
if (children && !Array.isArray(children))
children = [children]
if (openingElement && openingElement.attributes.length) {
for (const prop of openingElement.attributes) {
if (prop.name.name === 'ref') {
const value = prop.value?.expression?.name || prop.value.value
map[value] = transformTagName(openingElement.name.name)
}
}
}
if (children && children.length)
findJsxRefs(children, map, refs)
}
return {
refsMap: map,
refs,
}
}
export function findRefs(template: SFCTemplateBlock, refsMap: (string | [string, string])[]) {
const { ast } = template
return findRef(ast.children, {}, refsMap)
}
function findRef(children: any, map: any, refsMap: (string | [string, string])[] = []) {
for (const child of children) {
const { tag, props, children } = child
if (props && props.length) {
for (const prop of props) {
const { name, value } = prop
if (!value)
continue
let { content } = value
if ((name !== 'ref') || !content)
continue
for (const r of refsMap) {
if (Array.isArray(r) && r[1] === content) {
content = r[0]
}
}
const tagName = transformTagName(tag)
map[content] = tagName
}
}
if (children && children.length)
findRef(children, map, refsMap) as any
}
return map
}
export function parserSvelte(code: string, position: vscode.Position) {
const { html } = svelteParser(code)
const result = jsxDfs([html], null, position)
const map = {
refsMap: {},
refs: [],
}
if (result)
return Object.assign(result, map)
return {
type: 'script',
...map,
}
}
// let stop: any = null
// export const alias = getConfiguration('common-intellisense.alias') as Record<string, string>
// export async function findPkgUI(cwd?: string) {
// if (!cwd)
// return
// const pkg = await findUp('package.json', { cwd })
// if (!pkg)
// return
// if (stop)
// stop()
// stop = watchFiles(pkg, {
// onChange() {
// urlCache.clear()
// findUI()
// },
// })
// const p = JSON.parse(await fsp.readFile(pkg, 'utf-8'))
// const { dependencies, devDependencies } = p
// const result = []
// const aliasUiNames = Object.keys(alias)
// if (dependencies) {
// for (const key in dependencies) {
// if (UINames.includes(key) || aliasUiNames.includes(key))
// result.push([key, dependencies[key]])
// }
// }
// if (devDependencies) {
// for (const key in devDependencies) {
// if (UINames.includes(key) || aliasUiNames.includes(key))
// result.push([key, devDependencies[key]])
// }
// }
// return { pkg, uis: result }
// }
export function transformTagName(name: string) {
return name[0].toUpperCase() + name.replace(/(-\w)/g, (match: string) => match[1].toUpperCase()).slice(1)
}
export function isInAttribute(child: any, position: any, offset: number) {
const len = child.props.length
let end = null
const start = {
column: child.loc.start.column + child.tag.length + 1,
line: child.loc.start.line,
offset: child.loc.start.offset + child.tag.length + 1,
}
if (!len) {
const childNode = child.children?.[0]
if (childNode) {
end = {
line: childNode.loc.start.line,
column: childNode.loc.start.column - 1,
offset: childNode.loc.start.offset - 1,
}
}
else {
if (child.isSelfClosing) {
end = {
line: child.loc.end.line,
column: child.loc.end.column - 2,
offset: child.loc.end.offset - 2,
}
}
else {
const startOffset = start.offset
const match = child.loc.source.slice(child.tag.length + 1).match('>')!
const endOffset = startOffset + match.index
const _offset = getOffsetFromPosition(position)!
return (startOffset + offset < _offset) && (_offset <= endOffset + offset)
}
}
}
else {
const offsetX = child.props[len - 1].loc.end.offset - child.loc.start.offset
const x = child.loc.source.slice(offsetX).match('>').index!
end = {
column: child.props[len - 1].loc.end.column + 1 + x,
line: child.props[len - 1].loc.end.line,
offset: child.props[len - 1].loc.end.offset + 1 + x,
}
}
const _offset = getOffsetFromPosition(position)!
const startOffset = start.offset
const endOffset = end.offset
return (startOffset + offset < _offset) && (_offset <= endOffset + offset)
}
export function convertPositionToLoc(data: any) {
const { start, end } = data
const activeTextEditor = getActiveTextEditor()!
const document = activeTextEditor.document
return {
start: convertOffsetToLineColumn(document, start),
end: convertOffsetToLineColumn(document, end),
}
}
function convertOffsetToPosition(document: vscode.TextDocument, offset: number) {
return document.positionAt(offset)
}
function convertOffsetToLineColumn(document: vscode.TextDocument, offset: number) {
const position = convertOffsetToPosition(document, offset)
const lineText = document.lineAt(position.line).text
const line = position.line + 1
const column = position.character + 1
const lineOffset = document.offsetAt(position)
return { line, column, lineText, lineOffset }
}
const modules: any = {
children: [],
offset: 0,
}
export async function detectSlots(UiCompletions: any, uiDeps: any, prefix: string[]) {
const children = (await getTemplateAst(UiCompletions, uiDeps, prefix)).filter(item => item.children.length)
if (!children.length) {
modules.children = []
modules.offset = 0
return
}
modules.children = children
}
export function registerCodeLensProviderFn() {
const isZh = getLocale().includes('zh')
return registerCodeLensProvider(['vue', 'javascriptreact', 'typescriptreact', 'typescript'], {
provideCodeLenses() {
const languageId = getActiveTextEditorLanguageId()
if (languageId === 'typescript' && !isVine())
return []
const result: vscode.CodeLens[] = []
const children = modules.children
children.forEach((child: any) => {
const offset = child.offset
child.children.forEach((m: any) => {
const { child, slots } = m
const range = child.loc
const filters: string[] = []
for (const c of Array.from(child.children) as any) {
if (c.type === 'JSXElement') {
if (c.openingElement.name.name !== 'template')
continue
for (const p of c.openingElement.attributes) {
const namespace = p.name.namespace.name
if (namespace === 'v-slot') {
const slotName = p.name.name.name
filters.push(slotName)
break
}
}
}
else if (c.tag === 'template' && c.props) {
for (const p of c.props) {
if (p.name === 'slot') {
const slotName = p.arg.content
filters.push(slotName)
break
}
}
}
else if (c.codegenNode?.tag === 'template' && c.codegenNode.props) {
for (const p of c.codegenNode.props) {
if (p.name === 'slot') {
const slotName = p.arg.content
filters.push(slotName)
break
}
}
}
}
slots.filter((s: any) => !filters.includes(s.name)).forEach((s: any, i: number) => {
const { name, description, description_zh } = s
// 计算偏移量
let codeLensRange = null
if (isVine()) {
const fixedStart = getPosition(range.start.offset + offset).position
const fixedEnd = getPosition(range.end.offset + offset).position
codeLensRange = createRange(fixedStart, fixedEnd)
}
else {
codeLensRange = createRange(range.start.line - 1, range.start.column, range.end.line - 1, range.end.column)
}
result.push(new vscode.CodeLens(codeLensRange, {
title: `${i === 0 ? 'Slots: ' : ''}${name}`,
tooltip: isZh ? description_zh : description,
command: 'common-intellisense.slots',
arguments: [child, name, offset],
}))
})
})
})
return result
},
})
}
async function getTemplateAst(UiCompletions: any, uiDeps: any, prefix: string[]): Promise<[{ children: any, offset: number }] | []> {
const code = getActiveText()!
if (isVue()) {
const {
descriptor: { template, script, scriptSetup },
} = parse(code)
const _script = script || scriptSetup
if (!template) {
if (_script?.lang === 'tsx') {
const children = findAllJsxElements(_script.content)
return [{
children: await findUiTag(children, UiCompletions, [], new Set(), uiDeps, prefix),
offset: _script.loc.start.offset,
}]
}
return []
}
return [{
children: await findUiTag(template.ast.children, UiCompletions, [], new Set(), uiDeps, prefix),
offset: 0,
}]
}
else if (isVine()) {
const { vineFileCtx } = createVineFileCtx('', code)
if (!vineFileCtx.vineCompFns)
return []
return await Promise.all(vineFileCtx.vineCompFns.map(async (item) => {
const r = {
children: await findUiTag(item.templateAst?.children, UiCompletions, [], new Set(), uiDeps, prefix),
offset: item.templateStringNode?.quasi.quasis[0].start || 0,
}
return r
})) as any
}
else if (['javascriptreact', 'typescriptreact'].includes(getActiveTextEditorLanguageId()!)) {
const children = findAllJsxElements(code)
return [{
children: await findUiTag(children, UiCompletions, [], new Set(), uiDeps, prefix),
offset: 0,
}]
}
return []
}
const originTag = ['div', 'span', 'ul', 'li', 'ol', 'p', 'main', 'header', 'footer', 'template', 'img', 'aside', 'body', 'a', 'video', 'table', 'th', 'tr', 'td', 'form', 'input', 'label', 'button', 'article', 'section']
async function findUiTag(children: any, UiCompletions: any, result: any[] = [], cacheMap = new Set(), uiDeps: any, prefix: string[]) {
for (const child of children) {
let tag: string = child.tag
if (child.type === 'JSXElement')
tag = child.openingElement.name.name
if (!tag)
continue
const nextChildren = child.children
if (nextChildren?.length)
await findUiTag(nextChildren, UiCompletions, result, cacheMap, uiDeps, prefix)
const range = child.range ?? child.loc
if (cacheMap.has(range))
continue
if (originTag.includes(tag))
continue
const tagName = toCamel(`-${tag}`)
let target = UiCompletions[tagName] || await findDynamicComponent(tagName, {}, UiCompletions, prefix)
const importUiSource = uiDeps[tagName]
if (!target)
continue
if (importUiSource && target.uiName !== importUiSource) {
for (const p of prefix.filter(Boolean)) {
const realName = p[0].toUpperCase() + p.slice(1) + tagName
const newTarget = UiCompletions[realName]
if (!newTarget)
continue
if (newTarget.uiName === importUiSource) {
target = newTarget
break
}
}
}
if (!target || !target.rawSlots?.length)
continue
cacheMap.add(range)
result.push({
child,
slots: target.rawSlots,
})
}
return result
}
function findAllJsxElements(code: string) {
const ast = tsParser(code, { jsx: true, loc: true, range: true }) as any
const results: any = []
traverse(ast, (node) => {
if (node.type === 'JSXElement') {
results.push(node)
}
else if (node.type === 'ObjectExpression') {
const _node: any = node.properties?.find((p: any) => p?.key?.name === 'render')
|| node.properties?.find((p: any) => p?.key?.name === 'setup')
const t = _node?.value
if (t) {
traverse(t, (nextNode) => {
if (nextNode.type === 'JSXElement') {
const tag = (nextNode.openingElement.name as any)?.name
if (tag && !originTag.includes(tag))
results.push(nextNode)
}
})
}
}
})
return results
}
export function parserVine(code: string, position: vscode.Position) {
const { vineFileCtx } = createVineFileCtx('', code)
if (!vineFileCtx.vineCompFns.length)
return
return transformVine(vineFileCtx, position)
}
export function createVineFileCtx(sourceFileName: string, source: string) {
const compilerCtx = createCompilerCtx({
envMode: 'module',
vueCompilerOptions: {
// 'module' will break Volar virtual code's mapping
mode: 'function',
// These options below is for resolving conflicts
// with original compiler's mode: 'module'
cacheHandlers: false,
prefixIdentifiers: false,
scopeId: null,
},
inlineTemplate: false,
})
const vineCompileErrs: VineDiagnostic[] = []
const vineCompileWarns: VineDiagnostic[] = []
const compilerHooks: VineCompilerHooks = {
onError: err => vineCompileErrs.push(err),
onWarn: warn => vineCompileWarns.push(warn),
getCompilerCtx: () => compilerCtx,
}
const vineFileCtx = compileVineTypeScriptFile(
source,
sourceFileName,
{
compilerHooks,
babelParseOptions: {
tokens: true,
},
},
)
return {
vineFileCtx,
vineCompileErrs,
vineCompileWarns,
}
}
export function isSamePrefix(label: string, key: string) {
let labelName = label.split('=')[0]
if (labelName.indexOf(' ')) {
// 防止匹配到描述中的=
labelName = labelName.split(' ')[0]
}
return labelName === key
}
const IMPORT_REG = /import\s+(\S+)\s+from\s+['"]([^"']+.vue)['"]/g
export function getImportDeps(text: string) {
text = text.replace(/\/\*[\s\S]*?\*\/|\/\/.*/g, '')
const deps: Record<string, string> = {}
for (const match of text.matchAll(IMPORT_REG)) {
if (!match)
continue
const from = match[2]
if (!/^[./@]/.test(from))
continue
deps[match[1]] = from
}
return deps
}
export function getAbsoluteUrl(url: string) {
return path.resolve(getCurrentFileUrl()!, '..', url)
}
export async function findDynamicComponent(name: string, deps: Record<string, string>, UiCompletions: PropsConfig, prefix: string[], from?: string) {
// const prefix = optionsComponents.prefix
let target = findDynamic(name, UiCompletions, prefix, from)
if (target)
return target
let dep
if (dep = deps[name]) {
// 只往下找一层
const tag = await getTemplateParentElementName(getAbsoluteUrl(dep))
if (!tag)
return
target = findDynamic(tag, UiCompletions, prefix, from)
}
return target
}
function findDynamic(tag: string, UiCompletions: PropsConfig, prefix: string[], from?: string) {
let target: PropsConfigItem | null = UiCompletions[tag]
if (target && from && target.lib !== from) {
target = null
}
if (!target) {
for (const p of prefix) {
if (!p)
continue
const t = UiCompletions[p[0].toUpperCase() + p.slice(1) + tag]
if (from && t && t.lib === from) {
target = t
break
}
else if (t) {
target = t
break
}
}
}
return target
}
async function getTemplateParentElementName(url: string) {
const code = await fsp.readFile(url, 'utf-8')
// 如果有defineProps或者props的忽律,交给v-component-prompter处理
const {
descriptor: { template, script, scriptSetup },
} = parse(code)
if (script?.content && /^\s*props:\s*\{/.test(script.content))
return
if (scriptSetup?.content && /defineProps\(/.test(scriptSetup.content))
return
if (!template?.ast?.children?.length)
return
let result = ''
for (const child of template.ast.children) {
const node = child as any
if (node.tag) {
if (result) // 说明template下不是唯一父节点
return
result = node.tag
}
}
return result
}