Skip to content

Commit 99bbe07

Browse files
lozinskyota-meshi
andauthored
Add vue/no-implicit-coercion rule (#2639)
Co-authored-by: Yosuke Ota <[email protected]>
1 parent f62fde6 commit 99bbe07

File tree

4 files changed

+307
-0
lines changed

4 files changed

+307
-0
lines changed

Diff for: docs/rules/no-implicit-coercion.md

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
---
2+
pageClass: rule-details
3+
sidebarDepth: 0
4+
title: vue/no-implicit-coercion
5+
description: Disallow shorthand type conversions in `<template>`
6+
---
7+
8+
# vue/no-implicit-coercion
9+
10+
> Disallow shorthand type conversions in `<template>`
11+
12+
- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by this rule.
13+
14+
This rule is the same rule as core [no-implicit-coercion] rule but it applies to the expressions in `<template>`.
15+
16+
## :books: Further Reading
17+
18+
- [no-implicit-coercion]
19+
20+
[no-implicit-coercion]: https://eslint.org/docs/rules/no-implicit-coercion
21+
22+
## :rocket: Version
23+
24+
This rule was introduced in eslint-plugin-vue v9.33.0
25+
26+
## :mag: Implementation
27+
28+
- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-implicit-coercion.js)
29+
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-implicit-coercion.js)
30+
31+
<sup>Taken with ❤️ [from ESLint core](https://eslint.org/docs/latest/rules/no-implicit-coercion)</sup>

Diff for: lib/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ const plugin = {
126126
'no-export-in-script-setup': require('./rules/no-export-in-script-setup'),
127127
'no-expose-after-await': require('./rules/no-expose-after-await'),
128128
'no-extra-parens': require('./rules/no-extra-parens'),
129+
'no-implicit-coercion': require('./rules/no-implicit-coercion'),
129130
'no-invalid-model-keys': require('./rules/no-invalid-model-keys'),
130131
'no-irregular-whitespace': require('./rules/no-irregular-whitespace'),
131132
'no-lifecycle-after-await': require('./rules/no-lifecycle-after-await'),

Diff for: lib/rules/no-implicit-coercion.js

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/**
2+
* @author lozinsky <https://github.com/lozinsky>
3+
* See LICENSE file in root directory for full license.
4+
*/
5+
'use strict'
6+
7+
const utils = require('../utils')
8+
9+
// eslint-disable-next-line internal/no-invalid-meta
10+
module.exports = utils.wrapCoreRule('no-implicit-coercion', {
11+
applyDocument: true
12+
})

Diff for: tests/lib/rules/no-implicit-coercion.js

+263
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
/**
2+
* @author lozinsky <https://github.com/lozinsky>
3+
* See LICENSE file in root directory for full license.
4+
*/
5+
'use strict'
6+
7+
const { RuleTester, ESLint } = require('../../eslint-compat')
8+
const semver = require('semver')
9+
const rule = require('../../../lib/rules/no-implicit-coercion')
10+
11+
/**
12+
* @param {string} suggestedCode
13+
* @returns {string}
14+
*/
15+
function getExpectedErrorMessage(suggestedCode) {
16+
return semver.gte(ESLint.version, '9.0.0')
17+
? `Unexpected implicit coercion encountered. Use \`${suggestedCode}\` instead.`
18+
: `use \`${suggestedCode}\` instead.`
19+
}
20+
21+
const tester = new RuleTester({
22+
languageOptions: {
23+
parser: require('vue-eslint-parser'),
24+
ecmaVersion: 2020,
25+
sourceType: 'module'
26+
}
27+
})
28+
29+
tester.run('no-implicit-coercion', rule, {
30+
valid: [
31+
`<template><div :data-foo="Boolean(foo)" /></template>`,
32+
`<template><div :data-foo="foo.indexOf('.') !== -1" /></template>`,
33+
{
34+
filename: 'test.vue',
35+
code: `<template><div :data-foo="!!foo" /></template>`,
36+
options: [
37+
{
38+
boolean: false
39+
}
40+
]
41+
},
42+
{
43+
filename: 'test.vue',
44+
code: `<template><div :data-foo="~foo.indexOf('.')" /></template>`,
45+
options: [
46+
{
47+
boolean: false
48+
}
49+
]
50+
},
51+
`<template><div :data-foo="Number(foo)" /></template>`,
52+
...(semver.gte(ESLint.version, '8.28.0')
53+
? [`<template><div :data-foo="foo * 1/4" /></template>`]
54+
: []),
55+
{
56+
filename: 'test.vue',
57+
code: `<template><div :data-foo="+foo" /></template>`,
58+
options: [
59+
{
60+
number: false
61+
}
62+
]
63+
},
64+
{
65+
filename: 'test.vue',
66+
code: `<template><div :data-foo="1 * foo" /></template>`,
67+
options: [
68+
{
69+
number: false
70+
}
71+
]
72+
},
73+
`<template><div :data-foo="String(foo)" /></template>`,
74+
`<template><div :data-foo="\`\${foo}\`" /></template>`,
75+
{
76+
filename: 'test.vue',
77+
code: `<template><div :data-foo="'' + foo" /></template>`,
78+
options: [
79+
{
80+
string: false
81+
}
82+
]
83+
},
84+
{
85+
filename: 'test.vue',
86+
code: `<template><div :data-foo="\`\` + foo" /></template>`,
87+
options: [
88+
{
89+
string: false
90+
}
91+
]
92+
},
93+
{
94+
filename: 'test.vue',
95+
code: `<template><div :data-foo="!!foo" /></template>`,
96+
options: [
97+
{
98+
allow: ['!!']
99+
}
100+
]
101+
},
102+
{
103+
filename: 'test.vue',
104+
code: `<template><div :data-foo="~foo.indexOf('.')" /></template>`,
105+
options: [
106+
{
107+
allow: ['~']
108+
}
109+
]
110+
}
111+
],
112+
invalid: [
113+
{
114+
filename: 'test.vue',
115+
code: `<template><div :data-foo="!!foo" /></template>`,
116+
output: `<template><div :data-foo="Boolean(foo)" /></template>`,
117+
errors: [
118+
{
119+
message: getExpectedErrorMessage('Boolean(foo)'),
120+
line: 1,
121+
column: 27
122+
}
123+
]
124+
},
125+
{
126+
filename: 'test.vue',
127+
code: `<template><div :data-foo="~foo.indexOf('.')" /></template>`,
128+
output: null,
129+
errors: [
130+
{
131+
message: getExpectedErrorMessage("foo.indexOf('.') !== -1"),
132+
line: 1,
133+
column: 27
134+
}
135+
]
136+
},
137+
{
138+
filename: 'test.vue',
139+
code: `<template><div :data-foo="+foo" /></template>`,
140+
output: semver.gte(ESLint.version, '9.0.0')
141+
? null
142+
: `<template><div :data-foo="Number(foo)" /></template>`,
143+
errors: [
144+
{
145+
message: getExpectedErrorMessage('Number(foo)'),
146+
line: 1,
147+
column: 27,
148+
suggestions: semver.gte(ESLint.version, '9.0.0')
149+
? [
150+
{
151+
messageId: 'useRecommendation',
152+
data: { recommendation: 'Number(foo)' },
153+
output: '<template><div :data-foo="Number(foo)" /></template>'
154+
}
155+
]
156+
: []
157+
}
158+
]
159+
},
160+
{
161+
filename: 'test.vue',
162+
code: `<template><div :data-foo="1 * foo" /></template>`,
163+
output: semver.gte(ESLint.version, '9.0.0')
164+
? null
165+
: `<template><div :data-foo="Number(foo)" /></template>`,
166+
errors: [
167+
{
168+
message: getExpectedErrorMessage('Number(foo)'),
169+
line: 1,
170+
column: 27,
171+
suggestions: semver.gte(ESLint.version, '9.0.0')
172+
? [
173+
{
174+
messageId: 'useRecommendation',
175+
data: { recommendation: 'Number(foo)' },
176+
output: '<template><div :data-foo="Number(foo)" /></template>'
177+
}
178+
]
179+
: []
180+
}
181+
]
182+
},
183+
{
184+
filename: 'test.vue',
185+
code: `<template><div :data-foo="'' + foo" /></template>`,
186+
output: semver.gte(ESLint.version, '9.0.0')
187+
? null
188+
: `<template><div :data-foo="String(foo)" /></template>`,
189+
errors: [
190+
{
191+
message: getExpectedErrorMessage('String(foo)'),
192+
line: 1,
193+
column: 27,
194+
suggestions: semver.gte(ESLint.version, '9.0.0')
195+
? [
196+
{
197+
messageId: 'useRecommendation',
198+
data: { recommendation: 'String(foo)' },
199+
output: '<template><div :data-foo="String(foo)" /></template>'
200+
}
201+
]
202+
: []
203+
}
204+
]
205+
},
206+
{
207+
filename: 'test.vue',
208+
code: `<template><div :data-foo="\`\` + foo" /></template>`,
209+
output: semver.gte(ESLint.version, '9.0.0')
210+
? null
211+
: `<template><div :data-foo="String(foo)" /></template>`,
212+
errors: [
213+
{
214+
message: getExpectedErrorMessage('String(foo)'),
215+
line: 1,
216+
column: 27,
217+
suggestions: semver.gte(ESLint.version, '9.0.0')
218+
? [
219+
{
220+
messageId: 'useRecommendation',
221+
data: { recommendation: 'String(foo)' },
222+
output: '<template><div :data-foo="String(foo)" /></template>'
223+
}
224+
]
225+
: []
226+
}
227+
]
228+
},
229+
...(semver.gte(ESLint.version, '7.24.0')
230+
? [
231+
{
232+
filename: 'test.vue',
233+
code: `<template><div :data-foo="\`\${foo}\`" /></template>`,
234+
output: semver.gte(ESLint.version, '9.0.0')
235+
? null
236+
: `<template><div :data-foo="String(foo)" /></template>`,
237+
options: [
238+
{
239+
disallowTemplateShorthand: true
240+
}
241+
],
242+
errors: [
243+
{
244+
message: getExpectedErrorMessage('String(foo)'),
245+
line: 1,
246+
column: 27,
247+
suggestions: semver.gte(ESLint.version, '9.0.0')
248+
? [
249+
{
250+
messageId: 'useRecommendation',
251+
data: { recommendation: 'String(foo)' },
252+
output:
253+
'<template><div :data-foo="String(foo)" /></template>'
254+
}
255+
]
256+
: []
257+
}
258+
]
259+
}
260+
]
261+
: [])
262+
]
263+
})

0 commit comments

Comments
 (0)