Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: vuejs/eslint-plugin-vue
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v9.22.0
Choose a base ref
...
head repository: vuejs/eslint-plugin-vue
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: f84b8ee5b9e3b54a09209faff744ef2849a6cc3e
Choose a head ref
  • 8 commits
  • 27 files changed
  • 6 contributors

Commits on Feb 27, 2024

  1. fix(v-bind-style): only change kebabCase to camelCase (#2410)

    waynzh authored Feb 27, 2024

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    7c5e1ee View commit details
  2. Remove dead link from docs (#2411)

    travis5491811 authored Feb 27, 2024

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    bdddc98 View commit details

Commits on Feb 28, 2024

  1. Update links to GitHub repos with changed main branch name (#2412)

    Co-authored-by: Flo Edelmann <git@flo-edelmann.de>
    zzfn and FloEdelmann authored Feb 28, 2024

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    02e6bbf View commit details

Commits on Mar 5, 2024

  1. perf: remove unnecessary second filter for type narrowing (#2417)

    waynzh authored Mar 5, 2024

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    8e8e1e8 View commit details

Commits on Mar 9, 2024

  1. fix(v-for-delimiter-style): ignore Punctuator token (#2416)

    waynzh authored Mar 9, 2024

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    7c71c48 View commit details

Commits on Mar 11, 2024

  1. feat: support Vue APIs from auto imports (#2422)

    Co-authored-by: Flo Edelmann <git@flo-edelmann.de>
    antfu and FloEdelmann authored Mar 11, 2024

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    3abc04c View commit details
  2. feat(order-in-components): add side effects suggestions (#2423)

    waynzh authored Mar 11, 2024

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    e2dc84b View commit details
  3. 9.23.0

    ota-meshi committed Mar 11, 2024
    Copy the full SHA
    f84b8ee View commit details
2 changes: 1 addition & 1 deletion docs/rules/index.md
Original file line number Diff line number Diff line change
@@ -181,7 +181,7 @@ Rules in this category are enabled for all presets provided by eslint-plugin-vue
| [vue/no-lone-template](./no-lone-template.md) | disallow unnecessary `<template>` | | :three::two::warning: |
| [vue/no-multiple-slot-args](./no-multiple-slot-args.md) | disallow to pass multiple arguments to scoped slots | | :three::two::warning: |
| [vue/no-v-html](./no-v-html.md) | disallow use of v-html to prevent XSS attack | | :three::two::hammer: |
| [vue/order-in-components](./order-in-components.md) | enforce order of properties in components | :wrench: | :three::two::hammer: |
| [vue/order-in-components](./order-in-components.md) | enforce order of properties in components | :wrench::bulb: | :three::two::hammer: |
| [vue/this-in-template](./this-in-template.md) | disallow usage of `this` in template | :wrench: | :three::two::hammer: |

</rules-table>
4 changes: 0 additions & 4 deletions docs/rules/no-v-html.md
Original file line number Diff line number Diff line change
@@ -38,10 +38,6 @@ Nothing.

If you are certain the content passed to `v-html` is sanitized HTML you can disable this rule.

## :books: Further Reading

- [XSS in Vue.js](https://blog.sqreen.io/xss-in-vue-js/)

## :rocket: Version

This rule was introduced in eslint-plugin-vue v4.7.0
1 change: 1 addition & 0 deletions docs/rules/order-in-components.md
Original file line number Diff line number Diff line change
@@ -12,6 +12,7 @@ since: v3.2.0
- :gear: This rule is included in `"plugin:vue/vue3-recommended"` and `"plugin:vue/recommended"`.
- :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.
- :bulb: Some problems reported by this rule are manually fixable by editor [suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions).

## :book: Rule Details

44 changes: 43 additions & 1 deletion docs/user-guide/index.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
---
outline: deep
---

# User Guide

## :cd: Installation
@@ -318,7 +322,7 @@ If you are using JSX, you need to enable JSX in your ESLint configuration.
See also [ESLint - Specifying Parser Options](https://eslint.org/docs/user-guide/configuring#specifying-parser-options).

The same configuration is required when using JSX with TypeScript (TSX) in the `.vue` file.
See also [here](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/parser/README.md#parseroptionsecmafeaturesjsx).
See also [here](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/README.md#parseroptionsecmafeaturesjsx).
Note that you cannot use angle-bracket type assertion style (`var x = <foo>bar;`) when using `jsx: true`.

### Trouble with Visual Studio Code
@@ -386,3 +390,41 @@ Try searching for existing issues.
If it does not exist, you should open a new issue and share your repository to reproduce the issue.

[vue-eslint-parser]: https://github.com/vuejs/vue-eslint-parser

### Auto Imports Support

In [Nuxt 3](https://nuxt.com/) or with [`unplugin-auto-import`](https://github.com/unplugin/unplugin-auto-import), Vue APIs can be auto imported. To make rules like [`vue/no-ref-as-operand`](/rules/no-ref-as-operand.html) or [`vue/no-watch-after-await`](/rules/no-watch-after-await.html) work correctly with them, you can specify them in ESLint's [`globals`](https://eslint.org/docs/latest/use/configure/configuration-files-new#configuring-global-variables) options:

::: code-group

```json [Legacy Config]
// .eslintrc
{
"globals": {
"ref": "readonly",
"computed": "readonly",
"watch": "readonly",
"watchEffect": "readonly",
// ...more APIs
}
}
```

```js [Flat Config]
// eslint.config.js
export default [
{
languageOptions: {
globals: {
ref: 'readonly',
computed: 'readonly',
watch: 'readonly',
watchEffect: 'readonly',
// ...more APIs
}
}
}
]
```

:::
6 changes: 3 additions & 3 deletions lib/rules/enforce-style-attribute.js
Original file line number Diff line number Diff line change
@@ -92,9 +92,9 @@ module.exports = {
return {}
}

const topLevelElements = documentFragment.children.filter(isVElement)
const topLevelStyleTags = topLevelElements.filter(
(element) => element.rawName === 'style'
const topLevelStyleTags = documentFragment.children.filter(
/** @returns {element is VElement} */
(element) => isVElement(element) && element.rawName === 'style'
)

if (topLevelStyleTags.length === 0) {
7 changes: 2 additions & 5 deletions lib/rules/no-async-in-computed-properties.js
Original file line number Diff line number Diff line change
@@ -263,14 +263,11 @@ module.exports = {
/** @param {Program} program */
Program(program) {
const tracker = new ReferenceTracker(utils.getScope(context, program))
const traceMap = utils.createCompositionApiTraceMap({
[ReferenceTracker.ESM]: true,
for (const { node } of utils.iterateReferencesTraceMap(tracker, {
computed: {
[ReferenceTracker.CALL]: true
}
})

for (const { node } of tracker.iterateEsmReferences(traceMap)) {
})) {
if (node.type !== 'CallExpression') {
continue
}
15 changes: 7 additions & 8 deletions lib/rules/no-lifecycle-after-await.js
Original file line number Diff line number Diff line change
@@ -63,19 +63,18 @@ module.exports = {
/** @param {Program} program */
Program(program) {
const tracker = new ReferenceTracker(utils.getScope(context, program))
const traceMap = {
/** @type {TraceMap} */
vue: {
[ReferenceTracker.ESM]: true
}
}
/** @type {TraceMap} */
const traceMap = {}
for (const lifecycleHook of LIFECYCLE_HOOKS) {
traceMap.vue[lifecycleHook] = {
traceMap[lifecycleHook] = {
[ReferenceTracker.CALL]: true
}
}

for (const { node } of tracker.iterateEsmReferences(traceMap)) {
for (const { node } of utils.iterateReferencesTraceMap(
tracker,
traceMap
)) {
lifecycleHookCallNodes.add(node)
}
}
8 changes: 3 additions & 5 deletions lib/rules/no-side-effects-in-computed-properties.js
Original file line number Diff line number Diff line change
@@ -183,14 +183,12 @@ module.exports = {
/** @param {Program} program */
Program(program) {
const tracker = new ReferenceTracker(utils.getScope(context, program))
const traceMap = utils.createCompositionApiTraceMap({
[ReferenceTracker.ESM]: true,

for (const { node } of utils.iterateReferencesTraceMap(tracker, {
computed: {
[ReferenceTracker.CALL]: true
}
})

for (const { node } of tracker.iterateEsmReferences(traceMap)) {
})) {
if (node.type !== 'CallExpression') {
continue
}
20 changes: 8 additions & 12 deletions lib/rules/no-watch-after-await.js
Original file line number Diff line number Diff line change
@@ -78,19 +78,15 @@ module.exports = {
/** @param {Program} program */
Program(program) {
const tracker = new ReferenceTracker(utils.getScope(context, program))
const traceMap = {
vue: {
[ReferenceTracker.ESM]: true,
watch: {
[ReferenceTracker.CALL]: true
},
watchEffect: {
[ReferenceTracker.CALL]: true
}
}
}

for (const { node } of tracker.iterateEsmReferences(traceMap)) {
for (const { node } of utils.iterateReferencesTraceMap(tracker, {
watch: {
[ReferenceTracker.CALL]: true
},
watchEffect: {
[ReferenceTracker.CALL]: true
}
})) {
watchCallNodes.add(node)
}
}
97 changes: 58 additions & 39 deletions lib/rules/order-in-components.js
Original file line number Diff line number Diff line change
@@ -218,6 +218,7 @@ module.exports = {
url: 'https://eslint.vuejs.org/rules/order-in-components.html'
},
fixable: 'code', // null or "code" or "whitespace"
hasSuggestions: true,
schema: [
{
type: 'object',
@@ -231,7 +232,9 @@ module.exports = {
],
messages: {
order:
'The "{{name}}" property should be above the "{{firstUnorderedPropertyName}}" property on line {{line}}.'
'The "{{name}}" property should be above the "{{firstUnorderedPropertyName}}" property on line {{line}}.',
reorderWithSideEffects:
'Manually move "{{name}}" property above "{{firstUnorderedPropertyName}}" property on line {{line}} (might break side effects).'
}
},
/** @param {RuleContext} context */
@@ -255,6 +258,30 @@ module.exports = {
return num == null ? -1 : num
}

/**
* @param {Property} propertyNode
* @param {Property} unorderedPropertyNode
*/
function* handleFix(fixer, propertyNode, unorderedPropertyNode) {
const afterComma = sourceCode.getTokenAfter(propertyNode)
const hasAfterComma = isComma(afterComma)

const beforeComma = sourceCode.getTokenBefore(propertyNode)
const codeStart = beforeComma.range[1] // to include comments
const codeEnd = hasAfterComma
? afterComma.range[1]
: propertyNode.range[1]

const removeStart = hasAfterComma ? codeStart : beforeComma.range[0]
yield fixer.removeRange([removeStart, codeEnd])

const propertyCode =
sourceCode.text.slice(codeStart, codeEnd) + (hasAfterComma ? '' : ',')
const insertTarget = sourceCode.getTokenBefore(unorderedPropertyNode)

yield fixer.insertTextAfter(insertTarget, propertyCode)
}

/**
* @param {(Property | SpreadElement)[]} propertiesNodes
*/
@@ -287,6 +314,18 @@ module.exports = {

if (firstUnorderedProperty) {
const line = firstUnorderedProperty.node.loc.start.line
const propertyNode = property.node
const firstUnorderedPropertyNode = firstUnorderedProperty.node
const hasSideEffectsPossibility = propertiesNodes
.slice(
propertiesNodes.indexOf(firstUnorderedPropertyNode),
propertiesNodes.indexOf(propertyNode) + 1
)
.some(
(property) =>
!isNotSideEffectsNode(property, sourceCode.visitorKeys)
)

context.report({
node: property.node,
messageId: 'order',
@@ -295,44 +334,24 @@ module.exports = {
firstUnorderedPropertyName: firstUnorderedProperty.name,
line
},
*fix(fixer) {
const propertyNode = property.node
const firstUnorderedPropertyNode = firstUnorderedProperty.node
const hasSideEffectsPossibility = propertiesNodes
.slice(
propertiesNodes.indexOf(firstUnorderedPropertyNode),
propertiesNodes.indexOf(propertyNode) + 1
)
.some(
(property) =>
!isNotSideEffectsNode(property, sourceCode.visitorKeys)
)
if (hasSideEffectsPossibility) {
return
}
const afterComma = sourceCode.getTokenAfter(propertyNode)
const hasAfterComma = isComma(afterComma)

const beforeComma = sourceCode.getTokenBefore(propertyNode)
const codeStart = beforeComma.range[1] // to include comments
const codeEnd = hasAfterComma
? afterComma.range[1]
: propertyNode.range[1]

const removeStart = hasAfterComma
? codeStart
: beforeComma.range[0]
yield fixer.removeRange([removeStart, codeEnd])

const propertyCode =
sourceCode.text.slice(codeStart, codeEnd) +
(hasAfterComma ? '' : ',')
const insertTarget = sourceCode.getTokenBefore(
firstUnorderedPropertyNode
)

yield fixer.insertTextAfter(insertTarget, propertyCode)
}
fix: hasSideEffectsPossibility
? undefined
: (fixer) =>
handleFix(fixer, propertyNode, firstUnorderedPropertyNode),
suggest: hasSideEffectsPossibility
? [
{
messageId: 'reorderWithSideEffects',
data: {
name: property.name,
firstUnorderedPropertyName: firstUnorderedProperty.name,
line
},
fix: (fixer) =>
handleFix(fixer, propertyNode, firstUnorderedPropertyNode)
}
]
: undefined
})
}
}
7 changes: 4 additions & 3 deletions lib/rules/require-explicit-slots.js
Original file line number Diff line number Diff line change
@@ -59,9 +59,10 @@ module.exports = {
if (!documentFragment) {
return {}
}
const scripts = documentFragment.children
.filter(utils.isVElement)
.filter((element) => element.name === 'script')
const scripts = documentFragment.children.filter(
/** @returns {element is VElement} */
(element) => utils.isVElement(element) && element.name === 'script'
)
if (scripts.every((script) => !utils.hasAttribute(script, 'lang', 'ts'))) {
return {}
}
10 changes: 6 additions & 4 deletions lib/rules/return-in-computed-property.js
Original file line number Diff line number Diff line change
@@ -57,14 +57,16 @@ module.exports = {
/** @param {Program} program */
Program(program) {
const tracker = new ReferenceTracker(utils.getScope(context, program))
const traceMap = utils.createCompositionApiTraceMap({
[ReferenceTracker.ESM]: true,
const map = {
computed: {
[ReferenceTracker.CALL]: true
}
})
}

for (const { node } of tracker.iterateEsmReferences(traceMap)) {
for (const { node } of utils.iterateReferencesTraceMap(
tracker,
map
)) {
if (node.type !== 'CallExpression') {
continue
}
19 changes: 13 additions & 6 deletions lib/rules/v-bind-style.js
Original file line number Diff line number Diff line change
@@ -13,6 +13,14 @@ const casing = require('../utils/casing')
* @typedef { VDirective & { key: VBindDirectiveKey } } VBindDirective
*/

/**
* @param {string} name
* @returns {string}
*/
function kebabCaseToCamelCase(name) {
return casing.isKebabCase(name) ? casing.camelCase(name) : name
}

/**
* @param {VBindDirective} node
* @returns {boolean}
@@ -24,11 +32,10 @@ function isSameName(node) {
node.value?.expression?.type === 'Identifier'
? node.value.expression.name
: null
return Boolean(
attrName &&
valueName &&
casing.camelCase(attrName) === casing.camelCase(valueName)
)

if (!attrName || !valueName) return false

return kebabCaseToCamelCase(attrName) === kebabCaseToCamelCase(valueName)
}

/**
@@ -144,7 +151,7 @@ module.exports = {
} else if (node.key.argument.type === 'VIdentifier') {
yield fixer.insertTextAfter(
node,
`="${casing.camelCase(node.key.argument.rawName)}"`
`="${kebabCaseToCamelCase(node.key.argument.rawName)}"`
)
}
}
Loading