Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add
vue/no-child-content
rule (#1707)
* Add `vue/no-child-content` rule * Fix rule category * Add hints about available suggestions * Don't report whitespace-only child content * Report comments in `vue/no-child-content` * Simplify comment parsing with `tokenStore.getTokensBetween`
- Loading branch information
1 parent
6c64a09
commit f4c12cc
Showing
6 changed files
with
444 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
--- | ||
pageClass: rule-details | ||
sidebarDepth: 0 | ||
title: vue/no-child-content | ||
description: disallow element's child contents which would be overwritten by a directive like `v-html` or `v-text` | ||
--- | ||
# vue/no-child-content | ||
|
||
> disallow element's child contents which would be overwritten by a directive like `v-html` or `v-text` | ||
- :exclamation: <badge text="This rule has not been released yet." vertical="middle" type="error"> ***This rule has not been released yet.*** </badge> | ||
- :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 | ||
|
||
This rule reports child content of elements that have a directive which overwrites that child content. By default, those are `v-html` and `v-text`, additional ones (e.g. [Vue I18n's `v-t` directive](https://vue-i18n.intlify.dev/api/directive.html)) can be configured manually. | ||
|
||
<eslint-code-block :rules="{'vue/no-child-content': ['error']}"> | ||
|
||
```vue | ||
<template> | ||
<!-- ✓ GOOD --> | ||
<div>child content</div> | ||
<div v-html="replacesChildContent"></div> | ||
<!-- ✗ BAD --> | ||
<div v-html="replacesChildContent">child content</div> | ||
</template> | ||
``` | ||
|
||
</eslint-code-block> | ||
|
||
## :wrench: Options | ||
|
||
```json | ||
{ | ||
"vue/no-child-content": ["error", { | ||
"additionalDirectives": ["foo"] // checks v-foo directive | ||
}] | ||
} | ||
``` | ||
|
||
- `additionalDirectives` ... An array of additional directives to check, without the `v-` prefix. Empty by default; `v-html` and `v-text` are always checked. | ||
|
||
## :books: Further Reading | ||
|
||
- [`v-html` directive](https://v3.vuejs.org/api/directives.html#v-html) | ||
- [`v-text` directive](https://v3.vuejs.org/api/directives.html#v-text) | ||
|
||
## :mag: Implementation | ||
|
||
- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-child-content.js) | ||
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-child-content.js) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,164 @@ | ||
/** | ||
* @author Flo Edelmann | ||
* See LICENSE file in root directory for full license. | ||
*/ | ||
'use strict' | ||
const { defineTemplateBodyVisitor } = require('../utils') | ||
|
||
/** | ||
* @typedef {object} RuleOption | ||
* @property {string[]} additionalDirectives | ||
*/ | ||
|
||
/** | ||
* @param {VNode | Token} node | ||
* @returns {boolean} | ||
*/ | ||
function isWhiteSpaceTextNode(node) { | ||
return node.type === 'VText' && node.value.trim() === '' | ||
} | ||
|
||
/** | ||
* @param {Position} pos1 | ||
* @param {Position} pos2 | ||
* @returns {'less' | 'equal' | 'greater'} | ||
*/ | ||
function comparePositions(pos1, pos2) { | ||
if ( | ||
pos1.line < pos2.line || | ||
(pos1.line === pos2.line && pos1.column < pos2.column) | ||
) { | ||
return 'less' | ||
} | ||
|
||
if ( | ||
pos1.line > pos2.line || | ||
(pos1.line === pos2.line && pos1.column > pos2.column) | ||
) { | ||
return 'greater' | ||
} | ||
|
||
return 'equal' | ||
} | ||
|
||
/** | ||
* @param {(VNode | Token)[]} nodes | ||
* @returns {SourceLocation | undefined} | ||
*/ | ||
function getLocationRange(nodes) { | ||
/** @type {Position | undefined} */ | ||
let start | ||
/** @type {Position | undefined} */ | ||
let end | ||
|
||
for (const node of nodes) { | ||
if (!start || comparePositions(node.loc.start, start) === 'less') { | ||
start = node.loc.start | ||
} | ||
|
||
if (!end || comparePositions(node.loc.end, end) === 'greater') { | ||
end = node.loc.end | ||
} | ||
} | ||
|
||
if (start === undefined || end === undefined) { | ||
return undefined | ||
} | ||
|
||
return { start, end } | ||
} | ||
|
||
// ------------------------------------------------------------------------------ | ||
// Rule Definition | ||
// ------------------------------------------------------------------------------ | ||
|
||
module.exports = { | ||
meta: { | ||
hasSuggestions: true, | ||
type: 'problem', | ||
docs: { | ||
description: | ||
"disallow element's child contents which would be overwritten by a directive like `v-html` or `v-text`", | ||
categories: undefined, | ||
url: 'https://eslint.vuejs.org/rules/no-child-content.html' | ||
}, | ||
fixable: null, | ||
schema: [ | ||
{ | ||
type: 'object', | ||
additionalProperties: false, | ||
properties: { | ||
additionalDirectives: { | ||
type: 'array', | ||
uniqueItems: true, | ||
minItems: 1, | ||
items: { | ||
type: 'string' | ||
} | ||
} | ||
}, | ||
required: ['additionalDirectives'] | ||
} | ||
] | ||
}, | ||
/** @param {RuleContext} context */ | ||
create(context) { | ||
const directives = new Set(['html', 'text']) | ||
|
||
/** @type {RuleOption | undefined} */ | ||
const option = context.options[0] | ||
if (option !== undefined) { | ||
for (const directive of option.additionalDirectives) { | ||
directives.add(directive) | ||
} | ||
} | ||
|
||
return defineTemplateBodyVisitor(context, { | ||
/** @param {VDirective} directiveNode */ | ||
'VAttribute[directive=true]'(directiveNode) { | ||
const directiveName = directiveNode.key.name.name | ||
const elementNode = directiveNode.parent.parent | ||
|
||
if (elementNode.endTag === null) { | ||
return | ||
} | ||
|
||
const tokenStore = context.parserServices.getTemplateBodyTokenStore() | ||
const elementComments = tokenStore.getTokensBetween( | ||
elementNode.startTag, | ||
elementNode.endTag, | ||
{ | ||
includeComments: true, | ||
filter: (token) => token.type === 'HTMLComment' | ||
} | ||
) | ||
|
||
const childNodes = [...elementNode.children, ...elementComments] | ||
|
||
if ( | ||
directives.has(directiveName) && | ||
childNodes.length > 0 && | ||
childNodes.some((childNode) => !isWhiteSpaceTextNode(childNode)) | ||
) { | ||
context.report({ | ||
node: elementNode, | ||
loc: getLocationRange(childNodes), | ||
message: | ||
'Child content is disallowed because it will be overwritten by the v-{{ directiveName }} directive.', | ||
data: { directiveName }, | ||
suggest: [ | ||
{ | ||
desc: 'Remove child content.', | ||
*fix(fixer) { | ||
for (const childNode of childNodes) { | ||
yield fixer.remove(childNode) | ||
} | ||
} | ||
} | ||
] | ||
}) | ||
} | ||
} | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.