Skip to content
5 changes: 5 additions & 0 deletions .changeset/fair-phones-add.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"eslint-plugin-vue": minor
---

Added new `vue/no-literals-in-template` rule
2 changes: 2 additions & 0 deletions docs/rules/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,7 @@ For example:
| [vue/no-duplicate-class-names] | disallow duplication of class names in class attributes | :wrench: | :hammer: |
| [vue/no-empty-component-block] | disallow the `<template>` `<script>` `<style>` block to be empty | :wrench: | :hammer: |
| [vue/no-import-compiler-macros] | disallow importing Vue compiler macros | :wrench: | :warning: |
| [vue/no-literals-in-template] | disallow object, array, and function literals in template | | :hammer: |
| [vue/no-multiple-objects-in-class] | disallow passing multiple objects in an array to class | | :hammer: |
| [vue/no-negated-v-if-condition] | disallow negated conditions in v-if/v-else | :wrench: | :hammer: |
| [vue/no-potential-component-option-typo] | disallow a potential typo in your component property | :bulb: | :hammer: |
Expand Down Expand Up @@ -480,6 +481,7 @@ The following rules extend the rules provided by ESLint itself and apply them to
[vue/no-import-compiler-macros]: ./no-import-compiler-macros.md
[vue/no-irregular-whitespace]: ./no-irregular-whitespace.md
[vue/no-lifecycle-after-await]: ./no-lifecycle-after-await.md
[vue/no-literals-in-template]: ./no-literals-in-template.md
[vue/no-lone-template]: ./no-lone-template.md
[vue/no-loss-of-precision]: ./no-loss-of-precision.md
[vue/no-multi-spaces]: ./no-multi-spaces.md
Expand Down
85 changes: 85 additions & 0 deletions docs/rules/no-literals-in-template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
---
pageClass: rule-details
sidebarDepth: 0
title: vue/no-literals-in-template
description: disallow object, array, and function literals in template
---

# vue/no-literals-in-template

> disallow object, array, and function literals in template

- :exclamation: <badge text="This rule has not been released yet." vertical="middle" type="error"> _**This rule has not been released yet.**_ </badge>

## :book: Rule Details

This rule disallows object, array, and function literals in template `v-bind` directives.
These literals are created as new references on every rerender, which can cause the child component's watchers to trigger unnecessarily even when the object hasn't actually changed.

If the literal references a variable from a `v-for` directive or a scoped slot, it is ignored.

<eslint-code-block :rules="{'vue/no-literals-in-template': ['error']}">

```vue
<template>
<!-- ✓ GOOD -->
<div :arr="myArray" />
<div :obj="myObject" />
<div :callback="myFunction" />
<div arr="[]" />
<!-- class and style bindings are ignored -->
<div :class="{ active: isActive }" />
<div :style="{ color: 'red' }" />

<template v-for="i in arr">
<MyComponent :data="[i]" />
<MyComponent :data="{ index: i }" />
<MyComponent :callback="() => someFunction(someArgs, i)" />
</template>

<Child>
<template #default="{ foo }">
<MyComponent :data="[foo]" />
<MyComponent :data="{ val: foo }" />
<MyComponent :data="() => someFunction(someArgs, foo)" />
</template>
</Child>

<!-- ✗ BAD -->
<div :arr="[]" />
<div :arr="[1, 2, 3]" />
<div :obj="{}" />
<div :obj="{ name: 'Tom', age: 123 }" />
<div :callback="() => someFunction(someArgs)" />
<div :callback="function() { return 1 }" />

<template v-for="i in arr">
<MyComponent :data="[globalVars]" />
<MyComponent :data="{ index: globalVars }" />
<MyComponent :callback="() => someFunction(someArgs, globalVars)" />
</template>

<Child>
<template #default="{ foo }">
<MyComponent :data="[globalVars]" />
<MyComponent :data="{ val: globalVars }" />
<MyComponent :data="() => someFunction(someArgs, globalVars)" />
</template>
</Child>
</template>
```

</eslint-code-block>

## :wrench: Options

Nothing.

## :books: Further Reading

- [vuejs/vue#4060](https://github.com/vuejs/vue/issues/4060)

## :mag: Implementation

- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-literals-in-template.js)
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-literals-in-template.js)
1 change: 1 addition & 0 deletions lib/plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ const plugin = {
'no-import-compiler-macros': require('./rules/no-import-compiler-macros'),
'no-irregular-whitespace': require('./rules/no-irregular-whitespace'),
'no-lifecycle-after-await': require('./rules/no-lifecycle-after-await'),
'no-literals-in-template': require('./rules/no-literals-in-template'),
'no-lone-template': require('./rules/no-lone-template'),
'no-loss-of-precision': require('./rules/no-loss-of-precision'),
'no-multi-spaces': require('./rules/no-multi-spaces'),
Expand Down
91 changes: 91 additions & 0 deletions lib/rules/no-literals-in-template.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/**
* @author rzzf
* See LICENSE file in root directory for full license.
*/
'use strict'

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

/**
* @type {Record<string, 'object' | 'array' | 'function' | 'arrow function' | undefined>}
*/
const EXPRESSION_TYPES = {
ObjectExpression: 'object',
ArrayExpression: 'array',
FunctionExpression: 'function',
ArrowFunctionExpression: 'arrow function'
}

module.exports = {
meta: {
type: 'suggestion',
docs: {
description: 'disallow object, array, and function literals in template',
categories: undefined,
url: 'https://eslint.vuejs.org/rules/no-literals-in-template.html'
},
fixable: null,
schema: [],
messages: {
unexpected: 'Unexpected {{type}} literal in template.'
}
},
/** @param {RuleContext} context */
create(context) {
/** @type {Set<VElement>} */
const upperElements = new Set()

/**
* Checks whether the given node refers to a variable of the element.
* @param {Expression | VForExpression | VOnExpression | VSlotScopeExpression | VFilterSequenceExpression} node
*/
function hasReferenceUpperElementVariable(node) {
for (const element of upperElements) {
for (const vv of element.variables) {
for (const reference of vv.references) {
const { range } = reference.id
if (node.range[0] <= range[0] && range[1] <= node.range[1]) {
return true
}
}
}
}
return false
}

return utils.defineTemplateBodyVisitor(context, {
/** @param {VElement} node */
VElement(node) {
upperElements.add(node)
},
/** @param {VElement} node */
'VElement:exit'(node) {
upperElements.delete(node)
},
/**
* @param {VDirective} node
*/
"VAttribute[directive=true][key.name.name='bind']"(node) {
const expression = node.value?.expression
if (
!expression ||
(node.key.argument &&
node.key.argument.type === 'VIdentifier' &&
(node.key.argument.name === 'class' ||
node.key.argument.name === 'style'))
) {
return
}

const type = EXPRESSION_TYPES[expression.type]
if (type && !hasReferenceUpperElementVariable(expression)) {
context.report({
node: expression,
messageId: 'unexpected',
data: { type }
})
}
}
})
}
}
Loading
Loading