diff --git a/.eslintrc.js b/.eslintrc.js index 704f09732..af1b9e745 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -126,18 +126,21 @@ module.exports = { { files: ['lib/rules/*.js'], rules: { - 'consistent-docs-description': 'error', - 'no-invalid-meta': 'error', - 'no-invalid-meta-docs-categories': 'error', - 'eslint-plugin/require-meta-type': 'error', - 'require-meta-docs-url': [ + 'eslint-plugin/no-deprecated-context-methods': 'error', + 'eslint-plugin/no-only-tests': 'error', + 'eslint-plugin/prefer-object-rule': 'error', + 'eslint-plugin/require-meta-docs-description': 'error', + 'eslint-plugin/require-meta-docs-url': [ 'error', { pattern: `https://eslint.vuejs.org/rules/{{name}}.html` } ], - - 'eslint-plugin/fixer-return': 'off' + 'eslint-plugin/require-meta-has-suggestions': 'error', + 'eslint-plugin/require-meta-schema': 'error', + 'eslint-plugin/require-meta-type': 'error', + 'no-invalid-meta': 'error', + 'no-invalid-meta-docs-categories': 'error' } } ] diff --git a/eslint-internal-rules/.eslintrc.json b/eslint-internal-rules/.eslintrc.json index 936029a17..42c0f65b1 100644 --- a/eslint-internal-rules/.eslintrc.json +++ b/eslint-internal-rules/.eslintrc.json @@ -1,6 +1,6 @@ { "rules": { - "consistent-docs-description": "error", + "no-invalid-meta-docs-categories": "error", "no-invalid-meta": "error" } } diff --git a/eslint-internal-rules/consistent-docs-description.js b/eslint-internal-rules/consistent-docs-description.js deleted file mode 100644 index 6ff7f8673..000000000 --- a/eslint-internal-rules/consistent-docs-description.js +++ /dev/null @@ -1,141 +0,0 @@ -/** - * @fileoverview Internal rule to enforce meta.docs.description conventions. - * @author Vitor Balocco - */ - -'use strict' - -const ALLOWED_FIRST_WORDS = ['enforce', 'require', 'disallow'] - -// ------------------------------------------------------------------------------ -// Helpers -// ------------------------------------------------------------------------------ - -/** - * Gets the property of the Object node passed in that has the name specified. - * - * @param {string} property Name of the property to return. - * @param {ASTNode} node The ObjectExpression node. - * @returns {ASTNode} The Property node or null if not found. - */ -function getPropertyFromObject(property, node) { - if (node && node.type === 'ObjectExpression') { - const properties = node.properties - - for (let i = 0; i < properties.length; i++) { - if (properties[i].key.name === property) { - return properties[i] - } - } - } - return null -} - -/** - * Verifies that the meta.docs.description property follows our internal conventions. - * - * @param {RuleContext} context The ESLint rule context. - * @param {ASTNode} exportsNode ObjectExpression node that the rule exports. - * @returns {void} - */ -function checkMetaDocsDescription(context, exportsNode) { - if (exportsNode.type !== 'ObjectExpression') { - // if the exported node is not the correct format, "internal-no-invalid-meta" will already report this. - return - } - - const metaProperty = getPropertyFromObject('meta', exportsNode) - const metaDocs = - metaProperty && getPropertyFromObject('docs', metaProperty.value) - const metaDocsDescription = - metaDocs && getPropertyFromObject('description', metaDocs.value) - - if (!metaDocsDescription) { - // if there is no `meta.docs.description` property, "internal-no-invalid-meta" will already report this. - return - } - - const description = metaDocsDescription.value.value - - if (typeof description !== 'string') { - context.report({ - node: metaDocsDescription.value, - message: '`meta.docs.description` should be a string.' - }) - return - } - - if (description === '') { - context.report({ - node: metaDocsDescription.value, - message: '`meta.docs.description` should not be empty.' - }) - return - } - - if (description.indexOf(' ') === 0) { - context.report({ - node: metaDocsDescription.value, - message: '`meta.docs.description` should not start with whitespace.' - }) - return - } - - const firstWord = description.split(' ')[0] - - if (ALLOWED_FIRST_WORDS.indexOf(firstWord) === -1) { - context.report({ - node: metaDocsDescription.value, - message: - '`meta.docs.description` should start with one of the following words: {{ allowedWords }}. Started with "{{ firstWord }}" instead.', - data: { - allowedWords: ALLOWED_FIRST_WORDS.join(', '), - firstWord - } - }) - } - - if (description.endsWith('.')) { - context.report({ - node: metaDocsDescription.value, - message: '`meta.docs.description` should not end with `.`.', - fix(fixer) { - const pos = metaDocsDescription.range[1] - 2 - return fixer.removeRange([pos, pos + 1]) - } - }) - } -} - -// ------------------------------------------------------------------------------ -// Rule Definition -// ------------------------------------------------------------------------------ - -module.exports = { - meta: { - docs: { - description: - 'enforce correct conventions of `meta.docs.description` property in core rules', - categories: ['Internal'] - }, - fixable: 'code', - schema: [] - }, - - create(context) { - return { - AssignmentExpression(node) { - if ( - node.left && - node.right && - node.left.type === 'MemberExpression' && - node.left.object.name === 'module' && - node.left.property.name === 'exports' && - node.right.type === 'ObjectExpression' - ) { - checkMetaDocsDescription(context, node.right) - } - } - } - } -} diff --git a/eslint-internal-rules/no-invalid-meta-docs-categories.js b/eslint-internal-rules/no-invalid-meta-docs-categories.js index 74cfff1cd..5a86d16ab 100644 --- a/eslint-internal-rules/no-invalid-meta-docs-categories.js +++ b/eslint-internal-rules/no-invalid-meta-docs-categories.js @@ -108,16 +108,6 @@ function checkMetaValidity(context, exportsNode) { } } -/** - * Whether this node is the correct format for a rule definition or not. - * - * @param {ASTNode} node node that the rule exports. - * @returns {boolean} `true` if the exported node is the correct format for a rule definition - */ -function isCorrectExportsFormat(node) { - return node != null && node.type === 'ObjectExpression' -} - // ------------------------------------------------------------------------------ // Rule Definition // ------------------------------------------------------------------------------ @@ -149,15 +139,6 @@ module.exports = { }, 'Program:exit'(programNode) { - if (!isCorrectExportsFormat(exportsNode)) { - context.report({ - node: exportsNode || programNode, - message: - 'Rule does not export an Object. Make sure the rule follows the new rule format.' - }) - return - } - checkMetaValidity(context, exportsNode) } } diff --git a/eslint-internal-rules/no-invalid-meta.js b/eslint-internal-rules/no-invalid-meta.js index a032023f9..e96435cf9 100644 --- a/eslint-internal-rules/no-invalid-meta.js +++ b/eslint-internal-rules/no-invalid-meta.js @@ -49,18 +49,6 @@ function hasMetaDocs(metaPropertyNode) { return Boolean(getPropertyFromObject('docs', metaPropertyNode.value)) } -/** - * Whether this `meta` ObjectExpression has a `docs.description` property defined or not. - * - * @param {ASTNode} metaPropertyNode The `meta` ObjectExpression for this rule. - * @returns {boolean} `true` if a `docs.description` property exists. - */ -function hasMetaDocsDescription(metaPropertyNode) { - const metaDocs = getPropertyFromObject('docs', metaPropertyNode.value) - - return metaDocs && getPropertyFromObject('description', metaDocs.value) -} - /** * Whether this `meta` ObjectExpression has a `docs.category` property defined or not. * @@ -73,16 +61,6 @@ function hasMetaDocsCategories(metaPropertyNode) { return metaDocs && getPropertyFromObject('categories', metaDocs.value) } -/** - * Whether this `meta` ObjectExpression has a `schema` property defined or not. - * - * @param {ASTNode} metaPropertyNode The `meta` ObjectExpression for this rule. - * @returns {boolean} `true` if a `schema` property exists. - */ -function hasMetaSchema(metaPropertyNode) { - return getPropertyFromObject('schema', metaPropertyNode.value) -} - /** * Checks the validity of the meta definition of this rule and reports any errors found. * @@ -104,14 +82,6 @@ function checkMetaValidity(context, exportsNode) { return } - if (!hasMetaDocsDescription(metaProperty)) { - context.report( - metaProperty, - 'Rule is missing a meta.docs.description property.' - ) - return - } - if (!hasMetaDocsCategories(metaProperty)) { context.report( metaProperty, @@ -119,20 +89,6 @@ function checkMetaValidity(context, exportsNode) { ) return } - - if (!hasMetaSchema(metaProperty)) { - context.report(metaProperty, 'Rule is missing a meta.schema property.') - } -} - -/** - * Whether this node is the correct format for a rule definition or not. - * - * @param {ASTNode} node node that the rule exports. - * @returns {boolean} `true` if the exported node is the correct format for a rule definition - */ -function isCorrectExportsFormat(node) { - return node != null && node.type === 'ObjectExpression' } // ------------------------------------------------------------------------------ @@ -166,15 +122,6 @@ module.exports = { }, 'Program:exit'(programNode) { - if (!isCorrectExportsFormat(exportsNode)) { - context.report({ - node: exportsNode || programNode, - message: - 'Rule does not export an Object. Make sure the rule follows the new rule format.' - }) - return - } - checkMetaValidity(context, exportsNode) } } diff --git a/eslint-internal-rules/require-meta-docs-url.js b/eslint-internal-rules/require-meta-docs-url.js deleted file mode 100644 index 88cd9de0f..000000000 --- a/eslint-internal-rules/require-meta-docs-url.js +++ /dev/null @@ -1,280 +0,0 @@ -/** - * @author Toru Nagashima - * @author Teddy Katz - * - * Three functions `isNormalFunctionExpression`, `getKeyName`, and `getRuleInfo` - * are copied from https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/lib/utils.js - * - * I have a plan to send this rule to that plugin: https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/issues/55 - */ - -'use strict' - -// ----------------------------------------------------------------------------- -// Requirements -// ----------------------------------------------------------------------------- - -const path = require('path') - -// ----------------------------------------------------------------------------- -// Helpers -// ----------------------------------------------------------------------------- - -/** - * Determines whether a node is a 'normal' (i.e. non-async, non-generator) function expression. - * @param {ASTNode} node The node in question - * @returns {boolean} `true` if the node is a normal function expression - */ -function isNormalFunctionExpression(node) { - return ( - (node.type === 'FunctionExpression' || - node.type === 'ArrowFunctionExpression') && - !node.generator && - !node.async - ) -} - -/** - * Gets the key name of a Property, if it can be determined statically. - * @param {ASTNode} node The `Property` node - * @returns {string|null} The key name, or `null` if the name cannot be determined statically. - */ -function getKeyName(property) { - if (!property.computed && property.key.type === 'Identifier') { - return property.key.name - } - if (property.key.type === 'Literal') { - return `${property.key.value}` - } - if ( - property.key.type === 'TemplateLiteral' && - property.key.quasis.length === 1 - ) { - return property.key.quasis[0].value.cooked - } - return null -} - -/** -* Performs static analysis on an AST to try to determine the final value of `module.exports`. -* @param {ASTNode} ast The `Program` AST node -* @returns {Object} An object with keys `meta`, `create`, and `isNewStyle`. `meta` and `create` correspond to the AST nodes -for the final values of `module.exports.meta` and `module.exports.create`. `isNewStyle` will be `true` if `module.exports` -is an object, and `false` if module.exports is just the `create` function. If no valid ESLint rule info can be extracted -from the file, the return value will be `null`. -*/ -function getRuleInfo(ast) { - const INTERESTING_KEYS = new Set(['create', 'meta']) - let exportsVarOverridden = false - let exportsIsFunction = false - - const exportNodes = ast.body - .filter((statement) => statement.type === 'ExpressionStatement') - .map((statement) => statement.expression) - .filter((expression) => expression.type === 'AssignmentExpression') - .filter((expression) => expression.left.type === 'MemberExpression') - .reduce((currentExports, node) => { - if ( - node.left.object.type === 'Identifier' && - node.left.object.name === 'module' && - node.left.property.type === 'Identifier' && - node.left.property.name === 'exports' - ) { - exportsVarOverridden = true - - if (isNormalFunctionExpression(node.right)) { - // Check `module.exports = function () {}` - - exportsIsFunction = true - return { create: node.right, meta: null } - } else if (node.right.type === 'ObjectExpression') { - // Check `module.exports = { create: function () {}, meta: {} }` - - exportsIsFunction = false - return node.right.properties.reduce((parsedProps, prop) => { - const keyValue = getKeyName(prop) - if (INTERESTING_KEYS.has(keyValue)) { - parsedProps[keyValue] = prop.value - } - return parsedProps - }, {}) - } - return {} - } else if ( - !exportsIsFunction && - node.left.object.type === 'MemberExpression' && - node.left.object.object.type === 'Identifier' && - node.left.object.object.name === 'module' && - node.left.object.property.type === 'Identifier' && - node.left.object.property.name === 'exports' && - node.left.property.type === 'Identifier' && - INTERESTING_KEYS.has(node.left.property.name) - ) { - // Check `module.exports.create = () => {}` - - currentExports[node.left.property.name] = node.right - } else if ( - !exportsVarOverridden && - node.left.object.type === 'Identifier' && - node.left.object.name === 'exports' && - node.left.property.type === 'Identifier' && - INTERESTING_KEYS.has(node.left.property.name) - ) { - // Check `exports.create = () => {}` - - currentExports[node.left.property.name] = node.right - } - return currentExports - }, {}) - - return Object.prototype.hasOwnProperty.call(exportNodes, 'create') - ? Object.assign({ isNewStyle: !exportsIsFunction, meta: null }, exportNodes) - : null -} - -// ----------------------------------------------------------------------------- -// Rule Definition -// ----------------------------------------------------------------------------- - -module.exports = { - meta: { - docs: { - description: 'require rules to implement a meta.docs.url property', - categories: ['Rules'], - recommended: false - }, - fixable: 'code', - schema: [ - { - type: 'object', - properties: { - pattern: { type: 'string' } - }, - additionalProperties: false - } - ] - }, - - /** - * Creates AST event handlers for require-meta-docs-url. - * @param {RuleContext} context - The rule context. - * @returns {Object} AST event handlers. - */ - create(context) { - const options = context.options[0] || {} - const sourceCode = context.getSourceCode() - const filename = context.getFilename() - const ruleName = - filename === '' ? undefined : path.basename(filename, '.js') - const expectedUrl = - !options.pattern || !ruleName - ? undefined - : options.pattern.replace(/{{\s*name\s*}}/g, ruleName) - - /** - * Check whether a given node is the expected URL. - * @param {Node} node The node of property value to check. - * @returns {boolean} `true` if the node is the expected URL. - */ - function isExpectedUrl(node) { - return Boolean( - node && - node.type === 'Literal' && - typeof node.value === 'string' && - (expectedUrl === undefined || node.value === expectedUrl) - ) - } - - /** - * Insert a given property into a given object literal. - * @param {SourceCodeFixer} fixer The fixer. - * @param {Node} node The ObjectExpression node to insert a property. - * @param {string} propertyText The property code to insert. - * @returns {void} - */ - function insertProperty(fixer, node, propertyText) { - if (node.properties.length === 0) { - return fixer.replaceText(node, `{\n${propertyText}\n}`) - } - return fixer.insertTextAfter( - sourceCode.getLastToken(node.properties[node.properties.length - 1]), - `,\n${propertyText}` - ) - } - - return { - Program(node) { - const info = getRuleInfo(node) - if (!info) { - return - } - const metaNode = info.meta - const docsPropNode = - metaNode && - metaNode.properties && - metaNode.properties.find( - (p) => p.type === 'Property' && getKeyName(p) === 'docs' - ) - const urlPropNode = - docsPropNode && - docsPropNode.value.properties && - docsPropNode.value.properties.find( - (p) => p.type === 'Property' && getKeyName(p) === 'url' - ) - - if (isExpectedUrl(urlPropNode && urlPropNode.value)) { - return - } - - context.report({ - loc: - (urlPropNode && urlPropNode.value.loc) || - (docsPropNode && docsPropNode.value.loc) || - (metaNode && metaNode.loc) || - node.loc.start, - - message: !urlPropNode - ? 'Rules should export a `meta.docs.url` property.' - : !expectedUrl - ? '`meta.docs.url` property must be a string.' - : /* otherwise */ '`meta.docs.url` property must be `{{expectedUrl}}`.', - - data: { - expectedUrl - }, - - fix(fixer) { - if (expectedUrl) { - const urlString = JSON.stringify(expectedUrl) - if (urlPropNode) { - return fixer.replaceText(urlPropNode.value, urlString) - } - if ( - docsPropNode && - docsPropNode.value.type === 'ObjectExpression' - ) { - return insertProperty( - fixer, - docsPropNode.value, - `url: ${urlString}` - ) - } - if ( - !docsPropNode && - metaNode && - metaNode.type === 'ObjectExpression' - ) { - return insertProperty( - fixer, - metaNode, - `docs: {\nurl: ${urlString}\n}` - ) - } - } - return null - } - }) - } - } - } -} diff --git a/lib/rules/comment-directive.js b/lib/rules/comment-directive.js index 9ff9d2fdf..942860f60 100644 --- a/lib/rules/comment-directive.js +++ b/lib/rules/comment-directive.js @@ -1,7 +1,7 @@ /** * @author Toru Nagashima */ -/* eslint-disable eslint-plugin/report-message-format, consistent-docs-description */ +/* eslint-disable eslint-plugin/report-message-format */ 'use strict' @@ -285,7 +285,7 @@ module.exports = { meta: { type: 'problem', docs: { - description: 'support comment-directives in `