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 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
55 changes: 55 additions & 0 deletions docs/rules/no-literals-in-template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
---
pageClass: rule-details
sidebarDepth: 0
title: vue/no-literals-in-template
description: disallow object literals in template
---

# vue/no-literals-in-template

> disallow object 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 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.

<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' }" />

<!-- ✗ 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>
```

</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
62 changes: 62 additions & 0 deletions lib/rules/no-literals-in-template.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/**
* @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 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) {
return utils.defineTemplateBodyVisitor(context, {
/**
* @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) {
context.report({
node: expression,
messageId: 'unexpected',
data: { type }
})
}
}
})
}
}
160 changes: 160 additions & 0 deletions tests/lib/rules/no-literals-in-template.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
/**
* @author rzzf
* See LICENSE file in root directory for full license.
*/
'use strict'

const RuleTester = require('../../eslint-compat').RuleTester
const rule = require('../../../lib/rules/no-literals-in-template')

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

tester.run('no-literals-in-template', rule, {
valid: [
{
filename: 'test.vue',
code: '<template><div :arr="myArray"></div></template>'
},
{
filename: 'test.vue',
code: '<template><div :obj="myObject"></div></template>'
},
{
filename: 'test.vue',
code: '<template><div :callback="myFunction"></div></template>'
},
{
filename: 'test.vue',
code: '<template><div v-bind="myProps"></div></template>'
},
// Excluded attributes
{
filename: 'test.vue',
code: '<template><div :class="{ active: isActive }"></div></template>'
},
{
filename: 'test.vue',
code: `<template><div :style="{ color: 'red' }"></div></template>`
},
{
filename: 'test.vue',
code: `<template><div :class="['active', errorClass]"></div></template>`
},
{
filename: 'test.vue',
code: '<template><div :style="[baseStyles, overridingStyles]"></div></template>'
}
],
invalid: [
{
filename: 'test.vue',
code: '<template><div :arr="[]"></div></template>',
errors: [
{
message: 'Unexpected array literal in template.',
line: 1,
column: 22,
endLine: 1,
endColumn: 24
}
]
},
{
filename: 'test.vue',
code: '<template><div :arr="[1,2,3]"></div></template>',
errors: [
{
message: 'Unexpected array literal in template.',
line: 1,
column: 22,
endLine: 1,
endColumn: 29
}
]
},
{
filename: 'test.vue',
code: '<template><div :obj="{}"></div></template>',
errors: [
{
message: 'Unexpected object literal in template.',
line: 1,
column: 22,
endLine: 1,
endColumn: 24
}
]
},
{
filename: 'test.vue',
code: `<template><div :obj="{name:'Tom', age: 123}"></div></template>`,
errors: [
{
message: 'Unexpected object literal in template.',
line: 1,
column: 22,
endLine: 1,
endColumn: 44
}
]
},
{
filename: 'test.vue',
code: '<template><div :callback="() => someFunction(someArgs)"></div></template>',
errors: [
{
message: 'Unexpected arrow function literal in template.',
line: 1,
column: 27,
endLine: 1,
endColumn: 55
}
]
},
{
filename: 'test.vue',
code: '<template><div :callback="function() { return 1 }"></div></template>',
errors: [
{
message: 'Unexpected function literal in template.',
line: 1,
column: 27,
endLine: 1,
endColumn: 50
}
]
},
{
filename: 'test.vue',
code: '<template><div :arr="[...myArray]"></div></template>',
errors: [
{
message: 'Unexpected array literal in template.',
line: 1,
column: 22,
endLine: 1,
endColumn: 34
}
]
},
{
filename: 'test.vue',
code: '<template><div :arr="{...myObject}"></div></template>',
errors: [
{
message: 'Unexpected object literal in template.',
line: 1,
column: 22,
endLine: 1,
endColumn: 35
}
]
}
]
})
Loading