You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
366 lines
11 KiB
366 lines
11 KiB
/** |
|
* @author Toru Nagashima <https://github.com/mysticatea> |
|
*/ |
|
/* eslint-disable eslint-plugin/report-message-format */ |
|
|
|
'use strict' |
|
|
|
// ------------------------------------------------------------------------------ |
|
// Requirements |
|
// ------------------------------------------------------------------------------ |
|
|
|
const utils = require('../utils') |
|
|
|
/** |
|
* @typedef {object} RuleAndLocation |
|
* @property {string} RuleAndLocation.ruleId |
|
* @property {number} RuleAndLocation.index |
|
* @property {string} [RuleAndLocation.key] |
|
*/ |
|
// ----------------------------------------------------------------------------- |
|
// Helpers |
|
// ----------------------------------------------------------------------------- |
|
|
|
const COMMENT_DIRECTIVE_B = /^\s*(eslint-(?:en|dis)able)(?:\s+|$)/ |
|
const COMMENT_DIRECTIVE_L = /^\s*(eslint-disable(?:-next)?-line)(?:\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] |
|
} |
|
|
|
/** |
|
* Parse a given comment. |
|
* @param {RegExp} pattern The RegExp pattern to parse. |
|
* @param {string} comment The comment value to parse. |
|
* @returns {({type:string,rules:RuleAndLocation[]})|null} The parsing result. |
|
*/ |
|
function parse(pattern, comment) { |
|
const text = stripDirectiveComment(comment) |
|
const match = pattern.exec(text) |
|
if (match == null) { |
|
return null |
|
} |
|
|
|
const type = match[1] |
|
|
|
/** @type {RuleAndLocation[]} */ |
|
const rules = [] |
|
|
|
const rulesRe = /([^,\s]+)[,\s]*/g |
|
let startIndex = match[0].length |
|
rulesRe.lastIndex = startIndex |
|
|
|
let res |
|
while ((res = rulesRe.exec(text))) { |
|
const ruleId = res[1].trim() |
|
rules.push({ |
|
ruleId, |
|
index: startIndex |
|
}) |
|
startIndex = rulesRe.lastIndex |
|
} |
|
|
|
return { type, rules } |
|
} |
|
|
|
/** |
|
* Enable rules. |
|
* @param {RuleContext} context The rule context. |
|
* @param {{line:number,column:number}} loc The location information to enable. |
|
* @param { 'block' | 'line' } group The group to enable. |
|
* @param {string | null} rule The rule ID to enable. |
|
* @returns {void} |
|
*/ |
|
function enable(context, loc, group, rule) { |
|
if (!rule) { |
|
context.report({ |
|
loc, |
|
messageId: group === 'block' ? 'enableBlock' : 'enableLine' |
|
}) |
|
} else { |
|
context.report({ |
|
loc, |
|
messageId: group === 'block' ? 'enableBlockRule' : 'enableLineRule', |
|
data: { rule } |
|
}) |
|
} |
|
} |
|
|
|
/** |
|
* Disable rules. |
|
* @param {RuleContext} context The rule context. |
|
* @param {{line:number,column:number}} loc The location information to disable. |
|
* @param { 'block' | 'line' } group The group to disable. |
|
* @param {string | null} rule The rule ID to disable. |
|
* @param {string} key The disable directive key. |
|
* @returns {void} |
|
*/ |
|
function disable(context, loc, group, rule, key) { |
|
if (!rule) { |
|
context.report({ |
|
loc, |
|
messageId: group === 'block' ? 'disableBlock' : 'disableLine', |
|
data: { key } |
|
}) |
|
} else { |
|
context.report({ |
|
loc, |
|
messageId: group === 'block' ? 'disableBlockRule' : 'disableLineRule', |
|
data: { rule, key } |
|
}) |
|
} |
|
} |
|
|
|
/** |
|
* 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. |
|
* @param {boolean} reportUnusedDisableDirectives To report unused eslint-disable comments. |
|
* @returns {void} |
|
*/ |
|
function processBlock(context, comment, reportUnusedDisableDirectives) { |
|
const parsed = parse(COMMENT_DIRECTIVE_B, comment.value) |
|
if (parsed != null) { |
|
if (parsed.type === 'eslint-disable') { |
|
if (parsed.rules.length) { |
|
const rules = reportUnusedDisableDirectives |
|
? reportUnusedRules(context, comment, parsed.type, parsed.rules) |
|
: parsed.rules |
|
for (const rule of rules) { |
|
disable( |
|
context, |
|
comment.loc.start, |
|
'block', |
|
rule.ruleId, |
|
rule.key || '*' |
|
) |
|
} |
|
} else { |
|
const key = reportUnusedDisableDirectives |
|
? reportUnused(context, comment, parsed.type) |
|
: '' |
|
disable(context, comment.loc.start, 'block', null, key) |
|
} |
|
} else { |
|
if (parsed.rules.length) { |
|
for (const rule of parsed.rules) { |
|
enable(context, comment.loc.start, 'block', rule.ruleId) |
|
} |
|
} else { |
|
enable(context, comment.loc.start, 'block', null) |
|
} |
|
} |
|
} |
|
} |
|
|
|
/** |
|
* 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. |
|
* @param {boolean} reportUnusedDisableDirectives To report unused eslint-disable comments. |
|
* @returns {void} |
|
*/ |
|
function processLine(context, comment, reportUnusedDisableDirectives) { |
|
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 |
|
if (parsed.rules.length) { |
|
const rules = reportUnusedDisableDirectives |
|
? reportUnusedRules(context, comment, parsed.type, parsed.rules) |
|
: parsed.rules |
|
for (const rule of rules) { |
|
disable(context, { line, column }, 'line', rule.ruleId, rule.key || '') |
|
enable(context, { line: line + 1, column }, 'line', rule.ruleId) |
|
} |
|
} else { |
|
const key = reportUnusedDisableDirectives |
|
? reportUnused(context, comment, parsed.type) |
|
: '' |
|
disable(context, { line, column }, 'line', null, key) |
|
enable(context, { line: line + 1, column }, 'line', null) |
|
} |
|
} |
|
} |
|
|
|
/** |
|
* Reports unused disable directive. |
|
* Do not check the use of directives here. Filter the directives used with postprocess. |
|
* @param {RuleContext} context The rule context. |
|
* @param {Token} comment The comment token to report. |
|
* @param {string} kind The comment directive kind. |
|
* @returns {string} The report key |
|
*/ |
|
function reportUnused(context, comment, kind) { |
|
const loc = comment.loc |
|
|
|
context.report({ |
|
loc, |
|
messageId: 'unused', |
|
data: { kind } |
|
}) |
|
|
|
return locToKey(loc.start) |
|
} |
|
|
|
/** |
|
* Reports unused disable directive rules. |
|
* Do not check the use of directives here. Filter the directives used with postprocess. |
|
* @param {RuleContext} context The rule context. |
|
* @param {Token} comment The comment token to report. |
|
* @param {string} kind The comment directive kind. |
|
* @param {RuleAndLocation[]} rules To report rule. |
|
* @returns { { ruleId: string, key: string }[] } |
|
*/ |
|
function reportUnusedRules(context, comment, kind, rules) { |
|
const sourceCode = context.getSourceCode() |
|
const commentStart = comment.range[0] + 4 /* <!-- */ |
|
|
|
return rules.map((rule) => { |
|
const start = sourceCode.getLocFromIndex(commentStart + rule.index) |
|
const end = sourceCode.getLocFromIndex( |
|
commentStart + rule.index + rule.ruleId.length |
|
) |
|
|
|
context.report({ |
|
loc: { start, end }, |
|
messageId: 'unusedRule', |
|
data: { rule: rule.ruleId, kind } |
|
}) |
|
|
|
return { |
|
ruleId: rule.ruleId, |
|
key: locToKey(start) |
|
} |
|
}) |
|
} |
|
|
|
/** |
|
* Gets the key of location |
|
* @param {Position} location The location |
|
* @returns {string} The key |
|
*/ |
|
function locToKey(location) { |
|
return `line:${location.line},column${location.column}` |
|
} |
|
|
|
/** |
|
* 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(utils.isVElement) |
|
} |
|
/** |
|
* 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 |
|
// ----------------------------------------------------------------------------- |
|
|
|
module.exports = { |
|
meta: { |
|
type: 'problem', |
|
docs: { |
|
description: 'support comment-directives in `<template>`', // eslint-disable-line eslint-plugin/require-meta-docs-description |
|
categories: ['base'], |
|
url: 'https://eslint.vuejs.org/rules/comment-directive.html' |
|
}, |
|
schema: [ |
|
{ |
|
type: 'object', |
|
properties: { |
|
reportUnusedDisableDirectives: { |
|
type: 'boolean' |
|
} |
|
}, |
|
additionalProperties: false |
|
} |
|
], |
|
messages: { |
|
disableBlock: '--block {{key}}', |
|
enableBlock: '++block', |
|
disableLine: '--line {{key}}', |
|
enableLine: '++line', |
|
disableBlockRule: '-block {{rule}} {{key}}', |
|
enableBlockRule: '+block {{rule}}', |
|
disableLineRule: '-line {{rule}} {{key}}', |
|
enableLineRule: '+line {{rule}}', |
|
clear: 'clear', |
|
|
|
unused: 'Unused {{kind}} directive (no problems were reported).', |
|
unusedRule: |
|
"Unused {{kind}} directive (no problems were reported from '{{rule}}')." |
|
} |
|
}, |
|
/** |
|
* @param {RuleContext} context - The rule context. |
|
* @returns {RuleListener} AST event handlers. |
|
*/ |
|
create(context) { |
|
const options = context.options[0] || {} |
|
/** @type {boolean} */ |
|
const reportUnusedDisableDirectives = options.reportUnusedDisableDirectives |
|
const documentFragment = |
|
context.parserServices.getDocumentFragment && |
|
context.parserServices.getDocumentFragment() |
|
|
|
return { |
|
Program(node) { |
|
if (node.templateBody) { |
|
// Send directives to the post-process. |
|
for (const comment of node.templateBody.comments) { |
|
processBlock(context, comment, reportUnusedDisableDirectives) |
|
processLine(context, comment, reportUnusedDisableDirectives) |
|
} |
|
|
|
// Send a clear mark to the post-process. |
|
context.report({ |
|
loc: node.templateBody.loc.end, |
|
messageId: 'clear' |
|
}) |
|
} |
|
if (documentFragment) { |
|
// Send directives to the post-process. |
|
for (const comment of extractTopLevelDocumentFragmentComments( |
|
documentFragment |
|
)) { |
|
processBlock(context, comment, reportUnusedDisableDirectives) |
|
processLine(context, comment, reportUnusedDisableDirectives) |
|
} |
|
|
|
// Send a clear mark to the post-process. |
|
for (const element of extractTopLevelHTMLElements(documentFragment)) { |
|
context.report({ |
|
loc: element.loc.end, |
|
messageId: 'clear' |
|
}) |
|
} |
|
} |
|
} |
|
} |
|
} |
|
}
|
|
|