Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: deprecate nativeTags #2685

Merged
merged 5 commits into from Apr 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -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."
}
}
}
Expand Down
9 changes: 0 additions & 9 deletions packages/vue-language-core/schemas/vue-tsconfig.schema.json
Expand Up @@ -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": [ ],
Expand Down
2 changes: 1 addition & 1 deletion packages/vue-language-core/src/generators/script.ts
Expand Up @@ -812,7 +812,7 @@ declare function defineProp<T>(value?: T | (() => T), required?: boolean, rest?:
codes.push('/* Components */\n');
codes.push(`let __VLS_localComponents!: NonNullable<typeof __VLS_internalComponent extends { components: infer C } ? C : {}> & 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<typeof __VLS_name, typeof __VLS_internalComponent & typeof __VLS_publicComponent & (new () => { ${getSlotsPropertyName(vueCompilerOptions.target ?? 3)}: typeof __VLS_slots })>;\n`);
codes.push(`let __VLS_own!: import('./__VLS_types').SelfComponent<typeof __VLS_name, typeof __VLS_internalComponent & typeof __VLS_publicComponent & (new () => { ${getSlotsPropertyName(vueCompilerOptions.target)}: typeof __VLS_slots })>;\n`);
codes.push(`let __VLS_components!: typeof __VLS_otherComponents & Omit<typeof __VLS_own, keyof typeof __VLS_otherComponents>;\n`);

/* Style Scoped */
Expand Down
55 changes: 8 additions & 47 deletions packages/vue-language-core/src/generators/template.ts
Expand Up @@ -59,7 +59,6 @@ export function generate(
cssScopedClasses: string[] = [],
) {

const nativeTags = new Set(vueCompilerOptions.nativeTags);
const codes: Code[] = [];
const formatCodes: Code[] = [];
const cssCodes: Code[] = [];
Expand Down Expand Up @@ -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<typeof __VLS_components, ',
// order is important: https://github.com/johnsoncodehk/volar/issues/2010
`"${capitalize(camelize(tagName))}", `,
`"${camelize(tagName)}", `,
`"${tagName}"`,
'>\n',
);

Expand All @@ -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',
Expand Down Expand Up @@ -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'),
Expand All @@ -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',
Expand Down
1 change: 0 additions & 1 deletion packages/vue-language-core/src/types.ts
Expand Up @@ -21,7 +21,6 @@ export interface VueCompilerOptions {
extensions: string[];
strictTemplates: boolean;
skipTemplateCodegen: boolean;
nativeTags: string[];
dataAttributes: string[];
htmlAttributes: string[];
optionsWrapper: [string, string] | [];
Expand Down
17 changes: 9 additions & 8 deletions packages/vue-language-core/src/utils/localTypes.ts
Expand Up @@ -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<import('vue/jsx-runtime').JSX, PickNotAny<JSX.IntrinsicElements, Record<string, any>>>;
export type Element = PickNotAny<import('vue/jsx-runtime').JSX, JSX.Element>;

type IsAny<T> = boolean extends (T extends never ? true : false) ? true : false;
export type PickNotAny<A, B> = IsAny<A> extends true ? B : A;
Expand Down Expand Up @@ -58,11 +57,13 @@ export declare function withScope<T, K>(ctx: T, scope: K): ctx is T & K;
export declare function makeOptional<T>(t: T): { [K in keyof T]?: T[K] };

export type SelfComponent<N, C> = string extends N ? {} : N extends string ? { [P in N]: C } : {};
export type WithComponent<N0 extends string, Components, N1, N2 = unknown, N3 = unknown> =
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<Components, N1 extends string, N2 extends string, N0 extends string> =
IsAny<IntrinsicElements[N0]> extends true ? (
N1 extends keyof Components ? N1 extends N0 ? Pick<Components, N0> : { [K in N0]: Components[N1] } :
N2 extends keyof Components ? N2 extends N0 ? Pick<Components, N0> : { [K in N0]: Components[N2] } :
N0 extends keyof Components ? Pick<Components, N0> :
${vueCompilerOptions.strictTemplates ? '{}' : '{ [K in N0]: any }'}
) : Pick<IntrinsicElements, N0>;

export type FillingEventArg_ParametersLength<E extends (...args: any) => any> = IsAny<Parameters<E>> extends true ? -1 : Parameters<E>['length'];
export type FillingEventArg<E> = E extends (...args: any) => any ? FillingEventArg_ParametersLength<E> extends 0 ? ($event?: undefined) => ReturnType<E> : E : E;
Expand Down
33 changes: 0 additions & 33 deletions packages/vue-language-core/src/utils/ts.ts
Expand Up @@ -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>): VueCompilerOptions {
const target = vueOptions.target ?? 3.3;
return {
Expand All @@ -203,14 +178,6 @@ export function resolveVueCompilerOptions(vueOptions: Partial<VueCompilerOptions
extensions: vueOptions.extensions ?? ['.vue'],
strictTemplates: vueOptions.strictTemplates ?? false,
skipTemplateCodegen: vueOptions.skipTemplateCodegen ?? false,
nativeTags: vueOptions.nativeTags ?? [...new Set([
...HTML_TAGS.split(','),
...SVG_TAGS.split(','),
// fix https://github.com/johnsoncodehk/volar/issues/1340
'hgroup',
'slot',
'component',
])],
dataAttributes: vueOptions.dataAttributes ?? [],
htmlAttributes: vueOptions.htmlAttributes ?? ['aria-*'],
optionsWrapper: vueOptions.optionsWrapper ?? (
Expand Down
4 changes: 2 additions & 2 deletions packages/vue-language-server/src/languageServerPlugin.ts
Expand Up @@ -7,7 +7,7 @@ import { DetectNameCasingRequest, GetConvertAttrCasingEditsRequest, GetConvertTa
import { VueServerInitializationOptions } from './types';
import type * as ts from 'typescript/lib/tsserverlibrary';
import * as componentMeta from 'vue-component-meta';
import { resolveVueCompilerOptions, VueCompilerOptions } from '@volar/vue-language-core';
import { VueCompilerOptions } from '@volar/vue-language-core';

export function createServerPlugin(connection: Connection) {

Expand Down Expand Up @@ -121,7 +121,7 @@ export function createServerPlugin(connection: Connection) {
if (languageService?.context.typescript) {
const vueOptions = hostToVueOptions.get(languageService.context.host);
if (vueOptions) {
return nameCasing.convertAttrName(languageService.context, languageService.context.typescript, params.textDocument.uri, params.casing, resolveVueCompilerOptions(vueOptions));
return nameCasing.convertAttrName(languageService.context, languageService.context.typescript, params.textDocument.uri, params.casing);
}
}
});
Expand Down
41 changes: 36 additions & 5 deletions packages/vue-language-service/src/helpers.ts
Expand Up @@ -4,7 +4,6 @@ import * as CompilerDOM from '@vue/compiler-dom';
import { computed, ComputedRef } from '@vue/reactivity';
import { typesFileName } from '@volar/vue-language-core/out/utils/localTypes';
import { camelize, capitalize } from '@vue/shared';
import type { VueCompilerOptions } from './types';

import type * as ts from 'typescript/lib/tsserverlibrary';

Expand All @@ -13,7 +12,7 @@ export function checkPropsOfTag(
tsLs: ts.LanguageService,
sourceFile: embedded.VirtualFile,
tag: string,
vueCompilerOptions: VueCompilerOptions,
nativeTags: Set<string>,
requiredOnly = false,
) {

Expand All @@ -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])));
}
Expand Down Expand Up @@ -86,7 +85,7 @@ export function checkEventsOfTag(
tsLs: ts.LanguageService,
sourceFile: embedded.VirtualFile,
tag: string,
vueCompilerOptions: VueCompilerOptions,
nativeTags: Set<string>,
) {

const checker = tsLs.getProgram()!.getTypeChecker();
Expand All @@ -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])));
}
Expand Down Expand Up @@ -151,15 +150,47 @@ export function checkComponentNames(
ts: typeof import('typescript/lib/tsserverlibrary'),
tsLs: ts.LanguageService,
sourceFile: embedded.VirtualFile,
nativeTags: Set<string>,
) {
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<string>();

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,
Expand Down
16 changes: 9 additions & 7 deletions 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,
Expand All @@ -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) {
Expand Down Expand Up @@ -51,7 +52,6 @@ export async function convertAttrName(
_ts: NonNullable<LanguageServicePluginContext['typescript']>,
uri: string,
casing: AttrNameCasing,
vueCompilerOptions: VueCompilerOptions,
) {

const rootFile = context.documents.getSourceByUri(uri)?.root;
Expand All @@ -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) {
Expand Down Expand Up @@ -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[] = [];

Expand Down