Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve HTML comment directives. #1120

Merged
merged 2 commits into from May 15, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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')
})
})
})