Skip to content

Commit a3bbe3c

Browse files
committed
Improve HTML comment directives.
- Add the support of descriptions in directive comments. - Add the support for block-level directive comments.
1 parent 7c53cd4 commit a3bbe3c

File tree

5 files changed

+238
-18
lines changed

5 files changed

+238
-18
lines changed

docs/rules/comment-directive.md

+50-3
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ description: support comment-directives in `<template>`
99
1010
- :gear: This rule is included in all of `"plugin:vue/base"`, `"plugin:vue/essential"`, `"plugin:vue/vue3-essential"`, `"plugin:vue/strongly-recommended"`, `"plugin:vue/vue3-strongly-recommended"`, `"plugin:vue/recommended"` and `"plugin:vue/vue3-recommended"`.
1111

12-
Sole purpose of this rule is to provide `eslint-disable` functionality in `<template>`.
12+
Sole purpose of this rule is to provide `eslint-disable` functionality in the `<template>` and in the block level.
1313
It supports usage of the following comments:
1414

1515
- `eslint-disable`
@@ -34,8 +34,55 @@ This rule sends all `eslint-disable`-like comments as errors to the post-process
3434
```vue
3535
<template>
3636
<!-- eslint-disable-next-line vue/max-attributes-per-line -->
37-
<div a="1" b="2" c="3" d="4">
38-
</div>
37+
<div a="1" b="2" c="3" d="4" />
38+
</template>
39+
```
40+
41+
</eslint-code-block>
42+
43+
The `eslint-disable`-like comments can be used in the `<template>` and in the block level.
44+
45+
<eslint-code-block :rules="{'vue/comment-directive': ['error'], 'vue/max-attributes-per-line': ['error'], 'vue/component-tags-order': ['error'] }">
46+
47+
```vue
48+
<template>
49+
<!-- eslint-disable-next-line vue/max-attributes-per-line -->
50+
<div a="1" b="2" c="3" d="4" />
51+
</template>
52+
53+
<!-- eslint-disable-next-line vue/component-tags-order -->
54+
<script>
55+
</script>
56+
```
57+
58+
</eslint-code-block>
59+
60+
The `eslint-disable` comments has no effect after one block.
61+
62+
<eslint-code-block :rules="{'vue/comment-directive': ['error'], 'vue/max-attributes-per-line': ['error'], 'vue/component-tags-order': ['error'] }">
63+
64+
```vue
65+
<template>
66+
</template>
67+
68+
<!-- eslint-disable vue/component-tags-order -->
69+
<style> /* <- Warning has been disabled. */
70+
</style>
71+
72+
<script> /* <- Warning are not disabled. */
73+
</script>
74+
```
75+
76+
</eslint-code-block>
77+
78+
The `eslint-disable`-like comments can include descriptions to explain why the comment is necessary. The description must occur after the directive and is separated from the directive by two or more consecutive `-` characters. For example:
79+
80+
<eslint-code-block :rules="{'vue/comment-directive': ['error'], 'vue/max-attributes-per-line': ['error']}">
81+
82+
```vue
83+
<template>
84+
<!-- eslint-disable-next-line vue/max-attributes-per-line -- Here's a description about why this disabling is necessary. -->
85+
<div a="1" b="2" c="3" d="4" />
3986
</template>
4087
```
4188

docs/user-guide/README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ Vue.component('AsyncComponent', (resolve, reject) => {
114114

115115
### Disabling rules via `<!-- eslint-disable -->`
116116

117-
You can use `<!-- eslint-disable -->`-like HTML comments in the `<template>` of `.vue` files to disable a certain rule temporarily.
117+
You can use `<!-- eslint-disable -->`-like HTML comments in the `<template>` and in the block level of `.vue` files to disable a certain rule temporarily.
118118

119119
For example:
120120

lib/rules/comment-directive.js

+60-13
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,23 @@
1212
const COMMENT_DIRECTIVE_B = /^\s*(eslint-(?:en|dis)able)(?:\s+(\S|\S[\s\S]*\S))?\s*$/
1313
const COMMENT_DIRECTIVE_L = /^\s*(eslint-disable(?:-next)?-line)(?:\s+(\S|\S[\s\S]*\S))?\s*$/
1414

15+
/**
16+
* Remove the ignored part from a given directive comment and trim it.
17+
* @param {string} value The comment text to strip.
18+
* @returns {string} The stripped text.
19+
*/
20+
function stripDirectiveComment (value) {
21+
return value.split(/\s-{2,}\s/u)[0].trim()
22+
}
23+
1524
/**
1625
* Parse a given comment.
1726
* @param {RegExp} pattern The RegExp pattern to parse.
1827
* @param {string} comment The comment value to parse.
1928
* @returns {({type:string,rules:string[]})|null} The parsing result.
2029
*/
2130
function parse (pattern, comment) {
22-
const match = pattern.exec(comment)
31+
const match = pattern.exec(stripDirectiveComment(comment))
2332
if (match == null) {
2433
return null
2534
}
@@ -100,6 +109,28 @@ function processLine (context, comment) {
100109
}
101110
}
102111

112+
/**
113+
* Extracts the top-level elements in document fragment.
114+
* @param {VDocumentFragment} documentFragment The document fragment.
115+
* @returns {VElement[]} The top-level elements
116+
*/
117+
function extractTopLevelHTMLElements (documentFragment) {
118+
return documentFragment.children.filter(e => e.type === 'VElement')
119+
}
120+
/**
121+
* Extracts the top-level comments in document fragment.
122+
* @param {VDocumentFragment} documentFragment The document fragment.
123+
* @returns {Token[]} The top-level comments
124+
*/
125+
function extractTopLevelDocumentFragmentComments (documentFragment) {
126+
const elements = extractTopLevelHTMLElements(documentFragment)
127+
128+
return documentFragment.comments.filter(comment =>
129+
elements.every(element =>
130+
comment.range[1] <= element.range[0] || element.range[1] <= comment.range[0]
131+
))
132+
}
133+
103134
// -----------------------------------------------------------------------------
104135
// Rule Definition
105136
// -----------------------------------------------------------------------------
@@ -116,24 +147,40 @@ module.exports = {
116147
},
117148

118149
create (context) {
150+
const documentFragment = context.parserServices.getDocumentFragment && context.parserServices.getDocumentFragment()
151+
119152
return {
120153
Program (node) {
121-
if (!node.templateBody) {
122-
return
123-
}
154+
if (node.templateBody) {
155+
// Send directives to the post-process.
156+
for (const comment of node.templateBody.comments) {
157+
processBlock(context, comment)
158+
processLine(context, comment)
159+
}
124160

125-
// Send directives to the post-process.
126-
for (const comment of node.templateBody.comments) {
127-
processBlock(context, comment)
128-
processLine(context, comment)
161+
// Send a clear mark to the post-process.
162+
context.report({
163+
loc: node.templateBody.loc.end,
164+
message: 'clear'
165+
})
129166
}
167+
if (documentFragment) {
168+
// Send directives to the post-process.
169+
for (const comment of extractTopLevelDocumentFragmentComments(documentFragment)) {
170+
processBlock(context, comment)
171+
processLine(context, comment)
172+
}
130173

131-
// Send a clear mark to the post-process.
132-
context.report({
133-
loc: node.templateBody.loc.end,
134-
message: 'clear'
135-
})
174+
// Send a clear mark to the post-process.
175+
for (const element of extractTopLevelHTMLElements(documentFragment)) {
176+
context.report({
177+
loc: element.loc.end,
178+
message: 'clear'
179+
})
180+
}
181+
}
136182
}
137183
}
138184
}
139185
}
186+

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@
5353
"eslint-utils": "^2.0.0",
5454
"natural-compare": "^1.4.0",
5555
"semver": "^7.3.2",
56-
"vue-eslint-parser": "^7.0.0"
56+
"vue-eslint-parser": "^7.1.0"
5757
},
5858
"devDependencies": {
5959
"@types/node": "^13.13.5",

tests/lib/rules/comment-directive.js

+126
Original file line numberDiff line numberDiff line change
@@ -226,4 +226,130 @@ describe('comment-directive', () => {
226226
assert.deepEqual(messages[1].line, 5)
227227
})
228228
})
229+
230+
describe('allow description', () => {
231+
it('disable all rules if <!-- eslint-disable -- description -->', () => {
232+
const code = `
233+
<template>
234+
<!-- eslint-disable -- description -->
235+
<div id id="a">Hello</div>
236+
</template>
237+
`
238+
const messages = linter.executeOnText(code, 'test.vue').results[0].messages
239+
240+
assert.deepEqual(messages.length, 0)
241+
})
242+
243+
it('enable all rules if <!-- eslint-enable -- description -->', () => {
244+
const code = `
245+
<template>
246+
<!-- eslint-disable -- description -->
247+
<div id id="a">Hello</div>
248+
<!-- eslint-enable -- description -->
249+
<div id id="a">Hello</div>
250+
</template>
251+
`
252+
const messages = linter.executeOnText(code, 'test.vue').results[0].messages
253+
254+
assert.deepEqual(messages.length, 2)
255+
assert.deepEqual(messages[0].ruleId, 'vue/no-parsing-error')
256+
assert.deepEqual(messages[0].line, 6)
257+
assert.deepEqual(messages[1].ruleId, 'vue/no-duplicate-attributes')
258+
assert.deepEqual(messages[1].line, 6)
259+
})
260+
261+
it('enable specific rules if <!-- eslint-enable vue/no-duplicate-attributes -- description -->', () => {
262+
const code = `
263+
<template>
264+
<!-- eslint-disable vue/no-parsing-error, vue/no-duplicate-attributes -- description -->
265+
<div id id="a">Hello</div>
266+
<!-- eslint-enable vue/no-duplicate-attributes -- description -->
267+
<div id id="a">Hello</div>
268+
</template>
269+
`
270+
const messages = linter.executeOnText(code, 'test.vue').results[0].messages
271+
272+
assert.deepEqual(messages.length, 1)
273+
assert.deepEqual(messages[0].ruleId, 'vue/no-duplicate-attributes')
274+
assert.deepEqual(messages[0].line, 6)
275+
})
276+
277+
it('disable all rules if <!-- eslint-disable-line -- description -->', () => {
278+
const code = `
279+
<template>
280+
<div id id="a">Hello</div> <!-- eslint-disable-line -- description -->
281+
</template>
282+
`
283+
const messages = linter.executeOnText(code, 'test.vue').results[0].messages
284+
285+
assert.deepEqual(messages.length, 0)
286+
})
287+
288+
it('disable specific rules if <!-- eslint-disable-line vue/no-duplicate-attributes -- description -->', () => {
289+
const code = `
290+
<template>
291+
<div id id="a">Hello</div> <!-- eslint-disable-line vue/no-duplicate-attributes -- description -->
292+
</template>
293+
`
294+
const messages = linter.executeOnText(code, 'test.vue').results[0].messages
295+
296+
assert.deepEqual(messages.length, 1)
297+
assert.deepEqual(messages[0].ruleId, 'vue/no-parsing-error')
298+
})
299+
300+
it('disable all rules if <!-- eslint-disable-next-line -- description -->', () => {
301+
const code = `
302+
<template>
303+
<!-- eslint-disable-next-line -- description -->
304+
<div id id="a">Hello</div>
305+
</template>
306+
`
307+
const messages = linter.executeOnText(code, 'test.vue').results[0].messages
308+
309+
assert.deepEqual(messages.length, 0)
310+
})
311+
312+
it('disable specific rules if <!-- eslint-disable-next-line vue/no-duplicate-attributes -->', () => {
313+
const code = `
314+
<template>
315+
<!-- eslint-disable-next-line vue/no-duplicate-attributes -- description -->
316+
<div id id="a">Hello</div>
317+
</template>
318+
`
319+
const messages = linter.executeOnText(code, 'test.vue').results[0].messages
320+
321+
assert.deepEqual(messages.length, 1)
322+
assert.deepEqual(messages[0].ruleId, 'vue/no-parsing-error')
323+
})
324+
})
325+
326+
describe('block level directive', () => {
327+
it('disable all rules if <!-- eslint-disable -->', () => {
328+
const code = `
329+
<!-- eslint-disable -->
330+
<template>
331+
<div id id="a">Hello</div>
332+
</template>
333+
`
334+
const messages = linter.executeOnText(code, 'test.vue').results[0].messages
335+
336+
assert.deepEqual(messages.length, 0)
337+
})
338+
339+
it('don\'t disable rules if <!-- eslint-disable --> is on after block', () => {
340+
const code = `
341+
<!-- eslint-disable -->
342+
<i18n>
343+
</i18n>
344+
<template>
345+
<div id id="a">Hello</div>
346+
</template>
347+
`
348+
const messages = linter.executeOnText(code, 'test.vue').results[0].messages
349+
350+
assert.deepEqual(messages.length, 2)
351+
assert.deepEqual(messages[0].ruleId, 'vue/no-parsing-error')
352+
assert.deepEqual(messages[1].ruleId, 'vue/no-duplicate-attributes')
353+
})
354+
})
229355
})

0 commit comments

Comments
 (0)