diff --git a/.circleci/config.yml b/.circleci/config.yml index c10b2139e..b80032ae8 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -4,6 +4,8 @@ workflows: jobs: - node-v8 - node-v10 + - eslint-v7 + - eslint-v8 - node-v12 - node-v14 - lint @@ -54,6 +56,42 @@ jobs: <<: *node-base docker: - image: node:10 + eslint-v7: + docker: + - image: node:10 + steps: + - run: + name: Versions + command: npm version + - checkout + - run: + name: Install eslint@7 + command: | + npm install --save-exact eslint@7 + - run: + name: Install dependencies + command: npm install + - run: + name: Test + command: npm test + eslint-v8: + docker: + - image: node:14 + steps: + - run: + name: Versions + command: npm version + - checkout + - run: + name: Install eslint@8 + command: | + npm install --save-exact eslint@^8.0.0-0 + - run: + name: Install dependencies + command: npm install + - run: + name: Test + command: npm test node-v12: <<: *node-base docker: 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/docs/rules/README.md b/docs/rules/README.md index 4d87f7cc9..5f86b7fc7 100644 --- a/docs/rules/README.md +++ b/docs/rules/README.md @@ -324,6 +324,7 @@ For example: | [vue/no-use-computed-property-like-method](./no-use-computed-property-like-method.md) | disallow use computed property like method | | | [vue/no-useless-mustaches](./no-useless-mustaches.md) | disallow unnecessary mustache interpolations | :wrench: | | [vue/no-useless-v-bind](./no-useless-v-bind.md) | disallow unnecessary `v-bind` directives | :wrench: | +| [vue/no-v-text](./no-v-text.md) | disallow use of v-text | | | [vue/padding-line-between-blocks](./padding-line-between-blocks.md) | require or disallow padding lines between blocks | :wrench: | | [vue/require-direct-export](./require-direct-export.md) | require the component to be directly exported | | | [vue/require-emit-validator](./require-emit-validator.md) | require type definitions in emits | | diff --git a/docs/rules/no-v-text.md b/docs/rules/no-v-text.md new file mode 100644 index 000000000..84cece3a4 --- /dev/null +++ b/docs/rules/no-v-text.md @@ -0,0 +1,41 @@ +--- +pageClass: rule-details +sidebarDepth: 0 +title: vue/no-v-text +description: disallow use of v-text +since: v7.17.0 +--- +# vue/no-v-text + +> disallow use of v-text + +## :book: Rule Details + +This rule reports all uses of `v-text` directive. + + + +```vue + +``` + + + +## :wrench: Options + +Nothing. + +## :rocket: Version + +This rule was introduced in eslint-plugin-vue v7.17.0 + +## :mag: Implementation + +- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-v-text.js) +- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-v-text.js) diff --git a/docs/user-guide/README.md b/docs/user-guide/README.md index a056c912b..39e44d9ab 100644 --- a/docs/user-guide/README.md +++ b/docs/user-guide/README.md @@ -25,6 +25,8 @@ yarn add -D eslint eslint-plugin-vue - ESLint v6.2.0 and above - Node.js v8.10.0 and above +We have started supporting ESLint v8.0.0 beta, but note that beta support will be dropped once the stable version is released. + ::: ## :book: Usage @@ -93,13 +95,13 @@ If you installed [@vue/cli-plugin-eslint](https://github.com/vuejs/vue-cli/tree/ ### How to use a custom parser? -If you want to use custom parsers such as [babel-eslint](https://www.npmjs.com/package/babel-eslint) or [@typescript-eslint/parser](https://www.npmjs.com/package/@typescript-eslint/parser), you have to use the `parserOptions.parser` option instead of the `parser` option. Because this plugin requires [vue-eslint-parser](https://www.npmjs.com/package/vue-eslint-parser) to parse `.vue` files, this plugin doesn't work if you overwrite the `parser` option. +If you want to use custom parsers such as [@babel/eslint-parser](https://www.npmjs.com/package/@babel/eslint-parser) or [@typescript-eslint/parser](https://www.npmjs.com/package/@typescript-eslint/parser), you have to use the `parserOptions.parser` option instead of the `parser` option. Because this plugin requires [vue-eslint-parser](https://www.npmjs.com/package/vue-eslint-parser) to parse `.vue` files, this plugin doesn't work if you overwrite the `parser` option. ```diff -- "parser": "babel-eslint", +- "parser": "@typescript-eslint/parser", + "parser": "vue-eslint-parser", "parserOptions": { -+ "parser": "babel-eslint", ++ "parser": "@typescript-eslint/parser", "sourceType": "module" } ``` @@ -238,13 +240,13 @@ Make sure you have one of the following settings in your **.eslintrc**: - `"extends": ["plugin:vue/vue3-recommended"]` - `"extends": ["plugin:vue/base"]` -If you already use another parser (e.g. `"parser": "babel-eslint"`), please move it into `parserOptions`, so it doesn't collide with the `vue-eslint-parser` used by this plugin's configuration: +If you already use another parser (e.g. `"parser": "@typescript-eslint/parser"`), please move it into `parserOptions`, so it doesn't collide with the `vue-eslint-parser` used by this plugin's configuration: ```diff -- "parser": "babel-eslint", +- "parser": "@typescript-eslint/parser", + "parser": "vue-eslint-parser", "parserOptions": { -+ "parser": "babel-eslint", ++ "parser": "@typescript-eslint/parser", "ecmaVersion": 2020, "sourceType": "module" } @@ -331,7 +333,7 @@ Note that you cannot use angle-bracket type assertion style (`var x = bar;` - Turning off the rule in the ESLint configuration file does not ignore the warning. - Using the `` comment does not suppress warnings. - Duplicate warnings are displayed. -- Used `babel-eslint`, but the template still show `vue/no-parsing-error` warnings. +- Used `@babel/eslint-parser`, but the template still show `vue/no-parsing-error` warnings. You need to turn off Vetur's template validation by adding `vetur.validation.template: false` to your `.vscode/settings.json`. @@ -398,20 +400,18 @@ module.exports = { However, note that the AST generated by `espree` v8+ may not work well with some rules of `ESLint` v7.x. - #### Other Problems 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/index.js b/lib/index.js index d9352faa1..af29765ca 100644 --- a/lib/index.js +++ b/lib/index.js @@ -132,6 +132,7 @@ module.exports = { 'no-v-for-template-key': require('./rules/no-v-for-template-key'), 'no-v-html': require('./rules/no-v-html'), 'no-v-model-argument': require('./rules/no-v-model-argument'), + 'no-v-text': require('./rules/no-v-text'), 'no-watch-after-await': require('./rules/no-watch-after-await'), 'object-curly-newline': require('./rules/object-curly-newline'), 'object-curly-spacing': require('./rules/object-curly-spacing'), 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 `