diff --git a/packages/vue-language-core/schemas/vue-tsconfig.deprecated.schema.json b/packages/vue-language-core/schemas/vue-tsconfig.deprecated.schema.json index f1a0e2126..a165e396e 100644 --- a/packages/vue-language-core/schemas/vue-tsconfig.deprecated.schema.json +++ b/packages/vue-language-core/schemas/vue-tsconfig.deprecated.schema.json @@ -61,7 +61,12 @@ "deprecated": true }, "jsxTemplates": { - "deprecated": true + "deprecated": true, + "description": "Deprecated since v1.5.0." + }, + "nativeTags": { + "deprecated": true, + "description": "Deprecated since v1.5.1." } } } diff --git a/packages/vue-language-core/schemas/vue-tsconfig.schema.json b/packages/vue-language-core/schemas/vue-tsconfig.schema.json index f0d2697d3..1d938859f 100644 --- a/packages/vue-language-core/schemas/vue-tsconfig.schema.json +++ b/packages/vue-language-core/schemas/vue-tsconfig.schema.json @@ -28,15 +28,6 @@ "type": "boolean", "markdownDescription": "https://github.com/johnsoncodehk/volar/issues/577" }, - "nativeTags": { - "type": "array", - "default": [ - "div", - "img", - "..." - ], - "markdownDescription": "List of valid intrinsic elements." - }, "dataAttributes": { "type": "array", "default": [ ], diff --git a/packages/vue-language-core/src/generators/script.ts b/packages/vue-language-core/src/generators/script.ts index 2a700dfd9..a89ad83aa 100644 --- a/packages/vue-language-core/src/generators/script.ts +++ b/packages/vue-language-core/src/generators/script.ts @@ -812,7 +812,7 @@ declare function defineProp(value?: T | (() => T), required?: boolean, rest?: codes.push('/* Components */\n'); codes.push(`let __VLS_localComponents!: NonNullable & typeof __VLS_componentsOption & typeof __VLS_ctx;\n`); codes.push(`let __VLS_otherComponents!: typeof __VLS_localComponents & import('./__VLS_types').GlobalComponents;\n`); - codes.push(`let __VLS_own!: import('./__VLS_types').SelfComponent { ${getSlotsPropertyName(vueCompilerOptions.target ?? 3)}: typeof __VLS_slots })>;\n`); + codes.push(`let __VLS_own!: import('./__VLS_types').SelfComponent { ${getSlotsPropertyName(vueCompilerOptions.target)}: typeof __VLS_slots })>;\n`); codes.push(`let __VLS_components!: typeof __VLS_otherComponents & Omit;\n`); /* Style Scoped */ diff --git a/packages/vue-language-core/src/generators/template.ts b/packages/vue-language-core/src/generators/template.ts index 61ff32f1a..2a90f1b3d 100644 --- a/packages/vue-language-core/src/generators/template.ts +++ b/packages/vue-language-core/src/generators/template.ts @@ -59,7 +59,6 @@ export function generate( cssScopedClasses: string[] = [], ) { - const nativeTags = new Set(vueCompilerOptions.nativeTags); const codes: Code[] = []; const formatCodes: Code[] = []; const cssCodes: Code[] = []; @@ -160,26 +159,18 @@ export function generate( for (const tagName in tagNames) { - if (nativeTags.has(tagName)) - continue; - const isNamespacedTag = tagName.indexOf('.') >= 0; if (isNamespacedTag) continue; - const names = new Set([ - // order is important: https://github.com/johnsoncodehk/volar/issues/2010 - capitalize(camelize(tagName)), - camelize(tagName), - tagName, - ]); const varName = validTsVar.test(tagName) ? tagName : capitalize(camelize(tagName.replace(/:/g, '-'))); codes.push( - '& import("./__VLS_types").WithComponent<"', - varName, - '", typeof __VLS_components, ', - [...names].map(name => `'${name}'`).join(', '), + '& import("./__VLS_types").WithComponent\n', ); @@ -206,7 +197,7 @@ export function generate( for (const name of names) { for (const tagRange of tagRanges) { codes.push( - '__VLS_components', + name === tagName ? '__VLS_templateComponents' : '__VLS_components', ...createPropertyAccessCode([ name, 'template', @@ -493,26 +484,11 @@ export function generate( const propsFailedExps: CompilerDOM.SimpleExpressionNode[] = []; const tagOffsets = endTagOffset !== undefined ? [startTagOffset, endTagOffset] : [startTagOffset]; - const isIntrinsicElement = nativeTags.has(node.tag); const isNamespacedTag = node.tag.indexOf('.') >= 0; const componentVar = `__VLS_${elementIndex++}`; const componentInstanceVar = `__VLS_${elementIndex++}`; - if (isIntrinsicElement) { - codes.push( - 'const ', - componentVar, - ` = (await import('./__VLS_types')).asFunctionalComponent(({} as import('./__VLS_types').IntrinsicElements)[`, - ...createStringLiteralKeyCode([ - node.tag, - 'template', - tagOffsets[0], - capabilitiesPresets.diagnosticOnly, - ]), - ']);\n', - ); - } - else if (isNamespacedTag) { + if (isNamespacedTag) { codes.push( `const ${componentVar} = (await import('./__VLS_types')).asFunctionalComponent(${node.tag}, new ${node.tag}({`, ...createPropsCode(node, 'slots'), @@ -530,22 +506,7 @@ export function generate( } for (const offset of tagOffsets) { - if (isIntrinsicElement) { - codes.push( - `({} as import('./__VLS_types').IntrinsicElements)`, - ...createPropertyAccessCode([ - node.tag, - 'template', - offset, - { - ...capabilitiesPresets.tagReference, - ...capabilitiesPresets.tagHover, - }, - ]), - ';\n', - ); - } - else if (isNamespacedTag) { + if (isNamespacedTag) { codes.push( [node.tag, 'template', [offset, offset + node.tag.length], capabilitiesPresets.all], ';\n', diff --git a/packages/vue-language-core/src/types.ts b/packages/vue-language-core/src/types.ts index c838f9e00..f08678910 100644 --- a/packages/vue-language-core/src/types.ts +++ b/packages/vue-language-core/src/types.ts @@ -21,7 +21,6 @@ export interface VueCompilerOptions { extensions: string[]; strictTemplates: boolean; skipTemplateCodegen: boolean; - nativeTags: string[]; dataAttributes: string[]; htmlAttributes: string[]; optionsWrapper: [string, string] | []; diff --git a/packages/vue-language-core/src/utils/localTypes.ts b/packages/vue-language-core/src/utils/localTypes.ts index b4cddbefc..15ed41f94 100644 --- a/packages/vue-language-core/src/utils/localTypes.ts +++ b/packages/vue-language-core/src/utils/localTypes.ts @@ -14,10 +14,9 @@ import type { ObjectDirective, FunctionDirective, } from '${libName}'; -${vueCompilerOptions.target >= 3.3 ? `import { JSX } from 'vue/jsx-runtime';` : ''} -export type IntrinsicElements = JSX.IntrinsicElements; -export type Element = JSX.Element; +export type IntrinsicElements = PickNotAny>>; +export type Element = PickNotAny; type IsAny = boolean extends (T extends never ? true : false) ? true : false; export type PickNotAny = IsAny extends true ? B : A; @@ -58,11 +57,13 @@ export declare function withScope(ctx: T, scope: K): ctx is T & K; export declare function makeOptional(t: T): { [K in keyof T]?: T[K] }; export type SelfComponent = string extends N ? {} : N extends string ? { [P in N]: C } : {}; -export type WithComponent = - N1 extends keyof Components ? { [K in N0]: Components[N1] } : - N2 extends keyof Components ? { [K in N0]: Components[N2] } : - N3 extends keyof Components ? { [K in N0]: Components[N3] } : - ${vueCompilerOptions.strictTemplates ? '{}' : '{ [K in N0]: any }'}; +export type WithComponent = + IsAny extends true ? ( + N1 extends keyof Components ? N1 extends N0 ? Pick : { [K in N0]: Components[N1] } : + N2 extends keyof Components ? N2 extends N0 ? Pick : { [K in N0]: Components[N2] } : + N0 extends keyof Components ? Pick : + ${vueCompilerOptions.strictTemplates ? '{}' : '{ [K in N0]: any }'} + ) : Pick; export type FillingEventArg_ParametersLength any> = IsAny> extends true ? -1 : Parameters['length']; export type FillingEventArg = E extends (...args: any) => any ? FillingEventArg_ParametersLength extends 0 ? ($event?: undefined) => ReturnType : E : E; diff --git a/packages/vue-language-core/src/utils/ts.ts b/packages/vue-language-core/src/utils/ts.ts index 69920c028..a5f15f33a 100644 --- a/packages/vue-language-core/src/utils/ts.ts +++ b/packages/vue-language-core/src/utils/ts.ts @@ -170,31 +170,6 @@ function getVueCompilerOptions( } } -// https://developer.mozilla.org/en-US/docs/Web/HTML/Element -const HTML_TAGS = - 'html,body,base,head,link,meta,style,title,address,article,aside,footer,' + - 'header,hgroup,h1,h2,h3,h4,h5,h6,nav,section,div,dd,dl,dt,figcaption,' + - 'figure,picture,hr,img,li,main,ol,p,pre,ul,a,b,abbr,bdi,bdo,br,cite,code,' + - 'data,dfn,em,i,kbd,mark,q,rp,rt,ruby,s,samp,small,span,strong,sub,sup,' + - 'time,u,var,wbr,area,audio,map,track,video,embed,object,param,source,' + - 'canvas,script,noscript,del,ins,caption,col,colgroup,table,thead,tbody,td,' + - 'th,tr,button,datalist,fieldset,form,input,label,legend,meter,optgroup,' + - 'option,output,progress,select,textarea,details,dialog,menu,' + - 'summary,template,blockquote,iframe,tfoot'; - -// https://developer.mozilla.org/en-US/docs/Web/SVG/Element -const SVG_TAGS = - 'svg,animate,animateMotion,animateTransform,circle,clipPath,color-profile,' + - 'defs,desc,discard,ellipse,feBlend,feColorMatrix,feComponentTransfer,' + - 'feComposite,feConvolveMatrix,feDiffuseLighting,feDisplacementMap,' + - 'feDistanceLight,feDropShadow,feFlood,feFuncA,feFuncB,feFuncG,feFuncR,' + - 'feGaussianBlur,feImage,feMerge,feMergeNode,feMorphology,feOffset,' + - 'fePointLight,feSpecularLighting,feSpotLight,feTile,feTurbulence,filter,' + - 'foreignObject,g,hatch,hatchpath,image,line,linearGradient,marker,mask,' + - 'mesh,meshgradient,meshpatch,meshrow,metadata,mpath,path,pattern,' + - 'polygon,polyline,radialGradient,rect,set,solidcolor,stop,switch,symbol,' + - 'text,textPath,title,tspan,unknown,use,view'; - export function resolveVueCompilerOptions(vueOptions: Partial): VueCompilerOptions { const target = vueOptions.target ?? 3.3; return { @@ -203,14 +178,6 @@ export function resolveVueCompilerOptions(vueOptions: Partial, requiredOnly = false, ) { @@ -26,7 +25,7 @@ export function checkPropsOfTag( let componentSymbol = components.componentsType.getProperty(name[0]); - if (!componentSymbol && !vueCompilerOptions.nativeTags.includes(name[0])) { + if (!componentSymbol && !nativeTags.has(name[0])) { componentSymbol = components.componentsType.getProperty(camelize(name[0])) ?? components.componentsType.getProperty(capitalize(camelize(name[0]))); } @@ -86,7 +85,7 @@ export function checkEventsOfTag( tsLs: ts.LanguageService, sourceFile: embedded.VirtualFile, tag: string, - vueCompilerOptions: VueCompilerOptions, + nativeTags: Set, ) { const checker = tsLs.getProgram()!.getTypeChecker(); @@ -98,7 +97,7 @@ export function checkEventsOfTag( let componentSymbol = components.componentsType.getProperty(name[0]); - if (!componentSymbol && !vueCompilerOptions.nativeTags.includes(name[0])) { + if (!componentSymbol && !nativeTags.has(name[0])) { componentSymbol = components.componentsType.getProperty(camelize(name[0])) ?? components.componentsType.getProperty(capitalize(camelize(name[0]))); } @@ -151,15 +150,47 @@ export function checkComponentNames( ts: typeof import('typescript/lib/tsserverlibrary'), tsLs: ts.LanguageService, sourceFile: embedded.VirtualFile, + nativeTags: Set, ) { return getComponentsType(ts, tsLs, sourceFile) ?.componentsType ?.getProperties() .map(c => c.name) .filter(entry => entry.indexOf('$') === -1 && !entry.startsWith('_')) + .filter(entry => !nativeTags.has(entry)) ?? []; } +export function checkNativeTags( + ts: typeof import('typescript/lib/tsserverlibrary'), + tsLs: ts.LanguageService, + fileName: string, +) { + + const sharedTypesFileName = fileName.substring(0, fileName.lastIndexOf('/')) + '/' + typesFileName; + const result = new Set(); + + let tsSourceFile: ts.SourceFile | undefined; + + if (tsSourceFile = tsLs.getProgram()?.getSourceFile(sharedTypesFileName)) { + + const typeNode = tsSourceFile.statements.find((node): node is ts.TypeAliasDeclaration => ts.isTypeAliasDeclaration(node) && node.name.getText() === 'IntrinsicElements'); + const checker = tsLs.getProgram()?.getTypeChecker(); + + if (checker && typeNode) { + + const type = checker.getTypeFromTypeNode(typeNode.type); + const props = type.getProperties(); + + for (const prop of props) { + result.add(prop.name); + } + } + } + + return result; +} + export function getElementAttrs( ts: typeof import('typescript/lib/tsserverlibrary'), tsLs: ts.LanguageService, diff --git a/packages/vue-language-service/src/ideFeatures/nameCasing.ts b/packages/vue-language-service/src/ideFeatures/nameCasing.ts index c61045d38..9fee06dce 100644 --- a/packages/vue-language-service/src/ideFeatures/nameCasing.ts +++ b/packages/vue-language-service/src/ideFeatures/nameCasing.ts @@ -1,9 +1,9 @@ import { hyphenate } from '@vue/shared'; import { LanguageServicePluginContext, VirtualFile } from '@volar/language-service'; -import { checkComponentNames, getTemplateTagsAndAttrs, checkPropsOfTag } from '../helpers'; +import { checkComponentNames, getTemplateTagsAndAttrs, checkPropsOfTag, checkNativeTags } from '../helpers'; import * as vue from '@volar/vue-language-core'; import * as vscode from 'vscode-languageserver-protocol'; -import { AttrNameCasing, TagNameCasing, VueCompilerOptions } from '../types'; +import { AttrNameCasing, TagNameCasing } from '../types'; export async function convertTagName( context: LanguageServicePluginContext, @@ -23,7 +23,8 @@ export async function convertTagName( const template = desc.template; const document = context.documents.getDocumentByFileName(rootFile.snapshot, rootFile.fileName); const edits: vscode.TextEdit[] = []; - const components = checkComponentNames(_ts.module, _ts.languageService, rootFile); + const nativeTags = checkNativeTags(_ts.module, _ts.languageService, rootFile.fileName); + const components = checkComponentNames(_ts.module, _ts.languageService, rootFile, nativeTags); const tags = getTemplateTagsAndAttrs(rootFile); for (const [tagName, { offsets }] of tags) { @@ -51,7 +52,6 @@ export async function convertAttrName( _ts: NonNullable, uri: string, casing: AttrNameCasing, - vueCompilerOptions: VueCompilerOptions, ) { const rootFile = context.documents.getSourceByUri(uri)?.root; @@ -65,13 +65,14 @@ export async function convertAttrName( const template = desc.template; const document = context.documents.getDocumentByFileName(rootFile.snapshot, rootFile.fileName); const edits: vscode.TextEdit[] = []; - const components = checkComponentNames(_ts.module, _ts.languageService, rootFile); + const nativeTags = checkNativeTags(_ts.module, _ts.languageService, rootFile.fileName); + const components = checkComponentNames(_ts.module, _ts.languageService, rootFile, nativeTags); const tags = getTemplateTagsAndAttrs(rootFile); for (const [tagName, { attrs }] of tags) { const componentName = components.find(component => component === tagName || hyphenate(component) === tagName); if (componentName) { - const props = checkPropsOfTag(_ts.module, _ts.languageService, rootFile, componentName, vueCompilerOptions); + const props = checkPropsOfTag(_ts.module, _ts.languageService, rootFile, componentName, nativeTags); for (const [attrName, { offsets }] of attrs) { const propName = props.find(prop => prop === attrName || hyphenate(prop) === attrName); if (propName) { @@ -162,7 +163,8 @@ export function detect( } function getTagNameCase(file: VirtualFile): TagNameCasing[] { - const components = checkComponentNames(_ts.module, _ts.languageService, file); + const nativeTags = checkNativeTags(_ts.module, _ts.languageService, file.fileName); + const components = checkComponentNames(_ts.module, _ts.languageService, file, nativeTags); const tagNames = getTemplateTagsAndAttrs(file); const result: TagNameCasing[] = []; diff --git a/packages/vue-language-service/src/plugins/vue-template.ts b/packages/vue-language-service/src/plugins/vue-template.ts index 1163a01a9..5e3f00392 100644 --- a/packages/vue-language-service/src/plugins/vue-template.ts +++ b/packages/vue-language-service/src/plugins/vue-template.ts @@ -5,7 +5,7 @@ import { hyphenate, capitalize, camelize } from '@vue/shared'; import * as html from 'vscode-html-languageservice'; import * as vscode from 'vscode-languageserver-protocol'; import { TextDocument } from 'vscode-languageserver-textdocument'; -import { checkComponentNames, checkEventsOfTag, checkPropsOfTag, getElementAttrs } from '../helpers'; +import { checkComponentNames, checkEventsOfTag, checkPropsOfTag, getElementAttrs, checkNativeTags } from '../helpers'; import { getNameCasing } from '../ideFeatures/nameCasing'; import { AttrNameCasing, VueCompilerOptions, TagNameCasing } from '../types'; import { loadTemplateData, loadModelModifiersData } from './data'; @@ -67,7 +67,6 @@ export default function useVueTemplateLanguagePlugin = {}; let token: html.TokenType; let current: { @@ -133,13 +133,10 @@ export default function useVueTemplateLanguagePlugin= 0 ? components.find(component => component === tagName.split('.')[0]) - : components.find(component => - component === tagName - || (!options.vueCompilerOptions.nativeTags.includes(hyphenate(component)) && hyphenate(component) === tagName) - ); + : components.find(component => component === tagName || hyphenate(component) === tagName); const checkTag = tagName.indexOf('.') >= 0 ? tagName : component; if (checkTag) { - componentProps[checkTag] ??= checkPropsOfTag(_ts.module, _ts.languageService, virtualFile, checkTag, options.vueCompilerOptions, true); + componentProps[checkTag] ??= checkPropsOfTag(_ts.module, _ts.languageService, virtualFile, checkTag, nativeTags, true); current = { unburnedRequiredProps: [...componentProps[checkTag]], labelOffset: scanner.getTokenOffset() + scanner.getTokenLength(), @@ -293,10 +290,11 @@ export default function useVueTemplateLanguagePlugin !nativeTags.has(name)), + ...templateScriptData.map(hyphenate), ]); const offsetRange = { start: document.offsetAt(range.start), @@ -360,6 +358,7 @@ export default function useVueTemplateLanguagePlugin true, provideTags: () => { - const components = checkComponentNames(_ts.module, _ts.languageService, vueSourceFile) + const components = checkComponentNames(_ts.module, _ts.languageService, vueSourceFile, nativeTags) .filter(name => name !== 'Transition' && name !== 'TransitionGroup' @@ -410,8 +409,8 @@ export default function useVueTemplateLanguagePlugin { const attrs = getElementAttrs(_ts.module, _ts.languageService, vueSourceFile.fileName, tag); - const props = new Set(checkPropsOfTag(_ts.module, _ts.languageService, vueSourceFile, tag, options.vueCompilerOptions)); - const events = checkEventsOfTag(_ts.module, _ts.languageService, vueSourceFile, tag, options.vueCompilerOptions); + const props = new Set(checkPropsOfTag(_ts.module, _ts.languageService, vueSourceFile, tag, nativeTags)); + const events = checkEventsOfTag(_ts.module, _ts.languageService, vueSourceFile, tag, nativeTags); const attributes: html.IAttributeData[] = []; for (const prop of [...props, ...attrs]) { @@ -516,7 +515,8 @@ export default function useVueTemplateLanguagePlugin, vueSourceFile: vue.VueFile) { const replacement = getReplacement(completionList, map.sourceFileDocument); - const componentNames = new Set(checkComponentNames(_ts.module, _ts.languageService, vueSourceFile).map(hyphenate)); + const nativeTags = checkNativeTags(_ts.module, _ts.languageService, vueSourceFile.fileName); + const componentNames = new Set(checkComponentNames(_ts.module, _ts.languageService, vueSourceFile, nativeTags).map(hyphenate)); if (replacement) {