From 6589d4a5f62345dc3272821f35ba3d9fa009d626 Mon Sep 17 00:00:00 2001 From: Toru Nagashima Date: Tue, 9 Jan 2018 16:51:53 +0900 Subject: [PATCH 1/5] Chore: move `create` function into object literal I found that `eslint-plugin-eslint-plugin` rules are using `create` function to detect ESLint rule files. If `create` is not a method (includes a variable reference), `eslint-plugin-eslint-plugin` rules don't work properly. --- lib/rules/attribute-hyphenation.js | 88 +++++---- lib/rules/comment-directive.js | 30 ++- lib/rules/html-closing-bracket-newline.js | 85 ++++----- lib/rules/html-end-tags.js | 73 +++----- lib/rules/html-quotes.js | 90 ++++----- lib/rules/html-self-closing.js | 123 ++++++------ lib/rules/name-property-casing.js | 66 ++++--- lib/rules/no-async-in-computed-properties.js | 167 ++++++++--------- lib/rules/no-confusing-v-for-v-if.js | 41 ++-- lib/rules/no-dupe-keys.js | 60 +++--- lib/rules/no-duplicate-attributes.js | 97 +++++----- lib/rules/no-parsing-error.js | 69 +++---- lib/rules/no-reserved-keys.js | 66 ++++--- lib/rules/no-shared-component-data.js | 72 ++++--- .../no-side-effects-in-computed-properties.js | 100 +++++----- lib/rules/no-template-key.js | 41 ++-- lib/rules/no-textarea-mustache.js | 45 ++--- lib/rules/no-unused-vars.js | 51 +++-- lib/rules/order-in-components.js | 75 ++++---- lib/rules/require-component-is.js | 41 ++-- lib/rules/require-prop-types.js | 138 +++++++------- lib/rules/require-render-return.js | 60 +++--- lib/rules/require-v-for-key.js | 70 +++---- lib/rules/return-in-computed-property.js | 68 ++++--- lib/rules/script-indent.js | 6 +- lib/rules/v-bind-style.js | 57 +++--- lib/rules/v-on-style.js | 59 +++--- lib/rules/valid-template-root.js | 177 ++++++++---------- lib/rules/valid-v-bind.js | 59 +++--- lib/rules/valid-v-cloak.js | 69 +++---- lib/rules/valid-v-else-if.js | 115 +++++------- lib/rules/valid-v-else.js | 115 +++++------- lib/rules/valid-v-for.js | 163 ++++++++-------- lib/rules/valid-v-html.js | 69 +++---- lib/rules/valid-v-if.js | 101 +++++----- lib/rules/valid-v-model.js | 144 +++++++------- lib/rules/valid-v-on.js | 57 +++--- lib/rules/valid-v-once.js | 69 +++---- lib/rules/valid-v-pre.js | 69 +++---- lib/rules/valid-v-show.js | 69 +++---- lib/rules/valid-v-text.js | 69 +++---- 41 files changed, 1494 insertions(+), 1789 deletions(-) diff --git a/lib/rules/attribute-hyphenation.js b/lib/rules/attribute-hyphenation.js index 5dec55b99..61affc0f9 100644 --- a/lib/rules/attribute-hyphenation.js +++ b/lib/rules/attribute-hyphenation.js @@ -11,50 +11,6 @@ const casing = require('../utils/casing') // Rule Definition // ------------------------------------------------------------------------------ -function create (context) { - const sourceCode = context.getSourceCode() - const options = context.options[0] - const useHyphenated = options !== 'never' - - const caseConverter = casing.getConverter(useHyphenated ? 'kebab-case' : 'camelCase') - - function reportIssue (node, name) { - const text = sourceCode.getText(node.key) - - context.report({ - node: node.key, - loc: node.loc, - message: useHyphenated ? "Attribute '{{text}}' must be hyphenated." : "Attribute '{{text}}' cann't be hyphenated.", - data: { - text - }, - fix: fixer => fixer.replaceText(node.key, text.replace(name, caseConverter(name))) - }) - } - - function isIgnoredAttribute (value) { - if (value.indexOf('data-') !== -1 || value.indexOf('aria-') !== -1) { - return true - } - return useHyphenated ? value.toLowerCase() === value : !/-/.test(value) - } - - // ---------------------------------------------------------------------- - // Public - // ---------------------------------------------------------------------- - - return utils.defineTemplateBodyVisitor(context, { - VAttribute (node) { - if (!utils.isCustomComponent(node.parent.parent)) return - - const name = !node.directive ? node.key.rawName : node.key.name === 'bind' ? node.key.raw.argument : false - if (!name || isIgnoredAttribute(name)) return - - reportIssue(node, name) - } - }) -} - module.exports = { meta: { docs: { @@ -70,5 +26,47 @@ module.exports = { ] }, - create + create (context) { + const sourceCode = context.getSourceCode() + const options = context.options[0] + const useHyphenated = options !== 'never' + + const caseConverter = casing.getConverter(useHyphenated ? 'kebab-case' : 'camelCase') + + function reportIssue (node, name) { + const text = sourceCode.getText(node.key) + + context.report({ + node: node.key, + loc: node.loc, + message: useHyphenated ? "Attribute '{{text}}' must be hyphenated." : "Attribute '{{text}}' cann't be hyphenated.", + data: { + text + }, + fix: fixer => fixer.replaceText(node.key, text.replace(name, caseConverter(name))) + }) + } + + function isIgnoredAttribute (value) { + if (value.indexOf('data-') !== -1 || value.indexOf('aria-') !== -1) { + return true + } + return useHyphenated ? value.toLowerCase() === value : !/-/.test(value) + } + + // ---------------------------------------------------------------------- + // Public + // ---------------------------------------------------------------------- + + return utils.defineTemplateBodyVisitor(context, { + VAttribute (node) { + if (!utils.isCustomComponent(node.parent.parent)) return + + const name = !node.directive ? node.key.rawName : node.key.name === 'bind' ? node.key.raw.argument : false + if (!name || isIgnoredAttribute(name)) return + + reportIssue(node, name) + } + }) + } } diff --git a/lib/rules/comment-directive.js b/lib/rules/comment-directive.js index d9a890d0e..4a79cda4f 100644 --- a/lib/rules/comment-directive.js +++ b/lib/rules/comment-directive.js @@ -99,23 +99,6 @@ function processLine (context, comment) { } } -/** - * 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 // ----------------------------------------------------------------------------- @@ -129,5 +112,16 @@ module.exports = { }, schema: [] }, - create + + create (context) { + return { + Program (node) { + const comments = (node.templateBody && node.templateBody.comments) || [] + for (const comment of comments) { + processBlock(context, comment) + processLine(context, comment) + } + } + } + } } diff --git a/lib/rules/html-closing-bracket-newline.js b/lib/rules/html-closing-bracket-newline.js index c68643761..e596f566a 100644 --- a/lib/rules/html-closing-bracket-newline.js +++ b/lib/rules/html-closing-bracket-newline.js @@ -23,57 +23,11 @@ function getPhrase (lineBreaks) { } } -/** - * Creates AST event handlers for html-closing-bracket-newline. - * - * @param {RuleContext} context - The rule context. - * @returns {object} AST event handlers. - */ -function create (context) { - const options = context.options[0] || {} - const template = context.parserServices.getTemplateBodyTokenStore && context.parserServices.getTemplateBodyTokenStore() - - return utils.defineTemplateBodyVisitor(context, { - 'VStartTag, VEndTag' (node) { - const closingBracketToken = template.getLastToken(node) - if (closingBracketToken.type !== 'HTMLSelfClosingTagClose' && closingBracketToken.type !== 'HTMLTagClose') { - return - } - - const prevToken = template.getTokenBefore(closingBracketToken) - const type = (node.loc.start.line === prevToken.loc.end.line) ? 'singleline' : 'multiline' - const expectedLineBreaks = (options[type] === 'always') ? 1 : 0 - const actualLineBreaks = (closingBracketToken.loc.start.line - prevToken.loc.end.line) - - if (actualLineBreaks !== expectedLineBreaks) { - context.report({ - node, - loc: { - start: prevToken.loc.end, - end: closingBracketToken.loc.start - }, - message: 'Expected {{expected}} before closing bracket, but {{actual}} found.', - data: { - expected: getPhrase(expectedLineBreaks), - actual: getPhrase(actualLineBreaks) - }, - fix (fixer) { - const range = [prevToken.range[1], closingBracketToken.range[0]] - const text = '\n'.repeat(expectedLineBreaks) - return fixer.replaceTextRange(range, text) - } - }) - } - } - }) -} - // ------------------------------------------------------------------------------ // Rule Definition // ------------------------------------------------------------------------------ module.exports = { - create, meta: { docs: { description: "require or disallow a line break before tag's closing brackets", @@ -89,5 +43,44 @@ module.exports = { }, additionalProperties: false }] + }, + + create (context) { + const options = context.options[0] || {} + const template = context.parserServices.getTemplateBodyTokenStore && context.parserServices.getTemplateBodyTokenStore() + + return utils.defineTemplateBodyVisitor(context, { + 'VStartTag, VEndTag' (node) { + const closingBracketToken = template.getLastToken(node) + if (closingBracketToken.type !== 'HTMLSelfClosingTagClose' && closingBracketToken.type !== 'HTMLTagClose') { + return + } + + const prevToken = template.getTokenBefore(closingBracketToken) + const type = (node.loc.start.line === prevToken.loc.end.line) ? 'singleline' : 'multiline' + const expectedLineBreaks = (options[type] === 'always') ? 1 : 0 + const actualLineBreaks = (closingBracketToken.loc.start.line - prevToken.loc.end.line) + + if (actualLineBreaks !== expectedLineBreaks) { + context.report({ + node, + loc: { + start: prevToken.loc.end, + end: closingBracketToken.loc.start + }, + message: 'Expected {{expected}} before closing bracket, but {{actual}} found.', + data: { + expected: getPhrase(expectedLineBreaks), + actual: getPhrase(actualLineBreaks) + }, + fix (fixer) { + const range = [prevToken.range[1], closingBracketToken.range[0]] + const text = '\n'.repeat(expectedLineBreaks) + return fixer.replaceTextRange(range, text) + } + }) + } + } + }) } } diff --git a/lib/rules/html-end-tags.js b/lib/rules/html-end-tags.js index 39bb9a310..df71a7356 100644 --- a/lib/rules/html-end-tags.js +++ b/lib/rules/html-end-tags.js @@ -11,53 +11,11 @@ const utils = require('../utils') -// ------------------------------------------------------------------------------ -// Helpers -// ------------------------------------------------------------------------------ - -/** - * Creates AST event handlers for html-end-tags. - * - * @param {RuleContext} context - The rule context. - * @returns {Object} AST event handlers. - */ -function create (context) { - let hasInvalidEOF = false - - return utils.defineTemplateBodyVisitor(context, { - VElement (node) { - if (hasInvalidEOF) { - return - } - - const name = node.name - const isVoid = utils.isHtmlVoidElementName(name) - const isSelfClosing = node.startTag.selfClosing - const hasEndTag = node.endTag != null - - if (!isVoid && !hasEndTag && !isSelfClosing) { - context.report({ - node: node.startTag, - loc: node.startTag.loc, - message: "'<{{name}}>' should have end tag.", - data: { name }, - fix: (fixer) => fixer.insertTextAfter(node, ``) - }) - } - } - }, { - Program (node) { - hasInvalidEOF = utils.hasInvalidEOF(node) - } - }) -} - // ------------------------------------------------------------------------------ // Rule Definition // ------------------------------------------------------------------------------ module.exports = { - create, meta: { docs: { description: 'enforce end tag style', @@ -66,5 +24,36 @@ module.exports = { }, fixable: 'code', schema: [] + }, + + create (context) { + let hasInvalidEOF = false + + return utils.defineTemplateBodyVisitor(context, { + VElement (node) { + if (hasInvalidEOF) { + return + } + + const name = node.name + const isVoid = utils.isHtmlVoidElementName(name) + const isSelfClosing = node.startTag.selfClosing + const hasEndTag = node.endTag != null + + if (!isVoid && !hasEndTag && !isSelfClosing) { + context.report({ + node: node.startTag, + loc: node.startTag.loc, + message: "'<{{name}}>' should have end tag.", + data: { name }, + fix: (fixer) => fixer.insertTextAfter(node, ``) + }) + } + } + }, { + Program (node) { + hasInvalidEOF = utils.hasInvalidEOF(node) + } + }) } } diff --git a/lib/rules/html-quotes.js b/lib/rules/html-quotes.js index bd6431af4..e4dd66c35 100644 --- a/lib/rules/html-quotes.js +++ b/lib/rules/html-quotes.js @@ -11,62 +11,11 @@ const utils = require('../utils') -// ------------------------------------------------------------------------------ -// Helpers -// ------------------------------------------------------------------------------ - -/** - * Creates AST event handlers for html-quotes. - * - * @param {RuleContext} context - The rule context. - * @returns {Object} AST event handlers. - */ -function create (context) { - const sourceCode = context.getSourceCode() - const double = context.options[0] !== 'single' - const quoteChar = double ? '"' : "'" - const quoteName = double ? 'double quotes' : 'single quotes' - const quotePattern = double ? /"/g : /'/g - const quoteEscaped = double ? '"' : ''' - let hasInvalidEOF - - return utils.defineTemplateBodyVisitor(context, { - 'VAttribute[value!=null]' (node) { - if (hasInvalidEOF) { - return - } - - const text = sourceCode.getText(node.value) - const firstChar = text[0] - - if (firstChar !== quoteChar) { - context.report({ - node: node.value, - loc: node.value.loc, - message: 'Expected to be enclosed by {{kind}}.', - data: { kind: quoteName }, - fix (fixer) { - const contentText = (firstChar === "'" || firstChar === '"') ? text.slice(1, -1) : text - const replacement = quoteChar + contentText.replace(quotePattern, quoteEscaped) + quoteChar - - return fixer.replaceText(node.value, replacement) - } - }) - } - } - }, { - Program (node) { - hasInvalidEOF = utils.hasInvalidEOF(node) - } - }) -} - // ------------------------------------------------------------------------------ // Rule Definition // ------------------------------------------------------------------------------ module.exports = { - create, meta: { docs: { description: 'enforce quotes style of HTML attributes', @@ -77,5 +26,44 @@ module.exports = { schema: [ { enum: ['double', 'single'] } ] + }, + + create (context) { + const sourceCode = context.getSourceCode() + const double = context.options[0] !== 'single' + const quoteChar = double ? '"' : "'" + const quoteName = double ? 'double quotes' : 'single quotes' + const quotePattern = double ? /"/g : /'/g + const quoteEscaped = double ? '"' : ''' + let hasInvalidEOF + + return utils.defineTemplateBodyVisitor(context, { + 'VAttribute[value!=null]' (node) { + if (hasInvalidEOF) { + return + } + + const text = sourceCode.getText(node.value) + const firstChar = text[0] + + if (firstChar !== quoteChar) { + context.report({ + node: node.value, + loc: node.value.loc, + message: 'Expected to be enclosed by {{kind}}.', + data: { kind: quoteName }, + fix (fixer) { + const contentText = (firstChar === "'" || firstChar === '"') ? text.slice(1, -1) : text + const replacement = quoteChar + contentText.replace(quotePattern, quoteEscaped) + quoteChar + return fixer.replaceText(node.value, replacement) + } + }) + } + } + }, { + Program (node) { + hasInvalidEOF = utils.hasInvalidEOF(node) + } + }) } } diff --git a/lib/rules/html-self-closing.js b/lib/rules/html-self-closing.js index b3c5a7a90..51c38c3fc 100644 --- a/lib/rules/html-self-closing.js +++ b/lib/rules/html-self-closing.js @@ -79,76 +79,11 @@ function isEmpty (node, sourceCode) { return sourceCode.text.slice(start, end).trim() === '' } -/** - * Creates AST event handlers for html-self-closing. - * - * @param {RuleContext} context - The rule context. - * @returns {object} AST event handlers. - */ -function create (context) { - const sourceCode = context.getSourceCode() - const options = parseOptions(context.options[0]) - let hasInvalidEOF = false - - return utils.defineTemplateBodyVisitor(context, { - 'VElement' (node) { - if (hasInvalidEOF) { - return - } - - const elementType = getElementType(node) - const mode = options[elementType] - - if (mode === 'always' && !node.startTag.selfClosing && isEmpty(node, sourceCode)) { - context.report({ - node, - loc: node.loc, - message: 'Require self-closing on {{elementType}} (<{{name}}>).', - data: { elementType, name: node.rawName }, - fix: (fixer) => { - const tokens = context.parserServices.getTemplateBodyTokenStore() - const close = tokens.getLastToken(node.startTag) - if (close.type !== 'HTMLTagClose') { - return null - } - return fixer.replaceTextRange([close.range[0], node.range[1]], '/>') - } - }) - } - - if (mode === 'never' && node.startTag.selfClosing) { - context.report({ - node, - loc: node.loc, - message: 'Disallow self-closing on {{elementType}} (<{{name}}/>).', - data: { elementType, name: node.rawName }, - fix: (fixer) => { - const tokens = context.parserServices.getTemplateBodyTokenStore() - const close = tokens.getLastToken(node.startTag) - if (close.type !== 'HTMLSelfClosingTagClose') { - return null - } - if (elementType === ELEMENT_TYPE.VOID) { - return fixer.replaceText(close, '>') - } - return fixer.replaceText(close, `>`) - } - }) - } - } - }, { - Program (node) { - hasInvalidEOF = utils.hasInvalidEOF(node) - } - }) -} - // ------------------------------------------------------------------------------ // Rule Definition // ------------------------------------------------------------------------------ module.exports = { - create, meta: { docs: { description: 'enforce self-closing style', @@ -182,5 +117,63 @@ module.exports = { }], maxItems: 1 } + }, + + create (context) { + const sourceCode = context.getSourceCode() + const options = parseOptions(context.options[0]) + let hasInvalidEOF = false + + return utils.defineTemplateBodyVisitor(context, { + 'VElement' (node) { + if (hasInvalidEOF) { + return + } + + const elementType = getElementType(node) + const mode = options[elementType] + + if (mode === 'always' && !node.startTag.selfClosing && isEmpty(node, sourceCode)) { + context.report({ + node, + loc: node.loc, + message: 'Require self-closing on {{elementType}} (<{{name}}>).', + data: { elementType, name: node.rawName }, + fix: (fixer) => { + const tokens = context.parserServices.getTemplateBodyTokenStore() + const close = tokens.getLastToken(node.startTag) + if (close.type !== 'HTMLTagClose') { + return null + } + return fixer.replaceTextRange([close.range[0], node.range[1]], '/>') + } + }) + } + + if (mode === 'never' && node.startTag.selfClosing) { + context.report({ + node, + loc: node.loc, + message: 'Disallow self-closing on {{elementType}} (<{{name}}/>).', + data: { elementType, name: node.rawName }, + fix: (fixer) => { + const tokens = context.parserServices.getTemplateBodyTokenStore() + const close = tokens.getLastToken(node.startTag) + if (close.type !== 'HTMLSelfClosingTagClose') { + return null + } + if (elementType === ELEMENT_TYPE.VOID) { + return fixer.replaceText(close, '>') + } + return fixer.replaceText(close, `>`) + } + }) + } + } + }, { + Program (node) { + hasInvalidEOF = utils.hasInvalidEOF(node) + } + }) } } diff --git a/lib/rules/name-property-casing.js b/lib/rules/name-property-casing.js index e9422eb0e..e62b1781f 100644 --- a/lib/rules/name-property-casing.js +++ b/lib/rules/name-property-casing.js @@ -12,39 +12,6 @@ const allowedCaseOptions = ['PascalCase', 'kebab-case'] // Rule Definition // ------------------------------------------------------------------------------ -function create (context) { - const options = context.options[0] - const caseType = allowedCaseOptions.indexOf(options) !== -1 ? options : 'PascalCase' - - // ---------------------------------------------------------------------- - // Public - // ---------------------------------------------------------------------- - - return utils.executeOnVue(context, (obj) => { - const node = obj.properties - .find(item => ( - item.type === 'Property' && - item.key.name === 'name' && - item.value.type === 'Literal' - )) - - if (!node) return - - const value = casing.getConverter(caseType)(node.value.value) - if (value !== node.value.value) { - context.report({ - node: node.value, - message: 'Property name "{{value}}" is not {{caseType}}.', - data: { - value: node.value.value, - caseType: caseType - }, - fix: fixer => fixer.replaceText(node.value, node.value.raw.replace(node.value.value, value)) - }) - } - }) -} - module.exports = { meta: { docs: { @@ -60,5 +27,36 @@ module.exports = { ] }, - create + create (context) { + const options = context.options[0] + const caseType = allowedCaseOptions.indexOf(options) !== -1 ? options : 'PascalCase' + + // ---------------------------------------------------------------------- + // Public + // ---------------------------------------------------------------------- + + return utils.executeOnVue(context, (obj) => { + const node = obj.properties + .find(item => ( + item.type === 'Property' && + item.key.name === 'name' && + item.value.type === 'Literal' + )) + + if (!node) return + + const value = casing.getConverter(caseType)(node.value.value) + if (value !== node.value.value) { + context.report({ + node: node.value, + message: 'Property name "{{value}}" is not {{caseType}}.', + data: { + value: node.value.value, + caseType: caseType + }, + fix: fixer => fixer.replaceText(node.value, node.value.raw.replace(node.value.value, value)) + }) + } + }) + } } diff --git a/lib/rules/no-async-in-computed-properties.js b/lib/rules/no-async-in-computed-properties.js index 5327f82b0..4b7aea607 100644 --- a/lib/rules/no-async-in-computed-properties.js +++ b/lib/rules/no-async-in-computed-properties.js @@ -55,103 +55,102 @@ function isPromise (node) { return false } -function create (context) { - const forbiddenNodes = [] - - const expressionTypes = { - promise: 'asynchronous action', - await: 'await operator', - async: 'async function declaration', - new: 'Promise object', - timed: 'timed function' - } +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ - function onFunctionEnter (node) { - if (node.async) { - forbiddenNodes.push({ - node: node, - type: 'async' - }) +module.exports = { + meta: { + docs: { + description: 'disallow asynchronous actions in computed properties', + category: 'essential', + url: 'https://github.com/vuejs/eslint-plugin-vue/blob/master/docs/rules/no-async-in-computed-properties.md' + }, + fixable: null, + schema: [] + }, + + create (context) { + const forbiddenNodes = [] + + const expressionTypes = { + promise: 'asynchronous action', + await: 'await operator', + async: 'async function declaration', + new: 'Promise object', + timed: 'timed function' } - } - return Object.assign({}, - { - FunctionDeclaration: onFunctionEnter, + function onFunctionEnter (node) { + if (node.async) { + forbiddenNodes.push({ + node: node, + type: 'async' + }) + } + } - FunctionExpression: onFunctionEnter, + return Object.assign({}, + { + FunctionDeclaration: onFunctionEnter, - ArrowFunctionExpression: onFunctionEnter, + FunctionExpression: onFunctionEnter, - NewExpression (node) { - if (node.callee.name === 'Promise') { - forbiddenNodes.push({ - node: node, - type: 'new' - }) - } - }, + ArrowFunctionExpression: onFunctionEnter, - CallExpression (node) { - if (isPromise(node)) { - forbiddenNodes.push({ - node: node, - type: 'promise' - }) - } - if (isTimedFunction(node)) { + NewExpression (node) { + if (node.callee.name === 'Promise') { + forbiddenNodes.push({ + node: node, + type: 'new' + }) + } + }, + + CallExpression (node) { + if (isPromise(node)) { + forbiddenNodes.push({ + node: node, + type: 'promise' + }) + } + if (isTimedFunction(node)) { + forbiddenNodes.push({ + node: node, + type: 'timed' + }) + } + }, + + AwaitExpression (node) { forbiddenNodes.push({ node: node, - type: 'timed' + type: 'await' }) } }, - - AwaitExpression (node) { - forbiddenNodes.push({ - node: node, - type: 'await' - }) - } - }, - utils.executeOnVue(context, (obj) => { - const computedProperties = utils.getComputedProperties(obj) - - computedProperties.forEach(cp => { - forbiddenNodes.forEach(el => { - if ( - cp.value && - el.node.loc.start.line >= cp.value.loc.start.line && - el.node.loc.end.line <= cp.value.loc.end.line - ) { - context.report({ - node: el.node, - message: 'Unexpected {{expressionName}} in "{{propertyName}}" computed property.', - data: { - expressionName: expressionTypes[el.type], - propertyName: cp.key - } - }) - } + utils.executeOnVue(context, (obj) => { + const computedProperties = utils.getComputedProperties(obj) + + computedProperties.forEach(cp => { + forbiddenNodes.forEach(el => { + if ( + cp.value && + el.node.loc.start.line >= cp.value.loc.start.line && + el.node.loc.end.line <= cp.value.loc.end.line + ) { + context.report({ + node: el.node, + message: 'Unexpected {{expressionName}} in "{{propertyName}}" computed property.', + data: { + expressionName: expressionTypes[el.type], + propertyName: cp.key + } + }) + } + }) }) }) - }) - ) -} - -// ------------------------------------------------------------------------------ -// Rule Definition -// ------------------------------------------------------------------------------ - -module.exports = { - create, - meta: { - docs: { - description: 'disallow asynchronous actions in computed properties', - category: 'essential', - url: 'https://github.com/vuejs/eslint-plugin-vue/blob/master/docs/rules/no-async-in-computed-properties.md' - }, - fixable: null, - schema: [] + ) } } diff --git a/lib/rules/no-confusing-v-for-v-if.js b/lib/rules/no-confusing-v-for-v-if.js index e931be723..f19d5428d 100644 --- a/lib/rules/no-confusing-v-for-v-if.js +++ b/lib/rules/no-confusing-v-for-v-if.js @@ -30,41 +30,34 @@ function isUsingIterationVar (vIf) { ) } -/** - * Creates AST event handlers for no-confusing-v-for-v-if. - * - * @param {RuleContext} context - The rule context. - * @returns {Object} AST event handlers. - */ -function create (context) { - return utils.defineTemplateBodyVisitor(context, { - "VAttribute[directive=true][key.name='if']" (node) { - const element = node.parent.parent - - if (utils.hasDirective(element, 'for') && !isUsingIterationVar(node)) { - context.report({ - node, - loc: node.loc, - message: "This 'v-if' should be moved to the wrapper element." - }) - } - } - }) -} - // ------------------------------------------------------------------------------ // Rule Definition // ------------------------------------------------------------------------------ module.exports = { - create, meta: { docs: { description: 'disallow confusing `v-for` and `v-if` on the same element', category: 'recommended', url: 'https://github.com/vuejs/eslint-plugin-vue/blob/master/docs/rules/no-confusing-v-for-v-if.md' }, - fixable: false, + fixable: null, schema: [] + }, + + create (context) { + return utils.defineTemplateBodyVisitor(context, { + "VAttribute[directive=true][key.name='if']" (node) { + const element = node.parent.parent + + if (utils.hasDirective(element, 'for') && !isUsingIterationVar(node)) { + context.report({ + node, + loc: node.loc, + message: "This 'v-if' should be moved to the wrapper element." + }) + } + } + }) } } diff --git a/lib/rules/no-dupe-keys.js b/lib/rules/no-dupe-keys.js index eccb1a9dd..8fa3b514f 100644 --- a/lib/rules/no-dupe-keys.js +++ b/lib/rules/no-dupe-keys.js @@ -12,38 +12,6 @@ const utils = require('../utils') const GROUP_NAMES = ['props', 'computed', 'data', 'methods'] -function create (context) { - const options = context.options[0] || {} - const groups = new Set(GROUP_NAMES.concat(options.groups || [])) - - // ---------------------------------------------------------------------- - // Public - // ---------------------------------------------------------------------- - - return utils.executeOnVue(context, (obj) => { - const usedNames = [] - const properties = utils.iterateProperties(obj, groups) - - for (const o of properties) { - if (usedNames.indexOf(o.name) !== -1) { - context.report({ - node: o.node, - message: "Duplicated key '{{name}}'.", - data: { - name: o.name - } - }) - } - - usedNames.push(o.name) - } - }) -} - -// ------------------------------------------------------------------------------ -// Rule Definition -// ------------------------------------------------------------------------------ - module.exports = { meta: { docs: { @@ -65,5 +33,31 @@ module.exports = { ] }, - create + create (context) { + const options = context.options[0] || {} + const groups = new Set(GROUP_NAMES.concat(options.groups || [])) + + // ---------------------------------------------------------------------- + // Public + // ---------------------------------------------------------------------- + + return utils.executeOnVue(context, (obj) => { + const usedNames = [] + const properties = utils.iterateProperties(obj, groups) + + for (const o of properties) { + if (usedNames.indexOf(o.name) !== -1) { + context.report({ + node: o.node, + message: "Duplicated key '{{name}}'.", + data: { + name: o.name + } + }) + } + + usedNames.push(o.name) + } + }) + } } diff --git a/lib/rules/no-duplicate-attributes.js b/lib/rules/no-duplicate-attributes.js index cab14a6c8..147a6e9ac 100644 --- a/lib/rules/no-duplicate-attributes.js +++ b/lib/rules/no-duplicate-attributes.js @@ -30,69 +30,18 @@ function getName (attribute) { return null } -/** - * Creates AST event handlers for no-duplicate-attributes. - * - * @param {RuleContext} context - The rule context. - * @returns {Object} AST event handlers. - */ -function create (context) { - const options = context.options[0] || {} - const allowCoexistStyle = options.allowCoexistStyle !== false - const allowCoexistClass = options.allowCoexistClass !== false - - const directiveNames = new Set() - const attributeNames = new Set() - - function isDuplicate (name, isDirective) { - if ((allowCoexistStyle && name === 'style') || (allowCoexistClass && name === 'class')) { - return isDirective ? directiveNames.has(name) : attributeNames.has(name) - } - return directiveNames.has(name) || attributeNames.has(name) - } - - return utils.defineTemplateBodyVisitor(context, { - 'VStartTag' () { - directiveNames.clear() - attributeNames.clear() - }, - 'VAttribute' (node) { - const name = getName(node) - if (name == null) { - return - } - - if (isDuplicate(name, node.directive)) { - context.report({ - node, - loc: node.loc, - message: "Duplicate attribute '{{name}}'.", - data: { name } - }) - } - - if (node.directive) { - directiveNames.add(name) - } else { - attributeNames.add(name) - } - } - }) -} - // ------------------------------------------------------------------------------ // Rule Definition // ------------------------------------------------------------------------------ module.exports = { - create, meta: { docs: { description: 'disallow duplication of attributes', category: 'essential', url: 'https://github.com/vuejs/eslint-plugin-vue/blob/master/docs/rules/no-duplicate-attributes.md' }, - fixable: false, + fixable: null, schema: [ { @@ -107,5 +56,49 @@ module.exports = { } } ] + }, + + create (context) { + const options = context.options[0] || {} + const allowCoexistStyle = options.allowCoexistStyle !== false + const allowCoexistClass = options.allowCoexistClass !== false + + const directiveNames = new Set() + const attributeNames = new Set() + + function isDuplicate (name, isDirective) { + if ((allowCoexistStyle && name === 'style') || (allowCoexistClass && name === 'class')) { + return isDirective ? directiveNames.has(name) : attributeNames.has(name) + } + return directiveNames.has(name) || attributeNames.has(name) + } + + return utils.defineTemplateBodyVisitor(context, { + 'VStartTag' () { + directiveNames.clear() + attributeNames.clear() + }, + 'VAttribute' (node) { + const name = getName(node) + if (name == null) { + return + } + + if (isDuplicate(name, node.directive)) { + context.report({ + node, + loc: node.loc, + message: "Duplicate attribute '{{name}}'.", + data: { name } + }) + } + + if (node.directive) { + directiveNames.add(name) + } else { + attributeNames.add(name) + } + } + }) } } diff --git a/lib/rules/no-parsing-error.js b/lib/rules/no-parsing-error.js index 720396bca..265164773 100644 --- a/lib/rules/no-parsing-error.js +++ b/lib/rules/no-parsing-error.js @@ -49,55 +49,18 @@ const DEFAULT_OPTIONS = Object.freeze(Object.assign(Object.create(null), { 'x-invalid-namespace': true })) -/** - * Creates AST event handlers for no-parsing-error. - * - * @param {RuleContext} context - The rule context. - * @returns {Object} AST event handlers. - */ -function create (context) { - const options = Object.assign({}, DEFAULT_OPTIONS, context.options[0] || {}) - - return { - Program (program) { - const node = program.templateBody - if (node == null || node.errors == null) { - return - } - - for (const error of node.errors) { - if (error.code && !options[error.code]) { - continue - } - - context.report({ - node, - loc: { line: error.lineNumber, column: error.column }, - message: 'Parsing error: {{message}}.', - data: { - message: error.message.endsWith('.') - ? error.message.slice(0, -1) - : error.message - } - }) - } - } - } -} - // ------------------------------------------------------------------------------ // Rule Definition // ------------------------------------------------------------------------------ module.exports = { - create, meta: { docs: { description: 'disallow parsing errors in `