Skip to content

Commit

Permalink
Add vue/no-implicit-coercion rule (#2639)
Browse files Browse the repository at this point in the history
Co-authored-by: Yosuke Ota <otameshiyo23@gmail.com>
  • Loading branch information
lozinsky and ota-meshi authored Jan 8, 2025
1 parent f62fde6 commit 99bbe07
Show file tree
Hide file tree
Showing 4 changed files with 307 additions and 0 deletions.
31 changes: 31 additions & 0 deletions docs/rules/no-implicit-coercion.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
---
pageClass: rule-details
sidebarDepth: 0
title: vue/no-implicit-coercion
description: Disallow shorthand type conversions in `<template>`
---

# vue/no-implicit-coercion

> Disallow shorthand type conversions in `<template>`
- :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.

This rule is the same rule as core [no-implicit-coercion] rule but it applies to the expressions in `<template>`.

## :books: Further Reading

- [no-implicit-coercion]

[no-implicit-coercion]: https://eslint.org/docs/rules/no-implicit-coercion

## :rocket: Version

This rule was introduced in eslint-plugin-vue v9.33.0

## :mag: Implementation

- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-implicit-coercion.js)
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-implicit-coercion.js)

<sup>Taken with ❤️ [from ESLint core](https://eslint.org/docs/latest/rules/no-implicit-coercion)</sup>
1 change: 1 addition & 0 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ const plugin = {
'no-export-in-script-setup': require('./rules/no-export-in-script-setup'),
'no-expose-after-await': require('./rules/no-expose-after-await'),
'no-extra-parens': require('./rules/no-extra-parens'),
'no-implicit-coercion': require('./rules/no-implicit-coercion'),
'no-invalid-model-keys': require('./rules/no-invalid-model-keys'),
'no-irregular-whitespace': require('./rules/no-irregular-whitespace'),
'no-lifecycle-after-await': require('./rules/no-lifecycle-after-await'),
Expand Down
12 changes: 12 additions & 0 deletions lib/rules/no-implicit-coercion.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/**
* @author lozinsky <https://github.com/lozinsky>
* See LICENSE file in root directory for full license.
*/
'use strict'

const utils = require('../utils')

// eslint-disable-next-line internal/no-invalid-meta
module.exports = utils.wrapCoreRule('no-implicit-coercion', {
applyDocument: true
})
263 changes: 263 additions & 0 deletions tests/lib/rules/no-implicit-coercion.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,263 @@
/**
* @author lozinsky <https://github.com/lozinsky>
* See LICENSE file in root directory for full license.
*/
'use strict'

const { RuleTester, ESLint } = require('../../eslint-compat')
const semver = require('semver')
const rule = require('../../../lib/rules/no-implicit-coercion')

/**
* @param {string} suggestedCode
* @returns {string}
*/
function getExpectedErrorMessage(suggestedCode) {
return semver.gte(ESLint.version, '9.0.0')
? `Unexpected implicit coercion encountered. Use \`${suggestedCode}\` instead.`
: `use \`${suggestedCode}\` instead.`
}

const tester = new RuleTester({
languageOptions: {
parser: require('vue-eslint-parser'),
ecmaVersion: 2020,
sourceType: 'module'
}
})

tester.run('no-implicit-coercion', rule, {
valid: [
`<template><div :data-foo="Boolean(foo)" /></template>`,
`<template><div :data-foo="foo.indexOf('.') !== -1" /></template>`,
{
filename: 'test.vue',
code: `<template><div :data-foo="!!foo" /></template>`,
options: [
{
boolean: false
}
]
},
{
filename: 'test.vue',
code: `<template><div :data-foo="~foo.indexOf('.')" /></template>`,
options: [
{
boolean: false
}
]
},
`<template><div :data-foo="Number(foo)" /></template>`,
...(semver.gte(ESLint.version, '8.28.0')
? [`<template><div :data-foo="foo * 1/4" /></template>`]
: []),
{
filename: 'test.vue',
code: `<template><div :data-foo="+foo" /></template>`,
options: [
{
number: false
}
]
},
{
filename: 'test.vue',
code: `<template><div :data-foo="1 * foo" /></template>`,
options: [
{
number: false
}
]
},
`<template><div :data-foo="String(foo)" /></template>`,
`<template><div :data-foo="\`\${foo}\`" /></template>`,
{
filename: 'test.vue',
code: `<template><div :data-foo="'' + foo" /></template>`,
options: [
{
string: false
}
]
},
{
filename: 'test.vue',
code: `<template><div :data-foo="\`\` + foo" /></template>`,
options: [
{
string: false
}
]
},
{
filename: 'test.vue',
code: `<template><div :data-foo="!!foo" /></template>`,
options: [
{
allow: ['!!']
}
]
},
{
filename: 'test.vue',
code: `<template><div :data-foo="~foo.indexOf('.')" /></template>`,
options: [
{
allow: ['~']
}
]
}
],
invalid: [
{
filename: 'test.vue',
code: `<template><div :data-foo="!!foo" /></template>`,
output: `<template><div :data-foo="Boolean(foo)" /></template>`,
errors: [
{
message: getExpectedErrorMessage('Boolean(foo)'),
line: 1,
column: 27
}
]
},
{
filename: 'test.vue',
code: `<template><div :data-foo="~foo.indexOf('.')" /></template>`,
output: null,
errors: [
{
message: getExpectedErrorMessage("foo.indexOf('.') !== -1"),
line: 1,
column: 27
}
]
},
{
filename: 'test.vue',
code: `<template><div :data-foo="+foo" /></template>`,
output: semver.gte(ESLint.version, '9.0.0')
? null
: `<template><div :data-foo="Number(foo)" /></template>`,
errors: [
{
message: getExpectedErrorMessage('Number(foo)'),
line: 1,
column: 27,
suggestions: semver.gte(ESLint.version, '9.0.0')
? [
{
messageId: 'useRecommendation',
data: { recommendation: 'Number(foo)' },
output: '<template><div :data-foo="Number(foo)" /></template>'
}
]
: []
}
]
},
{
filename: 'test.vue',
code: `<template><div :data-foo="1 * foo" /></template>`,
output: semver.gte(ESLint.version, '9.0.0')
? null
: `<template><div :data-foo="Number(foo)" /></template>`,
errors: [
{
message: getExpectedErrorMessage('Number(foo)'),
line: 1,
column: 27,
suggestions: semver.gte(ESLint.version, '9.0.0')
? [
{
messageId: 'useRecommendation',
data: { recommendation: 'Number(foo)' },
output: '<template><div :data-foo="Number(foo)" /></template>'
}
]
: []
}
]
},
{
filename: 'test.vue',
code: `<template><div :data-foo="'' + foo" /></template>`,
output: semver.gte(ESLint.version, '9.0.0')
? null
: `<template><div :data-foo="String(foo)" /></template>`,
errors: [
{
message: getExpectedErrorMessage('String(foo)'),
line: 1,
column: 27,
suggestions: semver.gte(ESLint.version, '9.0.0')
? [
{
messageId: 'useRecommendation',
data: { recommendation: 'String(foo)' },
output: '<template><div :data-foo="String(foo)" /></template>'
}
]
: []
}
]
},
{
filename: 'test.vue',
code: `<template><div :data-foo="\`\` + foo" /></template>`,
output: semver.gte(ESLint.version, '9.0.0')
? null
: `<template><div :data-foo="String(foo)" /></template>`,
errors: [
{
message: getExpectedErrorMessage('String(foo)'),
line: 1,
column: 27,
suggestions: semver.gte(ESLint.version, '9.0.0')
? [
{
messageId: 'useRecommendation',
data: { recommendation: 'String(foo)' },
output: '<template><div :data-foo="String(foo)" /></template>'
}
]
: []
}
]
},
...(semver.gte(ESLint.version, '7.24.0')
? [
{
filename: 'test.vue',
code: `<template><div :data-foo="\`\${foo}\`" /></template>`,
output: semver.gte(ESLint.version, '9.0.0')
? null
: `<template><div :data-foo="String(foo)" /></template>`,
options: [
{
disallowTemplateShorthand: true
}
],
errors: [
{
message: getExpectedErrorMessage('String(foo)'),
line: 1,
column: 27,
suggestions: semver.gte(ESLint.version, '9.0.0')
? [
{
messageId: 'useRecommendation',
data: { recommendation: 'String(foo)' },
output:
'<template><div :data-foo="String(foo)" /></template>'
}
]
: []
}
]
}
]
: [])
]
})

0 comments on commit 99bbe07

Please sign in to comment.