6
6
'use strict'
7
7
8
8
const utils = require ( '../utils' )
9
+ const casing = require ( '../utils/casing' )
10
+
11
+ /**
12
+ * @typedef { VDirectiveKey & { name: VIdentifier & { name: 'bind' }, argument: VExpressionContainer | VIdentifier } } VBindDirectiveKey
13
+ * @typedef { VDirective & { key: VBindDirectiveKey } } VBindDirective
14
+ */
15
+
16
+ /**
17
+ * @param {VBindDirective } node
18
+ * @returns {boolean }
19
+ */
20
+ function isSameName ( node ) {
21
+ const attrName =
22
+ node . key . argument . type === 'VIdentifier' ? node . key . argument . rawName : null
23
+ const valueName =
24
+ node . value ?. expression ?. type === 'Identifier'
25
+ ? node . value . expression . name
26
+ : null
27
+ return Boolean (
28
+ attrName &&
29
+ valueName &&
30
+ casing . camelCase ( attrName ) === casing . camelCase ( valueName )
31
+ )
32
+ }
33
+
34
+ /**
35
+ * @param {VBindDirectiveKey } key
36
+ * @returns {number }
37
+ */
38
+ function getCutStart ( key ) {
39
+ const modifiers = key . modifiers
40
+ return modifiers . length > 0
41
+ ? modifiers [ modifiers . length - 1 ] . range [ 1 ]
42
+ : key . argument . range [ 1 ]
43
+ }
9
44
10
45
module . exports = {
11
46
meta : {
@@ -16,60 +51,113 @@ module.exports = {
16
51
url : 'https://eslint.vuejs.org/rules/v-bind-style.html'
17
52
} ,
18
53
fixable : 'code' ,
19
- schema : [ { enum : [ 'shorthand' , 'longform' ] } ] ,
54
+ schema : [
55
+ { enum : [ 'shorthand' , 'longform' ] } ,
56
+ {
57
+ type : 'object' ,
58
+ properties : {
59
+ sameNameShorthand : { enum : [ 'always' , 'never' , 'ignore' ] }
60
+ } ,
61
+ additionalProperties : false
62
+ }
63
+ ] ,
20
64
messages : {
21
65
expectedLonghand : "Expected 'v-bind' before ':'." ,
22
66
unexpectedLonghand : "Unexpected 'v-bind' before ':'." ,
23
- expectedLonghandForProp : "Expected 'v-bind:' instead of '.'."
67
+ expectedLonghandForProp : "Expected 'v-bind:' instead of '.'." ,
68
+ expectedShorthand : 'Expected same-name shorthand.' ,
69
+ unexpectedShorthand : 'Unexpected same-name shorthand.'
24
70
}
25
71
} ,
26
72
/** @param {RuleContext } context */
27
73
create ( context ) {
28
74
const preferShorthand = context . options [ 0 ] !== 'longform'
75
+ /** @type {"always" | "never" | "ignore" } */
76
+ const sameNameShorthand = context . options [ 1 ] ?. sameNameShorthand || 'ignore'
29
77
30
- return utils . defineTemplateBodyVisitor ( context , {
31
- /** @param {VDirective } node */
32
- "VAttribute[directive=true][key.name.name='bind'][key.argument!=null]" (
33
- node
34
- ) {
35
- const shorthandProp = node . key . name . rawName === '.'
36
- const shorthand = node . key . name . rawName === ':' || shorthandProp
37
- if ( shorthand === preferShorthand ) {
38
- return
39
- }
78
+ /** @param {VBindDirective } node */
79
+ function checkAttributeStyle ( node ) {
80
+ const shorthandProp = node . key . name . rawName === '.'
81
+ const shorthand = node . key . name . rawName === ':' || shorthandProp
82
+ if ( shorthand === preferShorthand ) {
83
+ return
84
+ }
40
85
41
- let messageId = 'expectedLonghand'
42
- if ( preferShorthand ) {
43
- messageId = 'unexpectedLonghand'
44
- } else if ( shorthandProp ) {
45
- messageId = 'expectedLonghandForProp'
46
- }
86
+ let messageId = 'expectedLonghand'
87
+ if ( preferShorthand ) {
88
+ messageId = 'unexpectedLonghand'
89
+ } else if ( shorthandProp ) {
90
+ messageId = 'expectedLonghandForProp'
91
+ }
92
+
93
+ context . report ( {
94
+ node,
95
+ loc : node . loc ,
96
+ messageId,
97
+ * fix ( fixer ) {
98
+ if ( preferShorthand ) {
99
+ yield fixer . remove ( node . key . name )
100
+ } else {
101
+ yield fixer . insertTextBefore ( node , 'v-bind' )
102
+
103
+ if ( shorthandProp ) {
104
+ // Replace `.` by `:`.
105
+ yield fixer . replaceText ( node . key . name , ':' )
47
106
48
- context . report ( {
49
- node,
50
- loc : node . loc ,
51
- messageId,
52
- * fix ( fixer ) {
53
- if ( preferShorthand ) {
54
- yield fixer . remove ( node . key . name )
55
- } else {
56
- yield fixer . insertTextBefore ( node , 'v-bind' )
57
-
58
- if ( shorthandProp ) {
59
- // Replace `.` by `:`.
60
- yield fixer . replaceText ( node . key . name , ':' )
61
-
62
- // Insert `.prop` modifier if it doesn't exist.
63
- const modifier = node . key . modifiers [ 0 ]
64
- const isAutoGeneratedPropModifier =
65
- modifier . name === 'prop' && modifier . rawName === ''
66
- if ( isAutoGeneratedPropModifier ) {
67
- yield fixer . insertTextBefore ( modifier , '.prop' )
68
- }
107
+ // Insert `.prop` modifier if it doesn't exist.
108
+ const modifier = node . key . modifiers [ 0 ]
109
+ const isAutoGeneratedPropModifier =
110
+ modifier . name === 'prop' && modifier . rawName === ''
111
+ if ( isAutoGeneratedPropModifier ) {
112
+ yield fixer . insertTextBefore ( modifier , '.prop' )
69
113
}
70
114
}
71
115
}
72
- } )
116
+ }
117
+ } )
118
+ }
119
+
120
+ /** @param {VBindDirective } node */
121
+ function checkAttributeSameName ( node ) {
122
+ if ( sameNameShorthand === 'ignore' || ! isSameName ( node ) ) return
123
+
124
+ const preferShorthand = sameNameShorthand === 'always'
125
+ const isShorthand = utils . isVBindSameNameShorthand ( node )
126
+ if ( isShorthand === preferShorthand ) {
127
+ return
128
+ }
129
+
130
+ const messageId = preferShorthand
131
+ ? 'expectedShorthand'
132
+ : 'unexpectedShorthand'
133
+
134
+ context . report ( {
135
+ node,
136
+ loc : node . loc ,
137
+ messageId,
138
+ * fix ( fixer ) {
139
+ if ( preferShorthand ) {
140
+ /** @type {Range } */
141
+ const valueRange = [ getCutStart ( node . key ) , node . range [ 1 ] ]
142
+
143
+ yield fixer . removeRange ( valueRange )
144
+ } else if ( node . key . argument . type === 'VIdentifier' ) {
145
+ yield fixer . insertTextAfter (
146
+ node ,
147
+ `="${ casing . camelCase ( node . key . argument . rawName ) } "`
148
+ )
149
+ }
150
+ }
151
+ } )
152
+ }
153
+
154
+ return utils . defineTemplateBodyVisitor ( context , {
155
+ /** @param {VBindDirective } node */
156
+ "VAttribute[directive=true][key.name.name='bind'][key.argument!=null]" (
157
+ node
158
+ ) {
159
+ checkAttributeSameName ( node )
160
+ checkAttributeStyle ( node )
73
161
}
74
162
} )
75
163
}
0 commit comments