Skip to content

New: directive comments (fixes #260) #320

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

Merged
merged 2 commits into from
Jan 6, 2018
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,20 @@ Vue.component('AsyncComponent', (resolve, reject) => {
})
```

### `eslint-disable` functionality in `<template>`

You can use `<!-- eslint-disable -->`-like HTML comments in `<template>` of `.vue` files. For example:

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

If you want to disallow `eslint-disable` functionality, please disable [vue/comment-directive](./docs/rules/comment-directive.md) rule.

## :gear: Configs

This plugin provides two predefined configs:
Expand All @@ -95,6 +109,7 @@ Enforce all the rules in this category, as well as all higher priority rules, wi

| | Rule ID | Description |
|:---|:--------|:------------|
| | [comment-directive](./docs/rules/comment-directive.md) | support comment-directives in `<template>` |
| | [jsx-uses-vars](./docs/rules/jsx-uses-vars.md) | prevent variables used in JSX to be marked as unused |


Expand Down
33 changes: 33 additions & 0 deletions docs/rules/comment-directive.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# support comment-directives in `<template>` (comment-directive)

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

- `eslint-disable`
- `eslint-enable`
- `eslint-disable-line`
- `eslint-disable-next-line`

For example:

```html
<template>
<!-- eslint-disable-next-line vue/max-attributes-per-line -->
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should also add this information in README

<div a="1" b="2" c="3" d="4">
</div>
</template>
```

> Note: we can't write HTML comments in tags.

This rule doesn't throw any warning.

## :book: Rule Details

ESLint doesn't provide any API to enhance `eslint-disable` functionality and ESLint rules cannot affect other rules. But ESLint provides [processors API](https://eslint.org/docs/developer-guide/working-with-plugins#processors-in-plugins).

This rule sends all `eslint-disable`-like comments as errors to the post-process of the `.vue` file processor, then the post-process removes all `vue/comment-directive` errors and the reported errors in disabled areas.

## :books: Further reading

- [Disabling rules with inline comments](https://eslint.org/docs/user-guide/configuring#disabling-rules-with-inline-comments)
1 change: 1 addition & 0 deletions lib/base-rules.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@
* in order to update it's content execute "npm run update"
*/
module.exports = {
"vue/comment-directive": "error",
"vue/jsx-uses-vars": "error"
}
1 change: 1 addition & 0 deletions lib/essential-rules.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
* in order to update it's content execute "npm run update"
*/
module.exports = {
"vue/comment-directive": "error",
"vue/jsx-uses-vars": "error",
"vue/no-async-in-computed-properties": "error",
"vue/no-dupe-keys": "error",
Expand Down
4 changes: 3 additions & 1 deletion lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@ const configs = requireAll({
dirname: resolve(__dirname, 'config'),
filter: /^([\w\-]+)\.js$/
})
const processor = require('./processor')

module.exports = {
rules,
configs
configs,
processors: { '.vue': processor }
}
60 changes: 60 additions & 0 deletions lib/processor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/**
* @author Toru Nagashima <https://github.com/mysticatea>
*/
'use strict'

module.exports = {
preprocess (code) {
return [code]
},

postprocess (messages) {
const state = {
block: {
disableAll: false,
disableRules: new Set()
},
line: {
disableAll: false,
disableRules: new Set()
}
}

// Filter messages which are in disabled area.
return messages[0].filter(message => {
if (message.ruleId === 'vue/comment-directive') {
const rules = message.message.split(' ')
const type = rules.shift()
const group = rules.shift()
switch (type) {
case '--':
state[group].disableAll = true
break
case '++':
state[group].disableAll = false
break
case '-':
for (const rule of rules) {
state[group].disableRules.add(rule)
}
break
case '+':
for (const rule of rules) {
state[group].disableRules.delete(rule)
}
break
}
return false
} else {
return !(
state.block.disableAll ||
state.line.disableAll ||
state.block.disableRules.has(message.ruleId) ||
state.line.disableRules.has(message.ruleId)
)
}
})
},

supportsAutofix: true
}
1 change: 1 addition & 0 deletions lib/recommended-rules.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
*/
module.exports = {
"vue/attribute-hyphenation": "error",
"vue/comment-directive": "error",
"vue/html-end-tags": "error",
"vue/html-indent": "error",
"vue/html-quotes": "error",
Expand Down
132 changes: 132 additions & 0 deletions lib/rules/comment-directive.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
/**
* @author Toru Nagashima <https://github.com/mysticatea>
*/

'use strict'

// -----------------------------------------------------------------------------
// Helpers
// -----------------------------------------------------------------------------

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*)?$/

/**
* 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)
if (match == null) {
return null
}

const type = match[1]
const rules = (match[2] || '')
.split(',')
.map(s => s.trim())
.filter(Boolean)

return { type, rules }
}

/**
* Enable rules.
* @param {RuleContext} context The rule context.
* @param {{line:number,column:number}} loc The location information to enable.
* @param {string} group The group to enable.
* @param {string[]} rules The rule IDs to enable.
* @returns {void}
*/
function enable (context, loc, group, rules) {
if (rules.length === 0) {
context.report({ loc, message: '++ {{group}}', data: { group }})
} else {
context.report({ loc, message: '+ {{group}} {{rules}}', data: { group, rules: rules.join(' ') }})
}
}

/**
* Disable rules.
* @param {RuleContext} context The rule context.
* @param {{line:number,column:number}} loc The location information to disable.
* @param {string} group The group to disable.
* @param {string[]} rules The rule IDs to disable.
* @returns {void}
*/
function disable (context, loc, group, rules) {
if (rules.length === 0) {
context.report({ loc, message: '-- {{group}}', data: { group }})
} else {
context.report({ loc, message: '- {{group}} {{rules}}', data: { group, rules: rules.join(' ') }})
}
}

/**
* Process a given comment token.
* If the comment is `eslint-disable` or `eslint-enable` then it reports the comment.
* @param {RuleContext} context The rule context.
* @param {Token} comment The comment token to process.
* @returns {void}
*/
function processBlock (context, comment) {
const parsed = parse(COMMENT_DIRECTIVE_B, comment.value)
if (parsed != null) {
if (parsed.type === 'eslint-disable') {
disable(context, comment.loc.start, 'block', parsed.rules)
} else {
enable(context, comment.loc.start, 'block', parsed.rules)
}
}
}

/**
* Process a given comment token.
* If the comment is `eslint-disable-line` or `eslint-disable-next-line` then it reports the comment.
* @param {RuleContext} context The rule context.
* @param {Token} comment The comment token to process.
* @returns {void}
*/
function processLine (context, comment) {
const parsed = parse(COMMENT_DIRECTIVE_L, comment.value)
if (parsed != null && comment.loc.start.line === comment.loc.end.line) {
const line = comment.loc.start.line + (parsed.type === 'eslint-disable-line' ? 0 : 1)
const column = -1
disable(context, { line, column }, 'line', parsed.rules)
enable(context, { line: line + 1, column }, 'line', parsed.rules)
}
}

/**
* The implementation of `vue/comment-directive` rule.
* @param {Program} node The program node to parse.
* @returns {Object} The visitor of this rule.
*/
function create (context) {
return {
Program (node) {
const comments = (node.templateBody && node.templateBody.comments) || []
for (const comment of comments) {
processBlock(context, comment)
processLine(context, comment)
}
}
}
}

// -----------------------------------------------------------------------------
// Rule Definition
// -----------------------------------------------------------------------------

module.exports = {
meta: {
docs: {
description: 'support comment-directives in `<template>`', // eslint-disable-line consistent-docs-description
category: 'base'
},
schema: []
},
create
}
1 change: 1 addition & 0 deletions lib/strongly-recommended-rules.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
*/
module.exports = {
"vue/attribute-hyphenation": "error",
"vue/comment-directive": "error",
"vue/html-end-tags": "error",
"vue/html-indent": "error",
"vue/html-self-closing": "error",
Expand Down
Loading