Skip to content

Commit c030176

Browse files
committed
Merge branch 'master' into rewrite-ts
2 parents 822e06f + 1c77cf9 commit c030176

File tree

6 files changed

+774
-0
lines changed

6 files changed

+774
-0
lines changed

.changeset/fair-phones-add.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"eslint-plugin-vue": minor
3+
---
4+
5+
Added new `vue/no-literals-in-template` rule

docs/rules/index.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,7 @@ For example:
237237
| [vue/no-duplicate-class-names] | disallow duplication of class names in class attributes | :wrench: | :hammer: |
238238
| [vue/no-empty-component-block] | disallow the `<template>` `<script>` `<style>` block to be empty | :wrench: | :hammer: |
239239
| [vue/no-import-compiler-macros] | disallow importing Vue compiler macros | :wrench: | :warning: |
240+
| [vue/no-literals-in-template] | disallow object, array, and function literals in template | | :hammer: |
240241
| [vue/no-multiple-objects-in-class] | disallow passing multiple objects in an array to class | | :hammer: |
241242
| [vue/no-negated-v-if-condition] | disallow negated conditions in v-if/v-else | :wrench: | :hammer: |
242243
| [vue/no-potential-component-option-typo] | disallow a potential typo in your component property | :bulb: | :hammer: |
@@ -480,6 +481,7 @@ The following rules extend the rules provided by ESLint itself and apply them to
480481
[vue/no-import-compiler-macros]: ./no-import-compiler-macros.md
481482
[vue/no-irregular-whitespace]: ./no-irregular-whitespace.md
482483
[vue/no-lifecycle-after-await]: ./no-lifecycle-after-await.md
484+
[vue/no-literals-in-template]: ./no-literals-in-template.md
483485
[vue/no-lone-template]: ./no-lone-template.md
484486
[vue/no-loss-of-precision]: ./no-loss-of-precision.md
485487
[vue/no-multi-spaces]: ./no-multi-spaces.md
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
---
2+
pageClass: rule-details
3+
sidebarDepth: 0
4+
title: vue/no-literals-in-template
5+
description: disallow object, array, and function literals in template
6+
---
7+
8+
# vue/no-literals-in-template
9+
10+
> disallow object, array, and function literals in template
11+
12+
- :exclamation: <badge text="This rule has not been released yet." vertical="middle" type="error"> _**This rule has not been released yet.**_ </badge>
13+
14+
## :book: Rule Details
15+
16+
This rule disallows object, array, and function literals in template `v-bind` directives.
17+
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.
18+
19+
If the literal references a variable from a `v-for` directive or a scoped slot, it is ignored.
20+
21+
<eslint-code-block :rules="{'vue/no-literals-in-template': ['error']}">
22+
23+
```vue
24+
<template>
25+
<!-- ✓ GOOD -->
26+
<div :arr="myArray" />
27+
<div :obj="myObject" />
28+
<div :callback="myFunction" />
29+
<div arr="[]" />
30+
<!-- class and style bindings are ignored -->
31+
<div :class="{ active: isActive }" />
32+
<div :style="{ color: 'red' }" />
33+
34+
<template v-for="i in arr">
35+
<MyComponent :data="[i]" />
36+
<MyComponent :data="{ index: i }" />
37+
<MyComponent :callback="() => someFunction(someArgs, i)" />
38+
</template>
39+
40+
<Child>
41+
<template #default="{ foo }">
42+
<MyComponent :data="[foo]" />
43+
<MyComponent :data="{ val: foo }" />
44+
<MyComponent :data="() => someFunction(someArgs, foo)" />
45+
</template>
46+
</Child>
47+
48+
<!-- ✗ BAD -->
49+
<div :arr="[]" />
50+
<div :arr="[1, 2, 3]" />
51+
<div :obj="{}" />
52+
<div :obj="{ name: 'Tom', age: 123 }" />
53+
<div :callback="() => someFunction(someArgs)" />
54+
<div :callback="function() { return 1 }" />
55+
56+
<template v-for="i in arr">
57+
<MyComponent :data="[globalVars]" />
58+
<MyComponent :data="{ index: globalVars }" />
59+
<MyComponent :callback="() => someFunction(someArgs, globalVars)" />
60+
</template>
61+
62+
<Child>
63+
<template #default="{ foo }">
64+
<MyComponent :data="[globalVars]" />
65+
<MyComponent :data="{ val: globalVars }" />
66+
<MyComponent :data="() => someFunction(someArgs, globalVars)" />
67+
</template>
68+
</Child>
69+
</template>
70+
```
71+
72+
</eslint-code-block>
73+
74+
## :wrench: Options
75+
76+
Nothing.
77+
78+
## :books: Further Reading
79+
80+
- [vuejs/vue#4060](https://github.com/vuejs/vue/issues/4060)
81+
82+
## :mag: Implementation
83+
84+
- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-literals-in-template.js)
85+
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-literals-in-template.js)

lib/plugin.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ import noImplicitCoercion from './rules/no-implicit-coercion.js'
106106
import noImportCompilerMacros from './rules/no-import-compiler-macros.js'
107107
import noIrregularWhitespace from './rules/no-irregular-whitespace.js'
108108
import noLifecycleAfterAwait from './rules/no-lifecycle-after-await.js'
109+
import noLiteralsInTemplate from './rules/no-literals-in-template.js'
109110
import noLoneTemplate from './rules/no-lone-template.js'
110111
import noLossOfPrecision from './rules/no-loss-of-precision.js'
111112
import noMultiSpaces from './rules/no-multi-spaces.js'
@@ -360,6 +361,7 @@ export default {
360361
'no-import-compiler-macros': noImportCompilerMacros,
361362
'no-irregular-whitespace': noIrregularWhitespace,
362363
'no-lifecycle-after-await': noLifecycleAfterAwait,
364+
'no-literals-in-template': noLiteralsInTemplate,
363365
'no-lone-template': noLoneTemplate,
364366
'no-loss-of-precision': noLossOfPrecision,
365367
'no-multi-spaces': noMultiSpaces,
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/**
2+
* @author rzzf
3+
* See LICENSE file in root directory for full license.
4+
*/
5+
'use strict'
6+
7+
const utils = require('../utils')
8+
9+
/**
10+
* @type {Record<string, 'object' | 'array' | 'function' | 'arrow function' | undefined>}
11+
*/
12+
const EXPRESSION_TYPES = {
13+
ObjectExpression: 'object',
14+
ArrayExpression: 'array',
15+
FunctionExpression: 'function',
16+
ArrowFunctionExpression: 'arrow function'
17+
}
18+
19+
module.exports = {
20+
meta: {
21+
type: 'suggestion',
22+
docs: {
23+
description: 'disallow object, array, and function literals in template',
24+
categories: undefined,
25+
url: 'https://eslint.vuejs.org/rules/no-literals-in-template.html'
26+
},
27+
fixable: null,
28+
schema: [],
29+
messages: {
30+
unexpected: 'Unexpected {{type}} literal in template.'
31+
}
32+
},
33+
/** @param {RuleContext} context */
34+
create(context) {
35+
/** @type {Set<VElement>} */
36+
const upperElements = new Set()
37+
38+
/**
39+
* Checks whether the given node refers to a variable of the element.
40+
* @param {Expression | VForExpression | VOnExpression | VSlotScopeExpression | VFilterSequenceExpression} node
41+
*/
42+
function hasReferenceUpperElementVariable(node) {
43+
for (const element of upperElements) {
44+
for (const variable of element.variables) {
45+
for (const reference of variable.references) {
46+
const { range } = reference.id
47+
if (node.range[0] <= range[0] && range[1] <= node.range[1]) {
48+
return true
49+
}
50+
}
51+
}
52+
}
53+
return false
54+
}
55+
56+
return utils.defineTemplateBodyVisitor(context, {
57+
/** @param {VElement} node */
58+
VElement(node) {
59+
upperElements.add(node)
60+
},
61+
/** @param {VElement} node */
62+
'VElement:exit'(node) {
63+
upperElements.delete(node)
64+
},
65+
/**
66+
* @param {VDirective} node
67+
*/
68+
"VAttribute[directive=true][key.name.name='bind']"(node) {
69+
const expression = node.value?.expression
70+
if (
71+
!expression ||
72+
(node.key.argument &&
73+
node.key.argument.type === 'VIdentifier' &&
74+
(node.key.argument.name === 'class' ||
75+
node.key.argument.name === 'style'))
76+
) {
77+
return
78+
}
79+
80+
const type = EXPRESSION_TYPES[expression.type]
81+
if (type && !hasReferenceUpperElementVariable(expression)) {
82+
context.report({
83+
node: expression,
84+
messageId: 'unexpected',
85+
data: { type }
86+
})
87+
}
88+
}
89+
})
90+
}
91+
}

0 commit comments

Comments
 (0)