Skip to content

Commit

Permalink
fix: correctly hyphen-case props (#3424)
Browse files Browse the repository at this point in the history
Co-authored-by: Johnson Chu <johnsoncodehk@gmail.com>
  • Loading branch information
so1ve and johnsoncodehk committed Sep 6, 2023
1 parent 929d833 commit 2f3282e
Show file tree
Hide file tree
Showing 11 changed files with 136 additions and 96 deletions.
14 changes: 6 additions & 8 deletions packages/vue-language-core/src/generators/script.ts
@@ -1,19 +1,17 @@
import { getLength, Segment } from '@volar/source-map';
import { FileRangeCapabilities, MirrorBehaviorCapabilities } from '@volar/language-core';
import type { TextRange } from '../types';
import * as SourceMaps from '@volar/source-map';
import { hyphenate } from '@vue/shared';
import { Segment, getLength } from '@volar/source-map';
import * as muggle from 'muggle-string';
import { posix as path } from 'path';
import type * as ts from 'typescript/lib/tsserverlibrary';
import type * as templateGen from '../generators/template';
import type { ScriptRanges } from '../parsers/scriptRanges';
import type { ScriptSetupRanges } from '../parsers/scriptSetupRanges';
import type { TextRange, VueCompilerOptions } from '../types';
import { Sfc } from '../types';
import type { VueCompilerOptions } from '../types';
import { getSlotsPropertyName } from '../utils/shared';
import { walkInterpolationFragment } from '../utils/transform';
import * as sharedTypes from '../utils/globalTypes';
import * as muggle from 'muggle-string';
import { getSlotsPropertyName, hyphenateTag } from '../utils/shared';
import { walkInterpolationFragment } from '../utils/transform';

export function generate(
ts: typeof import('typescript/lib/tsserverlibrary'),
Expand Down Expand Up @@ -974,7 +972,7 @@ declare function defineProp<T>(value?: T | (() => T), required?: boolean, rest?:

// fix import components unused report
for (const varName of bindingNames) {
if (!!htmlGen.tagNames[varName] || !!htmlGen.tagNames[hyphenate(varName)]) {
if (!!htmlGen.tagNames[varName] || !!htmlGen.tagNames[hyphenateTag(varName)]) {
usageVars.add(varName);
}
}
Expand Down
43 changes: 24 additions & 19 deletions packages/vue-language-core/src/generators/template.ts
@@ -1,12 +1,13 @@
import { Segment } from '@volar/source-map';
import { FileRangeCapabilities } from '@volar/language-core';
import { Segment } from '@volar/source-map';
import * as CompilerDOM from '@vue/compiler-dom';
import { camelize, capitalize, hyphenate } from '@vue/shared';
import { camelize, capitalize } from '@vue/shared';
import { minimatch } from 'minimatch';
import * as muggle from 'muggle-string';
import type * as ts from 'typescript/lib/tsserverlibrary';
import { Sfc, VueCompilerOptions } from '../types';
import { hyphenateAttr, hyphenateTag } from '../utils/shared';
import { colletVars, walkInterpolationFragment } from '../utils/transform';
import { minimatch } from 'minimatch';
import * as muggle from 'muggle-string';

const capabilitiesPresets = {
all: FileRangeCapabilities.full,
Expand Down Expand Up @@ -233,7 +234,7 @@ export function generate(
...capabilitiesPresets.tagReference,
rename: {
normalize: tagName === name ? capabilitiesPresets.tagReference.rename.normalize : camelizeComponentName,
apply: getRenameApply(tagName),
apply: getTagRenameApply(tagName),
},
},
]),
Expand Down Expand Up @@ -963,8 +964,8 @@ export function generate(
},
// onClickOutside -> @click-outside
apply(newName) {
const hName = hyphenate(newName);
if (hyphenate(newName).startsWith('on-')) {
const hName = hyphenateAttr(newName);
if (hyphenateAttr(newName).startsWith('on-')) {
return camelize(hName.slice('on-'.length));
}
return newName;
Expand Down Expand Up @@ -1188,7 +1189,7 @@ export function generate(

if (
(!prop.arg || (prop.arg.type === CompilerDOM.NodeTypes.SIMPLE_EXPRESSION && prop.arg.isStatic)) // isStatic
&& hyphenate(attrNameText) === attrNameText
&& hyphenateAttr(attrNameText) === attrNameText
&& !vueCompilerOptions.htmlAttributes.some(pattern => minimatch(attrNameText!, pattern))
) {
attrNameText = camelize(attrNameText);
Expand Down Expand Up @@ -1222,7 +1223,7 @@ export function generate(
...caps_attr,
rename: {
normalize: camelize,
apply: camelized ? hyphenate : noEditApply,
apply: camelized ? hyphenateAttr : noEditApply,
},
},
], (prop.loc as any).name_2 ?? ((prop.loc as any).name_2 = {})),
Expand All @@ -1238,7 +1239,7 @@ export function generate(
...caps_attr,
rename: {
normalize: camelize,
apply: camelized ? hyphenate : noEditApply,
apply: camelized ? hyphenateAttr : noEditApply,
},
},
], (prop.loc as any).name_2 ?? ((prop.loc as any).name_2 = {})),
Expand Down Expand Up @@ -1294,7 +1295,7 @@ export function generate(
let camelized = false;

if (
hyphenate(prop.name) === prop.name
hyphenateAttr(prop.name) === prop.name
&& !vueCompilerOptions.htmlAttributes.some(pattern => minimatch(attrNameText!, pattern))
) {
attrNameText = camelize(prop.name);
Expand All @@ -1317,7 +1318,7 @@ export function generate(
...caps_attr,
rename: {
normalize: camelize,
apply: camelized ? hyphenate : noEditApply,
apply: camelized ? hyphenateAttr : noEditApply,
},
},
], (prop.loc as any).name_1 ?? ((prop.loc as any).name_1 = {}))
Expand Down Expand Up @@ -1479,7 +1480,7 @@ export function generate(
},
rename: {
normalize: camelize,
apply: getRenameApply(prop.name),
apply: getPropRenameApply(prop.name),
},
},
],
Expand Down Expand Up @@ -1642,7 +1643,7 @@ export function generate(
...capabilitiesPresets.slotProp,
rename: {
normalize: camelize,
apply: getRenameApply(prop.arg.content),
apply: getPropRenameApply(prop.arg.content),
},
},
], prop.arg.loc),
Expand Down Expand Up @@ -1671,7 +1672,7 @@ export function generate(
...capabilitiesPresets.attr,
rename: {
normalize: camelize,
apply: getRenameApply(prop.name),
apply: getPropRenameApply(prop.name),
},
},
], prop.loc),
Expand Down Expand Up @@ -1952,8 +1953,12 @@ function camelizeComponentName(newName: string) {
return camelize('-' + newName);
}

function getRenameApply(oldName: string) {
return oldName === hyphenate(oldName) ? hyphenate : noEditApply;
function getTagRenameApply(oldName: string) {
return oldName === hyphenateTag(oldName) ? hyphenateTag : noEditApply;
}

function getPropRenameApply(oldName: string) {
return oldName === hyphenateAttr(oldName) ? hyphenateAttr : noEditApply;
}

function noEditApply(n: string) {
Expand All @@ -1965,7 +1970,7 @@ function getModelValuePropName(node: CompilerDOM.ElementNode, vueVersion: number
for (const modelName in vueCompilerOptions.experimentalModelPropName) {
const tags = vueCompilerOptions.experimentalModelPropName[modelName];
for (const tag in tags) {
if (node.tag === tag || node.tag === hyphenate(tag)) {
if (node.tag === tag || node.tag === hyphenateTag(tag)) {
const v = tags[tag];
if (typeof v === 'object') {
const arr = Array.isArray(v) ? v : [v];
Expand All @@ -1991,7 +1996,7 @@ function getModelValuePropName(node: CompilerDOM.ElementNode, vueVersion: number
for (const modelName in vueCompilerOptions.experimentalModelPropName) {
const tags = vueCompilerOptions.experimentalModelPropName[modelName];
for (const tag in tags) {
if (node.tag === tag || node.tag === hyphenate(tag)) {
if (node.tag === tag || node.tag === hyphenateTag(tag)) {
const attrs = tags[tag];
if (attrs === true) {
return modelName || undefined;
Expand Down
1 change: 1 addition & 0 deletions packages/vue-language-core/src/index.ts
Expand Up @@ -9,6 +9,7 @@ export * from './utils/parseSfc';

export * as scriptRanges from './parsers/scriptRanges';
export * as sharedTypes from './utils/globalTypes';
export * from './utils/shared';
export { tsCodegen } from './plugins/vue-tsx';

export * from '@volar/language-core';
Expand Down
2 changes: 1 addition & 1 deletion packages/vue-language-core/src/languageModule.ts
Expand Up @@ -115,7 +115,7 @@ export function createVueLanguage(
}

/**
* @deprecated planed to remove in 2.0, please use getOrCreateVueLanguage instead of
* @deprecated planed to remove in 2.0, please use createVueLanguage instead of
*/
export function createLanguages(
compilerOptions: ts.CompilerOptions = {},
Expand Down
13 changes: 13 additions & 0 deletions packages/vue-language-core/src/utils/shared.ts
@@ -1,3 +1,16 @@
import { hyphenate } from '@vue/shared';

export function getSlotsPropertyName(vueVersion: number) {
return vueVersion < 3 ? '$scopedSlots' : '$slots';
}

export { hyphenate as hyphenateTag } from '@vue/shared';

export function hyphenateAttr(str: string) {
let hyphencase = hyphenate(str);
// fix https://github.com/vuejs/core/issues/8811
if (str.length && str[0] !== str[0].toLowerCase()) {
hyphencase = '-' + hyphencase;
}
return hyphencase;
}
43 changes: 21 additions & 22 deletions packages/vue-language-service/src/ideFeatures/nameCasing.ts
@@ -1,21 +1,20 @@
import { hyphenate } from '@vue/shared';
import { ServiceContext, VirtualFile } from '@volar/language-service';
import { getComponentNames, getTemplateTagsAndAttrs, getPropsByTag } from '../helpers';
import * as vue from '@vue/language-core';
import { VueCompilerOptions, VueFile, hyphenateAttr, hyphenateTag } from '@vue/language-core';
import type { Provide } from 'volar-service-typescript';
import type * as vscode from 'vscode-languageserver-protocol';
import { getComponentNames, getPropsByTag, getTemplateTagsAndAttrs } from '../helpers';
import { AttrNameCasing, TagNameCasing } from '../types';
import type { Provide } from 'volar-service-typescript';

export async function convertTagName(
ts: typeof import('typescript/lib/tsserverlibrary'),
context: ServiceContext<Provide>,
uri: string,
casing: TagNameCasing,
vueCompilerOptions: vue.VueCompilerOptions,
vueCompilerOptions: VueCompilerOptions,
) {

const rootFile = context.documents.getSourceByUri(uri)?.root;
if (!(rootFile instanceof vue.VueFile))
if (!(rootFile instanceof VueFile))
return;

const desc = rootFile.sfc;
Expand All @@ -30,14 +29,14 @@ export async function convertTagName(
const tags = getTemplateTagsAndAttrs(rootFile);

for (const [tagName, { offsets }] of tags) {
const componentName = components.find(component => component === tagName || hyphenate(component) === tagName);
const componentName = components.find(component => component === tagName || hyphenateTag(component) === tagName);
if (componentName) {
for (const offset of offsets) {
const start = document.positionAt(template.startTagEnd + offset);
const end = document.positionAt(template.startTagEnd + offset + tagName.length);
const range: vscode.Range = { start, end };
if (casing === TagNameCasing.Kebab && tagName !== hyphenate(componentName)) {
edits.push({ range, newText: hyphenate(componentName) });
if (casing === TagNameCasing.Kebab && tagName !== hyphenateTag(componentName)) {
edits.push({ range, newText: hyphenateTag(componentName) });
}
if (casing === TagNameCasing.Pascal && tagName !== componentName) {
edits.push({ range, newText: componentName });
Expand All @@ -54,11 +53,11 @@ export async function convertAttrName(
context: ServiceContext,
uri: string,
casing: AttrNameCasing,
vueCompilerOptions: vue.VueCompilerOptions,
vueCompilerOptions: VueCompilerOptions,
) {

const rootFile = context.documents.getSourceByUri(uri)?.root;
if (!(rootFile instanceof vue.VueFile))
if (!(rootFile instanceof VueFile))
return;

const desc = rootFile.sfc;
Expand All @@ -73,18 +72,18 @@ export async function convertAttrName(
const tags = getTemplateTagsAndAttrs(rootFile);

for (const [tagName, { attrs }] of tags) {
const componentName = components.find(component => component === tagName || hyphenate(component) === tagName);
const componentName = components.find(component => component === tagName || hyphenateTag(component) === tagName);
if (componentName) {
const props = getPropsByTag(ts, languageService, rootFile, componentName, vueCompilerOptions);
for (const [attrName, { offsets }] of attrs) {
const propName = props.find(prop => prop === attrName || hyphenate(prop) === attrName);
const propName = props.find(prop => prop === attrName || hyphenateAttr(prop) === attrName);
if (propName) {
for (const offset of offsets) {
const start = document.positionAt(template.startTagEnd + offset);
const end = document.positionAt(template.startTagEnd + offset + attrName.length);
const range: vscode.Range = { start, end };
if (casing === AttrNameCasing.Kebab && attrName !== hyphenate(propName)) {
edits.push({ range, newText: hyphenate(propName) });
if (casing === AttrNameCasing.Kebab && attrName !== hyphenateAttr(propName)) {
edits.push({ range, newText: hyphenateAttr(propName) });
}
if (casing === AttrNameCasing.Camel && attrName !== propName) {
edits.push({ range, newText: propName });
Expand All @@ -102,7 +101,7 @@ export async function getNameCasing(
ts: typeof import('typescript/lib/tsserverlibrary'),
context: ServiceContext,
uri: string,
vueCompilerOptions: vue.VueCompilerOptions,
vueCompilerOptions: VueCompilerOptions,
) {

const detected = detect(ts, context, uri, vueCompilerOptions);
Expand All @@ -123,14 +122,14 @@ export function detect(
ts: typeof import('typescript/lib/tsserverlibrary'),
context: ServiceContext,
uri: string,
vueCompilerOptions: vue.VueCompilerOptions,
vueCompilerOptions: VueCompilerOptions,
): {
tag: TagNameCasing[],
attr: AttrNameCasing[],
} {

const rootFile = context.documents.getSourceByUri(uri)?.root;
if (!(rootFile instanceof vue.VueFile)) {
if (!(rootFile instanceof VueFile)) {
return {
tag: [],
attr: [],
Expand All @@ -152,7 +151,7 @@ export function detect(
for (const [_, { attrs }] of tags) {
for (const [tagName] of attrs) {
// attrName
if (tagName !== hyphenate(tagName)) {
if (tagName !== hyphenateTag(tagName)) {
result.push(AttrNameCasing.Camel);
break;
}
Expand All @@ -177,7 +176,7 @@ export function detect(
let anyComponentUsed = false;

for (const component of components) {
if (tagNames.has(component) || tagNames.has(hyphenate(component))) {
if (tagNames.has(component) || tagNames.has(hyphenateTag(component))) {
anyComponentUsed = true;
break;
}
Expand All @@ -188,15 +187,15 @@ export function detect(

for (const [tagName] of tagNames) {
// TagName
if (tagName !== hyphenate(tagName)) {
if (tagName !== hyphenateTag(tagName)) {
result.push(TagNameCasing.Pascal);
break;
}
}
for (const component of components) {
// Tagname -> tagname
// TagName -> tag-name
if (component !== hyphenate(component) && tagNames.has(hyphenate(component))) {
if (component !== hyphenateTag(component) && tagNames.has(hyphenateTag(component))) {
result.push(TagNameCasing.Kebab);
break;
}
Expand Down

0 comments on commit 2f3282e

Please sign in to comment.