Skip to content

Commit 99bbe07

Browse files
lozinskyota-meshi
andauthored
Add vue/no-implicit-coercion rule (#2639)
Co-authored-by: Yosuke Ota <otameshiyo23@gmail.com>
1 parent f62fde6 commit 99bbe07

File tree

4 files changed

+307
-0
lines changed

4 files changed

+307
-0
lines changed

docs/rules/no-implicit-coercion.md

Lines changed: 31 additions & 0 deletions
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>

lib/index.js

Lines changed: 1 addition & 0 deletions
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'),

lib/rules/no-implicit-coercion.js

Lines changed: 12 additions & 0 deletions
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+
})
Lines changed: 263 additions & 0 deletions
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy