Skip to content

Commit 9dc1d99

Browse files
authored
Add support for v-bind same-name shorthand (#215)
* Add support for v-bind same-name shorthand * fix * add test * fix
1 parent aa2de99 commit 9dc1d99

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+10904
-10
lines changed

Diff for: src/script-setup/scope-analyzer.ts

+1-9
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
isScriptElement,
1616
isScriptSetupElement,
1717
} from "../common/ast-utils"
18+
import { camelize } from "../utils/utils"
1819

1920
const BUILTIN_COMPONENTS = new Set([
2021
"template",
@@ -94,15 +95,6 @@ const COMPILER_MACROS_AT_ROOT = new Set([
9495
"defineModel",
9596
])
9697

97-
/**
98-
* `casing.camelCase()` converts the beginning to lowercase,
99-
* but does not convert the case of the beginning character when converting with Vue3.
100-
* @see https://github.com/vuejs/vue-next/blob/48de8a42b7fed7a03f7f1ff5d53d6a704252cafe/packages/shared/src/index.ts#L109
101-
*/
102-
function camelize(str: string) {
103-
return str.replace(/-(\w)/gu, (_, c) => (c ? c.toUpperCase() : ""))
104-
}
105-
10698
function capitalize(str: string) {
10799
return str[0].toUpperCase() + str.slice(1)
108100
}

Diff for: src/template/index.ts

+71
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import type { ParserOptions } from "../common/parser-options"
77
import { isSFCFile } from "../common/parser-options"
88
import type {
99
ESLintExpression,
10+
ESLintExtendedProgram,
11+
ESLintIdentifier,
1012
Reference,
1113
Token,
1214
VAttribute,
@@ -34,6 +36,7 @@ import {
3436
parseVOnExpression,
3537
parseSlotScopeExpression,
3638
parseGenericExpression,
39+
parseScriptFragment,
3740
} from "../script"
3841
import {
3942
createSimpleToken,
@@ -46,6 +49,7 @@ import {
4649
isTSLang,
4750
} from "../common/ast-utils"
4851
import { insertError } from "../common/error-utils"
52+
import { camelize } from "../utils/utils"
4953

5054
const shorthandSign = /^[.:@#]/u
5155
const shorthandNameMap = { ":": "bind", ".": "bind", "@": "on", "#": "slot" }
@@ -626,6 +630,14 @@ export function convertToDirective(
626630
}
627631

628632
if (node.value == null) {
633+
if (directive.key.name.name === "bind") {
634+
// v-bind same-name shorthand (Vue 3.4+)
635+
convertForVBindSameNameShorthandValue(
636+
directive,
637+
parserOptions,
638+
locationCalculator,
639+
)
640+
}
629641
return
630642
}
631643

@@ -677,6 +689,65 @@ export function convertToDirective(
677689
}
678690
}
679691

692+
function convertForVBindSameNameShorthandValue(
693+
directive: VDirective,
694+
parserOptions: ParserOptions,
695+
locationCalculator: LocationCalculatorForHtml,
696+
) {
697+
if (
698+
directive.key.name.name !== "bind" ||
699+
directive.key.argument == null ||
700+
directive.key.argument.type !== "VIdentifier"
701+
) {
702+
return
703+
}
704+
// v-bind same-name shorthand (Vue 3.4+)
705+
const vId = directive.key.argument
706+
const camelName = camelize(vId.name)
707+
let result: ESLintExtendedProgram | null = null
708+
try {
709+
result = parseScriptFragment(
710+
camelName,
711+
locationCalculator.getSubCalculatorAfter(vId.range[0]),
712+
parserOptions,
713+
)
714+
} catch (err) {
715+
debug("[template] Parse error: %s", err)
716+
}
717+
if (
718+
result == null ||
719+
result.ast.body.length !== 1 ||
720+
result.ast.body[0].type !== "ExpressionStatement" ||
721+
result.ast.body[0].expression.type !== "Identifier"
722+
) {
723+
return
724+
}
725+
const id: ESLintIdentifier = result.ast.body[0].expression
726+
id.range[1] = vId.range[1]
727+
id.loc.end = { ...vId.loc.end }
728+
if (id.end != null) {
729+
id.end = vId.end
730+
}
731+
directive.value = {
732+
type: "VExpressionContainer",
733+
range: [...vId.range],
734+
loc: {
735+
start: { ...vId.loc.start },
736+
end: { ...vId.loc.end },
737+
},
738+
parent: directive,
739+
expression: id,
740+
references: [
741+
{
742+
id,
743+
mode: "r",
744+
variable: null,
745+
},
746+
],
747+
}
748+
id.parent = directive.value
749+
}
750+
680751
/**
681752
* Parse the content of the given mustache.
682753
* @param parserOptions The parser options to parse expressions.

Diff for: src/utils/utils.ts

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/**
2+
* @see https://github.com/vuejs/vue-next/blob/48de8a42b7fed7a03f7f1ff5d53d6a704252cafe/packages/shared/src/index.ts#L109
3+
*/
4+
export function camelize(str: string) {
5+
return str.replace(/-(\w)/gu, (_, c) => (c ? c.toUpperCase() : ""))
6+
}

Diff for: test/fixtures/ast/directive-shorthands/ast.json

+61-1
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,67 @@
207207
}
208208
]
209209
},
210-
"value": null
210+
"value": {
211+
"type": "VExpressionContainer",
212+
"range": [
213+
21,
214+
22
215+
],
216+
"loc": {
217+
"start": {
218+
"column": 10,
219+
"line": 2
220+
},
221+
"end": {
222+
"column": 11,
223+
"line": 2
224+
}
225+
},
226+
"expression": {
227+
"type": "Identifier",
228+
"start": 21,
229+
"loc": {
230+
"start": {
231+
"line": 2,
232+
"column": 10
233+
},
234+
"end": {
235+
"column": 11,
236+
"line": 2
237+
}
238+
},
239+
"range": [
240+
21,
241+
22
242+
],
243+
"name": "a"
244+
},
245+
"references": [
246+
{
247+
"id": {
248+
"type": "Identifier",
249+
"start": 21,
250+
"loc": {
251+
"start": {
252+
"line": 2,
253+
"column": 10
254+
},
255+
"end": {
256+
"column": 11,
257+
"line": 2
258+
}
259+
},
260+
"range": [
261+
21,
262+
22
263+
],
264+
"name": "a"
265+
},
266+
"mode": "r",
267+
"variable": null
268+
}
269+
]
270+
}
211271
}
212272
]
213273
},

Diff for: test/fixtures/ast/directive-shorthands/tree.json

+11
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,17 @@
4545
"children": []
4646
}
4747
]
48+
},
49+
{
50+
"type": "VExpressionContainer",
51+
"text": "a",
52+
"children": [
53+
{
54+
"type": "Identifier",
55+
"text": "a",
56+
"children": []
57+
}
58+
]
4859
}
4960
]
5061
}

0 commit comments

Comments
 (0)