Skip to content

Commit 0fa1de2

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 59727a4 commit 0fa1de2

File tree

3 files changed

+249
-18
lines changed

3 files changed

+249
-18
lines changed

docs/rules/comment-directive.md

Lines changed: 50 additions & 3 deletions
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

lib/rules/comment-directive.js

Lines changed: 73 additions & 15 deletions
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
}
@@ -73,7 +82,7 @@ function disable (context, loc, group, rules) {
7382
* @returns {void}
7483
*/
7584
function processBlock (context, comment) {
76-
const parsed = parse(COMMENT_DIRECTIVE_B, comment.value)
85+
const parsed = parse(COMMENT_DIRECTIVE_B, getCommentValue(context, comment))
7786
if (parsed != null) {
7887
if (parsed.type === 'eslint-disable') {
7988
disable(context, comment.loc.start, 'block', parsed.rules)
@@ -91,7 +100,7 @@ function processBlock (context, comment) {
91100
* @returns {void}
92101
*/
93102
function processLine (context, comment) {
94-
const parsed = parse(COMMENT_DIRECTIVE_L, comment.value)
103+
const parsed = parse(COMMENT_DIRECTIVE_L, getCommentValue(context, comment))
95104
if (parsed != null && comment.loc.start.line === comment.loc.end.line) {
96105
const line = comment.loc.start.line + (parsed.type === 'eslint-disable-line' ? 0 : 1)
97106
const column = -1
@@ -100,6 +109,39 @@ 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+
/**
134+
* Gets the comment contents value.
135+
* @param {RuleContext} context The rule context.
136+
* @param {Token} comment The comment token to process.
137+
* @returns {string} The comment contents value.
138+
*/
139+
function getCommentValue (context, comment) {
140+
// Note: `comment.value` doesn't handle two consecutive hyphens correctly.
141+
// https://github.com/mysticatea/vue-eslint-parser/issues/72
142+
return context.getSourceCode().text.slice(comment.range[0] + 4, comment.range[1] - 3)
143+
}
144+
103145
// -----------------------------------------------------------------------------
104146
// Rule Definition
105147
// -----------------------------------------------------------------------------
@@ -116,24 +158,40 @@ module.exports = {
116158
},
117159

118160
create (context) {
161+
const documentFragment = context.parserServices.getDocumentFragment && context.parserServices.getDocumentFragment()
162+
119163
return {
120164
Program (node) {
121-
if (!node.templateBody) {
122-
return
123-
}
165+
if (node.templateBody) {
166+
// Send directives to the post-process.
167+
for (const comment of node.templateBody.comments) {
168+
processBlock(context, comment)
169+
processLine(context, comment)
170+
}
124171

125-
// Send directives to the post-process.
126-
for (const comment of node.templateBody.comments) {
127-
processBlock(context, comment)
128-
processLine(context, comment)
172+
// Send a clear mark to the post-process.
173+
context.report({
174+
loc: node.templateBody.loc.end,
175+
message: 'clear'
176+
})
129177
}
178+
if (documentFragment) {
179+
// Send directives to the post-process.
180+
for (const comment of extractTopLevelDocumentFragmentComments(documentFragment)) {
181+
processBlock(context, comment)
182+
processLine(context, comment)
183+
}
130184

131-
// Send a clear mark to the post-process.
132-
context.report({
133-
loc: node.templateBody.loc.end,
134-
message: 'clear'
135-
})
185+
// Send a clear mark to the post-process.
186+
for (const element of extractTopLevelHTMLElements(documentFragment)) {
187+
context.report({
188+
loc: element.loc.end,
189+
message: 'clear'
190+
})
191+
}
192+
}
136193
}
137194
}
138195
}
139196
}
197+

tests/lib/rules/comment-directive.js

Lines changed: 126 additions & 0 deletions
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)