Skip to content

Commit

Permalink
Improve HTML comment directives. (#1120)
Browse files Browse the repository at this point in the history
* Improve HTML comment directives.

- Add the support of descriptions in directive comments.
- Add the support for block-level directive comments.

* Fixed docs
  • Loading branch information
ota-meshi committed May 15, 2020
1 parent 8de47f1 commit 8ac8c32
Show file tree
Hide file tree
Showing 8 changed files with 261 additions and 18 deletions.
10 changes: 10 additions & 0 deletions docs/.vuepress/config.js
Expand Up @@ -63,6 +63,16 @@ if (deprecatedRules.length > 0) {
}

module.exports = {
configureWebpack (_config, _isServer) {
return {
resolve: {
alias: {
module: require.resolve('./shim/module')
}
}
}
},

base: '/',
title: 'eslint-plugin-vue',
description: 'Official ESLint plugin for Vue.js',
Expand Down
3 changes: 3 additions & 0 deletions docs/.vuepress/shim/module.js
@@ -0,0 +1,3 @@
module.exports = {
createRequire: () => () => null
}
53 changes: 50 additions & 3 deletions docs/rules/comment-directive.md
Expand Up @@ -9,7 +9,7 @@ description: support comment-directives in `<template>`
- :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"`.

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

- `eslint-disable`
Expand All @@ -34,8 +34,55 @@ This rule sends all `eslint-disable`-like comments as errors to the post-process
```vue
<template>
<!-- eslint-disable-next-line vue/max-attributes-per-line -->
<div a="1" b="2" c="3" d="4">
</div>
<div a="1" b="2" c="3" d="4" />
</template>
```

</eslint-code-block>

The `eslint-disable`-like comments can be used in the `<template>` and in the block level.

<eslint-code-block :rules="{'vue/comment-directive': ['error'], 'vue/max-attributes-per-line': ['error'], 'vue/component-tags-order': ['error'] }">

```vue
<template>
<!-- eslint-disable-next-line vue/max-attributes-per-line -->
<div a="1" b="2" c="3" d="4" />
</template>
<!-- eslint-disable-next-line vue/component-tags-order -->
<script>
</script>
```

</eslint-code-block>

The `eslint-disable` comments has no effect after one block.

<eslint-code-block :rules="{'vue/comment-directive': ['error'], 'vue/max-attributes-per-line': ['error'], 'vue/component-tags-order': ['error'] }">

```vue
<template>
</template>
<!-- eslint-disable vue/component-tags-order -->
<style> /* <- Warning has been disabled. */
</style>
<script> /* <- Warning are not disabled. */
</script>
```

</eslint-code-block>

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:

<eslint-code-block :rules="{'vue/comment-directive': ['error'], 'vue/max-attributes-per-line': ['error']}">

```vue
<template>
<!-- eslint-disable-next-line vue/max-attributes-per-line -- Here's a description about why this disabling is necessary. -->
<div a="1" b="2" c="3" d="4" />
</template>
```

Expand Down
10 changes: 10 additions & 0 deletions docs/rules/no-template-target-blank.md
Expand Up @@ -23,6 +23,8 @@ This rule disallows using `target="_blank"` attribute without `rel="noopener nor
</temlate>
```

</eslint-code-block>

## :wrench: Options

```json
Expand Down Expand Up @@ -51,6 +53,8 @@ This rule disallows using `target="_blank"` attribute without `rel="noopener nor
</temlate>
```

</eslint-code-block>

### `{ allowReferrer: true }`

<eslint-code-block :rules="{'vue/no-template-target-blank': ['error', { allowReferrer: true }]}">
Expand All @@ -65,6 +69,8 @@ This rule disallows using `target="_blank"` attribute without `rel="noopener nor
</temlate>
```

</eslint-code-block>

### `{ "enforceDynamicLinks": "always" }` (default)

<eslint-code-block :rules="{'vue/no-template-target-blank': ['error', { enforceDynamicLinks: 'never' }]}">
Expand All @@ -79,6 +85,8 @@ This rule disallows using `target="_blank"` attribute without `rel="noopener nor
</temlate>
```

</eslint-code-block>

### `{ "enforceDynamicLinks": "never" }`

<eslint-code-block :rules="{'vue/no-template-target-blank': ['error', { enforceDynamicLinks: 'never' }]}">
Expand All @@ -93,6 +101,8 @@ This rule disallows using `target="_blank"` attribute without `rel="noopener nor
</temlate>
```

</eslint-code-block>

## :mag: Implementation

- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-template-target-blank.js)
Expand Down
2 changes: 1 addition & 1 deletion docs/user-guide/README.md
Expand Up @@ -114,7 +114,7 @@ Vue.component('AsyncComponent', (resolve, reject) => {

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

You can use `<!-- eslint-disable -->`-like HTML comments in the `<template>` of `.vue` files to disable a certain rule temporarily.
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.

For example:

Expand Down
73 changes: 60 additions & 13 deletions lib/rules/comment-directive.js
Expand Up @@ -12,14 +12,23 @@
const COMMENT_DIRECTIVE_B = /^\s*(eslint-(?:en|dis)able)(?:\s+(\S|\S[\s\S]*\S))?\s*$/
const COMMENT_DIRECTIVE_L = /^\s*(eslint-disable(?:-next)?-line)(?:\s+(\S|\S[\s\S]*\S))?\s*$/

/**
* Remove the ignored part from a given directive comment and trim it.
* @param {string} value The comment text to strip.
* @returns {string} The stripped text.
*/
function stripDirectiveComment (value) {
return value.split(/\s-{2,}\s/u)[0].trim()
}

/**
* Parse a given comment.
* @param {RegExp} pattern The RegExp pattern to parse.
* @param {string} comment The comment value to parse.
* @returns {({type:string,rules:string[]})|null} The parsing result.
*/
function parse (pattern, comment) {
const match = pattern.exec(comment)
const match = pattern.exec(stripDirectiveComment(comment))
if (match == null) {
return null
}
Expand Down Expand Up @@ -100,6 +109,28 @@ function processLine (context, comment) {
}
}

/**
* Extracts the top-level elements in document fragment.
* @param {VDocumentFragment} documentFragment The document fragment.
* @returns {VElement[]} The top-level elements
*/
function extractTopLevelHTMLElements (documentFragment) {
return documentFragment.children.filter(e => e.type === 'VElement')
}
/**
* Extracts the top-level comments in document fragment.
* @param {VDocumentFragment} documentFragment The document fragment.
* @returns {Token[]} The top-level comments
*/
function extractTopLevelDocumentFragmentComments (documentFragment) {
const elements = extractTopLevelHTMLElements(documentFragment)

return documentFragment.comments.filter(comment =>
elements.every(element =>
comment.range[1] <= element.range[0] || element.range[1] <= comment.range[0]
))
}

// -----------------------------------------------------------------------------
// Rule Definition
// -----------------------------------------------------------------------------
Expand All @@ -116,24 +147,40 @@ module.exports = {
},

create (context) {
const documentFragment = context.parserServices.getDocumentFragment && context.parserServices.getDocumentFragment()

return {
Program (node) {
if (!node.templateBody) {
return
}
if (node.templateBody) {
// Send directives to the post-process.
for (const comment of node.templateBody.comments) {
processBlock(context, comment)
processLine(context, comment)
}

// Send directives to the post-process.
for (const comment of node.templateBody.comments) {
processBlock(context, comment)
processLine(context, comment)
// Send a clear mark to the post-process.
context.report({
loc: node.templateBody.loc.end,
message: 'clear'
})
}
if (documentFragment) {
// Send directives to the post-process.
for (const comment of extractTopLevelDocumentFragmentComments(documentFragment)) {
processBlock(context, comment)
processLine(context, comment)
}

// Send a clear mark to the post-process.
context.report({
loc: node.templateBody.loc.end,
message: 'clear'
})
// Send a clear mark to the post-process.
for (const element of extractTopLevelHTMLElements(documentFragment)) {
context.report({
loc: element.loc.end,
message: 'clear'
})
}
}
}
}
}
}

2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -53,7 +53,7 @@
"eslint-utils": "^2.0.0",
"natural-compare": "^1.4.0",
"semver": "^7.3.2",
"vue-eslint-parser": "^7.0.0"
"vue-eslint-parser": "^7.1.0"
},
"devDependencies": {
"@types/node": "^13.13.5",
Expand Down
126 changes: 126 additions & 0 deletions tests/lib/rules/comment-directive.js
Expand Up @@ -226,4 +226,130 @@ describe('comment-directive', () => {
assert.deepEqual(messages[1].line, 5)
})
})

describe('allow description', () => {
it('disable all rules if <!-- eslint-disable -- description -->', () => {
const code = `
<template>
<!-- eslint-disable -- description -->
<div id id="a">Hello</div>
</template>
`
const messages = linter.executeOnText(code, 'test.vue').results[0].messages

assert.deepEqual(messages.length, 0)
})

it('enable all rules if <!-- eslint-enable -- description -->', () => {
const code = `
<template>
<!-- eslint-disable -- description -->
<div id id="a">Hello</div>
<!-- eslint-enable -- description -->
<div id id="a">Hello</div>
</template>
`
const messages = linter.executeOnText(code, 'test.vue').results[0].messages

assert.deepEqual(messages.length, 2)
assert.deepEqual(messages[0].ruleId, 'vue/no-parsing-error')
assert.deepEqual(messages[0].line, 6)
assert.deepEqual(messages[1].ruleId, 'vue/no-duplicate-attributes')
assert.deepEqual(messages[1].line, 6)
})

it('enable specific rules if <!-- eslint-enable vue/no-duplicate-attributes -- description -->', () => {
const code = `
<template>
<!-- eslint-disable vue/no-parsing-error, vue/no-duplicate-attributes -- description -->
<div id id="a">Hello</div>
<!-- eslint-enable vue/no-duplicate-attributes -- description -->
<div id id="a">Hello</div>
</template>
`
const messages = linter.executeOnText(code, 'test.vue').results[0].messages

assert.deepEqual(messages.length, 1)
assert.deepEqual(messages[0].ruleId, 'vue/no-duplicate-attributes')
assert.deepEqual(messages[0].line, 6)
})

it('disable all rules if <!-- eslint-disable-line -- description -->', () => {
const code = `
<template>
<div id id="a">Hello</div> <!-- eslint-disable-line -- description -->
</template>
`
const messages = linter.executeOnText(code, 'test.vue').results[0].messages

assert.deepEqual(messages.length, 0)
})

it('disable specific rules if <!-- eslint-disable-line vue/no-duplicate-attributes -- description -->', () => {
const code = `
<template>
<div id id="a">Hello</div> <!-- eslint-disable-line vue/no-duplicate-attributes -- description -->
</template>
`
const messages = linter.executeOnText(code, 'test.vue').results[0].messages

assert.deepEqual(messages.length, 1)
assert.deepEqual(messages[0].ruleId, 'vue/no-parsing-error')
})

it('disable all rules if <!-- eslint-disable-next-line -- description -->', () => {
const code = `
<template>
<!-- eslint-disable-next-line -- description -->
<div id id="a">Hello</div>
</template>
`
const messages = linter.executeOnText(code, 'test.vue').results[0].messages

assert.deepEqual(messages.length, 0)
})

it('disable specific rules if <!-- eslint-disable-next-line vue/no-duplicate-attributes -->', () => {
const code = `
<template>
<!-- eslint-disable-next-line vue/no-duplicate-attributes -- description -->
<div id id="a">Hello</div>
</template>
`
const messages = linter.executeOnText(code, 'test.vue').results[0].messages

assert.deepEqual(messages.length, 1)
assert.deepEqual(messages[0].ruleId, 'vue/no-parsing-error')
})
})

describe('block level directive', () => {
it('disable all rules if <!-- eslint-disable -->', () => {
const code = `
<!-- eslint-disable -->
<template>
<div id id="a">Hello</div>
</template>
`
const messages = linter.executeOnText(code, 'test.vue').results[0].messages

assert.deepEqual(messages.length, 0)
})

it('don\'t disable rules if <!-- eslint-disable --> is on after block', () => {
const code = `
<!-- eslint-disable -->
<i18n>
</i18n>
<template>
<div id id="a">Hello</div>
</template>
`
const messages = linter.executeOnText(code, 'test.vue').results[0].messages

assert.deepEqual(messages.length, 2)
assert.deepEqual(messages[0].ruleId, 'vue/no-parsing-error')
assert.deepEqual(messages[1].ruleId, 'vue/no-duplicate-attributes')
})
})
})

0 comments on commit 8ac8c32

Please sign in to comment.