From ba2911413acd6c61cd966ab8471037ea4563451e Mon Sep 17 00:00:00 2001 From: Flo Edelmann Date: Fri, 22 Apr 2022 09:48:45 +0200 Subject: [PATCH 01/22] Meta: Install `eslint-plugin-unicorn` (#1857) * Install `eslint-plugin-unicorn` * Fix new lint issues --- .eslintrc.js | 19 +- docs/.vuepress/enhanceApp.js | 28 +- .../no-invalid-meta-docs-categories.js | 10 +- eslint-internal-rules/no-invalid-meta.js | 10 +- lib/processor.js | 8 +- lib/rules/attribute-hyphenation.js | 36 +- lib/rules/attributes-order.js | 79 +-- lib/rules/block-lang.js | 27 +- lib/rules/block-tag-newline.js | 5 +- lib/rules/comment-directive.js | 56 +-- lib/rules/component-api-style.js | 37 +- lib/rules/component-definition-name-casing.js | 31 +- .../component-name-in-template-casing.js | 5 +- lib/rules/component-options-name-casing.js | 8 +- lib/rules/component-tags-order.js | 51 +- lib/rules/experimental-script-setup-vars.js | 8 +- lib/rules/html-button-has-type.js | 17 +- lib/rules/html-comment-indent.js | 12 +- lib/rules/html-quotes.js | 12 +- lib/rules/match-component-file-name.js | 28 +- lib/rules/match-component-import-name.js | 62 ++- lib/rules/max-attributes-per-line.js | 124 ++--- lib/rules/max-len.js | 122 ++--- .../multiline-html-element-content-newline.js | 8 +- lib/rules/name-property-casing.js | 5 +- .../new-line-between-multi-line-property.js | 2 +- lib/rules/next-tick-style.js | 2 +- lib/rules/no-bare-strings-in-template.js | 2 +- lib/rules/no-child-content.js | 1 - .../no-deprecated-destroyed-lifecycle.js | 48 +- lib/rules/no-deprecated-props-default-this.js | 41 +- .../no-deprecated-router-link-tag-prop.js | 11 +- .../no-deprecated-v-on-number-modifiers.js | 4 +- lib/rules/no-dupe-keys.js | 9 +- lib/rules/no-dupe-v-else-if.js | 2 +- lib/rules/no-empty-component-block.js | 15 +- lib/rules/no-export-in-script-setup.js | 13 +- lib/rules/no-invalid-model-keys.js | 4 +- lib/rules/no-irregular-whitespace.js | 31 +- lib/rules/no-lone-template.js | 42 +- lib/rules/no-multiple-slot-args.js | 2 +- lib/rules/no-mutating-props.js | 155 +++--- lib/rules/no-parsing-error.js | 10 +- .../no-potential-component-option-typo.js | 12 +- lib/rules/no-reserved-component-names.js | 54 +- lib/rules/no-reserved-keys.js | 7 +- lib/rules/no-restricted-block.js | 14 +- lib/rules/no-restricted-call-after-await.js | 28 +- lib/rules/no-restricted-class.js | 8 +- lib/rules/no-restricted-component-options.js | 14 +- lib/rules/no-restricted-html-elements.js | 4 +- lib/rules/no-restricted-props.js | 2 +- lib/rules/no-restricted-static-attribute.js | 39 +- lib/rules/no-restricted-v-bind.js | 39 +- lib/rules/no-static-inline-styles.js | 151 +++--- lib/rules/no-template-shadow.js | 4 +- lib/rules/no-undef-components.js | 19 +- lib/rules/no-undef-properties.js | 26 +- lib/rules/no-unregistered-components.js | 99 ++-- lib/rules/no-unused-components.js | 57 ++- lib/rules/no-unused-refs.js | 84 ++-- lib/rules/no-unused-vars.js | 2 +- .../no-use-computed-property-like-method.js | 128 ++--- lib/rules/no-useless-mustaches.js | 2 +- lib/rules/no-useless-template-attributes.js | 97 ++-- lib/rules/no-useless-v-bind.js | 10 +- lib/rules/order-in-components.js | 24 +- lib/rules/padding-line-between-blocks.js | 9 +- lib/rules/prefer-import-from-vue.js | 19 +- lib/rules/prefer-prop-type-boolean-first.js | 10 +- lib/rules/prop-name-casing.js | 3 +- lib/rules/require-default-prop.js | 40 +- lib/rules/require-direct-export.js | 16 +- lib/rules/require-emit-validator.js | 8 +- lib/rules/require-explicit-emits.js | 39 +- lib/rules/require-expose.js | 36 +- lib/rules/require-prop-type-constructor.js | 42 +- lib/rules/require-prop-types.js | 30 +- lib/rules/require-valid-default-prop.js | 109 ++-- lib/rules/return-in-computed-property.js | 8 +- lib/rules/return-in-emits-validator.js | 11 +- lib/rules/script-setup-uses-vars.js | 19 +- ...singleline-html-element-content-newline.js | 2 +- lib/rules/sort-keys.js | 38 +- lib/rules/syntaxes/slot-attribute.js | 9 +- .../syntaxes/utils/can-convert-to-v-slot.js | 15 +- lib/rules/syntaxes/v-slot.js | 25 +- lib/rules/this-in-template.js | 4 +- lib/rules/use-v-on-exact.js | 45 +- lib/rules/v-bind-style.js | 13 +- lib/rules/v-for-delimiter-style.js | 2 +- lib/rules/v-on-function-call.js | 89 ++-- lib/rules/valid-define-emits.js | 39 +- lib/rules/valid-define-props.js | 39 +- lib/rules/valid-template-root.js | 2 +- lib/rules/valid-v-memo.js | 59 ++- lib/rules/valid-v-on.js | 4 +- lib/rules/valid-v-slot.js | 23 +- lib/utils/casing.js | 38 +- lib/utils/html-comments.js | 2 +- lib/utils/indent-common.js | 60 ++- lib/utils/indent-ts.js | 8 +- lib/utils/index.js | 474 +++++++++--------- lib/utils/property-references.js | 119 ++--- lib/utils/regexp.js | 2 +- lib/utils/selector.js | 62 +-- lib/utils/style-variables/index.js | 2 +- lib/utils/ts-ast-utils.js | 68 +-- package.json | 1 + tests/eslint-compat.js | 11 +- tests/lib/rules/comment-directive.js | 127 ++--- tests/lib/rules/html-indent.js | 8 +- tests/lib/rules/no-irregular-whitespace.js | 40 +- .../lib/rules/no-reserved-component-names.js | 4 +- tests/lib/rules/script-indent.js | 8 +- tests/lib/rules/this-in-template.js | 116 ++--- tests/lib/utils/index.js | 49 +- tests/lib/utils/vue-component.js | 24 +- tools/lib/categories.js | 2 +- tools/update-docs-rules-index.js | 8 +- tools/update-docs.js | 32 +- tools/update-lib-configs.js | 33 +- tools/update-no-layout-rules-config.js | 5 +- tools/update-vue3-export-names.js | 113 +++-- 124 files changed, 2198 insertions(+), 2151 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 3229de98d..6a944b5e1 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -14,9 +14,10 @@ module.exports = { 'plugin:eslint-plugin/recommended', 'prettier', 'plugin:node-dependencies/recommended', - 'plugin:jsonc/recommended-with-jsonc' + 'plugin:jsonc/recommended-with-jsonc', + 'plugin:unicorn/recommended' ], - plugins: ['eslint-plugin', 'prettier'], + plugins: ['eslint-plugin', 'prettier', 'unicorn'], rules: { 'accessor-pairs': 2, camelcase: [2, { properties: 'never' }], @@ -117,7 +118,19 @@ module.exports = { 'prefer-arrow-callback': 'error', 'prefer-spread': 'error', - 'dot-notation': 'error' + 'dot-notation': 'error', + + 'unicorn/consistent-function-scoping': [ + 'error', + { checkArrowFunctions: false } + ], + 'unicorn/filename-case': 'off', + 'unicorn/no-null': 'off', + 'unicorn/no-array-callback-reference': 'off', // doesn't work well with TypeScript's custom type guards + 'unicorn/no-useless-undefined': 'off', + 'unicorn/prefer-optional-catch-binding': 'off', // not supported by current ESLint parser version + 'unicorn/prefer-module': 'off', + 'unicorn/prevent-abbreviations': 'off' }, overrides: [ { diff --git a/docs/.vuepress/enhanceApp.js b/docs/.vuepress/enhanceApp.js index dc7e30771..03082d606 100644 --- a/docs/.vuepress/enhanceApp.js +++ b/docs/.vuepress/enhanceApp.js @@ -8,21 +8,19 @@ export default ( // siteData, // site metadata } ) => { - if (typeof window !== 'undefined') { - if (typeof window.process === 'undefined') { - window.process = new Proxy( - { - env: {}, - cwd: () => undefined - }, - { - get(target, name) { - // For debug - // console.log(name) - return target[name] - } + if (typeof window !== 'undefined' && typeof window.process === 'undefined') { + window.process = new Proxy( + { + env: {}, + cwd: () => undefined + }, + { + get(target, name) { + // For debug + // console.log(name) + return target[name] } - ) - } + } + ) } } diff --git a/eslint-internal-rules/no-invalid-meta-docs-categories.js b/eslint-internal-rules/no-invalid-meta-docs-categories.js index 5a86d16ab..b48125484 100644 --- a/eslint-internal-rules/no-invalid-meta-docs-categories.js +++ b/eslint-internal-rules/no-invalid-meta-docs-categories.js @@ -12,17 +12,17 @@ /** * Gets the property of the Object node passed in that has the name specified. * - * @param {string} property Name of the property to return. + * @param {string} propertyName 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) { +function getPropertyFromObject(propertyName, 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] + for (const property of properties) { + if (property.key.name === propertyName) { + return property } } } diff --git a/eslint-internal-rules/no-invalid-meta.js b/eslint-internal-rules/no-invalid-meta.js index e96435cf9..f1877ea4b 100644 --- a/eslint-internal-rules/no-invalid-meta.js +++ b/eslint-internal-rules/no-invalid-meta.js @@ -12,17 +12,17 @@ /** * Gets the property of the Object node passed in that has the name specified. * - * @param {string} property Name of the property to return. + * @param {string} propertyName 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) { +function getPropertyFromObject(propertyName, 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] + for (const property of properties) { + if (property.key.name === propertyName) { + return property } } } diff --git a/lib/processor.js b/lib/processor.js index 81ab94784..e0c10ef72 100644 --- a/lib/processor.js +++ b/lib/processor.js @@ -84,10 +84,10 @@ module.exports = { return false } else { const disableDirectiveKeys = [] - if (state.block.disableAllKeys.size) { + if (state.block.disableAllKeys.size > 0) { disableDirectiveKeys.push(...state.block.disableAllKeys) } - if (state.line.disableAllKeys.size) { + if (state.line.disableAllKeys.size > 0) { disableDirectiveKeys.push(...state.line.disableAllKeys) } if (message.ruleId) { @@ -101,7 +101,7 @@ module.exports = { } } - if (disableDirectiveKeys.length) { + if (disableDirectiveKeys.length > 0) { // Store used eslint-disable comment key usedDisableDirectiveKeys.push(...disableDirectiveKeys) return false @@ -111,7 +111,7 @@ module.exports = { } }) - if (unusedDisableDirectiveReports.size) { + if (unusedDisableDirectiveReports.size > 0) { for (const key of usedDisableDirectiveKeys) { // Remove used eslint-disable comments unusedDisableDirectiveReports.delete(key) diff --git a/lib/rules/attribute-hyphenation.js b/lib/rules/attribute-hyphenation.js index f9126a548..0502ae3bc 100644 --- a/lib/rules/attribute-hyphenation.js +++ b/lib/rules/attribute-hyphenation.js @@ -12,6 +12,26 @@ const svgAttributes = require('../utils/svg-attributes-weird-case.json') // Rule Definition // ------------------------------------------------------------------------------ +/** + * @param {VDirective | VAttribute} node + * @returns {string | null} + */ +function getAttributeName(node) { + if (!node.directive) { + return node.key.rawName + } + + if ( + node.key.name.name === 'bind' && + node.key.argument && + node.key.argument.type === 'VIdentifier' + ) { + return node.key.argument.rawName + } + + return null +} + module.exports = { meta: { type: 'suggestion', @@ -52,12 +72,10 @@ module.exports = { const option = context.options[0] const optionsPayload = context.options[1] const useHyphenated = option !== 'never' - let ignoredAttributes = ['data-', 'aria-', 'slot-scope'].concat( - svgAttributes - ) + const ignoredAttributes = ['data-', 'aria-', 'slot-scope', ...svgAttributes] if (optionsPayload && optionsPayload.ignore) { - ignoredAttributes = ignoredAttributes.concat(optionsPayload.ignore) + ignoredAttributes.push(...optionsPayload.ignore) } const caseConverter = casing.getExactConverter( @@ -112,14 +130,8 @@ module.exports = { ) return - const name = !node.directive - ? node.key.rawName - : node.key.name.name === 'bind' - ? node.key.argument && - node.key.argument.type === 'VIdentifier' && - node.key.argument.rawName - : /* otherwise */ false - if (!name || isIgnoredAttribute(name)) return + const name = getAttributeName(node) + if (name === null || isIgnoredAttribute(name)) return reportIssue(node, name) } diff --git a/lib/rules/attributes-order.js b/lib/rules/attributes-order.js index 0d9ea9ce8..d88e9c934 100644 --- a/lib/rules/attributes-order.js +++ b/lib/rules/attributes-order.js @@ -111,30 +111,31 @@ function getAttributeType(attribute) { if (attribute.directive) { if (!isVBind(attribute)) { const name = attribute.key.name.name - if (name === 'for') { - return ATTRS.LIST_RENDERING - } else if ( - name === 'if' || - name === 'else-if' || - name === 'else' || - name === 'show' || - name === 'cloak' - ) { - return ATTRS.CONDITIONALS - } else if (name === 'pre' || name === 'once') { - return ATTRS.RENDER_MODIFIERS - } else if (name === 'model') { - return ATTRS.TWO_WAY_BINDING - } else if (name === 'on') { - return ATTRS.EVENTS - } else if (name === 'html' || name === 'text') { - return ATTRS.CONTENT - } else if (name === 'slot') { - return ATTRS.SLOT - } else if (name === 'is') { - return ATTRS.DEFINITION - } else { - return ATTRS.OTHER_DIRECTIVES + switch (name) { + case 'for': + return ATTRS.LIST_RENDERING + case 'if': + case 'else-if': + case 'else': + case 'show': + case 'cloak': + return ATTRS.CONDITIONALS + case 'pre': + case 'once': + return ATTRS.RENDER_MODIFIERS + case 'model': + return ATTRS.TWO_WAY_BINDING + case 'on': + return ATTRS.EVENTS + case 'html': + case 'text': + return ATTRS.CONTENT + case 'slot': + return ATTRS.SLOT + case 'is': + return ATTRS.DEFINITION + default: + return ATTRS.OTHER_DIRECTIVES } } propName = @@ -144,16 +145,19 @@ function getAttributeType(attribute) { } else { propName = attribute.key.name } - if (propName === 'is') { - return ATTRS.DEFINITION - } else if (propName === 'id') { - return ATTRS.GLOBAL - } else if (propName === 'ref' || propName === 'key') { - return ATTRS.UNIQUE - } else if (propName === 'slot' || propName === 'slot-scope') { - return ATTRS.SLOT - } else { - return ATTRS.OTHER_ATTR + switch (propName) { + case 'is': + return ATTRS.DEFINITION + case 'id': + return ATTRS.GLOBAL + case 'ref': + case 'key': + return ATTRS.UNIQUE + case 'slot': + case 'slot-scope': + return ATTRS.SLOT + default: + return ATTRS.OTHER_ATTR } } @@ -213,13 +217,13 @@ function create(context) { /** @type { { [key: string]: number } } */ const attributePosition = {} - attributeOrder.forEach((item, i) => { + for (const [i, item] of attributeOrder.entries()) { if (Array.isArray(item)) { for (const attr of item) { attributePosition[attr] = i } } else attributePosition[item] = i - }) + } /** * @param {VAttribute | VDirective} node @@ -319,8 +323,7 @@ function create(context) { }) const results = [] - for (let index = 0; index < attributes.length; index++) { - const attr = attributes[index] + for (const [index, attr] of attributes.entries()) { const position = getPositionFromAttrIndex(index) if (position == null) { // The omitted order is skipped. diff --git a/lib/rules/block-lang.js b/lib/rules/block-lang.js index f1383c835..c1998eb5f 100644 --- a/lib/rules/block-lang.js +++ b/lib/rules/block-lang.js @@ -52,13 +52,21 @@ function getAllowsLangPhrase(lang) { /** * Normalizes a given option. * @param {string} blockName The block name. - * @param { UserBlockOptions } option An option to parse. + * @param {UserBlockOptions} option An option to parse. * @returns {BlockOptions} Normalized option. */ function normalizeOption(blockName, option) { - const lang = new Set( - Array.isArray(option.lang) ? option.lang : option.lang ? [option.lang] : [] - ) + /** @type {Set} */ + let lang + + if (Array.isArray(option.lang)) { + lang = new Set(option.lang) + } else if (typeof option.lang === 'string') { + lang = new Set([option.lang]) + } else { + lang = new Set() + } + let hasDefault = false for (const def of DEFAULT_LANGUAGES[blockName] || []) { if (lang.has(def)) { @@ -165,8 +173,7 @@ module.exports = { style: { allowNoLang: true } } ) - if (!Object.keys(options).length) { - // empty + if (Object.keys(options).length === 0) { return {} } @@ -198,11 +205,9 @@ module.exports = { if (!option.allowNoLang) { messageId = 'expected' } else if (option.lang.size === 0) { - if ((DEFAULT_LANGUAGES[tag] || []).includes(lang.value.value)) { - messageId = 'unexpectedDefault' - } else { - messageId = 'unexpected' - } + messageId = (DEFAULT_LANGUAGES[tag] || []).includes(lang.value.value) + ? 'unexpectedDefault' + : 'unexpected' } else { messageId = 'useOrNot' } diff --git a/lib/rules/block-tag-newline.js b/lib/rules/block-tag-newline.js index c88a36c27..1fe427afd 100644 --- a/lib/rules/block-tag-newline.js +++ b/lib/rules/block-tag-newline.js @@ -257,9 +257,8 @@ module.exports = { } const option = - options.multiline === options.singleline - ? options.singleline - : /[\n\r\u2028\u2029]/u.test(text.trim()) + options.multiline !== options.singleline && + /[\n\r\u2028\u2029]/u.test(text.trim()) ? options.multiline : options.singleline if (option === 'ignore') { diff --git a/lib/rules/comment-directive.js b/lib/rules/comment-directive.js index 942860f60..1f4cdd336 100644 --- a/lib/rules/comment-directive.js +++ b/lib/rules/comment-directive.js @@ -51,7 +51,7 @@ function parse(pattern, comment) { /** @type {RuleAndLocation[]} */ const rules = [] - const rulesRe = /([^,\s]+)[,\s]*/g + const rulesRe = /([^\s,]+)[\s,]*/g let startIndex = match[0].length rulesRe.lastIndex = startIndex @@ -126,35 +126,35 @@ function disable(context, loc, group, rule, key) { */ 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) + if (parsed === null) return + + if (parsed.type === 'eslint-disable') { + if (parsed.rules.length > 0) { + 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 { - 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) + const key = reportUnusedDisableDirectives + ? reportUnused(context, comment, parsed.type) + : '' + disable(context, comment.loc.start, 'block', null, key) + } + } else { + if (parsed.rules.length > 0) { + for (const rule of parsed.rules) { + enable(context, comment.loc.start, 'block', rule.ruleId) } + } else { + enable(context, comment.loc.start, 'block', null) } } } @@ -173,7 +173,7 @@ function processLine(context, comment, reportUnusedDisableDirectives) { const line = comment.loc.start.line + (parsed.type === 'eslint-disable-line' ? 0 : 1) const column = -1 - if (parsed.rules.length) { + if (parsed.rules.length > 0) { const rules = reportUnusedDisableDirectives ? reportUnusedRules(context, comment, parsed.type, parsed.rules) : parsed.rules diff --git a/lib/rules/component-api-style.js b/lib/rules/component-api-style.js index 9ebf01d68..12ad52e2d 100644 --- a/lib/rules/component-api-style.js +++ b/lib/rules/component-api-style.js @@ -43,17 +43,26 @@ function parseOptions(options) { /** @type {UserPreferOption} */ const preferOptions = options[0] || ['script-setup', 'composition'] for (const prefer of preferOptions) { - if (prefer === 'script-setup') { - opts.allowsSFC.scriptSetup = true - } else if (prefer === 'composition') { - opts.allowsSFC.composition = true - opts.allowsOther.composition = true - } else if (prefer === 'composition-vue2') { - opts.allowsSFC.compositionVue2 = true - opts.allowsOther.compositionVue2 = true - } else if (prefer === 'options') { - opts.allowsSFC.options = true - opts.allowsOther.options = true + switch (prefer) { + case 'script-setup': { + opts.allowsSFC.scriptSetup = true + break + } + case 'composition': { + opts.allowsSFC.composition = true + opts.allowsOther.composition = true + break + } + case 'composition-vue2': { + opts.allowsSFC.compositionVue2 = true + opts.allowsOther.compositionVue2 = true + break + } + case 'options': { + opts.allowsSFC.options = true + opts.allowsOther.options = true + break + } } } @@ -180,9 +189,9 @@ function isPreferScriptSetup(allowsOpt) { * @param {string} name */ function buildOptionPhrase(name) { - return LIFECYCLE_HOOK_OPTIONS.has(name) - ? `\`${name}\` lifecycle hook` - : name === 'setup' || name === 'render' + if (LIFECYCLE_HOOK_OPTIONS.has(name)) return `\`${name}\` lifecycle hook` + + return name === 'setup' || name === 'render' ? `\`${name}\` function` : `\`${name}\` option` } diff --git a/lib/rules/component-definition-name-casing.js b/lib/rules/component-definition-name-casing.js index 8e192df5e..103e3feb0 100644 --- a/lib/rules/component-definition-name-casing.js +++ b/lib/rules/component-definition-name-casing.js @@ -12,6 +12,19 @@ const allowedCaseOptions = ['PascalCase', 'kebab-case'] // Rule Definition // ------------------------------------------------------------------------------ +/** + * @param {Expression | SpreadElement} node + * @returns {node is (Literal | TemplateLiteral)} + */ +function canConvert(node) { + return ( + node.type === 'Literal' || + (node.type === 'TemplateLiteral' && + node.expressions.length === 0 && + node.quasis.length === 1) + ) +} + module.exports = { meta: { type: 'suggestion', @@ -30,8 +43,9 @@ module.exports = { /** @param {RuleContext} context */ create(context) { const options = context.options[0] - const caseType = - allowedCaseOptions.indexOf(options) !== -1 ? options : 'PascalCase' + const caseType = allowedCaseOptions.includes(options) + ? options + : 'PascalCase' // ---------------------------------------------------------------------- // Public @@ -71,19 +85,6 @@ module.exports = { } } - /** - * @param {Expression | SpreadElement} node - * @returns {node is (Literal | TemplateLiteral)} - */ - function canConvert(node) { - return ( - node.type === 'Literal' || - (node.type === 'TemplateLiteral' && - node.expressions.length === 0 && - node.quasis.length === 1) - ) - } - return Object.assign( {}, utils.executeOnCallVueComponent(context, (node) => { diff --git a/lib/rules/component-name-in-template-casing.js b/lib/rules/component-name-in-template-casing.js index b2f60a637..017e4cb08 100644 --- a/lib/rules/component-name-in-template-casing.js +++ b/lib/rules/component-name-in-template-casing.js @@ -58,8 +58,9 @@ module.exports = { create(context) { const caseOption = context.options[0] const options = context.options[1] || {} - const caseType = - allowedCaseOptions.indexOf(caseOption) !== -1 ? caseOption : defaultCase + const caseType = allowedCaseOptions.includes(caseOption) + ? caseOption + : defaultCase /** @type {RegExp[]} */ const ignores = (options.ignores || []).map(toRegExp) const registeredComponentsOnly = options.registeredComponentsOnly !== false diff --git a/lib/rules/component-options-name-casing.js b/lib/rules/component-options-name-casing.js index ad70c1546..37fe47ed7 100644 --- a/lib/rules/component-options-name-casing.js +++ b/lib/rules/component-options-name-casing.js @@ -64,14 +64,14 @@ module.exports = { return } - node.value.properties.forEach((property) => { + for (const property of node.value.properties) { if (property.type !== 'Property') { - return + continue } const name = getOptionsComponentName(property.key) if (!name || checkCase(name)) { - return + continue } context.report({ @@ -109,7 +109,7 @@ module.exports = { } ] }) - }) + } }) } } diff --git a/lib/rules/component-tags-order.js b/lib/rules/component-tags-order.js index dfd8482bf..60640dc39 100644 --- a/lib/rules/component-tags-order.js +++ b/lib/rules/component-tags-order.js @@ -25,6 +25,26 @@ const DEFAULT_ORDER = Object.freeze([['script', 'template'], 'style']) // Rule Definition // ------------------------------------------------------------------------------ +/** + * @param {VElement} element + * @return {string} + */ +function getAttributeString(element) { + return element.startTag.attributes + .map((attribute) => { + if (attribute.value && attribute.value.type !== 'VLiteral') { + return '' + } + + return `${attribute.key.name}${ + attribute.value && attribute.value.value + ? `=${attribute.value.value}` + : '' + }` + }) + .join(' ') +} + module.exports = { meta: { type: 'suggestion', @@ -74,7 +94,7 @@ module.exports = { /** @type {(string|string[])[]} */ const orderOptions = (context.options[0] && context.options[0].order) || DEFAULT_ORDER - orderOptions.forEach((selectorOrSelectors, index) => { + for (const [index, selectorOrSelectors] of orderOptions.entries()) { if (Array.isArray(selectorOrSelectors)) { for (const selector of selectorOrSelectors) { orders.push({ @@ -90,26 +110,6 @@ module.exports = { index }) } - }) - - /** - * @param {VElement} element - * @return {string} - */ - function getAttributeString(element) { - return element.startTag.attributes - .map((attribute) => { - if (attribute.value && attribute.value.type !== 'VLiteral') { - return '' - } - - return `${attribute.key.name}${ - attribute.value && attribute.value.value - ? `=${attribute.value.value}` - : '' - }` - }) - .join(' ') } /** @@ -136,13 +136,14 @@ module.exports = { } const elements = getTopLevelHTMLElements() - const elementWithOrders = elements.flatMap((element) => { + const elementsWithOrder = elements.flatMap((element) => { const order = getOrderElement(element) return order ? [{ order, element }] : [] }) const sourceCode = context.getSourceCode() - elementWithOrders.forEach(({ order: expected, element }, index) => { - const firstUnordered = elementWithOrders + for (const [index, elementWithOrders] of elementsWithOrder.entries()) { + const { order: expected, element } = elementWithOrders + const firstUnordered = elementsWithOrder .slice(0, index) .filter(({ order }) => expected.index < order.index) .sort((e1, e2) => e1.order.index - e2.order.index)[0] @@ -188,7 +189,7 @@ module.exports = { } }) } - }) + } } } } diff --git a/lib/rules/experimental-script-setup-vars.js b/lib/rules/experimental-script-setup-vars.js index 14c498de5..878f2f2b1 100644 --- a/lib/rules/experimental-script-setup-vars.js +++ b/lib/rules/experimental-script-setup-vars.js @@ -62,7 +62,7 @@ module.exports = { // @ts-ignore require('eslint-scope') ) - } catch (_e) { + } catch (_error) { context.report({ node: setupAttr, message: 'Can not be resolved eslint-scope.' @@ -75,7 +75,7 @@ module.exports = { // @ts-ignore require('espree') ) - } catch (_e) { + } catch (_error) { context.report({ node: setupAttr, message: 'Can not be resolved espree.' @@ -89,7 +89,7 @@ module.exports = { let vars try { vars = parseSetup(value, espree, eslintScope) - } catch (_e) { + } catch (_error) { context.report({ node: setupAttr.value, message: 'Parsing error.' @@ -217,7 +217,7 @@ function getESLintModule(name, fallback) { if (linterPath) { try { modulesCache[name] = createRequire(linterPath)(name) - } catch (_e) { + } catch (_error) { // ignore } } diff --git a/lib/rules/html-button-has-type.js b/lib/rules/html-button-has-type.js index 4ef7d2feb..4b0d76cbd 100644 --- a/lib/rules/html-button-has-type.js +++ b/lib/rules/html-button-has-type.js @@ -14,6 +14,15 @@ const utils = require('../utils') // Rule Definition // ------------------------------------------------------------------------------ +/** + * + * @param {string} type + * @returns {type is 'button' | 'submit' | 'reset'} + */ +function isButtonType(type) { + return type === 'button' || type === 'submit' || type === 'reset' +} + const optionDefaults = { button: true, submit: true, @@ -74,14 +83,6 @@ module.exports = { /** @type {Configuration} */ const configuration = Object.assign({}, optionDefaults, context.options[0]) - /** - * - * @param {string} type - * @returns {type is 'button' | 'submit' | 'reset'} - */ - function isButtonType(type) { - return type === 'button' || type === 'submit' || type === 'reset' - } /** * @param {ASTNode} node * @param {string} messageId diff --git a/lib/rules/html-comment-indent.js b/lib/rules/html-comment-indent.js index d5992b59b..56c81960f 100644 --- a/lib/rules/html-comment-indent.js +++ b/lib/rules/html-comment-indent.js @@ -46,10 +46,8 @@ function toDisplay(s, unitChar) { return `0 ${toUnit(unitChar)}s` } const char = s[0] - if (char === ' ' || char === '\t') { - if (s.split('').every((c) => c === char)) { - return `${s.length} ${toUnit(char)}${s.length === 1 ? '' : 's'}` - } + if ((char === ' ' || char === '\t') && [...s].every((c) => c === char)) { + return `${s.length} ${toUnit(char)}${s.length === 1 ? '' : 's'}` } return JSON.stringify(s) @@ -218,8 +216,8 @@ module.exports = { ) // validate indent charctor - for (let i = 0; i < actualOffsetIndentText.length; ++i) { - if (actualOffsetIndentText[i] !== options.indentChar) { + for (const [i, char] of [...actualOffsetIndentText].entries()) { + if (char !== options.indentChar) { context.report({ loc: { start: { line, column: baseIndentText.length + i }, @@ -228,7 +226,7 @@ module.exports = { messageId: 'unexpectedIndentationCharacter', data: { expected: toUnit(options.indentChar), - actual: toUnit(actualOffsetIndentText[i]) + actual: toUnit(char) }, fix: defineFix(line, actualIndentText, expectedIndentText) }) diff --git a/lib/rules/html-quotes.js b/lib/rules/html-quotes.js index 9f741b21b..a5523cb78 100644 --- a/lib/rules/html-quotes.js +++ b/lib/rules/html-quotes.js @@ -76,12 +76,12 @@ module.exports = { fix(fixer) { const contentText = quoted ? text.slice(1, -1) : text - const fixToDouble = - avoidEscape && !quoted && contentText.includes(quoteChar) - ? double - ? contentText.includes("'") - : !contentText.includes('"') - : double + let fixToDouble = double + if (avoidEscape && !quoted && contentText.includes(quoteChar)) { + fixToDouble = double + ? contentText.includes("'") + : !contentText.includes('"') + } const quotePattern = fixToDouble ? /"/g : /'/g const quoteEscaped = fixToDouble ? '"' : ''' diff --git a/lib/rules/match-component-file-name.js b/lib/rules/match-component-file-name.js index 8428516fb..57fd4ff89 100644 --- a/lib/rules/match-component-file-name.js +++ b/lib/rules/match-component-file-name.js @@ -16,6 +16,19 @@ const path = require('path') // Rule Definition // ------------------------------------------------------------------------------ +/** + * @param {Expression | SpreadElement} node + * @returns {node is (Literal | TemplateLiteral)} + */ +function canVerify(node) { + return ( + node.type === 'Literal' || + (node.type === 'TemplateLiteral' && + node.expressions.length === 0 && + node.quasis.length === 1) + ) +} + module.exports = { meta: { type: 'suggestion', @@ -106,19 +119,6 @@ module.exports = { } } - /** - * @param {Expression | SpreadElement} node - * @returns {node is (Literal | TemplateLiteral)} - */ - function canVerify(node) { - return ( - node.type === 'Literal' || - (node.type === 'TemplateLiteral' && - node.expressions.length === 0 && - node.quasis.length === 1) - ) - } - return Object.assign( {}, utils.executeOnCallVueComponent(context, (node) => { @@ -143,7 +143,7 @@ module.exports = { 'Program:exit'() { if (componentCount > 1) return - errors.forEach((error) => context.report(error)) + for (const error of errors) context.report(error) } } ) diff --git a/lib/rules/match-component-import-name.js b/lib/rules/match-component-import-name.js index 5babc8dc2..22eb4214a 100644 --- a/lib/rules/match-component-import-name.js +++ b/lib/rules/match-component-import-name.js @@ -7,6 +7,14 @@ const utils = require('../utils') const casing = require('../utils/casing') +/** + * @param {Identifier} identifier + * @return {Array} + */ +function getExpectedNames(identifier) { + return [casing.pascalCase(identifier.name), casing.kebabCase(identifier.name)] +} + module.exports = { meta: { type: 'problem', @@ -28,17 +36,6 @@ module.exports = { * @returns {RuleListener} */ create(context) { - /** - * @param {Identifier} identifier - * @return {Array} - */ - function getExpectedNames(identifier) { - return [ - casing.pascalCase(identifier.name), - casing.kebabCase(identifier.name) - ] - } - return utils.executeOnVueComponent(context, (obj) => { const components = utils.findProperty(obj, 'components') if ( @@ -49,31 +46,28 @@ module.exports = { return } - components.value.properties.forEach( - /** @param {Property | SpreadElement} property */ - (property) => { - if ( - property.type === 'SpreadElement' || - property.value.type !== 'Identifier' || - property.computed === true - ) { - return - } + for (const property of components.value.properties) { + if ( + property.type === 'SpreadElement' || + property.value.type !== 'Identifier' || + property.computed === true + ) { + continue + } - const importedName = utils.getStaticPropertyName(property) || '' - const expectedNames = getExpectedNames(property.value) - if (!expectedNames.includes(importedName)) { - context.report({ - node: property, - messageId: 'unexpected', - data: { - importedName, - expectedName: expectedNames.join(', ') - } - }) - } + const importedName = utils.getStaticPropertyName(property) || '' + const expectedNames = getExpectedNames(property.value) + if (!expectedNames.includes(importedName)) { + context.report({ + node: property, + messageId: 'unexpected', + data: { + importedName, + expectedName: expectedNames.join(', ') + } + }) } - ) + } }) } } diff --git a/lib/rules/max-attributes-per-line.js b/lib/rules/max-attributes-per-line.js index d0eb40fb3..7c4b02eea 100644 --- a/lib/rules/max-attributes-per-line.js +++ b/lib/rules/max-attributes-per-line.js @@ -9,6 +9,60 @@ // ------------------------------------------------------------------------------ const utils = require('../utils') +/** + * @param {any} options + */ +function parseOptions(options) { + const defaults = { + singleline: 1, + multiline: 1 + } + + if (options) { + if (typeof options.singleline === 'number') { + defaults.singleline = options.singleline + } else if ( + typeof options.singleline === 'object' && + typeof options.singleline.max === 'number' + ) { + defaults.singleline = options.singleline.max + } + + if (options.multiline) { + if (typeof options.multiline === 'number') { + defaults.multiline = options.multiline + } else if ( + typeof options.multiline === 'object' && + typeof options.multiline.max === 'number' + ) { + defaults.multiline = options.multiline.max + } + } + } + + return defaults +} + +/** + * @param {(VDirective | VAttribute)[]} attributes + */ +function groupAttrsByLine(attributes) { + const propsPerLine = [[attributes[0]]] + + for (let index = 1; index < attributes.length; index++) { + const previous = attributes[index - 1] + const current = attributes[index] + + if (previous.loc.end.line === current.loc.start.line) { + propsPerLine[propsPerLine.length - 1].push(current) + } else { + propsPerLine.push([current]) + } + } + + return propsPerLine +} + module.exports = { meta: { type: 'layout', @@ -79,16 +133,19 @@ module.exports = { if (!numberOfAttributes) return - if (utils.isSingleLine(node)) { - if (numberOfAttributes > singlelinemMaximum) { - showErrors(node.attributes.slice(singlelinemMaximum)) - } + if ( + utils.isSingleLine(node) && + numberOfAttributes > singlelinemMaximum + ) { + showErrors(node.attributes.slice(singlelinemMaximum)) } if (!utils.isSingleLine(node)) { - groupAttrsByLine(node.attributes) - .filter((attrs) => attrs.length > multilineMaximum) - .forEach((attrs) => showErrors(attrs.splice(multilineMaximum))) + for (const attrs of groupAttrsByLine(node.attributes)) { + if (attrs.length > multilineMaximum) { + showErrors(attrs.splice(multilineMaximum)) + } + } } } }) @@ -96,43 +153,12 @@ module.exports = { // ---------------------------------------------------------------------- // Helpers // ---------------------------------------------------------------------- - /** - * @param {any} options - */ - function parseOptions(options) { - const defaults = { - singleline: 1, - multiline: 1 - } - - if (options) { - if (typeof options.singleline === 'number') { - defaults.singleline = options.singleline - } else if (typeof options.singleline === 'object') { - if (typeof options.singleline.max === 'number') { - defaults.singleline = options.singleline.max - } - } - - if (options.multiline) { - if (typeof options.multiline === 'number') { - defaults.multiline = options.multiline - } else if (typeof options.multiline === 'object') { - if (typeof options.multiline.max === 'number') { - defaults.multiline = options.multiline.max - } - } - } - } - - return defaults - } /** * @param {(VDirective | VAttribute)[]} attributes */ function showErrors(attributes) { - attributes.forEach((prop, i) => { + for (const [i, prop] of attributes.entries()) { context.report({ node: prop, loc: prop.loc, @@ -155,25 +181,7 @@ module.exports = { return fixer.replaceTextRange(range, '\n') } }) - }) - } - - /** - * @param {(VDirective | VAttribute)[]} attributes - */ - function groupAttrsByLine(attributes) { - const propsPerLine = [[attributes[0]]] - - attributes.reduce((previous, current) => { - if (previous.loc.end.line === current.loc.start.line) { - propsPerLine[propsPerLine.length - 1].push(current) - } else { - propsPerLine.push([current]) - } - return current - }) - - return propsPerLine + } } } } diff --git a/lib/rules/max-len.js b/lib/rules/max-len.js index a5cf3965f..560500a3b 100644 --- a/lib/rules/max-len.js +++ b/lib/rules/max-len.js @@ -99,7 +99,7 @@ function computeLineLength(line, tabWidth) { extraCharacterCount += spaceCount - 1 // -1 for the replaced tab } - return Array.from(line).length + extraCharacterCount + return [...line].length + extraCharacterCount } /** @@ -157,34 +157,24 @@ function stripTrailingComment(line, comment) { } /** - * Ensure that an array exists at [key] on `object`, and add `value` to it. + * Group AST nodes by line number, both start and end. * - * @param { { [key: number]: Token[] } } object the object to mutate - * @param {number} key the object's key - * @param {Token} value the value to add - * @returns {void} + * @param {Token[]} nodes the AST nodes in question + * @returns { { [key: number]: Token[] } } the grouped nodes * @private */ -function ensureArrayAndPush(object, key, value) { - if (!Array.isArray(object[key])) { - object[key] = [] - } - object[key].push(value) -} - -/** - * A reducer to group an AST node by line number, both start and end. - * - * @param { { [key: number]: Token[] } } acc the accumulator - * @param {Token} node the AST node in question - * @returns { { [key: number]: Token[] } } the modified accumulator - * @private - */ -function groupByLineNumber(acc, node) { - for (let i = node.loc.start.line; i <= node.loc.end.line; ++i) { - ensureArrayAndPush(acc, i, node) +function groupByLineNumber(nodes) { + /** @type { { [key: number]: Token[] } } */ + const grouped = {} + for (const node of nodes) { + for (let i = node.loc.start.line; i <= node.loc.end.line; ++i) { + if (!Array.isArray(grouped[i])) { + grouped[i] = [] + } + grouped[i].push(node) + } } - return acc + return grouped } // ------------------------------------------------------------------------------ @@ -366,27 +356,26 @@ module.exports = { } } - /** @type {Range} */ + /** @type {Range | undefined} */ let scriptLinesRange - if (scriptTokens.length) { - if (scriptComments.length) { - scriptLinesRange = [ - Math.min( - scriptTokens[0].loc.start.line, - scriptComments[0].loc.start.line - ), - Math.max( - scriptTokens[scriptTokens.length - 1].loc.end.line, - scriptComments[scriptComments.length - 1].loc.end.line - ) - ] - } else { - scriptLinesRange = [ - scriptTokens[0].loc.start.line, - scriptTokens[scriptTokens.length - 1].loc.end.line - ] - } - } else if (scriptComments.length) { + if (scriptTokens.length > 0) { + scriptLinesRange = + scriptComments.length > 0 + ? [ + Math.min( + scriptTokens[0].loc.start.line, + scriptComments[0].loc.start.line + ), + Math.max( + scriptTokens[scriptTokens.length - 1].loc.end.line, + scriptComments[scriptComments.length - 1].loc.end.line + ) + ] + : [ + scriptTokens[0].loc.start.line, + scriptTokens[scriptTokens.length - 1].loc.end.line + ] + } else if (scriptComments.length > 0) { scriptLinesRange = [ scriptComments[0].loc.start.line, scriptComments[scriptComments.length - 1].loc.end.line @@ -401,31 +390,22 @@ module.exports = { const lines = sourceCode.lines const strings = getAllStrings() - const stringsByLine = strings.reduce(groupByLineNumber, {}) + const stringsByLine = groupByLineNumber(strings) const templateLiterals = getAllTemplateLiterals() - const templateLiteralsByLine = templateLiterals.reduce( - groupByLineNumber, - {} - ) + const templateLiteralsByLine = groupByLineNumber(templateLiterals) const regExpLiterals = getAllRegExpLiterals() - const regExpLiteralsByLine = regExpLiterals.reduce(groupByLineNumber, {}) + const regExpLiteralsByLine = groupByLineNumber(regExpLiterals) - const htmlAttributeValuesByLine = htmlAttributeValues.reduce( - groupByLineNumber, - {} - ) + const htmlAttributeValuesByLine = groupByLineNumber(htmlAttributeValues) const htmlTextContents = getAllHTMLTextContents() - const htmlTextContentsByLine = htmlTextContents.reduce( - groupByLineNumber, - {} - ) + const htmlTextContentsByLine = groupByLineNumber(htmlTextContents) - const commentsByLine = comments.reduce(groupByLineNumber, {}) + const commentsByLine = groupByLineNumber(comments) - lines.forEach((line, i) => { + for (const [i, line] of lines.entries()) { // i is zero-indexed, line numbers are one-indexed const lineNumber = i + 1 @@ -440,14 +420,12 @@ module.exports = { // check if line is inside a script or template. if (!inScript && !inTemplate) { // out of range. - return + continue } - const maxLength = - inScript && inTemplate - ? Math.max(scriptMaxLength, templateMaxLength) - : inScript - ? scriptMaxLength - : templateMaxLength + const maxLength = Math.max( + inScript ? scriptMaxLength : 0, + inTemplate ? templateMaxLength : 0 + ) if ( (ignoreStrings && stringsByLine[lineNumber]) || @@ -458,7 +436,7 @@ module.exports = { (ignoreHTMLTextContents && htmlTextContentsByLine[lineNumber]) ) { // ignore this line - return + continue } /* @@ -503,14 +481,14 @@ module.exports = { (ignoreUrls && URL_REGEXP.test(textToMeasure)) ) { // ignore this line - return + continue } const lineLength = computeLineLength(textToMeasure, tabWidth) const commentLengthApplies = lineIsComment && maxCommentLength if (lineIsComment && ignoreComments) { - return + continue } if (commentLengthApplies) { @@ -536,7 +514,7 @@ module.exports = { } }) } - }) + } } // -------------------------------------------------------------------------- diff --git a/lib/rules/multiline-html-element-content-newline.js b/lib/rules/multiline-html-element-content-newline.js index fa24dd250..cfa8d9778 100644 --- a/lib/rules/multiline-html-element-content-newline.js +++ b/lib/rules/multiline-html-element-content-newline.js @@ -29,7 +29,7 @@ function isMultilineElement(element) { function parseOptions(options) { return Object.assign( { - ignores: ['pre', 'textarea'].concat(INLINE_ELEMENTS), + ignores: ['pre', 'textarea', ...INLINE_ELEMENTS], ignoreWhenEmpty: true, allowEmptyLines: false }, @@ -131,11 +131,7 @@ module.exports = { * @param {number} lineBreaks */ function isInvalidLineBreaks(lineBreaks) { - if (allowEmptyLines) { - return lineBreaks === 0 - } else { - return lineBreaks !== 1 - } + return allowEmptyLines ? lineBreaks === 0 : lineBreaks !== 1 } return utils.defineTemplateBodyVisitor(context, { diff --git a/lib/rules/name-property-casing.js b/lib/rules/name-property-casing.js index 29c100db5..19a1827e3 100644 --- a/lib/rules/name-property-casing.js +++ b/lib/rules/name-property-casing.js @@ -33,8 +33,9 @@ module.exports = { /** @param {RuleContext} context */ create(context) { const options = context.options[0] - const caseType = - allowedCaseOptions.indexOf(options) !== -1 ? options : 'PascalCase' + const caseType = allowedCaseOptions.includes(options) + ? options + : 'PascalCase' // ---------------------------------------------------------------------- // Public diff --git a/lib/rules/new-line-between-multi-line-property.js b/lib/rules/new-line-between-multi-line-property.js index f0e3aa550..6dbde4586 100644 --- a/lib/rules/new-line-between-multi-line-property.js +++ b/lib/rules/new-line-between-multi-line-property.js @@ -100,7 +100,7 @@ module.exports = { * @param {ObjectExpression} node */ ObjectExpression(node) { - if (callStack.length) { + if (callStack.length > 0) { return } const properties = node.properties diff --git a/lib/rules/next-tick-style.js b/lib/rules/next-tick-style.js index d015bb5b7..992d7b0b5 100644 --- a/lib/rules/next-tick-style.js +++ b/lib/rules/next-tick-style.js @@ -128,7 +128,7 @@ module.exports = { } if ( - callExpression.arguments.length !== 0 || + callExpression.arguments.length > 0 || !isAwaitedPromise(callExpression) ) { context.report({ diff --git a/lib/rules/no-bare-strings-in-template.js b/lib/rules/no-bare-strings-in-template.js index 899700036..e604b83d8 100644 --- a/lib/rules/no-bare-strings-in-template.js +++ b/lib/rules/no-bare-strings-in-template.js @@ -43,7 +43,7 @@ const DEFAULT_ALLOWLIST = [ '}', '<', '>', - '\u00b7', // "·" + '\u00B7', // "·" '\u2022', // "•" '\u2010', // "‐" '\u2013', // "–" diff --git a/lib/rules/no-child-content.js b/lib/rules/no-child-content.js index 85636d1d4..138a425c1 100644 --- a/lib/rules/no-child-content.js +++ b/lib/rules/no-child-content.js @@ -137,7 +137,6 @@ module.exports = { if ( directives.has(directiveName) && - childNodes.length > 0 && childNodes.some((childNode) => !isWhiteSpaceTextNode(childNode)) ) { context.report({ diff --git a/lib/rules/no-deprecated-destroyed-lifecycle.js b/lib/rules/no-deprecated-destroyed-lifecycle.js index 9cb690cbc..0dc30883f 100644 --- a/lib/rules/no-deprecated-destroyed-lifecycle.js +++ b/lib/rules/no-deprecated-destroyed-lifecycle.js @@ -14,6 +14,30 @@ const utils = require('../utils') // Rule Definition // ------------------------------------------------------------------------------ +/** + * @param {RuleFixer} fixer + * @param {Property} property + * @param {string} newName + */ +function fix(fixer, property, newName) { + if (property.computed) { + if ( + property.key.type === 'Literal' || + property.key.type === 'TemplateLiteral' + ) { + return fixer.replaceTextRange( + [property.key.range[0] + 1, property.key.range[1] - 1], + newName + ) + } + return null + } + if (property.shorthand) { + return fixer.insertTextBefore(property.key, `${newName}:`) + } + return fixer.replaceText(property.key, newName) +} + module.exports = { meta: { type: 'problem', @@ -57,30 +81,6 @@ module.exports = { } }) } - - /** - * @param {RuleFixer} fixer - * @param {Property} property - * @param {string} newName - */ - function fix(fixer, property, newName) { - if (property.computed) { - if ( - property.key.type === 'Literal' || - property.key.type === 'TemplateLiteral' - ) { - return fixer.replaceTextRange( - [property.key.range[0] + 1, property.key.range[1] - 1], - newName - ) - } - return null - } - if (property.shorthand) { - return fixer.insertTextBefore(property.key, `${newName}:`) - } - return fixer.replaceText(property.key, newName) - } }) } } diff --git a/lib/rules/no-deprecated-props-default-this.js b/lib/rules/no-deprecated-props-default-this.js index 174c6bfae..6e26941ed 100644 --- a/lib/rules/no-deprecated-props-default-this.js +++ b/lib/rules/no-deprecated-props-default-this.js @@ -14,6 +14,27 @@ const utils = require('../utils') // Rule Definition // ------------------------------------------------------------------------------ +/** + * @param {Expression|SpreadElement|null} node + */ +function isFunctionIdentifier(node) { + return node && node.type === 'Identifier' && node.name === 'Function' +} + +/** + * @param {Expression} node + * @returns {boolean} + */ +function hasFunctionType(node) { + if (isFunctionIdentifier(node)) { + return true + } + if (node.type === 'ArrayExpression') { + return node.elements.some(isFunctionIdentifier) + } + return false +} + module.exports = { meta: { type: 'problem', @@ -74,26 +95,6 @@ module.exports = { } } - /** - * @param {Expression|SpreadElement|null} node - */ - function isFunctionIdentifier(node) { - return node && node.type === 'Identifier' && node.name === 'Function' - } - - /** - * @param {Expression} node - * @returns {boolean} - */ - function hasFunctionType(node) { - if (isFunctionIdentifier(node)) { - return true - } - if (node.type === 'ArrayExpression') { - return node.elements.some(isFunctionIdentifier) - } - return false - } return utils.defineVueVisitor(context, { onVueObjectEnter(node) { for (const prop of utils.getComponentPropsFromOptions(node)) { diff --git a/lib/rules/no-deprecated-router-link-tag-prop.js b/lib/rules/no-deprecated-router-link-tag-prop.js index 809466d6d..cab2d7ca3 100644 --- a/lib/rules/no-deprecated-router-link-tag-prop.js +++ b/lib/rules/no-deprecated-router-link-tag-prop.js @@ -23,11 +23,12 @@ function getComponentNames(context) { components = context.options[0].components } - return components.reduce((prev, curr) => { - prev.add(casing.kebabCase(curr)) - prev.add(casing.pascalCase(curr)) - return prev - }, new Set()) + return new Set( + components.flatMap((component) => [ + casing.kebabCase(component), + casing.pascalCase(component) + ]) + ) } // ------------------------------------------------------------------------------ diff --git a/lib/rules/no-deprecated-v-on-number-modifiers.js b/lib/rules/no-deprecated-v-on-number-modifiers.js index a7e91a095..08187e476 100644 --- a/lib/rules/no-deprecated-v-on-number-modifiers.js +++ b/lib/rules/no-deprecated-v-on-number-modifiers.js @@ -37,11 +37,11 @@ module.exports = { /** @param {VDirectiveKey} node */ "VAttribute[directive=true][key.name.name='on'] > VDirectiveKey"(node) { const modifier = node.modifiers.find((mod) => - Number.isInteger(parseInt(mod.name, 10)) + Number.isInteger(Number.parseInt(mod.name, 10)) ) if (!modifier) return - const keyCodes = parseInt(modifier.name, 10) + const keyCodes = Number.parseInt(modifier.name, 10) if (keyCodes > 9 || keyCodes < 0) { context.report({ node: modifier, diff --git a/lib/rules/no-dupe-keys.js b/lib/rules/no-dupe-keys.js index 8b5e8e54c..8d28b8952 100644 --- a/lib/rules/no-dupe-keys.js +++ b/lib/rules/no-dupe-keys.js @@ -40,18 +40,19 @@ module.exports = { /** @param {RuleContext} context */ create(context) { const options = context.options[0] || {} - const groups = new Set(GROUP_NAMES.concat(options.groups || [])) + const groups = new Set([...GROUP_NAMES, ...(options.groups || [])]) // ---------------------------------------------------------------------- // Public // ---------------------------------------------------------------------- return utils.executeOnVue(context, (obj) => { - const usedNames = [] + /** @type {Set} */ + const usedNames = new Set() const properties = utils.iterateProperties(obj, groups) for (const o of properties) { - if (usedNames.indexOf(o.name) !== -1) { + if (usedNames.has(o.name)) { context.report({ node: o.node, message: "Duplicated key '{{name}}'.", @@ -61,7 +62,7 @@ module.exports = { }) } - usedNames.push(o.name) + usedNames.add(o.name) } }) } diff --git a/lib/rules/no-dupe-v-else-if.js b/lib/rules/no-dupe-v-else-if.js index 8e3a3dec1..d0d74cfed 100644 --- a/lib/rules/no-dupe-v-else-if.js +++ b/lib/rules/no-dupe-v-else-if.js @@ -173,7 +173,7 @@ module.exports = { ) } )) - if (!operands.length) { + if (operands.length === 0) { context.report({ node: condition.node, messageId: 'unexpected' diff --git a/lib/rules/no-empty-component-block.js b/lib/rules/no-empty-component-block.js index 6378dcd83..fec130d94 100644 --- a/lib/rules/no-empty-component-block.js +++ b/lib/rules/no-empty-component-block.js @@ -21,14 +21,13 @@ const { isVElement } = require('../utils') function hasAttributeSrc(componentBlock) { const hasAttribute = componentBlock.startTag.attributes.length > 0 - const hasSrc = - componentBlock.startTag.attributes.filter( - (attribute) => - !attribute.directive && - attribute.key.name === 'src' && - attribute.value && - attribute.value.value !== '' - ).length > 0 + const hasSrc = componentBlock.startTag.attributes.some( + (attribute) => + !attribute.directive && + attribute.key.name === 'src' && + attribute.value && + attribute.value.value !== '' + ) return hasAttribute && hasSrc } diff --git a/lib/rules/no-export-in-script-setup.js b/lib/rules/no-export-in-script-setup.js index 99b6ba113..fe825fdd7 100644 --- a/lib/rules/no-export-in-script-setup.js +++ b/lib/rules/no-export-in-script-setup.js @@ -37,13 +37,12 @@ module.exports = { if (tsNode.exportKind === 'type') { return } - if (tsNode.type === 'ExportNamedDeclaration') { - if ( - tsNode.specifiers.length > 0 && - tsNode.specifiers.every((spec) => spec.exportKind === 'type') - ) { - return - } + if ( + tsNode.type === 'ExportNamedDeclaration' && + tsNode.specifiers.length > 0 && + tsNode.specifiers.every((spec) => spec.exportKind === 'type') + ) { + return } context.report({ node, diff --git a/lib/rules/no-invalid-model-keys.js b/lib/rules/no-invalid-model-keys.js index f86587ff1..f3a08eda7 100644 --- a/lib/rules/no-invalid-model-keys.js +++ b/lib/rules/no-invalid-model-keys.js @@ -9,7 +9,7 @@ const utils = require('../utils') // ------------------------------------------------------------------------------ // Rule Definition // ------------------------------------------------------------------------------ -const VALID_MODEL_KEYS = ['prop', 'event'] +const VALID_MODEL_KEYS = new Set(['prop', 'event']) module.exports = { meta: { @@ -42,7 +42,7 @@ module.exports = { if (!name) { continue } - if (VALID_MODEL_KEYS.indexOf(name) === -1) { + if (!VALID_MODEL_KEYS.has(name)) { context.report({ node: p, message: "Invalid key '{{name}}' in model option.", diff --git a/lib/rules/no-irregular-whitespace.js b/lib/rules/no-irregular-whitespace.js index b83b726a9..b823dbdc5 100644 --- a/lib/rules/no-irregular-whitespace.js +++ b/lib/rules/no-irregular-whitespace.js @@ -16,9 +16,9 @@ const utils = require('../utils') // ------------------------------------------------------------------------------ const ALL_IRREGULARS = - /[\f\v\u0085\ufeff\u00a0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u202f\u205f\u3000\u2028\u2029]/u + /[\f\v\u0085\uFEFF\u00A0\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u200B\u202F\u205F\u3000\u2028\u2029]/u const IRREGULAR_WHITESPACE = - /[\f\v\u0085\ufeff\u00a0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u202f\u205f\u3000]+/gmu + /[\f\v\u0085\uFEFF\u00A0\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u200B\u202F\u205F\u3000]+/gmu const IRREGULAR_LINE_TERMINATORS = /[\u2028\u2029]/gmu // ------------------------------------------------------------------------------ @@ -117,11 +117,12 @@ module.exports = { const shouldCheckStrings = skipStrings && typeof node.value === 'string' const shouldCheckRegExps = skipRegExps && Boolean(node.regex) - if (shouldCheckStrings || shouldCheckRegExps) { - // If we have irregular characters remove them from the errors list - if (ALL_IRREGULARS.test(sourceCode.getText(node))) { - removeWhitespaceError(node) - } + // If we have irregular characters, remove them from the errors list + if ( + (shouldCheckStrings || shouldCheckRegExps) && + ALL_IRREGULARS.test(sourceCode.getText(node)) + ) { + removeWhitespaceError(node) } } @@ -191,7 +192,7 @@ module.exports = { checkForIrregularWhitespace() - if (!errorIndexes.length) { + if (errorIndexes.length === 0) { return {} } const bodyVisitor = utils.defineTemplateBodyVisitor(context, { @@ -224,9 +225,13 @@ module.exports = { const templateBody = node.templateBody if (skipComments) { // First strip errors occurring in comment nodes. - sourceCode.getAllComments().forEach(removeInvalidNodeErrorsInComment) + for (const node of sourceCode.getAllComments()) { + removeInvalidNodeErrorsInComment(node) + } if (templateBody) { - templateBody.comments.forEach(removeInvalidNodeErrorsInComment) + for (const node of templateBody.comments) { + removeInvalidNodeErrorsInComment(node) + } } } @@ -241,13 +246,13 @@ module.exports = { (templateStart <= errorIndex && errorIndex < templateEnd) ) - // If we have any errors remaining report on them - errorIndexes.forEach((errorIndex) => { + // If we have any errors remaining, report on them + for (const errorIndex of errorIndexes) { context.report({ loc: sourceCode.getLocFromIndex(errorIndex), messageId: 'disallow' }) - }) + } } } } diff --git a/lib/rules/no-lone-template.js b/lib/rules/no-lone-template.js index b56c60815..009bbad76 100644 --- a/lib/rules/no-lone-template.js +++ b/lib/rules/no-lone-template.js @@ -27,6 +27,27 @@ const SPECIAL_TEMPLATE_DIRECTIVES = new Set([ // Rule Definition // ------------------------------------------------------------------------------ +/** + * @param {VAttribute | VDirective} attr + */ +function getKeyName(attr) { + if (attr.directive) { + if (attr.key.name.name !== 'bind') { + // no v-bind + return null + } + if ( + !attr.key.argument || + attr.key.argument.type === 'VExpressionContainer' + ) { + // unknown + return null + } + return attr.key.argument.name + } + return attr.key.name +} + module.exports = { meta: { type: 'problem', @@ -56,27 +77,6 @@ module.exports = { const options = context.options[0] || {} const ignoreAccessible = options.ignoreAccessible === true - /** - * @param {VAttribute | VDirective} attr - */ - function getKeyName(attr) { - if (attr.directive) { - if (attr.key.name.name !== 'bind') { - // no v-bind - return null - } - if ( - !attr.key.argument || - attr.key.argument.type === 'VExpressionContainer' - ) { - // unknown - return null - } - return attr.key.argument.name - } - return attr.key.name - } - return utils.defineTemplateBodyVisitor(context, { /** @param {VStartTag} node */ "VElement[name='template'][parent.type='VElement'] > VStartTag"(node) { diff --git a/lib/rules/no-multiple-slot-args.js b/lib/rules/no-multiple-slot-args.js index 474a56ba2..4cfe6075c 100644 --- a/lib/rules/no-multiple-slot-args.js +++ b/lib/rules/no-multiple-slot-args.js @@ -62,7 +62,7 @@ module.exports = { return } - if (!parent.arguments.length) { + if (parent.arguments.length === 0) { return } if (parent.arguments.length > 1) { diff --git a/lib/rules/no-mutating-props.js b/lib/rules/no-mutating-props.js index b7ffcd6f7..32c7555af 100644 --- a/lib/rules/no-mutating-props.js +++ b/lib/rules/no-mutating-props.js @@ -39,6 +39,55 @@ const GLOBALS_WHITE_LISTED = new Set([ 'BigInt' ]) +/** + * @param {ASTNode} node + * @returns {VExpressionContainer} + */ +function getVExpressionContainer(node) { + let n = node + while (n.type !== 'VExpressionContainer') { + n = /** @type {ASTNode} */ (n.parent) + } + return n +} + +/** + * @param {ASTNode} node + * @returns {node is Identifier} + */ +function isVmReference(node) { + if (node.type !== 'Identifier') { + return false + } + const parent = node.parent + if (parent.type === 'MemberExpression') { + if (parent.property === node) { + // foo.id + return false + } + } else if ( + parent.type === 'Property' && + parent.key === node && + !parent.computed + ) { + // {id: foo} + return false + } + + const exprContainer = getVExpressionContainer(node) + + for (const reference of exprContainer.references) { + if (reference.variable != null) { + // Not vm reference + continue + } + if (reference.id === node) { + return true + } + } + return false +} + module.exports = { meta: { type: 'suggestion', @@ -73,17 +122,6 @@ module.exports = { }) } - /** - * @param {ASTNode} node - * @returns {VExpressionContainer} - */ - function getVExpressionContainer(node) { - let n = node - while (n.type !== 'VExpressionContainer') { - n = /** @type {ASTNode} */ (n.parent) - } - return n - } /** * @param {MemberExpression|AssignmentProperty} node * @returns {string} @@ -100,40 +138,6 @@ module.exports = { } return '?unknown?' } - /** - * @param {ASTNode} node - * @returns {node is Identifier} - */ - function isVmReference(node) { - if (node.type !== 'Identifier') { - return false - } - const parent = node.parent - if (parent.type === 'MemberExpression') { - if (parent.property === node) { - // foo.id - return false - } - } else if (parent.type === 'Property') { - // {id: foo} - if (parent.key === node && !parent.computed) { - return false - } - } - - const exprContainer = getVExpressionContainer(node) - - for (const reference of exprContainer.references) { - if (reference.variable != null) { - // Not vm reference - continue - } - if (reference.id === node) { - return true - } - } - return false - } /** * @param {MemberExpression|Identifier} props @@ -155,28 +159,36 @@ module.exports = { if (!param) { return } - if (param.type === 'Identifier') { - yield { - node: param, - path + switch (param.type) { + case 'Identifier': { + yield { node: param, path } + break + } + case 'RestElement': { + yield* iteratePatternProperties(param.argument, path) + break } - } else if (param.type === 'RestElement') { - yield* iteratePatternProperties(param.argument, path) - } else if (param.type === 'AssignmentPattern') { - yield* iteratePatternProperties(param.left, path) - } else if (param.type === 'ObjectPattern') { - for (const prop of param.properties) { - if (prop.type === 'Property') { - const name = getPropertyNameText(prop) - yield* iteratePatternProperties(prop.value, [...path, name]) - } else if (prop.type === 'RestElement') { - yield* iteratePatternProperties(prop.argument, path) + case 'AssignmentPattern': { + yield* iteratePatternProperties(param.left, path) + break + } + case 'ObjectPattern': { + for (const prop of param.properties) { + if (prop.type === 'Property') { + const name = getPropertyNameText(prop) + yield* iteratePatternProperties(prop.value, [...path, name]) + } else if (prop.type === 'RestElement') { + yield* iteratePatternProperties(prop.argument, path) + } } + break } - } else if (param.type === 'ArrayPattern') { - for (let index = 0; index < param.elements.length; index++) { - const element = param.elements[index] - yield* iteratePatternProperties(element, [...path, `${index}`]) + case 'ArrayPattern': { + for (let index = 0; index < param.elements.length; index++) { + const element = param.elements[index] + yield* iteratePatternProperties(element, [...path, `${index}`]) + } + break } } } @@ -223,7 +235,7 @@ module.exports = { const globalScope = context.getSourceCode().scopeManager.globalScope if (globalScope) { for (const variable of globalScope.variables) { - if (variable.defs.length) { + if (variable.defs.length > 0) { yield variable.name } } @@ -231,7 +243,7 @@ module.exports = { (scope) => scope.type === 'module' ) for (const variable of (moduleScope && moduleScope.variables) || []) { - if (variable.defs.length) { + if (variable.defs.length > 0) { yield variable.name } } @@ -406,10 +418,13 @@ module.exports = { while (attr && attr.type !== 'VAttribute') { attr = attr.parent } - if (attr && attr.directive && attr.key.name.name === 'bind') { - if (!attr.key.modifiers.some((mod) => mod.name === 'sync')) { - return - } + if ( + attr && + attr.directive && + attr.key.name.name === 'bind' && + !attr.key.modifiers.some((mod) => mod.name === 'sync') + ) { + return } const nodes = utils.getMemberChaining(node) diff --git a/lib/rules/no-parsing-error.js b/lib/rules/no-parsing-error.js index 9a6b4f869..e20d1763a 100644 --- a/lib/rules/no-parsing-error.js +++ b/lib/rules/no-parsing-error.js @@ -67,10 +67,12 @@ module.exports = { schema: [ { type: 'object', - properties: Object.keys(DEFAULT_OPTIONS).reduce((ret, code) => { - ret[code] = { type: 'boolean' } - return ret - }, /** @type { { [key: string]: { type: 'boolean' } } } */ ({})), + properties: Object.fromEntries( + Object.keys(DEFAULT_OPTIONS).map((code) => [ + code, + { type: 'boolean' } + ]) + ), additionalProperties: false } ] diff --git a/lib/rules/no-potential-component-option-typo.js b/lib/rules/no-potential-component-option-typo.js index 72dae1d38..2ccfed861 100644 --- a/lib/rules/no-potential-component-option-typo.js +++ b/lib/rules/no-potential-component-option-typo.js @@ -72,7 +72,7 @@ module.exports = { } } const candidateOptionList = [...candidateOptionSet] - if (!candidateOptionList.length) { + if (candidateOptionList.length === 0) { return {} } return utils.executeOnVue(context, (obj) => { @@ -91,20 +91,20 @@ module.exports = { }) .filter(utils.isDef) - if (!componentInstanceOptions.length) { + if (componentInstanceOptions.length === 0) { return } - componentInstanceOptions.forEach((option) => { + for (const option of componentInstanceOptions) { const id = option.key const name = option.name if (candidateOptionSet.has(name)) { - return + continue } const potentialTypoList = candidateOptionList .map((o) => ({ option: o, distance: utils.editDistance(o, name) })) .filter(({ distance }) => distance <= threshold && distance > 0) .sort((a, b) => a.distance - b.distance) - if (potentialTypoList.length) { + if (potentialTypoList.length > 0) { context.report({ node: id, message: `'{{name}}' may be a typo, which is similar to option [{{option}}].`, @@ -120,7 +120,7 @@ module.exports = { })) }) } - }) + } }) } } diff --git a/lib/rules/no-reserved-component-names.js b/lib/rules/no-reserved-component-names.js index e32e182c6..68d2a2b75 100644 --- a/lib/rules/no-reserved-component-names.js +++ b/lib/rules/no-reserved-component-names.js @@ -51,6 +51,30 @@ const RESERVED_NAMES_IN_OTHERS = new Set([ // Rule Definition // ------------------------------------------------------------------------------ +/** + * @param {Expression | SpreadElement} node + * @returns {node is (Literal | TemplateLiteral)} + */ +function canVerify(node) { + return ( + node.type === 'Literal' || + (node.type === 'TemplateLiteral' && + node.expressions.length === 0 && + node.quasis.length === 1) + ) +} + +/** + * @param {string} name + * @returns {string} + */ +function getMessageId(name) { + if (RESERVED_NAMES_IN_HTML.has(name)) return 'reservedInHtml' + if (RESERVED_NAMES_IN_VUE.has(name)) return 'reservedInVue' + if (RESERVED_NAMES_IN_VUE3.has(name)) return 'reservedInVue3' + return 'reserved' +} + module.exports = { meta: { type: 'suggestion', @@ -97,19 +121,6 @@ module.exports = { ...RESERVED_NAMES_IN_OTHERS ]) - /** - * @param {Expression | SpreadElement} node - * @returns {node is (Literal | TemplateLiteral)} - */ - function canVerify(node) { - return ( - node.type === 'Literal' || - (node.type === 'TemplateLiteral' && - node.expressions.length === 0 && - node.quasis.length === 1) - ) - } - /** * @param {Literal | TemplateLiteral} node */ @@ -133,13 +144,7 @@ module.exports = { function report(node, name) { context.report({ node, - messageId: RESERVED_NAMES_IN_HTML.has(name) - ? 'reservedInHtml' - : RESERVED_NAMES_IN_VUE.has(name) - ? 'reservedInVue' - : RESERVED_NAMES_IN_VUE3.has(name) - ? 'reservedInVue3' - : 'reserved', + messageId: getMessageId(name), data: { name } @@ -159,10 +164,11 @@ module.exports = { }), utils.executeOnVue(context, (obj) => { // Report if a component has been registered locally with a reserved name. - utils - .getRegisteredComponents(obj) - .filter(({ name }) => reservedNames.has(name)) - .forEach(({ node, name }) => report(node, name)) + for (const { node, name } of utils.getRegisteredComponents(obj)) { + if (reservedNames.has(name)) { + report(node, name) + } + } const node = utils.findProperty(obj, 'name') diff --git a/lib/rules/no-reserved-keys.js b/lib/rules/no-reserved-keys.js index de2f9453e..a8e787734 100644 --- a/lib/rules/no-reserved-keys.js +++ b/lib/rules/no-reserved-keys.js @@ -57,8 +57,11 @@ module.exports = { /** @param {RuleContext} context */ create(context) { const options = context.options[0] || {} - const reservedKeys = new Set(RESERVED_KEYS.concat(options.reserved || [])) - const groups = new Set(GROUP_NAMES.concat(options.groups || [])) + const reservedKeys = new Set([ + ...RESERVED_KEYS, + ...(options.reserved || []) + ]) + const groups = new Set([...GROUP_NAMES, ...(options.groups || [])]) // ---------------------------------------------------------------------- // Public diff --git a/lib/rules/no-restricted-block.js b/lib/rules/no-restricted-block.js index b94ce0c17..8fc2b51be 100644 --- a/lib/rules/no-restricted-block.js +++ b/lib/rules/no-restricted-block.js @@ -44,6 +44,13 @@ function parseOption(option) { return parsed } +/** + * @param {VElement} block + */ +function defaultMessage(block) { + return `Using \`<${block.rawName}>\` is not allowed.` +} + module.exports = { meta: { type: 'suggestion', @@ -115,12 +122,5 @@ module.exports = { } } } - - /** - * @param {VElement} block - */ - function defaultMessage(block) { - return `Using \`<${block.rawName}>\` is not allowed.` - } } } diff --git a/lib/rules/no-restricted-call-after-await.js b/lib/rules/no-restricted-call-after-await.js index 523d6af12..7b6ff74e7 100644 --- a/lib/rules/no-restricted-call-after-await.js +++ b/lib/rules/no-restricted-call-after-await.js @@ -13,6 +13,20 @@ const utils = require('../utils') * @typedef {import('eslint-utils').TYPES.TraceKind} TraceKind */ +/** + * @param {string} id + */ +function safeRequireResolve(id) { + try { + if (fs.statSync(id).isDirectory()) { + return require.resolve(id) + } + } catch (_error) { + // ignore + } + return id +} + module.exports = { meta: { type: 'suggestion', @@ -75,19 +89,7 @@ module.exports = { /** @type {Record | null} */ let allLocalImports = null - /** - * @param {string} id - */ - function safeRequireResolve(id) { - try { - if (fs.statSync(id).isDirectory()) { - return require.resolve(id) - } - } catch (_e) { - // ignore - } - return id - } + /** * @param {Program} ast */ diff --git a/lib/rules/no-restricted-class.js b/lib/rules/no-restricted-class.js index b1dcccb07..aa921349f 100644 --- a/lib/rules/no-restricted-class.js +++ b/lib/rules/no-restricted-class.js @@ -128,11 +128,9 @@ module.exports = { * @param {VAttribute & { value: VLiteral } } node */ 'VAttribute[directive=false][key.name="class"]'(node) { - node.value.value - .split(/\s+/) - .forEach((className) => - reportForbiddenClass(className, node, context, forbiddenClasses) - ) + for (const className of node.value.value.split(/\s+/)) { + reportForbiddenClass(className, node, context, forbiddenClasses) + } }, /** @param {VExpressionContainer} node */ diff --git a/lib/rules/no-restricted-component-options.js b/lib/rules/no-restricted-component-options.js index e514a7b64..f50f091d5 100644 --- a/lib/rules/no-restricted-component-options.js +++ b/lib/rules/no-restricted-component-options.js @@ -108,6 +108,13 @@ function parseOption(option) { } } +/** + * @param {string[]} path + */ +function defaultMessage(path) { + return `Using \`${path.join('.')}\` is not allowed.` +} + module.exports = { meta: { type: 'suggestion', @@ -208,12 +215,5 @@ module.exports = { } } } - - /** - * @param {string[]} path - */ - function defaultMessage(path) { - return `Using \`${path.join('.')}\` is not allowed.` - } } } diff --git a/lib/rules/no-restricted-html-elements.js b/lib/rules/no-restricted-html-elements.js index b0874f1c0..e2f30ccd0 100644 --- a/lib/rules/no-restricted-html-elements.js +++ b/lib/rules/no-restricted-html-elements.js @@ -49,7 +49,7 @@ module.exports = { return } - context.options.forEach((option) => { + for (const option of context.options) { const message = option.message || `Unexpected use of forbidden HTML element ${node.rawName}.` @@ -61,7 +61,7 @@ module.exports = { node: node.startTag }) } - }) + } } }) } diff --git a/lib/rules/no-restricted-props.js b/lib/rules/no-restricted-props.js index 7a92b9e51..564bf0773 100644 --- a/lib/rules/no-restricted-props.js +++ b/lib/rules/no-restricted-props.js @@ -154,7 +154,7 @@ function createSuggest(node, option, withDefault) { if (node.type === 'Literal' || node.type === 'TemplateLiteral') { replaceText = JSON.stringify(option.suggest) } else if (node.type === 'Identifier') { - replaceText = /^[a-z]\w*$/iu.exec(option.suggest) + replaceText = /^[a-z]\w*$/iu.test(option.suggest) ? option.suggest : JSON.stringify(option.suggest) } else { diff --git a/lib/rules/no-restricted-static-attribute.js b/lib/rules/no-restricted-static-attribute.js index 24cab62b8..a71b29e49 100644 --- a/lib/rules/no-restricted-static-attribute.js +++ b/lib/rules/no-restricted-static-attribute.js @@ -79,6 +79,24 @@ function parseOption(option) { return parsed } +/** + * @param {VAttribute} node + * @param {ParsedOption} option + */ +function defaultMessage(node, option) { + const key = node.key.rawName + let value = '' + if (option.useValue) { + value = node.value == null ? '` set to `true' : `="${node.value.value}"` + } + + let on = '' + if (option.useElement) { + on = ` on \`<${node.parent.parent.rawName}>\`` + } + return `Using \`${key + value}\`${on} is not allowed.` +} + module.exports = { meta: { type: 'suggestion', @@ -117,7 +135,7 @@ module.exports = { }, /** @param {RuleContext} context */ create(context) { - if (!context.options.length) { + if (context.options.length === 0) { return {} } /** @type {ParsedOption[]} */ @@ -141,24 +159,5 @@ module.exports = { } } }) - - /** - * @param {VAttribute} node - * @param {ParsedOption} option - */ - function defaultMessage(node, option) { - const key = node.key.rawName - const value = !option.useValue - ? '' - : node.value == null - ? '` set to `true' - : `="${node.value.value}"` - - let on = '' - if (option.useElement) { - on = ` on \`<${node.parent.parent.rawName}>\`` - } - return `Using \`${key + value}\`${on} is not allowed.` - } } } diff --git a/lib/rules/no-restricted-v-bind.js b/lib/rules/no-restricted-v-bind.js index 3cc46e715..2fdb5b744 100644 --- a/lib/rules/no-restricted-v-bind.js +++ b/lib/rules/no-restricted-v-bind.js @@ -91,6 +91,25 @@ function parseOption(option) { return parsed } +/** + * @param {VDirectiveKey} key + * @param {ParsedOption} option + */ +function defaultMessage(key, option) { + const vbind = key.name.rawName === ':' ? '' : 'v-bind' + const arg = + key.argument != null && key.argument.type === 'VIdentifier' + ? `:${key.argument.rawName}` + : '' + const mod = + option.modifiers.length > 0 ? `.${option.modifiers.join('.')}` : '' + let on = '' + if (option.useElement) { + on = ` on \`<${key.parent.parent.parent.rawName}>\`` + } + return `Using \`${vbind + arg + mod}\`${on} is not allowed.` +} + module.exports = { meta: { type: 'suggestion', @@ -159,25 +178,5 @@ module.exports = { } } }) - - /** - * @param {VDirectiveKey} key - * @param {ParsedOption} option - */ - function defaultMessage(key, option) { - const vbind = key.name.rawName === ':' ? '' : 'v-bind' - const arg = - key.argument != null && key.argument.type === 'VIdentifier' - ? `:${key.argument.rawName}` - : '' - const mod = option.modifiers.length - ? `.${option.modifiers.join('.')}` - : '' - let on = '' - if (option.useElement) { - on = ` on \`<${key.parent.parent.parent.rawName}>\`` - } - return `Using \`${vbind + arg + mod}\`${on} is not allowed.` - } } } diff --git a/lib/rules/no-static-inline-styles.js b/lib/rules/no-static-inline-styles.js index 7dce27e1a..7b59ff1a5 100644 --- a/lib/rules/no-static-inline-styles.js +++ b/lib/rules/no-static-inline-styles.js @@ -5,6 +5,82 @@ 'use strict' const utils = require('../utils') + +/** + * Checks whether if the given property node is a static value. + * @param {Property} prop property node to check + * @returns {boolean} `true` if the given property node is a static value. + */ +function isStaticValue(prop) { + return ( + !prop.computed && + prop.value.type === 'Literal' && + (prop.key.type === 'Identifier' || prop.key.type === 'Literal') + ) +} + +/** + * Gets the static properties of a given expression node. + * - If `SpreadElement` or computed property exists, it gets only the static properties before it. + * `:style="{ color: 'red', display: 'flex', ...spread, width: '16px' }"` + * ^^^^^^^^^^^^ ^^^^^^^^^^^^^^^ + * - If non-static object exists, it gets only the static properties up to that object. + * `:style="[ { color: 'red' }, { display: 'flex', color, width: '16px' }, { height: '16px' } ]"` + * ^^^^^^^^^^^^ ^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^ + * - If all properties are static properties, it returns one root node. + * `:style="[ { color: 'red' }, { display: 'flex', width: '16px' } ]"` + * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + * @param {VDirective} node `:style` node to check + * @returns {Property[] | [VDirective]} the static properties. + */ +function getReportNodes(node) { + const { value } = node + if (!value) { + return [] + } + const { expression } = value + if (!expression) { + return [] + } + + let elements + if (expression.type === 'ObjectExpression') { + elements = [expression] + } else if (expression.type === 'ArrayExpression') { + elements = expression.elements + } else { + return [] + } + const staticProperties = [] + for (const element of elements) { + if (!element) { + continue + } + if (element.type !== 'ObjectExpression') { + return staticProperties + } + + let isAllStatic = true + for (const prop of element.properties) { + if (prop.type === 'SpreadElement' || prop.computed) { + // If `SpreadElement` or computed property exists, it gets only the static properties before it. + return staticProperties + } + if (isStaticValue(prop)) { + staticProperties.push(prop) + } else { + isAllStatic = false + } + } + if (!isAllStatic) { + // If non-static object exists, it gets only the static properties up to that object. + return staticProperties + } + } + // If all properties are static properties, it returns one root node. + return [node] +} + module.exports = { meta: { type: 'suggestion', @@ -32,81 +108,6 @@ module.exports = { }, /** @param {RuleContext} context */ create(context) { - /** - * Checks whether if the given property node is a static value. - * @param {Property} prop property node to check - * @returns {boolean} `true` if the given property node is a static value. - */ - function isStaticValue(prop) { - return ( - !prop.computed && - prop.value.type === 'Literal' && - (prop.key.type === 'Identifier' || prop.key.type === 'Literal') - ) - } - - /** - * Gets the static properties of a given expression node. - * - If `SpreadElement` or computed property exists, it gets only the static properties before it. - * `:style="{ color: 'red', display: 'flex', ...spread, width: '16px' }"` - * ^^^^^^^^^^^^ ^^^^^^^^^^^^^^^ - * - If non-static object exists, it gets only the static properties up to that object. - * `:style="[ { color: 'red' }, { display: 'flex', color, width: '16px' }, { height: '16px' } ]"` - * ^^^^^^^^^^^^ ^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^ - * - If all properties are static properties, it returns one root node. - * `:style="[ { color: 'red' }, { display: 'flex', width: '16px' } ]"` - * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - * @param {VDirective} node `:style` node to check - * @returns {Property[] | [VDirective]} the static properties. - */ - function getReportNodes(node) { - const { value } = node - if (!value) { - return [] - } - const { expression } = value - if (!expression) { - return [] - } - - let elements - if (expression.type === 'ObjectExpression') { - elements = [expression] - } else if (expression.type === 'ArrayExpression') { - elements = expression.elements - } else { - return [] - } - const staticProperties = [] - for (const element of elements) { - if (!element) { - continue - } - if (element.type !== 'ObjectExpression') { - return staticProperties - } - - let isAllStatic = true - for (const prop of element.properties) { - if (prop.type === 'SpreadElement' || prop.computed) { - // If `SpreadElement` or computed property exists, it gets only the static properties before it. - return staticProperties - } - if (isStaticValue(prop)) { - staticProperties.push(prop) - } else { - isAllStatic = false - } - } - if (!isAllStatic) { - // If non-static object exists, it gets only the static properties up to that object. - return staticProperties - } - } - // If all properties are static properties, it returns one root node. - return [node] - } - /** * Reports if the value is static. * @param {VDirective} node `:style` node to check diff --git a/lib/rules/no-template-shadow.js b/lib/rules/no-template-shadow.js index e3e803792..227abe715 100644 --- a/lib/rules/no-template-shadow.js +++ b/lib/rules/no-template-shadow.js @@ -95,9 +95,7 @@ module.exports = { } }), utils.executeOnVue(context, (obj) => { - const properties = Array.from( - utils.iterateProperties(obj, new Set(GROUP_NAMES)) - ) + const properties = utils.iterateProperties(obj, new Set(GROUP_NAMES)) for (const node of properties) { jsVars.add(node.name) } diff --git a/lib/rules/no-undef-components.js b/lib/rules/no-undef-components.js index e56f0412c..f6e66400c 100644 --- a/lib/rules/no-undef-components.js +++ b/lib/rules/no-undef-components.js @@ -161,10 +161,8 @@ module.exports = { // Check namespace // https://github.com/vuejs/core/blob/ae4b0783d78670b6e942ae2a4e3ec6efbbffa158/packages/compiler-core/src/transforms/transformElement.ts#L305 const dotIndex = rawName.indexOf('.') - if (dotIndex > 0) { - if (existsSetupReference(rawName.slice(0, dotIndex))) { - return - } + if (dotIndex > 0 && existsSetupReference(rawName.slice(0, dotIndex))) { + return } context.report({ @@ -227,13 +225,12 @@ module.exports = { return } const kebabCaseName = casing.kebabCase(rawName) - if (registeredComponentKebabCaseNames.includes(kebabCaseName)) { - if ( - // Component registered as `foo-bar` cannot be used as `FooBar` - !casing.isPascalCase(rawName) - ) { - return - } + if ( + registeredComponentKebabCaseNames.includes(kebabCaseName) && + !casing.isPascalCase(rawName) + ) { + // Component registered as `foo-bar` cannot be used as `FooBar` + return } context.report({ diff --git a/lib/rules/no-undef-properties.js b/lib/rules/no-undef-properties.js index c1213aa96..7641d3afb 100644 --- a/lib/rules/no-undef-properties.js +++ b/lib/rules/no-undef-properties.js @@ -124,6 +124,13 @@ module.exports = { const propertyReferenceExtractor = definePropertyReferenceExtractor(context) const programNode = context.getSourceCode().ast + /** + * @param {ASTNode} node + */ + function isScriptSetupProgram(node) { + return node === programNode + } + /** Vue component context */ class VueComponentContext { constructor() { @@ -140,7 +147,7 @@ module.exports = { * @param {boolean} [options.props] */ verifyReferences(references, options) { - const that = this + const report = this.report.bind(this) verifyUndefProperties(this.defineProperties, references, null) /** @@ -159,14 +166,12 @@ module.exports = { const prop = defineProperties.get && defineProperties.get(refName) if (prop) { - if (options && options.props) { - if (!prop.isProps) { - that.report(nodes[0], referencePathName, 'undefProps') - continue - } + if (options && options.props && !prop.isProps) { + report(nodes[0], referencePathName, 'undefProps') + continue } } else { - that.report(nodes[0], referencePathName, 'undef') + report(nodes[0], referencePathName, 'undef') continue } @@ -238,13 +243,6 @@ module.exports = { const exported = keys.find(isScriptSetupProgram) || keys.find(utils.isInExportDefault) return exported && vueComponentContextMap.get(exported) - - /** - * @param {ASTNode} node - */ - function isScriptSetupProgram(node) { - return node === programNode - } } /** diff --git a/lib/rules/no-unregistered-components.js b/lib/rules/no-unregistered-components.js index b4651c384..729685083 100644 --- a/lib/rules/no-unregistered-components.js +++ b/lib/rules/no-unregistered-components.js @@ -126,55 +126,56 @@ module.exports = { }, "VElement[name='template'][parent.type='VDocumentFragment']:exit"() { // All registered components, transformed to kebab-case - const registeredComponentNames = registeredComponents.map( - ({ name }) => casing.kebabCase(name) + const registeredComponentNames = new Set( + registeredComponents.map(({ name }) => casing.kebabCase(name)) ) // All registered components using kebab-case syntax - const componentsRegisteredAsKebabCase = registeredComponents - .filter(({ name }) => name === casing.kebabCase(name)) - .map(({ name }) => name) - - usedComponentNodes - .filter(({ name }) => { - const kebabCaseName = casing.kebabCase(name) - - // Check ignored patterns in first place - if ( - ignorePatterns.find((pattern) => { - const regExp = new RegExp(pattern) - return ( - regExp.test(kebabCaseName) || - regExp.test(casing.pascalCase(name)) || - regExp.test(casing.camelCase(name)) || - regExp.test(casing.snakeCase(name)) || - regExp.test(name) - ) - }) - ) - return false - - // Component registered as `foo-bar` cannot be used as `FooBar` - if ( - casing.isPascalCase(name) && - componentsRegisteredAsKebabCase.indexOf(kebabCaseName) !== -1 - ) { - return true - } + const componentsRegisteredAsKebabCase = new Set( + registeredComponents.flatMap(({ name }) => + name === casing.kebabCase(name) ? [name] : [] + ) + ) - // Otherwise - return registeredComponentNames.indexOf(kebabCaseName) === -1 - }) - .forEach(({ node, name }) => - context.report({ - node, - message: - 'The "{{name}}" component has been used but not registered.', - data: { - name - } + const reportNodes = usedComponentNodes.filter(({ name }) => { + const kebabCaseName = casing.kebabCase(name) + + // Check ignored patterns in first place + if ( + ignorePatterns.some((pattern) => { + const regExp = new RegExp(pattern) + return ( + regExp.test(kebabCaseName) || + regExp.test(casing.pascalCase(name)) || + regExp.test(casing.camelCase(name)) || + regExp.test(casing.snakeCase(name)) || + regExp.test(name) + ) }) ) + return false + + // Component registered as `foo-bar` cannot be used as `FooBar` + if ( + casing.isPascalCase(name) && + componentsRegisteredAsKebabCase.has(kebabCaseName) + ) { + return true + } + + // Otherwise + return !registeredComponentNames.has(kebabCaseName) + }) + for (const { node, name } of reportNodes) { + context.report({ + node, + message: + 'The "{{name}}" component has been used but not registered.', + data: { + name + } + }) + } } }, utils.executeOnVue(context, (obj) => { @@ -182,13 +183,11 @@ module.exports = { const nameProperty = utils.findProperty(obj, 'name') - if (nameProperty) { - if (nameProperty.value.type === 'Literal') { - registeredComponents.push({ - node: nameProperty, - name: `${nameProperty.value.value}` - }) - } + if (nameProperty && nameProperty.value.type === 'Literal') { + registeredComponents.push({ + node: nameProperty, + name: `${nameProperty.value.value}` + }) } }) ) diff --git a/lib/rules/no-unused-components.js b/lib/rules/no-unused-components.js index 8497dd961..0cbaec9f7 100644 --- a/lib/rules/no-unused-components.js +++ b/lib/rules/no-unused-components.js @@ -106,36 +106,39 @@ module.exports = { ) return - registeredComponents - .filter(({ name }) => { - // If the component name is PascalCase or camelCase - // it can be used in various of ways inside template, - // like "theComponent", "The-component" etc. - // but except snake_case - if (casing.isPascalCase(name) || casing.isCamelCase(name)) { - return ![...usedComponents].some((n) => { - return ( - n.indexOf('_') === -1 && + for (const { node, name } of registeredComponents) { + // If the component name is PascalCase or camelCase + // it can be used in various of ways inside template, + // like "theComponent", "The-component" etc. + // but except snake_case + if (casing.isPascalCase(name) || casing.isCamelCase(name)) { + if ( + [...usedComponents].some( + (n) => + !n.includes('_') && (name === casing.pascalCase(n) || - casing.camelCase(n) === name) - ) - }) - } else { - // In any other case the used component name must exactly match - // the registered name - return !usedComponents.has(name) + name === casing.camelCase(n)) + ) + ) { + return + } + } else { + // In any other case the used component name must exactly match + // the registered name + if (usedComponents.has(name)) { + return + } + } + + context.report({ + node, + message: + 'The "{{name}}" component has been registered but not used.', + data: { + name } }) - .forEach(({ node, name }) => - context.report({ - node, - message: - 'The "{{name}}" component has been registered but not used.', - data: { - name - } - }) - ) + } } }, utils.executeOnVue(context, (obj) => { diff --git a/lib/rules/no-unused-refs.js b/lib/rules/no-unused-refs.js index 2c47bc035..248f11c59 100644 --- a/lib/rules/no-unused-refs.js +++ b/lib/rules/no-unused-refs.js @@ -101,47 +101,56 @@ module.exports = { node = node.parent } const parent = node.parent - if (parent.type === 'AssignmentExpression') { - if (parent.right === node) { - if (parent.left.type === 'ObjectPattern') { - // `({foo} = $refs)` - extractUsedForObjectPattern(parent.left) - } else if (parent.left.type === 'Identifier') { - // `foo = $refs` - hasUnknown = true + switch (parent.type) { + case 'AssignmentExpression': { + if (parent.right === node) { + if (parent.left.type === 'ObjectPattern') { + // `({foo} = $refs)` + extractUsedForObjectPattern(parent.left) + } else if (parent.left.type === 'Identifier') { + // `foo = $refs` + hasUnknown = true + } } + break } - } else if (parent.type === 'VariableDeclarator') { - if (parent.init === node) { - if (parent.id.type === 'ObjectPattern') { - // `const {foo} = $refs` - extractUsedForObjectPattern(parent.id) - } else if (parent.id.type === 'Identifier') { - // `const foo = $refs` - hasUnknown = true + case 'VariableDeclarator': { + if (parent.init === node) { + if (parent.id.type === 'ObjectPattern') { + // `const {foo} = $refs` + extractUsedForObjectPattern(parent.id) + } else if (parent.id.type === 'Identifier') { + // `const foo = $refs` + hasUnknown = true + } } + break } - } else if (parent.type === 'MemberExpression') { - if (parent.object === node) { - // `$refs.foo` - const name = utils.getStaticPropertyName(parent) - if (name) { - usedRefs.add(name) - } else { + case 'MemberExpression': { + if (parent.object === node) { + // `$refs.foo` + const name = utils.getStaticPropertyName(parent) + if (name) { + usedRefs.add(name) + } else { + hasUnknown = true + } + } + break + } + case 'CallExpression': { + const argIndex = parent.arguments.indexOf(node) + if (argIndex > -1) { + // `foo($refs)` hasUnknown = true } + break } - } else if (parent.type === 'CallExpression') { - const argIndex = parent.arguments.indexOf(node) - if (argIndex > -1) { - // `foo($refs)` + case 'ForInStatement': + case 'ReturnStatement': { hasUnknown = true + break } - } else if ( - parent.type === 'ForInStatement' || - parent.type === 'ReturnStatement' - ) { - hasUnknown = true } } @@ -228,11 +237,12 @@ module.exports = { } /** @type {Identifier | MemberExpression} */ let refsNode = id - if (id.parent.type === 'MemberExpression') { - if (id.parent.property === id) { - // `this.$refs.foo` - refsNode = id.parent - } + if ( + id.parent.type === 'MemberExpression' && + id.parent.property === id + ) { + // `this.$refs.foo` + refsNode = id.parent } extractUsedForPattern(refsNode) } diff --git a/lib/rules/no-unused-vars.js b/lib/rules/no-unused-vars.js index 517480abc..430d3bedf 100644 --- a/lib/rules/no-unused-vars.js +++ b/lib/rules/no-unused-vars.js @@ -104,7 +104,7 @@ module.exports = { for (let i = variables.length - 1; i >= 0; i--) { const variable = variables[i] - if (variable.references.length) { + if (variable.references.length > 0) { hasAfterUsed = true continue } diff --git a/lib/rules/no-use-computed-property-like-method.js b/lib/rules/no-use-computed-property-like-method.js index d5c4430d7..e865f0443 100644 --- a/lib/rules/no-use-computed-property-like-method.js +++ b/lib/rules/no-use-computed-property-like-method.js @@ -64,7 +64,7 @@ function resolvedExpressions(context, node) { } } - if (!resolvedSet.size) { + if (resolvedSet.size === 0) { resolvedSet.add(node) } @@ -84,10 +84,11 @@ function resolvedExpressions(context, node) { if (id.parent.id === id && id.parent.init) { yield* resolvedExpressionsInternal(id.parent.init) } - } else if (id.parent.type === 'AssignmentExpression') { - if (id.parent.left === id) { - yield* resolvedExpressionsInternal(id.parent.right) - } + } else if ( + id.parent.type === 'AssignmentExpression' && + id.parent.left === id + ) { + yield* resolvedExpressionsInternal(id.parent.right) } } } @@ -201,13 +202,12 @@ function maybeFunction(context, node) { ) { continue } - if (expr.type === 'ConditionalExpression') { - if ( - !maybeFunction(context, expr.consequent) && - !maybeFunction(context, expr.alternate) - ) { - continue - } + if ( + expr.type === 'ConditionalExpression' && + !maybeFunction(context, expr.consequent) && + !maybeFunction(context, expr.alternate) + ) { + continue } const evaluated = eslintUtils.getStaticValue( expr, @@ -289,30 +289,39 @@ class ComponentStack { if (property.type === 'array') { continue } - if (property.groupName === 'data') { - maybeFunctions.set( - property.name, - maybeFunction(context, property.property.value) - ) - } else if (property.groupName === 'props') { - const types = getComponentPropsTypes(context, property) - maybeFunctions.set( - property.name, - !types || types.some((type) => !NATIVE_NOT_FUNCTION_TYPES.has(type)) - ) - } else if (property.groupName === 'computed') { - let value = property.property.value - if (value.type === 'ObjectExpression') { - const getProp = utils.findProperty(value, 'get') - if (getProp) { - value = getProp.value + switch (property.groupName) { + case 'data': { + maybeFunctions.set( + property.name, + maybeFunction(context, property.property.value) + ) + break + } + case 'props': { + const types = getComponentPropsTypes(context, property) + maybeFunctions.set( + property.name, + !types || types.some((type) => !NATIVE_NOT_FUNCTION_TYPES.has(type)) + ) + break + } + case 'computed': { + let value = property.property.value + if (value.type === 'ObjectExpression') { + const getProp = utils.findProperty(value, 'get') + if (getProp) { + value = getProp.value + } } + processFunction(property.name, value, 'computed') + break + } + case 'methods': { + const value = property.property.value + processFunction(property.name, value, 'methods') + maybeFunctions.set(property.name, true) + break } - processFunction(property.name, value, 'computed') - } else if (property.groupName === 'methods') { - const value = property.property.value - processFunction(property.name, value, 'methods') - maybeFunctions.set(property.name, true) } } this.maybeFunctions = maybeFunctions @@ -410,34 +419,41 @@ class ComponentStack { if (!maybeFunction(this.context, expr)) { continue } - if (expr.type === 'MemberExpression') { - if (utils.isThis(expr.object, this.context)) { - const name = utils.getStaticPropertyName(expr) - if (name && !this.maybeFunctionProperty(name)) { - continue + switch (expr.type) { + case 'MemberExpression': { + if (utils.isThis(expr.object, this.context)) { + const name = utils.getStaticPropertyName(expr) + if (name && !this.maybeFunctionProperty(name)) { + continue + } } + break } - } else if (expr.type === 'CallExpression') { - if ( - expr.callee.type === 'MemberExpression' && - utils.isThis(expr.callee.object, this.context) - ) { - const name = utils.getStaticPropertyName(expr.callee) - const fnData = this.functions.find((data) => data.name === name) + case 'CallExpression': { if ( - fnData && - fnData.kind === 'methods' && - !fnData.maybeReturnFunction(this) + expr.callee.type === 'MemberExpression' && + utils.isThis(expr.callee.object, this.context) ) { - continue + const name = utils.getStaticPropertyName(expr.callee) + const fnData = this.functions.find((data) => data.name === name) + if ( + fnData && + fnData.kind === 'methods' && + !fnData.maybeReturnFunction(this) + ) { + continue + } } + break } - } else if (expr.type === 'ConditionalExpression') { - if ( - !this.maybeFunctionExpression(expr.consequent) && - !this.maybeFunctionExpression(expr.alternate) - ) { - continue + case 'ConditionalExpression': { + if ( + !this.maybeFunctionExpression(expr.consequent) && + !this.maybeFunctionExpression(expr.alternate) + ) { + continue + } + break } } // It could be a function because we don't know what it is. diff --git a/lib/rules/no-useless-mustaches.js b/lib/rules/no-useless-mustaches.js index 8e5a6cee1..51e3853ba 100644 --- a/lib/rules/no-useless-mustaches.js +++ b/lib/rules/no-useless-mustaches.js @@ -144,7 +144,7 @@ module.exports = { return null } - return fixer.replaceText(node, text.replace(/\\([\s\S])/g, '$1')) + return fixer.replaceText(node, text.replace(/\\([\S\s])/g, '$1')) } }) } diff --git a/lib/rules/no-useless-template-attributes.js b/lib/rules/no-useless-template-attributes.js index fca048e55..f86384a22 100644 --- a/lib/rules/no-useless-template-attributes.js +++ b/lib/rules/no-useless-template-attributes.js @@ -26,6 +26,54 @@ const SPECIAL_TEMPLATE_DIRECTIVES = new Set([ // ------------------------------------------------------------------------------ // Rule Definition // ------------------------------------------------------------------------------ +/** + * @param {VAttribute | VDirective} attr + */ +function getKeyName(attr) { + if (attr.directive) { + if (attr.key.name.name !== 'bind') { + // no v-bind + return null + } + if ( + !attr.key.argument || + attr.key.argument.type === 'VExpressionContainer' + ) { + // unknown + return null + } + return attr.key.argument.name + } + return attr.key.name +} + +/** + * @param {VAttribute | VDirective} attr + */ +function isFragmentTemplateAttribute(attr) { + if (attr.directive) { + const directiveName = attr.key.name.name + if (SPECIAL_TEMPLATE_DIRECTIVES.has(directiveName)) { + return true + } + if (directiveName === 'slot-scope') { + // `slot-scope` is deprecated in Vue.js 2.6 + return true + } + if (directiveName === 'scope') { + // `scope` is deprecated in Vue.js 2.5 + return true + } + } + + const keyName = getKeyName(attr) + if (keyName === 'slot') { + // `slot` is deprecated in Vue.js 2.6 + return true + } + + return false +} module.exports = { meta: { @@ -44,55 +92,6 @@ module.exports = { }, /** @param {RuleContext} context */ create(context) { - /** - * @param {VAttribute | VDirective} attr - */ - function getKeyName(attr) { - if (attr.directive) { - if (attr.key.name.name !== 'bind') { - // no v-bind - return null - } - if ( - !attr.key.argument || - attr.key.argument.type === 'VExpressionContainer' - ) { - // unknown - return null - } - return attr.key.argument.name - } - return attr.key.name - } - - /** - * @param {VAttribute | VDirective} attr - */ - function isFragmentTemplateAttribute(attr) { - if (attr.directive) { - const directiveName = attr.key.name.name - if (SPECIAL_TEMPLATE_DIRECTIVES.has(directiveName)) { - return true - } - if (directiveName === 'slot-scope') { - // `slot-scope` is deprecated in Vue.js 2.6 - return true - } - if (directiveName === 'scope') { - // `scope` is deprecated in Vue.js 2.5 - return true - } - } - - const keyName = getKeyName(attr) - if (keyName === 'slot') { - // `slot` is deprecated in Vue.js 2.6 - return true - } - - return false - } - return utils.defineTemplateBodyVisitor(context, { /** @param {VStartTag} node */ "VElement[name='template'][parent.type='VElement'] > VStartTag"(node) { diff --git a/lib/rules/no-useless-v-bind.js b/lib/rules/no-useless-v-bind.js index 749a292e4..9f9da6e27 100644 --- a/lib/rules/no-useless-v-bind.js +++ b/lib/rules/no-useless-v-bind.js @@ -48,8 +48,8 @@ module.exports = { * @param {VDirective} node the node to check */ function verify(node) { - const { value } = node - if (!value || node.key.modifiers.length) { + const { value, key } = node + if (!value || key.modifiers.length > 0) { return } const { expression } = value @@ -120,11 +120,11 @@ module.exports = { const text = sourceCode.getText(value) const quoteChar = text[0] - const shorthand = node.key.name.rawName === ':' + const shorthand = key.name.rawName === ':' /** @type {Range} */ const keyDirectiveRange = [ - node.key.name.range[0], - node.key.name.range[1] + (shorthand ? 0 : 1) + key.name.range[0], + key.name.range[1] + (shorthand ? 0 : 1) ] yield fixer.removeRange(keyDirectiveRange) diff --git a/lib/rules/order-in-components.js b/lib/rules/order-in-components.js index 83558aa39..83e814413 100644 --- a/lib/rules/order-in-components.js +++ b/lib/rules/order-in-components.js @@ -104,13 +104,15 @@ function getOrderMap(order) { /** @type {Map} */ const orderMap = new Map() - order.forEach((property, i) => { + for (const [i, property] of order.entries()) { if (Array.isArray(property)) { - property.forEach((p) => orderMap.set(p, i)) + for (const p of property) { + orderMap.set(p, i) + } } else { orderMap.set(property, i) } - }) + } return orderMap } @@ -126,13 +128,13 @@ const ARITHMETIC_OPERATORS = ['+', '-', '*', '/', '%', '**' /* es2016 */] const BITWISE_OPERATORS = ['&', '|', '^', '~', '<<', '>>', '>>>'] const COMPARISON_OPERATORS = ['==', '!=', '===', '!==', '>', '>=', '<', '<='] const RELATIONAL_OPERATORS = ['in', 'instanceof'] -const ALL_BINARY_OPERATORS = [ +const ALL_BINARY_OPERATORS = new Set([ ...ARITHMETIC_OPERATORS, ...BITWISE_OPERATORS, ...COMPARISON_OPERATORS, ...RELATIONAL_OPERATORS -] -const LOGICAL_OPERATORS = ['&&', '||', '??' /* es2020 */] +]) +const LOGICAL_OPERATORS = new Set(['&&', '||', '??' /* es2020 */]) /** * Result `true` if the node is sure that there are no side effects @@ -179,9 +181,9 @@ function isNotSideEffectsNode(node, visitorKeys) { (node.type !== 'UnaryExpression' || !['!', '~', '+', '-', 'typeof'].includes(node.operator)) && (node.type !== 'BinaryExpression' || - !ALL_BINARY_OPERATORS.includes(node.operator)) && + !ALL_BINARY_OPERATORS.has(node.operator)) && (node.type !== 'LogicalExpression' || - !LOGICAL_OPERATORS.includes(node.operator)) && + !LOGICAL_OPERATORS.has(node.operator)) && node.type !== 'MemberExpression' && node.type !== 'ConditionalExpression' && // es2015 @@ -267,10 +269,10 @@ module.exports = { } }) - properties.forEach((property, i) => { + for (const [i, property] of properties.entries()) { const orderPos = getOrderPosition(property.name) if (orderPos < 0) { - return + continue } const propertiesAbove = properties.slice(0, i) const unorderedProperties = propertiesAbove @@ -333,7 +335,7 @@ module.exports = { } }) } - }) + } } return utils.executeOnVue(context, (obj) => { diff --git a/lib/rules/padding-line-between-blocks.js b/lib/rules/padding-line-between-blocks.js index c1b652455..f5f4d089c 100644 --- a/lib/rules/padding-line-between-blocks.js +++ b/lib/rules/padding-line-between-blocks.js @@ -41,7 +41,7 @@ function verifyForNever(context, prevBlock, nextBlock, betweenTokens) { } prev = tokenOrNode } - if (!paddingLines.length) { + if (paddingLines.length === 0) { return } @@ -170,9 +170,10 @@ module.exports = { (token) => token.type !== 'HTMLWhitespace' ), ...documentFragment.comments - ].sort((a, b) => - a.range[0] > b.range[0] ? 1 : a.range[0] < b.range[0] ? -1 : 0 - ) + ].sort((a, b) => { + if (a.range[0] > b.range[0]) return 1 + return a.range[0] < b.range[0] ? -1 : 0 + }) } let token = tokens.shift() diff --git a/lib/rules/prefer-import-from-vue.js b/lib/rules/prefer-import-from-vue.js index 1fb4ecf55..ef154854b 100644 --- a/lib/rules/prefer-import-from-vue.js +++ b/lib/rules/prefer-import-from-vue.js @@ -28,12 +28,19 @@ const SUBSET_AT_VUE_MODULES = new Set(['@vue/runtime-dom', '@vue/runtime-core']) */ function* extractImportNames(node) { for (const specifier of node.specifiers) { - if (specifier.type === 'ImportDefaultSpecifier') { - yield 'default' - } else if (specifier.type === 'ImportNamespaceSpecifier') { - yield null // all - } else if (specifier.type === 'ImportSpecifier') { - yield specifier.imported.name + switch (specifier.type) { + case 'ImportDefaultSpecifier': { + yield 'default' + break + } + case 'ImportNamespaceSpecifier': { + yield null // all + break + } + case 'ImportSpecifier': { + yield specifier.imported.name + break + } } } } diff --git a/lib/rules/prefer-prop-type-boolean-first.js b/lib/rules/prefer-prop-type-boolean-first.js index 215262d3b..055e16129 100644 --- a/lib/rules/prefer-prop-type-boolean-first.js +++ b/lib/rules/prefer-prop-type-boolean-first.js @@ -37,7 +37,7 @@ function checkArrayExpression(node, context) { fix: (fixer) => { const sourceCode = context.getSourceCode() - const elements = node.elements.slice() + const elements = [...node.elements] elements.splice(booleanTypeIndex, 1) const code = elements .filter(utils.isDef) @@ -103,12 +103,16 @@ module.exports = { return utils.compositingVisitors( utils.defineScriptSetupVisitor(context, { onDefinePropsEnter(_, props) { - props.forEach(checkProperty) + for (const prop of props) { + checkProperty(prop) + } } }), utils.executeOnVue(context, (obj) => { const props = utils.getComponentPropsFromOptions(obj) - props.forEach(checkProperty) + for (const prop of props) { + checkProperty(prop) + } }) ) } diff --git a/lib/rules/prop-name-casing.js b/lib/rules/prop-name-casing.js index 916dff0cd..29386d6bc 100644 --- a/lib/rules/prop-name-casing.js +++ b/lib/rules/prop-name-casing.js @@ -18,8 +18,7 @@ const allowedCaseOptions = ['camelCase', 'snake_case'] /** @param {RuleContext} context */ function create(context) { const options = context.options[0] - const caseType = - allowedCaseOptions.indexOf(options) !== -1 ? options : 'camelCase' + const caseType = allowedCaseOptions.includes(options) ? options : 'camelCase' const checker = casing.getChecker(caseType) // ---------------------------------------------------------------------- diff --git a/lib/rules/require-default-prop.js b/lib/rules/require-default-prop.js index 7cfdc7efa..7aff2ebac 100644 --- a/lib/rules/require-default-prop.js +++ b/lib/rules/require-default-prop.js @@ -27,6 +27,26 @@ const NATIVE_TYPES = new Set([ // Rule Definition // ------------------------------------------------------------------------------ +/** + * Detects whether given value node is a Boolean type + * @param {Expression} value + * @return {boolean} + */ +function isValueNodeOfBooleanType(value) { + if (value.type === 'Identifier' && value.name === 'Boolean') { + return true + } + if (value.type === 'ArrayExpression') { + const elements = value.elements.filter(isDef) + return ( + elements.length === 1 && + elements[0].type === 'Identifier' && + elements[0].name === 'Boolean' + ) + } + return false +} + module.exports = { meta: { type: 'suggestion', @@ -102,26 +122,6 @@ module.exports = { return !propIsRequired(prop.value) && !propHasDefault(prop.value) } - /** - * Detects whether given value node is a Boolean type - * @param {Expression} value - * @return {boolean} - */ - function isValueNodeOfBooleanType(value) { - if (value.type === 'Identifier' && value.name === 'Boolean') { - return true - } - if (value.type === 'ArrayExpression') { - const elements = value.elements.filter(isDef) - return ( - elements.length === 1 && - elements[0].type === 'Identifier' && - elements[0].name === 'Boolean' - ) - } - return false - } - /** * Detects whether given prop node is a Boolean * @param {ComponentObjectProp} prop diff --git a/lib/rules/require-direct-export.js b/lib/rules/require-direct-export.js index 18897eb07..05d80c38f 100644 --- a/lib/rules/require-direct-export.js +++ b/lib/rules/require-direct-export.js @@ -60,18 +60,18 @@ module.exports = { callee, arguments: [firstArg] } = node - if (firstArg && firstArg.type === 'ObjectExpression') { - if ( - (callee.type === 'Identifier' && - callee.name === 'defineComponent') || + if ( + firstArg && + firstArg.type === 'ObjectExpression' && + ((callee.type === 'Identifier' && + callee.name === 'defineComponent') || (callee.type === 'MemberExpression' && callee.object.type === 'Identifier' && callee.object.name === 'Vue' && callee.property.type === 'Identifier' && - callee.property.name === 'extend') - ) { - return - } + callee.property.name === 'extend')) + ) { + return } } if (!disallowFunctional) { diff --git a/lib/rules/require-emit-validator.js b/lib/rules/require-emit-validator.js index 24c6a9c55..9ab8e6bdf 100644 --- a/lib/rules/require-emit-validator.js +++ b/lib/rules/require-emit-validator.js @@ -89,11 +89,15 @@ module.exports = { return utils.compositingVisitors( utils.executeOnVue(context, (obj) => { - utils.getComponentEmitsFromOptions(obj).forEach(checker) + for (const emit of utils.getComponentEmitsFromOptions(obj)) { + checker(emit) + } }), utils.defineScriptSetupVisitor(context, { onDefineEmitsEnter(_node, emits) { - emits.forEach(checker) + for (const emit of emits) { + checker(emit) + } } }) ) diff --git a/lib/rules/require-explicit-emits.js b/lib/rules/require-explicit-emits.js index aa83b882f..1a69c982a 100644 --- a/lib/rules/require-explicit-emits.js +++ b/lib/rules/require-explicit-emits.js @@ -27,7 +27,7 @@ const { capitalize } = require('../utils/casing') // Helpers // ------------------------------------------------------------------------------ -const FIX_EMITS_AFTER_OPTIONS = [ +const FIX_EMITS_AFTER_OPTIONS = new Set([ 'setup', 'data', 'computed', @@ -53,7 +53,7 @@ const FIX_EMITS_AFTER_OPTIONS = [ 'renderTracked', 'renderTriggered', 'errorCaptured' -] +]) // ------------------------------------------------------------------------------ // Rule Definition // ------------------------------------------------------------------------------ @@ -383,20 +383,17 @@ module.exports = { onVueObjectExit(node, { type }) { const emits = vueEmitsDeclarations.get(node) if ( - !vueTemplateDefineData || - (vueTemplateDefineData.type !== 'export' && - vueTemplateDefineData.type !== 'setup') + (!vueTemplateDefineData || + (vueTemplateDefineData.type !== 'export' && + vueTemplateDefineData.type !== 'setup')) && + emits && + (type === 'mark' || type === 'export' || type === 'definition') ) { - if ( - emits && - (type === 'mark' || type === 'export' || type === 'definition') - ) { - vueTemplateDefineData = { - type, - define: node, - emits, - props: vuePropsDeclarations.get(node) || [] - } + vueTemplateDefineData = { + type, + define: node, + emits, + props: vuePropsDeclarations.get(node) || [] } } setupContexts.delete(node) @@ -420,7 +417,7 @@ function buildSuggest(define, emits, nameNode, context) { const emitsKind = define.type === 'ObjectExpression' ? '`emits` option' : '`defineEmits`' const certainEmits = emits.filter((e) => e.key) - if (certainEmits.length) { + if (certainEmits.length > 0) { const last = certainEmits[certainEmits.length - 1] return [ { @@ -476,7 +473,7 @@ function buildSuggest(define, emits, nameNode, context) { return fixer.insertTextAfter( leftBracket, `'${nameNode.value}'${ - emitsOptionValue.elements.length ? ',' : '' + emitsOptionValue.elements.length > 0 ? ',' : '' }` ) } @@ -494,7 +491,7 @@ function buildSuggest(define, emits, nameNode, context) { return fixer.insertTextAfter( leftBrace, `'${nameNode.value}': null${ - emitsOptionValue.properties.length ? ',' : '' + emitsOptionValue.properties.length > 0 ? ',' : '' }` ) } @@ -506,7 +503,7 @@ function buildSuggest(define, emits, nameNode, context) { const sourceCode = context.getSourceCode() const afterOptionNode = propertyNodes.find((p) => - FIX_EMITS_AFTER_OPTIONS.includes(utils.getStaticPropertyName(p) || '') + FIX_EMITS_AFTER_OPTIONS.has(utils.getStaticPropertyName(p) || '') ) return [ { @@ -518,7 +515,7 @@ function buildSuggest(define, emits, nameNode, context) { sourceCode.getTokenBefore(afterOptionNode), `\nemits: ['${nameNode.value}'],` ) - } else if (object.properties.length) { + } else if (object.properties.length > 0) { const before = propertyNodes[propertyNodes.length - 1] || object.properties[object.properties.length - 1] @@ -553,7 +550,7 @@ function buildSuggest(define, emits, nameNode, context) { sourceCode.getTokenBefore(afterOptionNode), `\nemits: {'${nameNode.value}': null},` ) - } else if (object.properties.length) { + } else if (object.properties.length > 0) { const before = propertyNodes[propertyNodes.length - 1] || object.properties[object.properties.length - 1] diff --git a/lib/rules/require-expose.js b/lib/rules/require-expose.js index 91ca589d5..df96ddb66 100644 --- a/lib/rules/require-expose.js +++ b/lib/rules/require-expose.js @@ -16,7 +16,7 @@ const { const utils = require('../utils') const { getVueComponentDefinitionType } = require('../utils') -const FIX_EXPOSE_BEFORE_OPTIONS = [ +const FIX_EXPOSE_BEFORE_OPTIONS = new Set([ 'name', 'components', 'directives', @@ -27,7 +27,7 @@ const FIX_EXPOSE_BEFORE_OPTIONS = [ 'inheritAttrs', 'props', 'emits' -] +]) /** * @param {Property | SpreadElement} node @@ -131,10 +131,8 @@ module.exports = { if (def.type === 'FunctionName') { return true } - if (def.type === 'Variable') { - if (def.node.init) { - return isFunction(def.node.init) - } + if (def.type === 'Variable' && def.node.init) { + return isFunction(def.node.init) } } } @@ -239,20 +237,24 @@ module.exports = { returnFunction: false } - if (node.type === 'ArrowFunctionExpression' && node.expression) { - if (isFunction(node.body)) { - scopeStack.returnFunction = true - } + if ( + node.type === 'ArrowFunctionExpression' && + node.expression && + isFunction(node.body) + ) { + scopeStack.returnFunction = true } }, ReturnStatement(node) { if (!scopeStack) { return } - if (!scopeStack.returnFunction && node.argument) { - if (isFunction(node.argument)) { - scopeStack.returnFunction = true - } + if ( + !scopeStack.returnFunction && + node.argument && + isFunction(node.argument) + ) { + scopeStack.returnFunction = true } }, ':function:exit'(node) { @@ -305,7 +307,7 @@ function buildSuggest(object, context) { const sourceCode = context.getSourceCode() const beforeOptionNode = propertyNodes.find((p) => - FIX_EXPOSE_BEFORE_OPTIONS.includes(utils.getStaticPropertyName(p) || '') + FIX_EXPOSE_BEFORE_OPTIONS.has(utils.getStaticPropertyName(p) || '') ) const allProps = [ ...new Set( @@ -320,7 +322,7 @@ function buildSuggest(object, context) { messageId: 'addExposeOptionForEmpty', fix: buildFix('expose: []') }, - ...(allProps.length + ...(allProps.length > 0 ? [ { messageId: 'addExposeOptionForAll', @@ -344,7 +346,7 @@ function buildSuggest(object, context) { return (fixer) => { if (beforeOptionNode) { return fixer.insertTextAfter(beforeOptionNode, `,\n${text}`) - } else if (object.properties.length) { + } else if (object.properties.length > 0) { const after = propertyNodes[0] || object.properties[0] return fixer.insertTextAfter( sourceCode.getTokenBefore(after), diff --git a/lib/rules/require-prop-type-constructor.js b/lib/rules/require-prop-type-constructor.js index aa022faee..121a503ab 100644 --- a/lib/rules/require-prop-type-constructor.js +++ b/lib/rules/require-prop-type-constructor.js @@ -17,19 +17,19 @@ const { isDef } = require('../utils') const message = 'The "{{name}}" property should be a constructor.' -const forbiddenTypes = [ +const forbiddenTypes = new Set([ 'Literal', 'TemplateLiteral', 'BinaryExpression', 'UpdateExpression' -] +]) /** * @param {ESNode} node */ function isForbiddenType(node) { return ( - forbiddenTypes.indexOf(node.type) > -1 && + forbiddenTypes.has(node.type) && !(node.type === 'Literal' && node.value == null && !node.bigint) ) } @@ -57,27 +57,27 @@ module.exports = { const nodes = node.type === 'ArrayExpression' ? node.elements.filter(isDef) : [node] - nodes - .filter((prop) => isForbiddenType(prop)) - .forEach((prop) => - context.report({ - node: prop, - message, - data: { - name: propName - }, - fix: (fixer) => { - if (prop.type === 'Literal' || prop.type === 'TemplateLiteral') { - const newText = utils.getStringLiteralValue(prop, true) + for (const prop of nodes) { + if (!isForbiddenType(prop)) continue - if (newText) { - return fixer.replaceText(prop, newText) - } + context.report({ + node: prop, + message, + data: { + name: propName + }, + fix: (fixer) => { + if (prop.type === 'Literal' || prop.type === 'TemplateLiteral') { + const newText = utils.getStringLiteralValue(prop, true) + + if (newText) { + return fixer.replaceText(prop, newText) } - return null } - }) - ) + return null + } + }) + } } /** @param {ComponentProp[]} props */ diff --git a/lib/rules/require-prop-types.js b/lib/rules/require-prop-types.js index b57bfc6fc..3239deb9b 100644 --- a/lib/rules/require-prop-types.js +++ b/lib/rules/require-prop-types.js @@ -64,18 +64,26 @@ module.exports = { if (!value) { hasType = false - } else if (value.type === 'ObjectExpression') { - // foo: { - hasType = objectHasType(value) - } else if (value.type === 'ArrayExpression') { - // foo: [ - hasType = value.elements.length > 0 - } else if ( - value.type === 'FunctionExpression' || - value.type === 'ArrowFunctionExpression' - ) { - hasType = false + } else { + switch (value.type) { + case 'ObjectExpression': { + // foo: { + hasType = objectHasType(value) + break + } + case 'ArrayExpression': { + // foo: [ + hasType = value.elements.length > 0 + break + } + case 'FunctionExpression': + case 'ArrowFunctionExpression': { + hasType = false + break + } + } } + if (!hasType) { const name = propName || diff --git a/lib/rules/require-valid-default-prop.js b/lib/rules/require-valid-default-prop.js index 21191e86f..5208dbdde 100644 --- a/lib/rules/require-valid-default-prop.js +++ b/lib/rules/require-valid-default-prop.js @@ -151,64 +151,74 @@ module.exports = { */ function getValueType(targetNode) { const node = utils.skipChainExpression(targetNode) - if (node.type === 'CallExpression') { - // Symbol(), Number() ... - if ( - node.callee.type === 'Identifier' && - NATIVE_TYPES.has(node.callee.name) - ) { + switch (node.type) { + case 'CallExpression': { + // Symbol(), Number() ... + if ( + node.callee.type === 'Identifier' && + NATIVE_TYPES.has(node.callee.name) + ) { + return { + function: false, + type: node.callee.name + } + } + break + } + case 'TemplateLiteral': { + // String return { function: false, - type: node.callee.name + type: 'String' } } - } else if (node.type === 'TemplateLiteral') { - // String - return { - function: false, - type: 'String' + case 'Literal': { + // String, Boolean, Number + if (node.value === null && !node.bigint) return null + const type = node.bigint ? 'BigInt' : capitalize(typeof node.value) + if (NATIVE_TYPES.has(type)) { + return { + function: false, + type + } + } + break } - } else if (node.type === 'Literal') { - // String, Boolean, Number - if (node.value === null && !node.bigint) return null - const type = node.bigint ? 'BigInt' : capitalize(typeof node.value) - if (NATIVE_TYPES.has(type)) { + case 'ArrayExpression': { + // Array return { function: false, - type + type: 'Array' } } - } else if (node.type === 'ArrayExpression') { - // Array - return { - function: false, - type: 'Array' - } - } else if (node.type === 'ObjectExpression') { - // Object - return { - function: false, - type: 'Object' - } - } else if (node.type === 'FunctionExpression') { - return { - function: true, - expression: false, - type: 'Function', - functionBody: node.body, - returnTypes: [] + case 'ObjectExpression': { + // Object + return { + function: false, + type: 'Object' + } } - } else if (node.type === 'ArrowFunctionExpression') { - if (node.expression) { - const valueType = getValueType(node.body) + case 'FunctionExpression': { return { function: true, - expression: true, + expression: false, type: 'Function', functionBody: node.body, - returnType: valueType ? valueType.type : null + returnTypes: [] } - } else { + } + case 'ArrowFunctionExpression': { + if (node.expression) { + const valueType = getValueType(node.body) + return { + function: true, + expression: true, + type: 'Function', + functionBody: node.body, + returnType: valueType ? valueType.type : null + } + } + return { function: true, expression: false, @@ -237,7 +247,7 @@ module.exports = { "Type of the default value for '{{name}}' prop must be a {{types}}.", data: { name: propName, - types: Array.from(expectedTypeNames).join(' or ').toLowerCase() + types: [...expectedTypeNames].join(' or ').toLowerCase() } }) } @@ -279,15 +289,16 @@ module.exports = { if (!defType) continue if (!defType.function) { - if (typeNames.has(defType.type)) { - if (!FUNCTION_VALUE_TYPES.has(defType.type)) { - continue - } + if ( + typeNames.has(defType.type) && + !FUNCTION_VALUE_TYPES.has(defType.type) + ) { + continue } report( defExpr, prop, - Array.from(typeNames).map((type) => + [...typeNames].map((type) => FUNCTION_VALUE_TYPES.has(type) ? 'Function' : type ) ) diff --git a/lib/rules/return-in-computed-property.js b/lib/rules/return-in-computed-property.js index 9908fe4fe..5c29aa440 100644 --- a/lib/rules/return-in-computed-property.js +++ b/lib/rules/return-in-computed-property.js @@ -87,7 +87,7 @@ module.exports = { utils.executeOnFunctionsWithoutReturn( treatUndefinedAsUnspecified, (node) => { - computedProperties.forEach((cp) => { + for (const cp of computedProperties) { if (cp.value && cp.value.parent === node) { context.report({ node, @@ -98,15 +98,15 @@ module.exports = { } }) } - }) - computedFunctionNodes.forEach((cf) => { + } + for (const cf of computedFunctionNodes) { if (cf === node) { context.report({ node, message: 'Expected to return a value in computed function.' }) } - }) + } } ) ) diff --git a/lib/rules/return-in-emits-validator.js b/lib/rules/return-in-emits-validator.js index 94e94ff48..a3c2e2578 100644 --- a/lib/rules/return-in-emits-validator.js +++ b/lib/rules/return-in-emits-validator.js @@ -20,15 +20,16 @@ function isFalsy(node) { if (node.type === 'Literal') { if (node.bigint) { return node.bigint === '0' - } else if (!node.value) { - return true } - } else if (node.type === 'Identifier') { - if (node.name === 'undefined' || node.name === 'NaN') { + if (!node.value) { return true } } - return false + + return ( + node.type === 'Identifier' && + (node.name === 'undefined' || node.name === 'NaN') + ) } // ------------------------------------------------------------------------------ // Rule Definition diff --git a/lib/rules/script-setup-uses-vars.js b/lib/rules/script-setup-uses-vars.js index 0e0f95878..3cb5eef30 100644 --- a/lib/rules/script-setup-uses-vars.js +++ b/lib/rules/script-setup-uses-vars.js @@ -16,6 +16,16 @@ const casing = require('../utils/casing') // Rule Definition // ------------------------------------------------------------------------------ +/** + * `casing.camelCase()` converts the beginning to lowercase, + * but does not convert the case of the beginning character when converting with Vue3. + * @see https://github.com/vuejs/vue-next/blob/2749c15170ad4913e6530a257db485d4e7ed2283/packages/shared/src/index.ts#L116 + * @param {string} str + */ +function camelize(str) { + return str.replace(/-(\w)/g, (_, c) => (c ? c.toUpperCase() : '')) +} + module.exports = { meta: { type: 'problem', @@ -50,15 +60,6 @@ module.exports = { } } - /** - * `casing.camelCase()` converts the beginning to lowercase, - * but does not convert the case of the beginning character when converting with Vue3. - * @see https://github.com/vuejs/vue-next/blob/2749c15170ad4913e6530a257db485d4e7ed2283/packages/shared/src/index.ts#L116 - * @param {string} str - */ - function camelize(str) { - return str.replace(/-(\w)/g, (_, c) => (c ? c.toUpperCase() : '')) - } /** * @see https://github.com/vuejs/vue-next/blob/2749c15170ad4913e6530a257db485d4e7ed2283/packages/compiler-core/src/transforms/transformElement.ts#L333 * @param {string} name diff --git a/lib/rules/singleline-html-element-content-newline.js b/lib/rules/singleline-html-element-content-newline.js index 2ac1eba9a..3bf124fe1 100644 --- a/lib/rules/singleline-html-element-content-newline.js +++ b/lib/rules/singleline-html-element-content-newline.js @@ -29,7 +29,7 @@ function isSinglelineElement(element) { function parseOptions(options) { return Object.assign( { - ignores: ['pre', 'textarea'].concat(INLINE_ELEMENTS), + ignores: ['pre', 'textarea', ...INLINE_ELEMENTS], ignoreWhenNoAttributes: true, ignoreWhenEmpty: true }, diff --git a/lib/rules/sort-keys.js b/lib/rules/sort-keys.js index e84774ddc..320d74f13 100644 --- a/lib/rules/sort-keys.js +++ b/lib/rules/sort-keys.js @@ -135,17 +135,20 @@ module.exports = { const options = context.options[1] const order = context.options[0] || 'asc' - /** @type {string[]} */ - const ignoreGrandchildrenOf = (options && - options.ignoreGrandchildrenOf) || [ - 'computed', - 'directives', - 'inject', - 'props', - 'watch' - ] - /** @type {string[]} */ - const ignoreChildrenOf = (options && options.ignoreChildrenOf) || ['model'] + /** @type {Set} */ + const ignoreGrandchildrenOf = new Set( + (options && options.ignoreGrandchildrenOf) || [ + 'computed', + 'directives', + 'inject', + 'props', + 'watch' + ] + ) + /** @type {Set} */ + const ignoreChildrenOf = new Set( + (options && options.ignoreChildrenOf) || ['model'] + ) const insensitive = options && options.caseSensitive === false const minKeys = options && options.minKeys const natural = options && options.natural @@ -215,14 +218,11 @@ module.exports = { vueState.within = true // Judge whether to ignore the property. - if (chainLevel === 1) { - if (ignoreChildrenOf.includes(propName)) { - vueState.ignore = true - } - } else if (chainLevel === 2) { - if (ignoreGrandchildrenOf.includes(propName)) { - vueState.ignore = true - } + if ( + (chainLevel === 1 && ignoreChildrenOf.has(propName)) || + (chainLevel === 2 && ignoreGrandchildrenOf.has(propName)) + ) { + vueState.ignore = true } } else { // chaining has broken. diff --git a/lib/rules/syntaxes/slot-attribute.js b/lib/rules/syntaxes/slot-attribute.js index 4cfe2099c..3292cd9de 100644 --- a/lib/rules/syntaxes/slot-attribute.js +++ b/lib/rules/syntaxes/slot-attribute.js @@ -74,11 +74,10 @@ module.exports = { (attr.key.name.name === 'slot-scope' || attr.key.name.name === 'scope') ) - const nameArgument = slotName - ? vBind - ? `:[${slotName}]` - : `:${slotName}` - : '' + let nameArgument = '' + if (slotName) { + nameArgument = vBind ? `:[${slotName}]` : `:${slotName}` + } const scopeValue = scopeAttr && scopeAttr.value ? `=${sourceCode.getText(scopeAttr.value)}` diff --git a/lib/rules/syntaxes/utils/can-convert-to-v-slot.js b/lib/rules/syntaxes/utils/can-convert-to-v-slot.js index 3bdb7e1bc..69c26cf43 100644 --- a/lib/rules/syntaxes/utils/can-convert-to-v-slot.js +++ b/lib/rules/syntaxes/utils/can-convert-to-v-slot.js @@ -80,7 +80,7 @@ function getSlotVForVariableIfUsingIterationVars(slot, vFor) { vFor && vFor.value && /** @type {VForExpression} */ (vFor.value.expression) const variables = expr && getUsingIterationVars(slot.value, slot.parent.parent) - return expr && variables && variables.length ? { expr, variables } : null + return expr && variables && variables.length > 0 ? { expr, variables } : null } /** @@ -204,14 +204,11 @@ function equalSlotVForVariables(a, b, tokenStore) { return false } } - for (const v of a.variables) { - if (!checkedVarNames.has(v.id.name)) { - if (b.variables.every((bv) => v.id.name !== bv.id.name)) { - return false - } - } - } - return true + return a.variables.every( + (v) => + checkedVarNames.has(v.id.name) || + b.variables.some((bv) => v.id.name === bv.id.name) + ) /** * Determines whether the two given nodes are considered to be equal. diff --git a/lib/rules/syntaxes/v-slot.js b/lib/rules/syntaxes/v-slot.js index 9a9ba1fba..a6a4b4ebd 100644 --- a/lib/rules/syntaxes/v-slot.js +++ b/lib/rules/syntaxes/v-slot.js @@ -3,23 +3,22 @@ * See LICENSE file in root directory for full license. */ 'use strict' + +/** + * Checks whether the given node can convert to the `slot`. + * @param {VDirective} vSlotAttr node of `v-slot` + * @returns {boolean} `true` if the given node can convert to the `slot` + */ +function canConvertToSlot(vSlotAttr) { + return vSlotAttr.parent.parent.name === 'template' +} + module.exports = { supported: '>=2.6.0', /** @param {RuleContext} context @returns {TemplateListener} */ createTemplateBodyVisitor(context) { const sourceCode = context.getSourceCode() - /** - * Checks whether the given node can convert to the `slot`. - * @param {VDirective} vSlotAttr node of `v-slot` - * @returns {boolean} `true` if the given node can convert to the `slot` - */ - function canConvertToSlot(vSlotAttr) { - if (vSlotAttr.parent.parent.name !== 'template') { - return false - } - return true - } /** * Convert to `slot` and `slot-scope`. * @param {RuleFixer} fixer fixer @@ -28,7 +27,7 @@ module.exports = { */ function fixVSlotToSlot(fixer, vSlotAttr) { const key = vSlotAttr.key - if (key.modifiers.length) { + if (key.modifiers.length > 0) { // unknown modifiers return null } @@ -54,7 +53,7 @@ module.exports = { if (scopedValueNode) { attrs.push(`slot-scope=${sourceCode.getText(scopedValueNode)}`) } - if (!attrs.length) { + if (attrs.length === 0) { attrs.push('slot') // useless } return fixer.replaceText(vSlotAttr, attrs.join(' ')) diff --git a/lib/rules/this-in-template.js b/lib/rules/this-in-template.js index 671ac56a0..cd03c2106 100644 --- a/lib/rules/this-in-template.js +++ b/lib/rules/this-in-template.js @@ -54,7 +54,7 @@ module.exports = { scopeStack = { parent: scopeStack, nodes: scopeStack - ? scopeStack.nodes.slice() // make copy + ? [...scopeStack.nodes] // make copy : [] } if (node.variables) { @@ -83,7 +83,7 @@ module.exports = { !propertyName || scopeStack.nodes.some((el) => el.name === propertyName) || RESERVED_NAMES.has(propertyName) || // this.class | this['class'] - /^[0-9].*$|[^a-zA-Z0-9_$]/.test(propertyName) // this['0aaaa'] | this['foo-bar bas'] + /^\d.*$|[^\w$]/.test(propertyName) // this['0aaaa'] | this['foo-bar bas'] ) { return } diff --git a/lib/rules/use-v-on-exact.js b/lib/rules/use-v-on-exact.js index 0a1790658..75c72c12d 100644 --- a/lib/rules/use-v-on-exact.js +++ b/lib/rules/use-v-on-exact.js @@ -82,17 +82,18 @@ function hasSystemModifier(modifiers) { * with keys represinting each event name * * @param {EventDirective[]} events - * @returns { { [key: string]: EventDirective[] } } { click: [], keypress: [] } + * @returns { { [key: string]: EventDirective[] } } { click: [], keypress: [] } */ function groupEvents(events) { - return events.reduce((acc, event) => { - if (acc[event.name]) { - acc[event.name].push(event) - } else { - acc[event.name] = [event] + /** @type { { [key: string]: EventDirective[] } } */ + const grouped = {} + for (const event of events) { + if (!grouped[event.name]) { + grouped[event.name] = [] } - return acc - }, /** @type { { [key: string]: EventDirective[] } }*/ ({})) + grouped[event.name].push(event) + } + return grouped } /** @@ -141,9 +142,9 @@ function hasConflictedModifiers(baseEvent, event) { const baseEventSystemModifiers = getSystemModifiersString(baseEvent.modifiers) return ( - baseEvent.modifiers.length >= 1 && + baseEvent.modifiers.length > 0 && baseEventSystemModifiers !== eventSystemModifiers && - baseEventSystemModifiers.indexOf(eventSystemModifiers) > -1 + baseEventSystemModifiers.includes(eventSystemModifiers) ) } @@ -154,14 +155,16 @@ function hasConflictedModifiers(baseEvent, event) { * @returns {EventDirective[]} conflicted events, without duplicates */ function findConflictedEvents(events) { - return events.reduce((acc, event) => { - return [ - ...acc, + /** @type {EventDirective[]} */ + const conflictedEvents = [] + for (const event of events) { + conflictedEvents.push( ...events - .filter((evt) => !acc.find((e) => evt === e)) // No duplicates + .filter((evt) => !conflictedEvents.includes(evt)) // No duplicates .filter(hasConflictedModifiers.bind(null, event)) - ] - }, /** @type {EventDirective[]} */ ([])) + ) + } + return conflictedEvents } // ------------------------------------------------------------------------------ @@ -204,24 +207,24 @@ module.exports = { const grouppedEvents = groupEvents(events) - Object.keys(grouppedEvents).forEach((eventName) => { + for (const eventName of Object.keys(grouppedEvents)) { const eventsInGroup = grouppedEvents[eventName] const hasEventWithKeyModifier = eventsInGroup.some((event) => hasSystemModifier(event.modifiers) ) - if (!hasEventWithKeyModifier) return + if (!hasEventWithKeyModifier) continue const conflictedEvents = findConflictedEvents(eventsInGroup) - conflictedEvents.forEach((e) => { + for (const e of conflictedEvents) { context.report({ node: e.node, loc: e.node.loc, message: "Consider to use '.exact' modifier." }) - }) - }) + } + } } }) } diff --git a/lib/rules/v-bind-style.js b/lib/rules/v-bind-style.js index e6878a349..4e3c638be 100644 --- a/lib/rules/v-bind-style.js +++ b/lib/rules/v-bind-style.js @@ -41,14 +41,17 @@ module.exports = { return } + let message = "Expected 'v-bind' before ':'." + if (preferShorthand) { + message = "Unexpected 'v-bind' before ':'." + } else if (shorthandProp) { + message = "Expected 'v-bind:' instead of '.'." + } + context.report({ node, loc: node.loc, - message: preferShorthand - ? "Unexpected 'v-bind' before ':'." - : shorthandProp - ? "Expected 'v-bind:' instead of '.'." - : /* otherwise */ "Expected 'v-bind' before ':'.", + message, *fix(fixer) { if (preferShorthand) { yield fixer.remove(node.key.name) diff --git a/lib/rules/v-for-delimiter-style.js b/lib/rules/v-for-delimiter-style.js index a50b6a592..2dca5eecd 100644 --- a/lib/rules/v-for-delimiter-style.js +++ b/lib/rules/v-for-delimiter-style.js @@ -42,7 +42,7 @@ module.exports = { const delimiterToken = /** @type {Token} */ ( tokenStore.getTokenAfter( - node.left.length + node.left.length > 0 ? node.left[node.left.length - 1] : tokenStore.getFirstToken(node), (token) => token.type !== 'Punctuator' || token.value !== ')' diff --git a/lib/rules/v-on-function-call.js b/lib/rules/v-on-function-call.js index 90da5ed43..b72548daa 100644 --- a/lib/rules/v-on-function-call.js +++ b/lib/rules/v-on-function-call.js @@ -30,6 +30,45 @@ function isQuote(token) { ) } +/** + * @param {VOnExpression} node + * @returns {CallExpression | null} + */ +function getInvalidNeverCallExpression(node) { + /** @type {ExpressionStatement} */ + let exprStatement + let body = node.body + while (true) { + const statements = body.filter((st) => st.type !== 'EmptyStatement') + if (statements.length !== 1) { + return null + } + const statement = statements[0] + if (statement.type === 'ExpressionStatement') { + exprStatement = statement + break + } + if (statement.type === 'BlockStatement') { + body = statement.body + continue + } + return null + } + const expression = exprStatement.expression + if (expression.type !== 'CallExpression' || expression.arguments.length > 0) { + return null + } + if (expression.optional) { + // Allow optional chaining + return null + } + const callee = expression.callee + if (callee.type !== 'Identifier') { + return null + } + return expression +} + // ------------------------------------------------------------------------------ // Rule Definition // ------------------------------------------------------------------------------ @@ -61,45 +100,6 @@ module.exports = { create(context) { const always = context.options[0] === 'always' - /** - * @param {VOnExpression} node - * @returns {CallExpression | null} - */ - function getInvalidNeverCallExpression(node) { - /** @type {ExpressionStatement} */ - let exprStatement - let body = node.body - while (true) { - const statements = body.filter((st) => st.type !== 'EmptyStatement') - if (statements.length !== 1) { - return null - } - const statement = statements[0] - if (statement.type === 'ExpressionStatement') { - exprStatement = statement - break - } - if (statement.type === 'BlockStatement') { - body = statement.body - continue - } - return null - } - const expression = exprStatement.expression - if (expression.type !== 'CallExpression' || expression.arguments.length) { - return null - } - if (expression.optional) { - // Allow optional chaining - return null - } - const callee = expression.callee - if (callee.type !== 'Identifier') { - return null - } - return expression - } - if (always) { return utils.defineTemplateBodyVisitor(context, { /** @param {Identifier} node */ @@ -153,11 +153,12 @@ module.exports = { return } - if (expression.callee.type === 'Identifier') { - if (useArgsMethods.has(expression.callee.name)) { - // The behavior of target method can change given the arguments. - return - } + if ( + expression.callee.type === 'Identifier' && + useArgsMethods.has(expression.callee.name) + ) { + // The behavior of target method can change given the arguments. + return } context.report({ diff --git a/lib/rules/valid-define-emits.js b/lib/rules/valid-define-emits.js index d7480d538..dc23ac3d4 100644 --- a/lib/rules/valid-define-emits.js +++ b/lib/rules/valid-define-emits.js @@ -47,8 +47,8 @@ module.exports = { onDefineEmitsEnter(node) { defineEmitsNodes.push(node) - if (node.arguments.length >= 1) { - if (node.typeParameters && node.typeParameters.params.length >= 1) { + if (node.arguments.length > 0) { + if (node.typeParameters && node.typeParameters.params.length > 0) { // `defineEmits` has both a literal type and an argument. context.report({ node, @@ -73,26 +73,23 @@ module.exports = { const variable = findVariable(context.getScope(), node) if ( variable && - variable.references.some((ref) => ref.identifier === node) + variable.references.some((ref) => ref.identifier === node) && + variable.defs.length > 0 && + variable.defs.every( + (def) => + def.type !== 'ImportBinding' && + utils.inRange(scriptSetup.range, def.name) && + !utils.inRange(defineEmits.range, def.name) + ) ) { - if ( - variable.defs.length && - variable.defs.every( - (def) => - def.type !== 'ImportBinding' && - utils.inRange(scriptSetup.range, def.name) && - !utils.inRange(defineEmits.range, def.name) - ) - ) { - if (utils.withinTypeNode(node)) { - continue - } - //`defineEmits` are referencing locally declared variables. - context.report({ - node, - messageId: 'referencingLocally' - }) + if (utils.withinTypeNode(node)) { + continue } + //`defineEmits` are referencing locally declared variables. + context.report({ + node, + messageId: 'referencingLocally' + }) } } } @@ -109,7 +106,7 @@ module.exports = { }), { 'Program:exit'() { - if (!defineEmitsNodes.length) { + if (defineEmitsNodes.length === 0) { return } if (defineEmitsNodes.length > 1) { diff --git a/lib/rules/valid-define-props.js b/lib/rules/valid-define-props.js index 849437b1e..8521bd511 100644 --- a/lib/rules/valid-define-props.js +++ b/lib/rules/valid-define-props.js @@ -48,8 +48,8 @@ module.exports = { onDefinePropsEnter(node) { definePropsNodes.push(node) - if (node.arguments.length >= 1) { - if (node.typeParameters && node.typeParameters.params.length >= 1) { + if (node.arguments.length > 0) { + if (node.typeParameters && node.typeParameters.params.length > 0) { // `defineProps` has both a literal type and an argument. context.report({ node, @@ -74,26 +74,23 @@ module.exports = { const variable = findVariable(context.getScope(), node) if ( variable && - variable.references.some((ref) => ref.identifier === node) + variable.references.some((ref) => ref.identifier === node) && + variable.defs.length > 0 && + variable.defs.every( + (def) => + def.type !== 'ImportBinding' && + utils.inRange(scriptSetup.range, def.name) && + !utils.inRange(defineProps.range, def.name) + ) ) { - if ( - variable.defs.length && - variable.defs.every( - (def) => - def.type !== 'ImportBinding' && - utils.inRange(scriptSetup.range, def.name) && - !utils.inRange(defineProps.range, def.name) - ) - ) { - if (utils.withinTypeNode(node)) { - continue - } - //`defineProps` are referencing locally declared variables. - context.report({ - node, - messageId: 'referencingLocally' - }) + if (utils.withinTypeNode(node)) { + continue } + //`defineProps` are referencing locally declared variables. + context.report({ + node, + messageId: 'referencingLocally' + }) } } } @@ -110,7 +107,7 @@ module.exports = { }), { 'Program:exit'() { - if (!definePropsNodes.length) { + if (definePropsNodes.length === 0) { return } if (definePropsNodes.length > 1) { diff --git a/lib/rules/valid-template-root.js b/lib/rules/valid-template-root.js index 703ab8b8e..7612ae85f 100644 --- a/lib/rules/valid-template-root.js +++ b/lib/rules/valid-template-root.js @@ -47,7 +47,7 @@ module.exports = { } } - if (hasSrc && rootElements.length) { + if (hasSrc && rootElements.length > 0) { for (const element of rootElements) { context.report({ node: element, diff --git a/lib/rules/valid-v-memo.js b/lib/rules/valid-v-memo.js index e3c85fcf4..a5f548ae5 100644 --- a/lib/rules/valid-v-memo.js +++ b/lib/rules/valid-v-memo.js @@ -85,31 +85,40 @@ module.exports = { const expressions = [node.value.expression] let expression while ((expression = expressions.pop())) { - if ( - expression.type === 'ObjectExpression' || - expression.type === 'ClassExpression' || - expression.type === 'ArrowFunctionExpression' || - expression.type === 'FunctionExpression' || - expression.type === 'Literal' || - expression.type === 'TemplateLiteral' || - expression.type === 'UnaryExpression' || - expression.type === 'BinaryExpression' || - expression.type === 'UpdateExpression' - ) { - context.report({ - node: expression, - messageId: 'expectedArray' - }) - } else if (expression.type === 'AssignmentExpression') { - expressions.push(expression.right) - } else if (expression.type === 'TSAsExpression') { - expressions.push(expression.expression) - } else if (expression.type === 'SequenceExpression') { - expressions.push( - expression.expressions[expression.expressions.length - 1] - ) - } else if (expression.type === 'ConditionalExpression') { - expressions.push(expression.consequent, expression.alternate) + switch (expression.type) { + case 'ObjectExpression': + case 'ClassExpression': + case 'ArrowFunctionExpression': + case 'FunctionExpression': + case 'Literal': + case 'TemplateLiteral': + case 'UnaryExpression': + case 'BinaryExpression': + case 'UpdateExpression': { + context.report({ + node: expression, + messageId: 'expectedArray' + }) + break + } + case 'AssignmentExpression': { + expressions.push(expression.right) + break + } + case 'TSAsExpression': { + expressions.push(expression.expression) + break + } + case 'SequenceExpression': { + expressions.push( + expression.expressions[expression.expressions.length - 1] + ) + break + } + case 'ConditionalExpression': { + expressions.push(expression.consequent, expression.alternate) + break + } } } } diff --git a/lib/rules/valid-v-on.js b/lib/rules/valid-v-on.js index 864a5ccf3..62deb4e47 100644 --- a/lib/rules/valid-v-on.js +++ b/lib/rules/valid-v-on.js @@ -56,9 +56,9 @@ function isValidModifier(modifierNode, customModifiers) { // built-in aliases VALID_MODIFIERS.has(modifier) || // keyCode - Number.isInteger(parseInt(modifier, 10)) || + Number.isInteger(Number.parseInt(modifier, 10)) || // keyAlias (an Unicode character) - Array.from(modifier).length === 1 || + [...modifier].length === 1 || // keyAlias (special keys) KEY_ALIASES.has(modifier) || // custom modifiers diff --git a/lib/rules/valid-v-slot.js b/lib/rules/valid-v-slot.js index df0fa7c65..c33107966 100644 --- a/lib/rules/valid-v-slot.js +++ b/lib/rules/valid-v-slot.js @@ -100,7 +100,7 @@ function filterSameSlot( return true }) ) - .filter((slots) => slots.length >= 1) + .filter((slots) => slots.length > 0) } /** @@ -142,14 +142,11 @@ function equalVSlotVForVariables(a, b, tokenStore) { return false } } - for (const v of a.variables) { - if (!checkedVarNames.has(v.id.name)) { - if (b.variables.every((bv) => v.id.name !== bv.id.name)) { - return false - } - } - } - return true + return a.variables.every( + (v) => + checkedVarNames.has(v.id.name) || + b.variables.some((bv) => v.id.name === bv.id.name) + ) /** * Determines whether the two given nodes are considered to be equal. @@ -176,7 +173,7 @@ function getVSlotVForVariableIfUsingIterationVars(vSlot, vFor) { vFor && vFor.value && /** @type {VForExpression} */ (vFor.value.expression) const variables = expr && getUsingIterationVars(vSlot.key.argument, vSlot.parent.parent) - return expr && variables && variables.length ? { expr, variables } : null + return expr && variables && variables.length > 0 ? { expr, variables } : null } /** @@ -235,8 +232,8 @@ function isUsingScopeVar(vSlot) { */ function hasInvalidModifiers(vSlot, allowModifiers) { return allowModifiers - ? vSlot.key.argument == null && vSlot.key.modifiers.length >= 1 - : vSlot.key.modifiers.length >= 1 + ? vSlot.key.argument == null && vSlot.key.modifiers.length > 0 + : vSlot.key.modifiers.length > 0 } module.exports = { @@ -317,7 +314,7 @@ module.exports = { messageId: 'namedSlotMustBeOnTemplate' }) } - if (ownerElement === element && vSlotGroupsOnChildren.length >= 1) { + if (ownerElement === element && vSlotGroupsOnChildren.length > 0) { context.report({ node, messageId: 'defaultSlotMustBeOnTemplate' diff --git a/lib/utils/casing.js b/lib/utils/casing.js index b2f2e89b5..9f911951d 100644 --- a/lib/utils/casing.js +++ b/lib/utils/casing.js @@ -41,15 +41,12 @@ function kebabCase(str) { * @param {string} str */ function isKebabCase(str) { - if ( - hasUpper(str) || - hasSymbols(str) || - /^-/u.exec(str) || // starts with hyphen is not kebab-case - /_|--|\s/u.exec(str) - ) { - return false - } - return true + return ( + !hasUpper(str) && + !hasSymbols(str) && + !str.startsWith('-') && // starts with hyphen is not kebab-case + !/_|--|\s/u.test(str) + ) } /** @@ -69,10 +66,7 @@ function snakeCase(str) { * @param {string} str */ function isSnakeCase(str) { - if (hasUpper(str) || hasSymbols(str) || /-|__|\s/u.exec(str)) { - return false - } - return true + return !hasUpper(str) && !hasSymbols(str) && !/-|__|\s/u.test(str) } /** @@ -92,14 +86,7 @@ function camelCase(str) { * @param {string} str */ function isCamelCase(str) { - if ( - hasSymbols(str) || - /^[A-Z]/u.exec(str) || - /-|_|\s/u.exec(str) // kebab or snake or space - ) { - return false - } - return true + return !hasSymbols(str) && !/^[A-Z]/u.test(str) && !/-|_|\s/u.test(str) } /** @@ -116,14 +103,7 @@ function pascalCase(str) { * @param {string} str */ function isPascalCase(str) { - if ( - hasSymbols(str) || - /^[a-z]/u.exec(str) || - /-|_|\s/u.exec(str) // kebab or snake or space - ) { - return false - } - return true + return !hasSymbols(str) && !/^[a-z]/u.test(str) && !/-|_|\s/u.test(str) } const convertersMap = { diff --git a/lib/utils/html-comments.js b/lib/utils/html-comments.js index ec957af71..5e2d3f1f4 100644 --- a/lib/utils/html-comments.js +++ b/lib/utils/html-comments.js @@ -22,7 +22,7 @@ const utils = require('./') const COMMENT_DIRECTIVE = /^\s*eslint-(?:en|dis)able/ const IE_CONDITIONAL_IF = /^\[if\s+/ -const IE_CONDITIONAL_ENDIF = /\[endif\]$/ +const IE_CONDITIONAL_ENDIF = /\[endif]$/ /** @type { 'HTMLCommentOpen' } */ const TYPE_HTML_COMMENT_OPEN = 'HTMLCommentOpen' diff --git a/lib/utils/indent-common.js b/lib/utils/indent-common.js index 020b6373b..efcde09df 100644 --- a/lib/utils/indent-common.js +++ b/lib/utils/indent-common.js @@ -42,14 +42,14 @@ const { defineVisitor: tsDefineVisitor } = require('./indent-ts') // ------------------------------------------------------------------------------ // Helpers // ------------------------------------------------------------------------------ -const LT_CHAR = /[\r\n\u2028\u2029]/ -const LINES = /[^\r\n\u2028\u2029]+(?:$|\r\n|[\r\n\u2028\u2029])/g +const LT_CHAR = /[\n\r\u2028\u2029]/ +const LINES = /[^\n\r\u2028\u2029]+(?:$|\r\n|[\n\r\u2028\u2029])/g const BLOCK_COMMENT_PREFIX = /^\s*\*/ const ITERATION_OPTS = Object.freeze({ includeComments: true, filter: isNotWhitespace }) -const PREFORMATTED_ELEMENT_NAMES = ['pre', 'textarea'] +const PREFORMATTED_ELEMENT_NAMES = new Set(['pre', 'textarea']) /** * @typedef {object} IndentOptions @@ -329,7 +329,7 @@ module.exports.defineVisitor = function create( * @returns {{firstToken:Token,lastToken:Token}} The gotten tokens. */ function getFirstAndLastTokens(node, borderOffset = 0) { - borderOffset |= 0 + borderOffset = Math.trunc(borderOffset) let firstToken = tokenStore.getFirstToken(node) let lastToken = tokenStore.getLastToken(node) @@ -366,14 +366,13 @@ module.exports.defineVisitor = function create( const leftToken = left && tokenStore.getFirstToken(left) const rightToken = right && tokenStore.getFirstToken(right) - if (nodeList.length >= 1) { + if (nodeList.length > 0) { let baseToken = null let lastToken = left const alignTokensBeforeBaseToken = [] const alignTokens = [] - for (let i = 0; i < nodeList.length; ++i) { - const node = nodeList[i] + for (const node of nodeList) { if (node == null) { // Holes of an array. continue @@ -643,8 +642,7 @@ module.exports.defineVisitor = function create( function getExpectedIndents(tokens) { const expectedIndents = [] - for (let i = 0; i < tokens.length; ++i) { - const token = tokens[i] + for (const [i, token] of tokens.entries()) { const offsetInfo = offsets.get(token) if (offsetInfo != null) { @@ -668,13 +666,13 @@ module.exports.defineVisitor = function create( } } } - if (!expectedIndents.length) { + if (expectedIndents.length === 0) { return null } return { expectedIndent: expectedIndents[0], - expectedBaseIndent: expectedIndents.reduce((a, b) => Math.min(a, b)) + expectedBaseIndent: Math.min(...expectedIndents) } } @@ -751,8 +749,8 @@ module.exports.defineVisitor = function create( const actualIndent = token.loc.start.column const unit = options.indentChar === '\t' ? 'tab' : 'space' - for (let i = 0; i < indentText.length; ++i) { - if (indentText[i] !== options.indentChar) { + for (const [i, char] of [...indentText].entries()) { + if (char !== options.indentChar) { context.report({ loc: { start: { line, column: i }, @@ -762,7 +760,7 @@ module.exports.defineVisitor = function create( 'Expected {{expected}} character, but found {{actual}} character.', data: { expected: JSON.stringify(options.indentChar), - actual: JSON.stringify(indentText[i]) + actual: JSON.stringify(char) }, fix: defineFix(token, actualIndent, expectedIndent) }) @@ -871,17 +869,15 @@ module.exports.defineVisitor = function create( if (offsetInfo != null) { if (offsetInfo.baseline) { // This is a baseline token, so the expected indent is the column of this token. - if (options.indentChar === ' ') { - offsetInfo.expectedIndent = Math.max( - 0, - token.loc.start.column + expectedBaseIndent - actualIndent - ) - } else { - // In hard-tabs mode, it cannot align tokens strictly, so use one additional offset. - // But the additional offset isn't needed if it's at the beginning of the line. - offsetInfo.expectedIndent = - expectedBaseIndent + (token === tokens[0] ? 0 : 1) - } + offsetInfo.expectedIndent = + options.indentChar === ' ' + ? Math.max( + 0, + token.loc.start.column + expectedBaseIndent - actualIndent + ) + : // In hard-tabs mode, it cannot align tokens strictly, so use one additional offset. + // But the additional offset isn't needed if it's at the beginning of the line. + expectedBaseIndent + (token === tokens[0] ? 0 : 1) baseline.add(token) } else if (baseline.has(offsetInfo.baseToken)) { // The base token is a baseline token on this line, so inherit it. @@ -954,7 +950,7 @@ module.exports.defineVisitor = function create( }, /** @param {VElement} node */ VElement(node) { - if (!PREFORMATTED_ELEMENT_NAMES.includes(node.name)) { + if (!PREFORMATTED_ELEMENT_NAMES.has(node.name)) { const isTopLevel = node.parent.type !== 'VElement' const offset = isTopLevel ? options.baseIndent : 1 processNodeList( @@ -1317,7 +1313,7 @@ module.exports.defineVisitor = function create( node.source, lastToken ) - if (assertionTokens.length) { + if (assertionTokens.length > 0) { const assertToken = /** @type {Token} */ (assertionTokens.shift()) setOffset(assertToken, 0, exportToken) const assertionOpen = assertionTokens.shift() @@ -1382,7 +1378,7 @@ module.exports.defineVisitor = function create( node.source, lastToken ) - if (assertionTokens.length) { + if (assertionTokens.length > 0) { const assertToken = /** @type {Token} */ (assertionTokens.shift()) setOffset(assertToken, 0, exportToken) const assertionOpen = assertionTokens.shift() @@ -1564,7 +1560,7 @@ module.exports.defineVisitor = function create( } } } - if (namedSpecifiers.length) { + if (namedSpecifiers.length > 0) { const leftBrace = tokenStore.getTokenBefore(namedSpecifiers[0]) const rightBrace = /** @type {Token} */ ( tokenStore.getTokenAfter( @@ -1606,7 +1602,7 @@ module.exports.defineVisitor = function create( node.source, lastToken ) - if (assertionTokens.length) { + if (assertionTokens.length > 0) { const assertToken = /** @type {Token} */ (assertionTokens.shift()) setOffset(assertToken, 0, importToken) const assertionOpen = assertionTokens.shift() @@ -1751,7 +1747,7 @@ module.exports.defineVisitor = function create( node.consequent[0].type === 'BlockStatement' ) { setOffset(tokenStore.getFirstToken(node.consequent[0]), 0, caseToken) - } else if (node.consequent.length >= 1) { + } else if (node.consequent.length > 0) { setOffset(tokenStore.getFirstToken(node.consequent[0]), 1, caseToken) processNodeList(node.consequent, null, null, 0) } @@ -1993,7 +1989,7 @@ module.exports.defineVisitor = function create( comments = [] } } - if (tokensOnSameLine.length >= 1 && tokensOnSameLine.some(isNotComment)) { + if (tokensOnSameLine.some(isNotComment)) { validate(tokensOnSameLine, comments, lastValidatedToken) } } diff --git a/lib/utils/indent-ts.js b/lib/utils/indent-ts.js index 496e892cd..f2f5b69cf 100644 --- a/lib/utils/indent-ts.js +++ b/lib/utils/indent-ts.js @@ -191,7 +191,7 @@ function defineVisitor({ tokenStore.getFirstToken(node.superClass) ) } - if (node.implements != null && node.implements.length) { + if (node.implements != null && node.implements.length > 0) { const classToken = tokenStore.getFirstToken(node) const implementsToken = tokenStore.getTokenBefore(node.implements[0]) setOffset(implementsToken, 1, classToken) @@ -723,7 +723,7 @@ function defineVisitor({ tokenStore.getFirstToken(node.id) ) } - if (node.extends != null && node.extends.length) { + if (node.extends != null && node.extends.length > 0) { const extendsToken = tokenStore.getTokenBefore(node.extends[0]) setOffset(extendsToken, 1, interfaceToken) processNodeList(node.extends, extendsToken, null, 1) @@ -1277,12 +1277,12 @@ function defineVisitor({ }) setOffset(secondToken, 0, atToken) const parent = /** @type {any} */ (node.parent) - const { decorators } = parent + const { decorators, range } = parent if (!decorators || decorators.length === 0) { return } if (decorators[0] === node) { - if (parent.range[0] === node.range[0]) { + if (range[0] === node.range[0]) { const startParentToken = tokenStore.getTokenAfter( decorators[decorators.length - 1] ) diff --git a/lib/utils/index.js b/lib/utils/index.js index 69cd84a31..fba712a18 100644 --- a/lib/utils/index.js +++ b/lib/utils/index.js @@ -54,7 +54,7 @@ const VUE3_BUILTIN_COMPONENT_NAMES = new Set( ) const path = require('path') const vueEslintParser = require('vue-eslint-parser') -const { traverseNodes, getFallbackKeys } = vueEslintParser.AST +const { traverseNodes, getFallbackKeys, NS } = vueEslintParser.AST const { findVariable } = require('eslint-utils') const { getComponentPropsFromTypeDefine, @@ -234,9 +234,9 @@ function wrapContextToOverrideTokenMethods(context, tokenStore, options) { }) containerScopes.set(exprContainer, scope) return scope - } catch (e) { + } catch (error) { // ignore - // console.log(e) + // console.log(error) } return null @@ -269,7 +269,6 @@ function wrapContextToOverrideReportMethodToSkipDynamicArgument(context) { } /** @type {Range[]} */ const directiveKeyRanges = [] - const traverseNodes = vueEslintParser.AST.traverseNodes traverseNodes(templateBody, { enterNode(node, parent) { if ( @@ -744,7 +743,7 @@ module.exports = { if (connected) { elementChain.push(childNode) } else { - if (elementChain.length) { + if (elementChain.length > 0) { yield elementChain } elementChain = [childNode] @@ -753,7 +752,7 @@ module.exports = { vIf = false } } - if (elementChain.length) { + if (elementChain.length > 0) { yield elementChain } }, @@ -792,7 +791,7 @@ module.exports = { * @returns {boolean} `true` if the node is a HTML element. */ isHtmlElementNode(node) { - return node.namespace === vueEslintParser.AST.NS.HTML + return node.namespace === NS.HTML }, /** @@ -801,7 +800,7 @@ module.exports = { * @returns {boolean} `true` if the name is a SVG element. */ isSvgElementNode(node) { - return node.namespace === vueEslintParser.AST.NS.SVG + return node.namespace === NS.SVG }, /** @@ -810,7 +809,7 @@ module.exports = { * @returns {boolean} `true` if the node is a MathML element. */ isMathMLElementNode(node) { - return node.namespace === vueEslintParser.AST.NS.MathML + return node.namespace === NS.MathML }, /** @@ -1180,11 +1179,9 @@ module.exports = { * @param {any[]} args */ function callVisitor(key, node, ...args) { - if (visitor[key]) { - if (inScriptSetup(node)) { - // @ts-expect-error - visitor[key](node, ...args) - } + if (visitor[key] && inScriptSetup(node)) { + // @ts-expect-error + visitor[key](node, ...args) } } @@ -1379,7 +1376,7 @@ module.exports = { calleeObject.type === 'Identifier' && // calleeObject.name === 'Vue' && // Any names can be used in Vue.js 3.x. e.g. app.component() callee.property === node && - callExpr.arguments.length >= 1 + callExpr.arguments.length > 0 ) { cb(callExpr) } @@ -1402,14 +1399,23 @@ module.exports = { const name = /** @type {GroupName | null} */ (getStaticPropertyName(item)) if (!name || !groups.has(name)) continue - if (item.value.type === 'ArrayExpression') { - yield* this.iterateArrayExpression(item.value, name) - } else if (item.value.type === 'ObjectExpression') { - yield* this.iterateObjectExpression(item.value, name) - } else if (item.value.type === 'FunctionExpression') { - yield* this.iterateFunctionExpression(item.value, name) - } else if (item.value.type === 'ArrowFunctionExpression') { - yield* this.iterateArrowFunctionExpression(item.value, name) + switch (item.value.type) { + case 'ArrayExpression': { + yield* this.iterateArrayExpression(item.value, name) + break + } + case 'ObjectExpression': { + yield* this.iterateObjectExpression(item.value, name) + break + } + case 'FunctionExpression': { + yield* this.iterateFunctionExpression(item.value, name) + break + } + case 'ArrowFunctionExpression': { + yield* this.iterateArrowFunctionExpression(item.value, name) + break + } } } }, @@ -1453,29 +1459,28 @@ module.exports = { ) { const name = getStaticPropertyName(item) if (name) { - if (item.kind === 'set') { - // find getter pair - if ( - node.properties.some((item2) => { - if (item2.type === 'Property' && item2.kind === 'get') { - if (!usedGetter) { - usedGetter = new Set() - } - if (usedGetter.has(item2)) { - return false - } - const getterName = getStaticPropertyName(item2) - if (getterName === name) { - usedGetter.add(item2) - return true - } + // find getter pair + if ( + item.kind === 'set' && + node.properties.some((item2) => { + if (item2.type === 'Property' && item2.kind === 'get') { + if (!usedGetter) { + usedGetter = new Set() } - return false - }) - ) { - // has getter pair - continue - } + if (usedGetter.has(item2)) { + return false + } + const getterName = getStaticPropertyName(item2) + if (getterName === name) { + usedGetter.add(item2) + return true + } + } + return false + }) + ) { + // has getter pair + continue } yield { type: 'object', @@ -1552,18 +1557,13 @@ module.exports = { /** @type {FuncInfo | null} */ let funcInfo = null - /** @param {CodePathSegment} segment */ - function isReachable(segment) { - return segment.reachable - } - function isValidReturn() { if (!funcInfo) { return true } if ( funcInfo.codePath && - funcInfo.codePath.currentSegments.some(isReachable) + funcInfo.codePath.currentSegments.some((segment) => segment.reachable) ) { return false } @@ -1761,14 +1761,11 @@ module.exports = { return false } const parent = node.parent - if (parent.type === 'MemberExpression') { - if (parent.property === node) { - return false - } - } else if (parent.type === 'Property') { - if (parent.key === node && !parent.computed) { - return false - } + if ( + (parent.type === 'MemberExpression' && parent.property === node) || + (parent.type === 'Property' && parent.key === node && !parent.computed) + ) { + return false } const variable = findVariable(context.getScope(), node) @@ -1799,52 +1796,61 @@ module.exports = { let node = props let target = node.parent while (true) { - if (target.type === 'AssignmentExpression') { - if (target.left === node) { - // this.xxx <=|+=|-=> + switch (target.type) { + case 'AssignmentExpression': { + if (target.left === node) { + // this.xxx <=|+=|-=> + return { + kind: 'assignment', + node: target, + pathNodes + } + } + break + } + case 'UpdateExpression': { + // this.xxx <++|--> return { - kind: 'assignment', + kind: 'update', node: target, pathNodes } } - } else if (target.type === 'UpdateExpression') { - // this.xxx <++|--> - return { - kind: 'update', - node: target, - pathNodes - } - } else if (target.type === 'CallExpression') { - if (pathNodes.length > 0 && target.callee === node) { - const mem = pathNodes[pathNodes.length - 1] - const callName = getStaticPropertyName(mem) - if ( - callName && - /^(?:push|pop|shift|unshift|reverse|splice|sort|copyWithin|fill)$/u.exec( - callName - ) - ) { - // this.xxx.push() - pathNodes.pop() - return { - kind: 'call', - node: target, - pathNodes + case 'CallExpression': { + if (pathNodes.length > 0 && target.callee === node) { + const mem = pathNodes[pathNodes.length - 1] + const callName = getStaticPropertyName(mem) + if ( + callName && + /^(?:push|pop|shift|unshift|reverse|splice|sort|copyWithin|fill)$/u.test( + callName + ) + ) { + // this.xxx.push() + pathNodes.pop() + return { + kind: 'call', + node: target, + pathNodes + } } } + break } - } else if (target.type === 'MemberExpression') { - if (target.object === node) { - pathNodes.push(target) + case 'MemberExpression': { + if (target.object === node) { + pathNodes.push(target) + node = target + target = target.parent + continue // loop + } + break + } + case 'ChainExpression': { node = target target = target.parent continue // loop } - } else if (target.type === 'ChainExpression') { - node = target - target = target.parent - continue // loop } return null @@ -1881,16 +1887,11 @@ module.exports = { if (tokensL.length !== tokensR.length) { return false } - for (let i = 0; i < tokensL.length; ++i) { - if ( - tokensL[i].type !== tokensR[i].type || - tokensL[i].value !== tokensR[i].value - ) { - return false - } - } - return true + return tokensL.every( + (token, i) => + token.type === tokensR[i].type && token.value === tokensR[i].value + ) } } @@ -2344,10 +2345,12 @@ function getStringLiteralValue(node, stringOnly) { } return null } - if (node.type === 'TemplateLiteral') { - if (node.expressions.length === 0 && node.quasis.length === 1) { - return node.quasis[0].value.cooked - } + if ( + node.type === 'TemplateLiteral' && + node.expressions.length === 0 && + node.quasis.length === 1 + ) { + return node.quasis[0].value.cooked } return null } @@ -2489,15 +2492,14 @@ function getVueComponentDefinitionType(node) { } return null +} - /** @param {CallExpression} node */ - function isObjectArgument(node) { - return ( - node.arguments.length > 0 && - skipTSAsExpression(node.arguments.slice(-1)[0]).type === - 'ObjectExpression' - ) - } +/** @param {CallExpression} node */ +function isObjectArgument(node) { + return ( + node.arguments.length > 0 && + skipTSAsExpression(node.arguments.slice(-1)[0]).type === 'ObjectExpression' + ) } /** @@ -2512,7 +2514,7 @@ function isVueInstance(node) { node.type === 'NewExpression' && callee.type === 'Identifier' && callee.name === 'Vue' && - node.arguments.length && + node.arguments.length > 0 && skipTSAsExpression(node.arguments[0]).type === 'ObjectExpression' ) } @@ -2528,40 +2530,51 @@ function getVueObjectType(context, node) { return null } const parent = getParent(node) - if (parent.type === 'ExportDefaultDeclaration') { - // export default {} in .vue || .jsx - const filePath = context.getFilename() - if ( - isVueComponentFile(parent, filePath) && - skipTSAsExpression(parent.declaration) === node - ) { - const scriptSetup = getScriptSetupElement(context) + switch (parent.type) { + case 'ExportDefaultDeclaration': { + // export default {} in .vue || .jsx + const filePath = context.getFilename() if ( - scriptSetup && - scriptSetup.range[0] <= parent.range[0] && - parent.range[1] <= scriptSetup.range[1] + isVueComponentFile(parent, filePath) && + skipTSAsExpression(parent.declaration) === node ) { - // `export default` in ` ` - const messages = ( - await eslint.lintText(code, { filePath: 'test.vue' }) - )[0].messages + const messages = await lintMessages(code) assert.strictEqual(messages.length, 1) assert.strictEqual(messages[0].ruleId, 'no-unused-vars') @@ -129,9 +124,7 @@ describe('comment-directive', () => {
Hello
` - const messages = ( - await eslint.lintText(code, { filePath: 'test.vue' }) - )[0].messages + const messages = await lintMessages(code) assert.deepEqual(messages.length, 0) }) @@ -144,9 +137,7 @@ describe('comment-directive', () => {
Hello
` - const messages = ( - await eslint.lintText(code, { filePath: 'test.vue' }) - )[0].messages + const messages = await lintMessages(code) assert.deepEqual(messages.length, 0) }) @@ -157,9 +148,7 @@ describe('comment-directive', () => {
Hello
` - const messages = ( - await eslint.lintText(code, { filePath: 'test.vue' }) - )[0].messages + const messages = await lintMessages(code) assert.deepEqual(messages.length, 1) assert.deepEqual(messages[0].ruleId, 'vue/no-parsing-error') @@ -173,9 +162,7 @@ describe('comment-directive', () => { ` - const messages = ( - await eslint.lintText(code, { filePath: 'test.vue' }) - )[0].messages + const messages = await lintMessages(code) assert.deepEqual(messages.length, 2) assert.deepEqual(messages[0].ruleId, 'vue/no-parsing-error') @@ -191,9 +178,7 @@ describe('comment-directive', () => {
Hello
` - const messages = ( - await eslint.lintText(code, { filePath: 'test.vue' }) - )[0].messages + const messages = await lintMessages(code) assert.deepEqual(messages.length, 0) }) @@ -205,9 +190,7 @@ describe('comment-directive', () => {
Hello
` - const messages = ( - await eslint.lintText(code, { filePath: 'test.vue' }) - )[0].messages + const messages = await lintMessages(code) assert.deepEqual(messages.length, 1) assert.deepEqual(messages[0].ruleId, 'vue/no-parsing-error') @@ -222,9 +205,7 @@ describe('comment-directive', () => { ` - const messages = ( - await eslint.lintText(code, { filePath: 'test.vue' }) - )[0].messages + const messages = await lintMessages(code) assert.deepEqual(messages.length, 2) assert.deepEqual(messages[0].ruleId, 'vue/no-parsing-error') @@ -240,9 +221,7 @@ describe('comment-directive', () => { ` - const messages = ( - await eslint.lintText(code, { filePath: 'test.vue' }) - )[0].messages + const messages = await lintMessages(code) assert.deepEqual(messages.length, 2) assert.deepEqual(messages[0].ruleId, 'vue/no-parsing-error') @@ -260,9 +239,7 @@ describe('comment-directive', () => {
Hello
` - const messages = ( - await eslint.lintText(code, { filePath: 'test.vue' }) - )[0].messages + const messages = await lintMessages(code) assert.deepEqual(messages.length, 0) }) @@ -276,9 +253,7 @@ describe('comment-directive', () => {
Hello
` - const messages = ( - await eslint.lintText(code, { filePath: 'test.vue' }) - )[0].messages + const messages = await lintMessages(code) assert.deepEqual(messages.length, 2) assert.deepEqual(messages[0].ruleId, 'vue/no-parsing-error') @@ -296,9 +271,7 @@ describe('comment-directive', () => {
Hello
` - const messages = ( - await eslint.lintText(code, { filePath: 'test.vue' }) - )[0].messages + const messages = await lintMessages(code) assert.deepEqual(messages.length, 1) assert.deepEqual(messages[0].ruleId, 'vue/no-duplicate-attributes') @@ -311,9 +284,7 @@ describe('comment-directive', () => {
Hello
` - const messages = ( - await eslint.lintText(code, { filePath: 'test.vue' }) - )[0].messages + const messages = await lintMessages(code) assert.deepEqual(messages.length, 0) }) @@ -324,9 +295,7 @@ describe('comment-directive', () => {
Hello
` - const messages = ( - await eslint.lintText(code, { filePath: 'test.vue' }) - )[0].messages + const messages = await lintMessages(code) assert.deepEqual(messages.length, 1) assert.deepEqual(messages[0].ruleId, 'vue/no-parsing-error') @@ -339,9 +308,7 @@ describe('comment-directive', () => {
Hello
` - const messages = ( - await eslint.lintText(code, { filePath: 'test.vue' }) - )[0].messages + const messages = await lintMessages(code) assert.deepEqual(messages.length, 0) }) @@ -353,9 +320,7 @@ describe('comment-directive', () => {
Hello
` - const messages = ( - await eslint.lintText(code, { filePath: 'test.vue' }) - )[0].messages + const messages = await lintMessages(code) assert.deepEqual(messages.length, 1) assert.deepEqual(messages[0].ruleId, 'vue/no-parsing-error') @@ -370,9 +335,7 @@ describe('comment-directive', () => {
Hello
` - const messages = ( - await eslint.lintText(code, { filePath: 'test.vue' }) - )[0].messages + const messages = await lintMessages(code) assert.deepEqual(messages.length, 0) }) @@ -387,9 +350,7 @@ describe('comment-directive', () => { ` - const messages = ( - await eslint.lintText(code, { filePath: 'test.vue' }) - )[0].messages + const messages = await lintMessages(code) assert.deepEqual(messages.length, 2) assert.deepEqual(messages[0].ruleId, 'vue/no-parsing-error') @@ -417,6 +378,12 @@ describe('comment-directive', () => { }, useEslintrc: false }) + + async function lintMessages(code) { + const result = await eslint.lintText(code, { filePath: 'test.vue' }) + return result[0].messages + } + it('report unused ', async () => { const code = ` ` - const messages = ( - await eslint.lintText(code, { filePath: 'test.vue' }) - )[0].messages + const messages = await lintMessages(code) assert.deepEqual(messages.length, 1) assert.deepEqual(messages[0].ruleId, 'vue/comment-directive') @@ -447,9 +412,7 @@ describe('comment-directive', () => { ` - const messages = ( - await eslint.lintText(code, { filePath: 'test.vue' }) - )[0].messages + const messages = await lintMessages(code) assert.deepEqual(messages.length, 0) }) @@ -464,9 +427,7 @@ describe('comment-directive', () => { ` - const messages = ( - await eslint.lintText(code, { filePath: 'test.vue' }) - )[0].messages + const messages = await lintMessages(code) assert.deepEqual(messages.length, 1) assert.deepEqual(messages[0].ruleId, 'vue/comment-directive') @@ -486,9 +447,7 @@ describe('comment-directive', () => { ` - const messages = ( - await eslint.lintText(code, { filePath: 'test.vue' }) - )[0].messages + const messages = await lintMessages(code) assert.deepEqual(messages.length, 2) @@ -518,9 +477,7 @@ describe('comment-directive', () => { ` - const messages = ( - await eslint.lintText(code, { filePath: 'test.vue' }) - )[0].messages + const messages = await lintMessages(code) assert.deepEqual(messages.length, 4) @@ -554,9 +511,7 @@ describe('comment-directive', () => { ` - const messages = ( - await eslint.lintText(code, { filePath: 'test.vue' }) - )[0].messages + const messages = await lintMessages(code) assert.deepEqual(messages.length, 0) }) @@ -570,9 +525,7 @@ describe('comment-directive', () => { ` - const messages = ( - await eslint.lintText(code, { filePath: 'test.vue' }) - )[0].messages + const messages = await lintMessages(code) assert.deepEqual(messages.length, 0) }) diff --git a/tests/lib/rules/html-indent.js b/tests/lib/rules/html-indent.js index 8340f9423..1c817f1a3 100644 --- a/tests/lib/rules/html-indent.js +++ b/tests/lib/rules/html-indent.js @@ -58,10 +58,10 @@ function loadPatterns(additionalValid, additionalInvalid) { const lines = output.split('\n').map((text, number) => ({ number, text, - indentSize: (/^[ \t]+/.exec(text) || [''])[0].length + indentSize: (/^[\t ]+/.exec(text) || [''])[0].length })) const code = lines - .map((line) => line.text.replace(/^[ \t]+/, '')) + .map((line) => line.text.replace(/^[\t ]+/, '')) .join('\n') const errors = lines .map((line) => @@ -82,8 +82,8 @@ function loadPatterns(additionalValid, additionalInvalid) { .filter(Boolean) return { - valid: valid.concat(additionalValid), - invalid: invalid.concat(additionalInvalid) + valid: [...valid, ...additionalValid], + invalid: [...invalid, ...additionalInvalid] } } diff --git a/tests/lib/rules/no-irregular-whitespace.js b/tests/lib/rules/no-irregular-whitespace.js index 9c3a40cdc..52c6dd0e4 100644 --- a/tests/lib/rules/no-irregular-whitespace.js +++ b/tests/lib/rules/no-irregular-whitespace.js @@ -11,17 +11,37 @@ const tester = new RuleTester({ parserOptions: { ecmaVersion: 2018 } }) -const IRREGULAR_WHITESPACES = - '\f\v\u0085\ufeff\u00a0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u202f\u205f\u3000'.split( - '' - ) -const IRREGULAR_LINE_TERMINATORS = '\u2028\u2029'.split('') -const ALL_IRREGULAR_WHITESPACES = [].concat( - IRREGULAR_WHITESPACES, - IRREGULAR_LINE_TERMINATORS -) +const IRREGULAR_WHITESPACES = [ + '\f', + '\v', + '\u0085', + '\uFEFF', + '\u00A0', + '\u1680', + '\u180E', + '\u2000', + '\u2001', + '\u2002', + '\u2003', + '\u2004', + '\u2005', + '\u2006', + '\u2007', + '\u2008', + '\u2009', + '\u200A', + '\u200B', + '\u202F', + '\u205F', + '\u3000' +] +const IRREGULAR_LINE_TERMINATORS = ['\u2028', '\u2029'] +const ALL_IRREGULAR_WHITESPACES = [ + ...IRREGULAR_WHITESPACES, + ...IRREGULAR_LINE_TERMINATORS +] const ALL_IRREGULAR_WHITESPACE_CODES = ALL_IRREGULAR_WHITESPACES.map((s) => - `000${s.charCodeAt(0).toString(16)}`.slice(-4) + `000${s.codePointAt(0).toString(16)}`.slice(-4) ) tester.run('no-irregular-whitespace', rule, { diff --git a/tests/lib/rules/no-reserved-component-names.js b/tests/lib/rules/no-reserved-component-names.js index cdb3ab7a2..126123dd4 100644 --- a/tests/lib/rules/no-reserved-component-names.js +++ b/tests/lib/rules/no-reserved-component-names.js @@ -14,9 +14,7 @@ const RuleTester = require('eslint').RuleTester const htmlElements = require('../../../lib/utils/html-elements.json') const RESERVED_NAMES_IN_HTML = new Set([ ...htmlElements, - ...htmlElements.map( - (word) => word[0].toUpperCase() + word.substring(1, word.length) - ) + ...htmlElements.map((word) => word[0].toUpperCase() + word.slice(1)) ]) // ------------------------------------------------------------------------------ diff --git a/tests/lib/rules/script-indent.js b/tests/lib/rules/script-indent.js index 229ea3273..2efb8b889 100644 --- a/tests/lib/rules/script-indent.js +++ b/tests/lib/rules/script-indent.js @@ -76,10 +76,10 @@ function loadPatterns(additionalValid, additionalInvalid) { const lines = output.split('\n').map((text, number) => ({ number, text, - indentSize: (/^[ \t]+/.exec(text) || [''])[0].length + indentSize: (/^[\t ]+/.exec(text) || [''])[0].length })) const code = lines - .map((line) => line.text.replace(/^[ \t]+/, '')) + .map((line) => line.text.replace(/^[\t ]+/, '')) .join('\n') const errors = lines .map((line) => @@ -100,8 +100,8 @@ function loadPatterns(additionalValid, additionalInvalid) { .filter(Boolean) return { - valid: valid.concat(additionalValid), - invalid: invalid.concat(additionalInvalid) + valid: [...valid, ...additionalValid], + invalid: [...invalid, ...additionalInvalid] } } diff --git a/tests/lib/rules/this-in-template.js b/tests/lib/rules/this-in-template.js index 5d081f468..220331b3b 100644 --- a/tests/lib/rules/this-in-template.js +++ b/tests/lib/rules/this-in-template.js @@ -190,7 +190,7 @@ function createInvalidTests(prefix, options, message, type) { )}bar">`, errors: [{ message, type }], options - } + }, // We cannot use `.` in dynamic arguments because the right of the `.` becomes a modifier. // { @@ -198,8 +198,7 @@ function createInvalidTests(prefix, options, message, type) { // errors: [{ message, type }], // options // } - ].concat( - options[0] === 'always' + ...(options[0] === 'always' ? [] : [ { @@ -214,68 +213,63 @@ function createInvalidTests(prefix, options, message, type) { errors: [{ message, type }], options } - ] - ) + ]) + ] } ruleTester.run('this-in-template', rule, { - valid: ['', '', ''] - .concat(createValidTests('', [])) - .concat(createValidTests('', ['never'])) - .concat(createValidTests('this.', ['always'])) - .concat(createValidTests('this?.', ['always'])), - invalid: [] - .concat( - createInvalidTests( - 'this.', - [], - "Unexpected usage of 'this'.", - 'ThisExpression' - ), - createInvalidTests( - 'this?.', - [], - "Unexpected usage of 'this'.", - 'ThisExpression' - ) - ) - .concat( - createInvalidTests( - 'this.', - ['never'], - "Unexpected usage of 'this'.", - 'ThisExpression' - ), - createInvalidTests( - 'this?.', - ['never'], - "Unexpected usage of 'this'.", - 'ThisExpression' - ) - ) - .concat( - createInvalidTests('', ['always'], "Expected 'this'.", 'Identifier') - ) - .concat([ - { - code: ``, - output: ``, - errors: ["Unexpected usage of 'this'."], - options: ['never'] - }, - { - code: ``, - output: ``, - errors: ["Unexpected usage of 'this'."], - options: ['never'] - } - ]) + valid: [ + '', + '', + '', + ...createValidTests('', []), + ...createValidTests('', ['never']), + ...createValidTests('this.', ['always']), + ...createValidTests('this?.', ['always']) + ], + invalid: [ + ...createInvalidTests( + 'this.', + [], + "Unexpected usage of 'this'.", + 'ThisExpression' + ), + ...createInvalidTests( + 'this?.', + [], + "Unexpected usage of 'this'.", + 'ThisExpression' + ), + ...createInvalidTests( + 'this.', + ['never'], + "Unexpected usage of 'this'.", + 'ThisExpression' + ), + ...createInvalidTests( + 'this?.', + ['never'], + "Unexpected usage of 'this'.", + 'ThisExpression' + ), + ...createInvalidTests('', ['always'], "Expected 'this'.", 'Identifier'), + { + code: ``, + output: ``, + errors: ["Unexpected usage of 'this'."], + options: ['never'] + }, + { + code: ``, + output: ``, + errors: ["Unexpected usage of 'this'."], + options: ['never'] + } + ] }) function suggestionPrefix(prefix, options) { - if (options[0] === 'always' && !['this.', 'this?.'].includes(prefix)) { - return 'this.' - } else { - return '' - } + return options[0] === 'always' && !['this.', 'this?.'].includes(prefix) + ? 'this.' + : '' } diff --git a/tests/lib/utils/index.js b/tests/lib/utils/index.js index 89c81942d..9b02ba46b 100644 --- a/tests/lib/utils/index.js +++ b/tests/lib/utils/index.js @@ -4,12 +4,11 @@ const espree = require('espree') const utils = require('../../../lib/utils/index') const assert = require('assert') -describe('getComputedProperties', () => { - const parse = function (code) { - return espree.parse(code, { ecmaVersion: 2020 }).body[0].declarations[0] - .init - } +function parse(code) { + return espree.parse(code, { ecmaVersion: 2020 }).body[0].declarations[0].init +} +describe('getComputedProperties', () => { it('should return empty array when there is no computed property', () => { const node = parse(`const test = { name: 'test', @@ -111,11 +110,6 @@ describe('getComputedProperties', () => { }) describe('getStaticPropertyName', () => { - const parse = function (code) { - return espree.parse(code, { ecmaVersion: 2020 }).body[0].declarations[0] - .init - } - it('should parse property expression with identifier', () => { const node = parse(`const test = { computed: { } }`) @@ -137,11 +131,6 @@ describe('getStaticPropertyName', () => { }) describe('getStringLiteralValue', () => { - const parse = function (code) { - return espree.parse(code, { ecmaVersion: 2020 }).body[0].declarations[0] - .init - } - it('should parse literal', () => { const node = parse(`const test = { ['computed'] () {} }`) @@ -157,11 +146,6 @@ describe('getStringLiteralValue', () => { }) describe('getMemberChaining', () => { - const parse = function (code) { - return espree.parse(code, { ecmaVersion: 2020 }).body[0].declarations[0] - .init - } - const jsonIgnoreKeys = ['expression', 'object'] it('should parse MemberExpression', () => { @@ -276,11 +260,6 @@ describe('getMemberChaining', () => { }) describe('getRegisteredComponents', () => { - const parse = function (code) { - return espree.parse(code, { ecmaVersion: 2020 }).body[0].declarations[0] - .init - } - it('should return empty array when there are no components registered', () => { const node = parse(`const test = { name: 'test', @@ -336,15 +315,13 @@ describe('getRegisteredComponents', () => { }) }) -describe('getComponentProps', () => { - const parse = function (code) { - const data = espree.parse(code, { ecmaVersion: 2020 }).body[0] - .declarations[0].init - return utils.getComponentProps(data) - } +function parseProps(code) { + return utils.getComponentPropsFromOptions(parse(code)) +} +describe('getComponentProps', () => { it('should return empty array when there is no component props', () => { - const props = parse(`const test = { + const props = parseProps(`const test = { name: 'test', data() { return {} @@ -355,7 +332,7 @@ describe('getComponentProps', () => { }) it('should return empty array when component props is empty array', () => { - const props = parse(`const test = { + const props = parseProps(`const test = { name: 'test', props: [] }`) @@ -364,7 +341,7 @@ describe('getComponentProps', () => { }) it('should return empty array when component props is empty object', () => { - const props = parse(`const test = { + const props = parseProps(`const test = { name: 'test', props: {} }`) @@ -373,7 +350,7 @@ describe('getComponentProps', () => { }) it('should return computed props', () => { - const props = parse(`const test = { + const props = parseProps(`const test = { name: 'test', ...test, data() { @@ -412,7 +389,7 @@ describe('getComponentProps', () => { }) it('should return computed from array props', () => { - const props = parse(`const test = { + const props = parseProps(`const test = { name: 'test', data() { return {} diff --git a/tests/lib/utils/vue-component.js b/tests/lib/utils/vue-component.js index 91da3d28c..c2fb7aaf5 100644 --- a/tests/lib/utils/vue-component.js +++ b/tests/lib/utils/vue-component.js @@ -255,7 +255,7 @@ function invalidTests(ext) { // ${ext} `, parserOptions, - errors: (ext === 'js' ? [] : [makeError(2)]).concat([makeError(4)]) + errors: [...(ext === 'js' ? [] : [makeError(2)]), makeError(4)] }, { filename: `test.${ext}`, @@ -293,7 +293,7 @@ function invalidTests(ext) { // ${ext} `, parserOptions, - errors: (ext === 'js' ? [] : [makeError(3)]).concat([makeError(6)]) + errors: [...(ext === 'js' ? [] : [makeError(3)]), makeError(6)] }, { filename: `test.${ext}`, @@ -310,7 +310,7 @@ function invalidTests(ext) { // ${ext} `, parserOptions, - errors: (ext === 'js' ? [] : [makeError(2)]).concat([makeError(8)]) + errors: [...(ext === 'js' ? [] : [makeError(2)]), makeError(8)] }, { filename: `test.${ext}`, @@ -332,11 +332,11 @@ ruleTester.run('vue-component', rule, { filename: 'test.js', code: `export default { }`, parserOptions - } - ] - .concat(validTests('js')) - .concat(validTests('jsx')) - .concat(validTests('vue')), + }, + ...validTests('js'), + ...validTests('jsx'), + ...validTests('vue') + ], invalid: [ { filename: 'test.vue', @@ -349,9 +349,9 @@ ruleTester.run('vue-component', rule, { code: `export default { }`, parserOptions, errors: [makeError(1)] - } + }, + ...invalidTests('js'), + ...invalidTests('jsx'), + ...invalidTests('vue') ] - .concat(invalidTests('js')) - .concat(invalidTests('jsx')) - .concat(invalidTests('vue')) }) diff --git a/tools/lib/categories.js b/tools/lib/categories.js index 7223fd87b..1d8550fb3 100644 --- a/tools/lib/categories.js +++ b/tools/lib/categories.js @@ -77,4 +77,4 @@ module.exports = categoryIds (rule) => !rule.meta.deprecated ) })) - .filter((category) => category.rules.length >= 1) + .filter((category) => category.rules.length > 0) diff --git a/tools/update-docs-rules-index.js b/tools/update-docs-rules-index.js index 9b177d7ac..606d67179 100644 --- a/tools/update-docs-rules-index.js +++ b/tools/update-docs-rules-index.js @@ -68,7 +68,7 @@ ${category.rules.map(toRuleRow).join('\n')} .join('') // ----------------------------------------------------------------------------- -if (uncategorizedRules.length || uncategorizedExtensionRule.length) { +if (uncategorizedRules.length > 0 || uncategorizedExtensionRule.length > 0) { rulesTableContent += ` ## Uncategorized @@ -88,14 +88,14 @@ For example: \`\`\` ` } -if (uncategorizedRules.length) { +if (uncategorizedRules.length > 0) { rulesTableContent += ` | Rule ID | Description | | |:--------|:------------|:---| ${uncategorizedRules.map(toRuleRow).join('\n')} ` } -if (uncategorizedExtensionRule.length) { +if (uncategorizedExtensionRule.length > 0) { rulesTableContent += ` ### Extension Rules @@ -108,7 +108,7 @@ ${uncategorizedExtensionRule.map(toRuleRow).join('\n')} } // ----------------------------------------------------------------------------- -if (deprecatedRules.length >= 1) { +if (deprecatedRules.length > 0) { rulesTableContent += ` ## Deprecated diff --git a/tools/update-docs.js b/tools/update-docs.js index 12deca0ea..6f2d2255a 100644 --- a/tools/update-docs.js +++ b/tools/update-docs.js @@ -107,11 +107,9 @@ class DocFile { const fileIntroPattern = /^---\n(.*\n)+---\n*/g - if (fileIntroPattern.test(this.content)) { - this.content = this.content.replace(fileIntroPattern, computed) - } else { - this.content = `${computed}${this.content.trim()}\n` - } + this.content = fileIntroPattern.test(this.content) + ? this.content.replace(fileIntroPattern, computed) + : `${computed}${this.content.trim()}\n` return this } @@ -159,17 +157,15 @@ class DocFile { } // Add an empty line after notes. - if (notes.length >= 1) { + if (notes.length > 0) { notes.push('', '') } - const headerPattern = /#.+\n\n*[^\n]*\n+(?:- .+\n)*\n*/ + const headerPattern = /#.+\n+[^\n]*\n+(?:- .+\n)*\n*/ const header = `${title}\n\n${notes.join('\n')}` - if (headerPattern.test(this.content)) { - this.content = this.content.replace(headerPattern, header) - } else { - this.content = `${header}${this.content.trim()}\n` - } + this.content = headerPattern.test(this.content) + ? this.content.replace(headerPattern, header) + : `${header}${this.content.trim()}\n` return this } @@ -178,7 +174,7 @@ class DocFile { const { meta } = this.rule this.content = this.content.replace( - /)\n+```/gm, + /()\n+```/gm, '$1\n\n```' ) this.content = this.content.replace( @@ -219,11 +215,9 @@ ${ ` : '' }` - if (footerPattern.test(this.content)) { - this.content = this.content.replace(footerPattern, footer) - } else { - this.content = `${this.content.trim()}\n\n${footer}` - } + this.content = footerPattern.test(this.content) + ? this.content.replace(footerPattern, footer) + : `${this.content.trim()}\n\n${footer}` return this } diff --git a/tools/update-lib-configs.js b/tools/update-lib-configs.js index 26376b78a..f75aff4fa 100644 --- a/tools/update-lib-configs.js +++ b/tools/update-lib-configs.js @@ -14,7 +14,7 @@ const path = require('path') const eslint = require('eslint') const categories = require('./lib/categories') -const errorCategories = ['base', 'essential', 'vue3-essential'] +const errorCategories = new Set(['base', 'essential', 'vue3-essential']) const extendsCategories = { base: null, @@ -29,20 +29,21 @@ const extendsCategories = { } function formatRules(rules, categoryId) { - const obj = rules.reduce((setting, rule) => { - let options = errorCategories.includes(categoryId) ? 'error' : 'warn' - const defaultOptions = - rule.meta && rule.meta.docs && rule.meta.docs.defaultOptions - if (defaultOptions) { - const v = categoryId.startsWith('vue3') ? 3 : 2 - const defaultOption = defaultOptions[`vue${v}`] - if (defaultOption) { - options = [options, ...defaultOption] + const obj = Object.fromEntries( + rules.map((rule) => { + let options = errorCategories.has(categoryId) ? 'error' : 'warn' + const defaultOptions = + rule.meta && rule.meta.docs && rule.meta.docs.defaultOptions + if (defaultOptions) { + const v = categoryId.startsWith('vue3') ? 3 : 2 + const defaultOption = defaultOptions[`vue${v}`] + if (defaultOption) { + options = [options, ...defaultOption] + } } - } - setting[rule.ruleId] = options - return setting - }, {}) + return [rule.ruleId, options] + }) + ) return JSON.stringify(obj, null, 2) } @@ -85,12 +86,12 @@ module.exports = { // Update files. const ROOT = path.resolve(__dirname, '../lib/configs/') -categories.forEach((category) => { +for (const category of categories) { const filePath = path.join(ROOT, `${category.categoryId}.js`) const content = formatCategory(category) fs.writeFileSync(filePath, content) -}) +} // Format files. async function format() { diff --git a/tools/update-no-layout-rules-config.js b/tools/update-no-layout-rules-config.js index b0361b155..ee4690e41 100644 --- a/tools/update-no-layout-rules-config.js +++ b/tools/update-no-layout-rules-config.js @@ -17,10 +17,7 @@ const rules = require('./lib/rules') const rulesToDisable = rules.filter(({ meta }) => meta.type === 'layout') function formatRules(rules) { - const obj = rules.reduce((setting, rule) => { - setting[rule.ruleId] = 'off' - return setting - }, {}) + const obj = Object.fromEntries(rules.map((rule) => [rule.ruleId, 'off'])) return JSON.stringify(obj, null, 2) } diff --git a/tools/update-vue3-export-names.js b/tools/update-vue3-export-names.js index fcd31b3e3..8a3de00ea 100644 --- a/tools/update-vue3-export-names.js +++ b/tools/update-vue3-export-names.js @@ -33,39 +33,51 @@ async function* extractExportNames(m) { range: true }) for (const node of rootNode.body) { - if (node.type === 'ExportAllDeclaration') { - if (node.exported) { - yield node.exported.name - } else { - for await (const name of extractExportNames(node.source.value)) { - yield name + switch (node.type) { + case 'ExportAllDeclaration': { + if (node.exported) { + yield node.exported.name + } else { + for await (const name of extractExportNames(node.source.value)) { + yield name + } } + break } - } else if (node.type === 'ExportNamedDeclaration') { - if (node.declaration) { - if ( - node.declaration.type === 'ClassDeclaration' || - node.declaration.type === 'ClassExpression' || - node.declaration.type === 'FunctionDeclaration' || - node.declaration.type === 'TSDeclareFunction' || - node.declaration.type === 'TSEnumDeclaration' || - node.declaration.type === 'TSInterfaceDeclaration' || - node.declaration.type === 'TSTypeAliasDeclaration' - ) { - yield node.declaration.id.name - } else if (node.declaration.type === 'VariableDeclaration') { - for (const decl of node.declaration.declarations) { - yield* extractNamesFromPattern(decl.id) + case 'ExportNamedDeclaration': { + if (node.declaration) { + switch (node.declaration.type) { + case 'ClassDeclaration': + case 'ClassExpression': + case 'FunctionDeclaration': + case 'TSDeclareFunction': + case 'TSEnumDeclaration': + case 'TSInterfaceDeclaration': + case 'TSTypeAliasDeclaration': { + yield node.declaration.id.name + break + } + case 'VariableDeclaration': { + for (const decl of node.declaration.declarations) { + yield* extractNamesFromPattern(decl.id) + } + break + } + case 'TSModuleDeclaration': { + //? + break + } } - } else if (node.declaration.type === 'TSModuleDeclaration') { - //? } + for (const spec of node.specifiers) { + yield spec.exported.name + } + break } - for (const spec of node.specifiers) { - yield spec.exported.name + case 'ExportDefaultDeclaration': { + yield 'default' + break } - } else if (node.type === 'ExportDefaultDeclaration') { - yield 'default' } } } @@ -83,26 +95,39 @@ async function* extractExportNames(m) { * @param {Identifier|ArrayPattern|ObjectPattern|AssignmentPattern|MemberExpression|RestElement} node */ function* extractNamesFromPattern(node) { - if (node.type === 'Identifier') { - yield node.name - } else if (node.type === 'ArrayPattern') { - for (const element of node.elements) { - yield* extractNamesFromPattern(element) + switch (node.type) { + case 'Identifier': { + yield node.name + break + } + case 'ArrayPattern': { + for (const element of node.elements) { + yield* extractNamesFromPattern(element) + } + break } - } else if (node.type === 'ObjectPattern') { - for (const prop of node.properties) { - if (prop.type === 'Property') { - yield prop.key.name - } else if (prop.type === 'RestElement') { - yield* extractNamesFromPattern(prop) + case 'ObjectPattern': { + for (const prop of node.properties) { + if (prop.type === 'Property') { + yield prop.key.name + } else if (prop.type === 'RestElement') { + yield* extractNamesFromPattern(prop) + } } + break + } + case 'AssignmentPattern': { + yield* extractNamesFromPattern(node.left) + break + } + case 'RestElement': { + yield* extractNamesFromPattern(node.argument) + break + } + case 'MemberExpression': { + // ? + break } - } else if (node.type === 'AssignmentPattern') { - yield* extractNamesFromPattern(node.left) - } else if (node.type === 'RestElement') { - yield* extractNamesFromPattern(node.argument) - } else if (node.type === 'MemberExpression') { - // ? } } async function resolveTypeContents(m) { From bd13174c22eab0e0aa8d8d0a1e4f7cb82b9dbdd1 Mon Sep 17 00:00:00 2001 From: Flo Edelmann Date: Fri, 22 Apr 2022 11:33:48 +0200 Subject: [PATCH 02/22] Fix interactive docs for `vue/match-component-import-name` (#1862) --- docs/rules/match-component-import-name.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/rules/match-component-import-name.md b/docs/rules/match-component-import-name.md index 7abf7eca1..fd88f9ff2 100644 --- a/docs/rules/match-component-import-name.md +++ b/docs/rules/match-component-import-name.md @@ -13,7 +13,7 @@ since: v8.7.0 By default, this rule will validate that the imported name matches the name of the components object property identifer. Note that "matches" means that the imported name matches either the PascalCase or kebab-case version of the components object property identifer. If you would like to enforce that it must match only one of PascalCase or kebab-case, use this rule in conjunction with the rule [vue/component-definition-name-casing](./component-definition-name-casing.md). - + ```vue + + + `, + output: ` + + + + `, + errors: [ + { + message: message('defineProps'), + line: 11 + } + ] } ] }) From cdcf75e4ecf3ddab806ef33da77510022777d0da Mon Sep 17 00:00:00 2001 From: Yosuke Ota Date: Fri, 22 Apr 2022 20:56:29 +0900 Subject: [PATCH 04/22] Improved rule list doc (#1860) * Improved rule list doc * update markdownlint config * Update tools/update-docs-rules-index.js Co-authored-by: Flo Edelmann * Update docs/.vuepress/components/rules-table.vue Co-authored-by: Flo Edelmann * Update docs/.vuepress/components/rules-table.vue Co-authored-by: Flo Edelmann * Update tools/update-docs-rules-index.js Co-authored-by: Flo Edelmann * Update tools/update-docs-rules-index.js Co-authored-by: Flo Edelmann * Update tools/lib/utils.js Co-authored-by: Flo Edelmann * Update tools/lib/utils.js Co-authored-by: Flo Edelmann * Update tools/update-docs-rules-index.js Co-authored-by: Flo Edelmann * update * update markdownlint config Co-authored-by: Flo Edelmann --- .markdownlint.yml | 2 + docs/.vuepress/components/rules-table.vue | 90 +++ docs/.vuepress/styles/index.styl | 6 + docs/rules/README.md | 645 +++++++++------------- package.json | 1 - tools/lib/utils.js | 42 ++ tools/update-docs-rules-index.js | 156 +++++- tools/update-docs.js | 36 +- 8 files changed, 543 insertions(+), 435 deletions(-) create mode 100644 docs/.vuepress/components/rules-table.vue create mode 100644 tools/lib/utils.js diff --git a/.markdownlint.yml b/.markdownlint.yml index cb76e21d5..b300725cc 100644 --- a/.markdownlint.yml +++ b/.markdownlint.yml @@ -5,6 +5,8 @@ no-inline-html: - badge - eslint-code-block - sup + - rules-table + - span # enforce consistency code-block-style: diff --git a/docs/.vuepress/components/rules-table.vue b/docs/.vuepress/components/rules-table.vue new file mode 100644 index 000000000..29d31c4c6 --- /dev/null +++ b/docs/.vuepress/components/rules-table.vue @@ -0,0 +1,90 @@ + + + + + diff --git a/docs/.vuepress/styles/index.styl b/docs/.vuepress/styles/index.styl index 48680bbf3..724c68247 100644 --- a/docs/.vuepress/styles/index.styl +++ b/docs/.vuepress/styles/index.styl @@ -18,3 +18,9 @@ } } } + +.theme-container.rule-list .theme-default-content + max-width: 1280px; + +.emoji + font-family: "Segoe UI Emoji", "Segoe UI Symbol", "Apple Color Emoji", "Noto Color Emoji", "Noto Emoji", sans-serif diff --git a/docs/rules/README.md b/docs/rules/README.md index 26ce0e59d..9a54292c9 100644 --- a/docs/rules/README.md +++ b/docs/rules/README.md @@ -1,5 +1,6 @@ --- sidebarDepth: 0 +pageClass: rule-list --- # Available rules @@ -12,282 +13,172 @@ sidebarDepth: 0 :bulb: Indicates that some problems reported by the rule are manually fixable by editor [suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions). ::: -## Base Rules (Enabling Correct ESLint Parsing) - -Enforce all the rules in this category, as well as all higher priority rules, with: - -```json -{ - "extends": "plugin:vue/base" -} -``` - -| Rule ID | Description | | -|:--------|:------------|:---| -| [vue/comment-directive](./comment-directive.md) | support comment-directives in ` diff --git a/docs/rules/no-restricted-custom-event.md b/docs/rules/no-restricted-custom-event.md index bc48def0a..b77feac14 100644 --- a/docs/rules/no-restricted-custom-event.md +++ b/docs/rules/no-restricted-custom-event.md @@ -31,7 +31,7 @@ This rule takes a list of strings, where each string is a custom event name or p + ` + } + ] + } + ] + }, + { + filename: 'MyComponent.vue', + code: ` + + `, + options: [{ extensions: ['vue'] }], + parser: require.resolve('vue-eslint-parser'), + parserOptions, + errors: [ + { + message: + 'Component name `MComponent` should match file name `MyComponent`.', + suggestions: [ + { + desc: 'Rename component to match file name.', + output: ` + + ` + } + ] } ] }, @@ -640,7 +747,20 @@ ruleTester.run('match-component-file-name', rule, { errors: [ { message: - 'Component name `MComponent` should match file name `MyComponent`.' + 'Component name `MComponent` should match file name `MyComponent`.', + suggestions: [ + { + desc: 'Rename component to match file name.', + output: ` + + ` + } + ] } ] }, @@ -659,7 +779,46 @@ ruleTester.run('match-component-file-name', rule, { errors: [ { message: - 'Component name `MComponent` should match file name `MyComponent`.' + 'Component name `MComponent` should match file name `MyComponent`.', + suggestions: [ + { + desc: 'Rename component to match file name.', + output: ` + new Vue({ + name: 'MyComponent', + template: '
' + }) + ` + } + ] + } + ] + }, + { + filename: 'MyComponent.js', + code: ` + new Vue({ + name: "MComponent", + template: '
' + }) + `, + options: [{ extensions: ['js'] }], + parserOptions, + errors: [ + { + message: + 'Component name `MComponent` should match file name `MyComponent`.', + suggestions: [ + { + desc: 'Rename component to match file name.', + output: ` + new Vue({ + name: "MyComponent", + template: '
' + }) + ` + } + ] } ] }, @@ -676,7 +835,18 @@ ruleTester.run('match-component-file-name', rule, { errors: [ { message: - 'Component name `MComponent` should match file name `MyComponent`.' + 'Component name `MComponent` should match file name `MyComponent`.', + suggestions: [ + { + desc: 'Rename component to match file name.', + output: ` + new Vue({ + name: \`MyComponent\`, + template: '
' + }) + ` + } + ] } ] }, @@ -692,7 +862,43 @@ ruleTester.run('match-component-file-name', rule, { errors: [ { message: - 'Component name `MComponent` should match file name `MyComponent`.' + 'Component name `MComponent` should match file name `MyComponent`.', + suggestions: [ + { + desc: 'Rename component to match file name.', + output: ` + Vue.mixin({ + name: 'MyComponent', + }) + ` + } + ] + } + ] + }, + { + filename: 'MyComponent.js', + code: ` + Vue.mixin({ + name: "MComponent", + }) + `, + options: [{ extensions: ['js'] }], + parserOptions, + errors: [ + { + message: + 'Component name `MComponent` should match file name `MyComponent`.', + suggestions: [ + { + desc: 'Rename component to match file name.', + output: ` + Vue.mixin({ + name: "MyComponent", + }) + ` + } + ] } ] }, @@ -708,7 +914,17 @@ ruleTester.run('match-component-file-name', rule, { errors: [ { message: - 'Component name `MComponent` should match file name `MyComponent`.' + 'Component name `MComponent` should match file name `MyComponent`.', + suggestions: [ + { + desc: 'Rename component to match file name.', + output: ` + Vue.mixin({ + name: \`MyComponent\`, + }) + ` + } + ] } ] }, @@ -724,7 +940,43 @@ ruleTester.run('match-component-file-name', rule, { errors: [ { message: - 'Component name `MComponent` should match file name `MyComponent`.' + 'Component name `MComponent` should match file name `MyComponent`.', + suggestions: [ + { + desc: 'Rename component to match file name.', + output: ` + Vue.component('MyComponent', { + template: '
' + }) + ` + } + ] + } + ] + }, + { + filename: 'MyComponent.js', + code: ` + Vue.component("MComponent", { + template: '
' + }) + `, + options: [{ extensions: ['js'] }], + parserOptions, + errors: [ + { + message: + 'Component name `MComponent` should match file name `MyComponent`.', + suggestions: [ + { + desc: 'Rename component to match file name.', + output: ` + Vue.component("MyComponent", { + template: '
' + }) + ` + } + ] } ] }, @@ -740,7 +992,17 @@ ruleTester.run('match-component-file-name', rule, { errors: [ { message: - 'Component name `MComponent` should match file name `MyComponent`.' + 'Component name `MComponent` should match file name `MyComponent`.', + suggestions: [ + { + desc: 'Rename component to match file name.', + output: ` + Vue.component(\`MyComponent\`, { + template: '
' + }) + ` + } + ] } ] }, @@ -756,7 +1018,17 @@ ruleTester.run('match-component-file-name', rule, { errors: [ { message: - 'Component name `MComponent` should match file name `MyComponent`.' + 'Component name `MComponent` should match file name `MyComponent`.', + suggestions: [ + { + desc: 'Rename component to match file name.', + output: ` + app.component(\`MyComponent\`, { + template: '
' + }) + ` + } + ] } ] }, @@ -775,7 +1047,18 @@ ruleTester.run('match-component-file-name', rule, { errors: [ { message: - 'Component name `my-component` should match file name `MyComponent`.' + 'Component name `my-component` should match file name `MyComponent`.', + suggestions: [ + { + desc: 'Rename component to match file name.', + output: ` + export default { + name: 'MyComponent', + render() { return
} + } + ` + } + ] } ] }, @@ -792,7 +1075,18 @@ ruleTester.run('match-component-file-name', rule, { errors: [ { message: - 'Component name `MyComponent` should match file name `my-component`.' + 'Component name `MyComponent` should match file name `my-component`.', + suggestions: [ + { + desc: 'Rename component to match file name.', + output: ` + export default { + name: 'my-component', + render() { return
} + } + ` + } + ] } ] } diff --git a/typings/eslint-plugin-vue/util-types/ast/es-ast.ts b/typings/eslint-plugin-vue/util-types/ast/es-ast.ts index bafff6d2b..16a734d2e 100644 --- a/typings/eslint-plugin-vue/util-types/ast/es-ast.ts +++ b/typings/eslint-plugin-vue/util-types/ast/es-ast.ts @@ -347,6 +347,7 @@ export interface PrivateIdentifier extends HasParentNode { export interface Literal extends HasParentNode { type: 'Literal' value: string | boolean | null | number | RegExp | BigInt + raw: string regex?: { pattern: string flags: string From 19c6e8656ea009f40403f2a8a5b4caf4f7cbd33a Mon Sep 17 00:00:00 2001 From: Flo Edelmann Date: Wed, 11 May 2022 08:31:32 +0200 Subject: [PATCH 11/22] Enable `vue/html-closing-bracket-*` for top-level tags (#1883) * Enable `vue/html-closing-bracket-*` for top-level tags * Switch to much simpler `defineDocumentVisitor` * Add type annotation again --- lib/rules/block-tag-newline.js | 13 +- lib/rules/html-closing-bracket-newline.js | 2 +- lib/rules/html-closing-bracket-spacing.js | 2 +- .../lib/rules/html-closing-bracket-newline.js | 150 ++++++++++++++++++ .../lib/rules/html-closing-bracket-spacing.js | 107 +++++++++++++ 5 files changed, 263 insertions(+), 11 deletions(-) diff --git a/lib/rules/block-tag-newline.js b/lib/rules/block-tag-newline.js index 1fe427afd..8459f5760 100644 --- a/lib/rules/block-tag-newline.js +++ b/lib/rules/block-tag-newline.js @@ -347,13 +347,6 @@ module.exports = { const verify = normalizeOptionValue(context.options[0]) - /** - * @returns {VElement[]} - */ - function getTopLevelHTMLElements() { - return documentFragment.children.filter(utils.isVElement) - } - return utils.defineTemplateBodyVisitor( context, {}, @@ -364,8 +357,10 @@ module.exports = { return } - for (const element of getTopLevelHTMLElements()) { - verify(element) + for (const element of documentFragment.children) { + if (utils.isVElement(element)) { + verify(element) + } } } } diff --git a/lib/rules/html-closing-bracket-newline.js b/lib/rules/html-closing-bracket-newline.js index 6963de8f1..9ac1d0f52 100644 --- a/lib/rules/html-closing-bracket-newline.js +++ b/lib/rules/html-closing-bracket-newline.js @@ -68,7 +68,7 @@ module.exports = { context.parserServices.getTemplateBodyTokenStore && context.parserServices.getTemplateBodyTokenStore() - return utils.defineTemplateBodyVisitor(context, { + return utils.defineDocumentVisitor(context, { /** @param {VStartTag | VEndTag} node */ 'VStartTag, VEndTag'(node) { const closingBracketToken = template.getLastToken(node) diff --git a/lib/rules/html-closing-bracket-spacing.js b/lib/rules/html-closing-bracket-spacing.js index 95de6d01f..e60fba491 100644 --- a/lib/rules/html-closing-bracket-spacing.js +++ b/lib/rules/html-closing-bracket-spacing.js @@ -92,7 +92,7 @@ module.exports = { context.parserServices.getTemplateBodyTokenStore() const options = parseOptions(context.options[0], tokens) - return utils.defineTemplateBodyVisitor(context, { + return utils.defineDocumentVisitor(context, { /** @param {VStartTag | VEndTag} node */ 'VStartTag, VEndTag'(node) { const type = options.detectType(node) diff --git a/tests/lib/rules/html-closing-bracket-newline.js b/tests/lib/rules/html-closing-bracket-newline.js index 4c07c80db..b72c46b97 100644 --- a/tests/lib/rules/html-closing-bracket-newline.js +++ b/tests/lib/rules/html-closing-bracket-newline.js @@ -337,6 +337,156 @@ tester.run('html-closing-bracket-newline', rule, { 'Expected 1 line break before closing bracket, but no line breaks found.', 'Expected 1 line break before closing bracket, but no line breaks found.' ] + }, + { + code: ` + + + + `, + output: ` + + + + `, + options: [ + { + singleline: 'never', + multiline: 'never' + } + ], + errors: [ + { + message: + 'Expected no line breaks before closing bracket, but 1 line break found.', + line: 2, + column: 18, + endLine: 3, + endColumn: 9 + }, + { + message: + 'Expected no line breaks before closing bracket, but 1 line break found.', + line: 4, + column: 19, + endLine: 5, + endColumn: 9 + }, + { + message: + 'Expected no line breaks before closing bracket, but 1 line break found.', + line: 6, + column: 16, + endLine: 7, + endColumn: 9 + }, + { + message: + 'Expected no line breaks before closing bracket, but 1 line break found.', + line: 8, + column: 17, + endLine: 9, + endColumn: 9 + }, + { + message: + 'Expected no line breaks before closing bracket, but 1 line break found.', + line: 10, + column: 15, + endLine: 11, + endColumn: 9 + }, + { + message: + 'Expected no line breaks before closing bracket, but 1 line break found.', + line: 11, + column: 17, + endLine: 12, + endColumn: 9 + } + ] + }, + { + code: ` + + + + `, + output: ` + + + + `, + options: [ + { + singleline: 'always', + multiline: 'always' + } + ], + errors: [ + { + message: + 'Expected 1 line break before closing bracket, but no line breaks found.', + line: 2, + column: 18, + endColumn: 18 + }, + { + message: + 'Expected 1 line break before closing bracket, but no line breaks found.', + line: 3, + column: 19, + endColumn: 19 + }, + { + message: + 'Expected 1 line break before closing bracket, but no line breaks found.', + line: 4, + column: 16, + endColumn: 16 + }, + { + message: + 'Expected 1 line break before closing bracket, but no line breaks found.', + line: 5, + column: 17, + endColumn: 17 + }, + { + message: + 'Expected 1 line break before closing bracket, but no line breaks found.', + line: 6, + column: 15, + endColumn: 15 + }, + { + message: + 'Expected 1 line break before closing bracket, but no line breaks found.', + line: 6, + column: 23, + endColumn: 23 + } + ] } ] }) diff --git a/tests/lib/rules/html-closing-bracket-spacing.js b/tests/lib/rules/html-closing-bracket-spacing.js index 154ba87b2..af03d0bfd 100644 --- a/tests/lib/rules/html-closing-bracket-spacing.js +++ b/tests/lib/rules/html-closing-bracket-spacing.js @@ -114,6 +114,56 @@ ruleTester.run('html-closing-bracket-spacing', rule, { } ] }, + { + code: ` + + + + `, + output: ` + + + + `, + errors: [ + { + message: "Expected no space before '>', but found.", + line: 2, + column: 18, + endColumn: 20 + }, + { + message: "Expected no space before '>', but found.", + line: 2, + column: 30, + endColumn: 32 + }, + { + message: "Expected no space before '>', but found.", + line: 3, + column: 16, + endColumn: 18 + }, + { + message: "Expected no space before '>', but found.", + line: 3, + column: 26, + endColumn: 28 + }, + { + message: "Expected no space before '>', but found.", + line: 4, + column: 15, + endColumn: 17 + }, + { + message: "Expected no space before '>', but found.", + line: 4, + column: 24, + endColumn: 26 + } + ] + }, { code: '', output: '', @@ -144,6 +194,63 @@ ruleTester.run('html-closing-bracket-spacing', rule, { endColumn: 10 } ] + }, + { + code: ` + + + + `, + output: ` + + + + `, + options: [ + { + startTag: 'always', + endTag: 'always', + selfClosingTag: 'never' + } + ], + errors: [ + { + message: "Expected a space before '>', but not found.", + line: 2, + column: 18, + endColumn: 19 + }, + { + message: "Expected a space before '>', but not found.", + line: 2, + column: 29, + endColumn: 30 + }, + { + message: "Expected a space before '>', but not found.", + line: 3, + column: 16, + endColumn: 17 + }, + { + message: "Expected a space before '>', but not found.", + line: 3, + column: 25, + endColumn: 26 + }, + { + message: "Expected a space before '>', but not found.", + line: 4, + column: 15, + endColumn: 16 + }, + { + message: "Expected a space before '>', but not found.", + line: 4, + column: 23, + endColumn: 24 + } + ] } ] }) From 2936553d38e721ef59e8ccf0ab7856712eec3681 Mon Sep 17 00:00:00 2001 From: Yosuke Ota Date: Thu, 12 May 2022 08:49:20 +0900 Subject: [PATCH 12/22] Change default casing of `vue/custom-event-name-casing` rule to camelCase (#1846) --- docs/rules/custom-event-name-casing.md | 30 ++-- lib/rules/custom-event-name-casing.js | 2 +- tests/lib/rules/custom-event-name-casing.js | 143 ++++++++++++++++++-- 3 files changed, 151 insertions(+), 24 deletions(-) diff --git a/docs/rules/custom-event-name-casing.md b/docs/rules/custom-event-name-casing.md index a2c4d3644..29afde056 100644 --- a/docs/rules/custom-event-name-casing.md +++ b/docs/rules/custom-event-name-casing.md @@ -13,7 +13,7 @@ Define a style for custom event name casing for consistency purposes. ## :book: Rule Details -This rule aims to warn the custom event names other than the configured casing. +This rule aims to warn the custom event names other than the configured casing. (Default is **camelCase**.) Vue 2 recommends using kebab-case for custom event names. @@ -27,28 +27,28 @@ In Vue 3, using either camelCase or kebab-case for your custom event name does n See [Guide - Custom Events] for more details. -This rule enforces kebab-case by default. +This rule enforces camelCase by default. ```vue - ` + `, + options: ['kebab-case'] }, { filename: 'test.vue', @@ -67,7 +68,8 @@ tester.run('custom-event-name-casing', rule, { } } - ` + `, + options: ['kebab-case'] }, { filename: 'test.vue', @@ -92,7 +94,8 @@ tester.run('custom-event-name-casing', rule, { } } - ` + `, + options: ['kebab-case'] }, { filename: 'test.vue', @@ -117,7 +120,8 @@ tester.run('custom-event-name-casing', rule, { } } - ` + `, + options: ['kebab-case'] }, { filename: 'test.vue', @@ -149,7 +153,8 @@ tester.run('custom-event-name-casing', rule, { }, } - ` + `, + options: ['kebab-case'] }, { filename: 'test.vue', @@ -165,7 +170,8 @@ tester.run('custom-event-name-casing', rule, { }, } - ` + `, + options: ['kebab-case'] }, { filename: 'test.vue', @@ -269,6 +275,60 @@ tester.run('custom-event-name-casing', rule, { `, options: ['camelCase'] + }, + // Default + { + filename: 'test.vue', + code: ` + + + ` + }, + + // kebab-case + { + filename: 'test.vue', + code: ` + + + `, + options: ['kebab-case'] } ], invalid: [ @@ -296,6 +356,7 @@ tester.run('custom-event-name-casing', rule, { } `, + options: ['kebab-case'], errors: [ { message: "Custom event name 'fooBar' must be kebab-case.", @@ -344,6 +405,7 @@ tester.run('custom-event-name-casing', rule, { } `, + options: ['kebab-case'], errors: [ "Custom event name 'fooBar' must be kebab-case.", "Custom event name 'barBaz' must be kebab-case.", @@ -374,6 +436,7 @@ tester.run('custom-event-name-casing', rule, { } `, + options: ['kebab-case'], errors: [ "Custom event name 'fooBar' must be kebab-case.", "Custom event name 'barBaz' must be kebab-case.", @@ -448,6 +511,7 @@ tester.run('custom-event-name-casing', rule, { "Custom event name 'click/row' must be kebab-case." ] }, + // camelCase { filename: 'test.vue', code: ` @@ -479,6 +543,69 @@ tester.run('custom-event-name-casing', rule, { "Custom event name 'baz-qux' must be camelCase." ] }, + // Default + { + filename: 'test.vue', + code: ` + + + `, + errors: [ + "Custom event name 'foo-bar' must be camelCase.", + "Custom event name 'bar-baz' must be camelCase.", + "Custom event name 'baz-qux' must be camelCase." + ] + }, + // kebab-case + { + filename: 'test.vue', + code: ` + + + `, + options: ['kebab-case'], + errors: [ + "Custom event name 'fooBar' must be kebab-case.", + "Custom event name 'barBaz' must be kebab-case.", + "Custom event name 'bazQux' must be kebab-case." + ] + }, { filename: 'test.vue', code: ` @@ -490,8 +617,8 @@ tester.run('custom-event-name-casing', rule, { `, errors: [ { - message: "Custom event name 'fooBar' must be kebab-case.", - line: 4 + message: "Custom event name 'foo-bar' must be camelCase.", + line: 5 } ] } From 58cb406fece88b88f50d43f92f0d70d4b42e8132 Mon Sep 17 00:00:00 2001 From: Yosuke Ota Date: Thu, 12 May 2022 08:49:45 +0900 Subject: [PATCH 13/22] Drop support for Node.js v12 (#1847) * Drop support for Node.js v12 * Use ecmaVersion: 'latest' --- .circleci/config.yml | 7 +------ .eslintrc.js | 4 ++-- docs/.vuepress/components/eslint-code-block.vue | 2 +- docs/user-guide/README.md | 2 +- package.json | 2 +- 5 files changed, 6 insertions(+), 11 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index e695f422a..0ba3d33dc 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -5,7 +5,6 @@ workflows: - eslint-v6 - eslint-v7 - ts-eslint-v4 - - node-v12 - node-v14 - node-v16 - lint @@ -36,7 +35,7 @@ jobs: eslint-v6: docker: - - image: node:12 + - image: node:14 steps: - run: name: Versions @@ -88,10 +87,6 @@ jobs: - run: name: Test command: npm test - node-v12: - <<: *node-base - docker: - - image: node:12 node-v14: <<: *node-base docker: diff --git a/.eslintrc.js b/.eslintrc.js index 6a944b5e1..da047d7b9 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -3,7 +3,7 @@ module.exports = { root: true, parserOptions: { - ecmaVersion: 2018 + ecmaVersion: 'latest' }, env: { es6: true, @@ -137,7 +137,7 @@ module.exports = { files: ['./**/*.vue'], parser: require.resolve('vue-eslint-parser'), parserOptions: { - ecmaVersion: 2020, + ecmaVersion: 'latest', sourceType: 'module' } }, diff --git a/docs/.vuepress/components/eslint-code-block.vue b/docs/.vuepress/components/eslint-code-block.vue index 2c6abd2cc..f71e606a3 100644 --- a/docs/.vuepress/components/eslint-code-block.vue +++ b/docs/.vuepress/components/eslint-code-block.vue @@ -90,7 +90,7 @@ export default { rules: this.rules, parser: 'vue-eslint-parser', parserOptions: { - ecmaVersion: 2020, + ecmaVersion: 'latest', sourceType: 'module', ecmaFeatures: { jsx: true diff --git a/docs/user-guide/README.md b/docs/user-guide/README.md index b083a6bd3..28e586d24 100644 --- a/docs/user-guide/README.md +++ b/docs/user-guide/README.md @@ -23,7 +23,7 @@ yarn add -D eslint eslint-plugin-vue ::: tip Requirements - ESLint v6.2.0 and above -- Node.js v12.22.x, v14.17.x, v16.x and above +- Node.js v14.17.x, v16.x and above ::: diff --git a/package.json b/package.json index 093aab994..d783f3da0 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,7 @@ "url": "https://github.com/vuejs/eslint-plugin-vue/issues" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^14.17.0 || >=16.0.0" }, "peerDependencies": { "eslint": "^6.2.0 || ^7.0.0 || ^8.0.0" From 186833afa946420565ca5081b09740665b8dc0bf Mon Sep 17 00:00:00 2001 From: Yosuke Ota Date: Thu, 12 May 2022 08:50:07 +0900 Subject: [PATCH 14/22] Change presets configs and remove unused internal methods (#1848) * Change presets configs and remove unused internal methods * fix test * format * Rename no-invalid-model-keys to valid-model-definition * Update preset * Add rules to essential preset * Fix lint errors after merge conflict resolution Co-authored-by: Flo Edelmann --- docs/rules/README.md | 27 ++-- docs/rules/no-child-content.md | 1 + docs/rules/no-export-in-script-setup.md | 2 +- docs/rules/no-expose-after-await.md | 2 + docs/rules/no-invalid-model-keys.md | 2 + docs/rules/no-ref-as-operand.md | 2 +- docs/rules/no-reserved-component-names.md | 2 + docs/rules/no-setup-props-destructure.md | 2 +- .../no-use-computed-property-like-method.md | 2 + docs/rules/no-v-text-v-html-on-component.md | 2 + docs/rules/prefer-import-from-vue.md | 1 + docs/rules/return-in-emits-validator.md | 2 +- docs/rules/valid-define-emits.md | 2 +- docs/rules/valid-define-props.md | 2 +- docs/rules/valid-model-definition.md | 116 ++++++++++++++ .../no-invalid-meta-docs-categories.js | 6 +- eslint-internal-rules/no-invalid-meta.js | 6 +- lib/configs/essential.js | 11 ++ lib/configs/vue3-essential.js | 6 + lib/index.js | 1 + lib/rules/no-child-content.js | 2 +- lib/rules/no-export-in-script-setup.js | 2 +- lib/rules/no-expose-after-await.js | 3 +- lib/rules/no-invalid-model-keys.js | 49 +----- lib/rules/no-ref-as-operand.js | 2 +- lib/rules/no-reserved-component-names.js | 2 +- lib/rules/no-setup-props-destructure.js | 2 +- .../no-use-computed-property-like-method.js | 2 +- lib/rules/no-v-text-v-html-on-component.js | 4 +- lib/rules/prefer-import-from-vue.js | 4 +- lib/rules/return-in-emits-validator.js | 2 +- lib/rules/valid-define-emits.js | 2 +- lib/rules/valid-define-props.js | 2 +- lib/rules/valid-model-definition.js | 57 +++++++ lib/utils/index.js | 12 -- tests/lib/rules/valid-model-definition.js | 151 ++++++++++++++++++ 36 files changed, 397 insertions(+), 98 deletions(-) create mode 100644 docs/rules/valid-model-definition.md create mode 100644 lib/rules/valid-model-definition.js create mode 100644 tests/lib/rules/valid-model-definition.js diff --git a/docs/rules/README.md b/docs/rules/README.md index 867cbe2bc..ffb8f2323 100644 --- a/docs/rules/README.md +++ b/docs/rules/README.md @@ -45,6 +45,7 @@ Rules in this category are enabled for all presets provided by eslint-plugin-vue | [vue/multi-word-component-names](./multi-word-component-names.md) | require component names to be always multi-word | | :three::two::hammer: | | [vue/no-arrow-functions-in-watch](./no-arrow-functions-in-watch.md) | disallow using arrow functions to define watcher | | :three::two::warning: | | [vue/no-async-in-computed-properties](./no-async-in-computed-properties.md) | disallow asynchronous actions in computed properties | | :three::two::warning: | +| [vue/no-child-content](./no-child-content.md) | disallow element's child contents which would be overwritten by a directive like `v-html` or `v-text` | :bulb: | :three::two::warning: | | [vue/no-computed-properties-in-data](./no-computed-properties-in-data.md) | disallow accessing computed properties in `data`. | | :three::two::warning: | | [vue/no-custom-modifiers-on-v-model](./no-custom-modifiers-on-v-model.md) | disallow custom modifiers on v-model used on the component | | :two::warning: | | [vue/no-deprecated-data-object-declaration](./no-deprecated-data-object-declaration.md) | disallow using deprecated object declaration on data (in Vue.js 3.0.0+) | :wrench: | :three::warning: | @@ -69,27 +70,32 @@ Rules in this category are enabled for all presets provided by eslint-plugin-vue | [vue/no-dupe-keys](./no-dupe-keys.md) | disallow duplication of field names | | :three::two::warning: | | [vue/no-dupe-v-else-if](./no-dupe-v-else-if.md) | disallow duplicate conditions in `v-if` / `v-else-if` chains | | :three::two::warning: | | [vue/no-duplicate-attributes](./no-duplicate-attributes.md) | disallow duplication of attributes | | :three::two::warning: | -| [vue/no-export-in-script-setup](./no-export-in-script-setup.md) | disallow `export` in ` +``` + + + + + +```vue + +``` + + + + + +```vue + +``` + + + + + +```vue + +``` + + + + + +```vue + +``` + + + + + +```vue + +``` + + + +## :mag: Implementation + +- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/valid-model-definition.js) +- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/valid-model-definition.js) diff --git a/eslint-internal-rules/no-invalid-meta-docs-categories.js b/eslint-internal-rules/no-invalid-meta-docs-categories.js index b48125484..c95691c36 100644 --- a/eslint-internal-rules/no-invalid-meta-docs-categories.js +++ b/eslint-internal-rules/no-invalid-meta-docs-categories.js @@ -18,10 +18,8 @@ */ function getPropertyFromObject(propertyName, node) { if (node && node.type === 'ObjectExpression') { - const properties = node.properties - - for (const property of properties) { - if (property.key.name === propertyName) { + for (const property of node.properties) { + if (property.type === 'Property' && property.key.name === propertyName) { return property } } diff --git a/eslint-internal-rules/no-invalid-meta.js b/eslint-internal-rules/no-invalid-meta.js index f1877ea4b..45057c9bf 100644 --- a/eslint-internal-rules/no-invalid-meta.js +++ b/eslint-internal-rules/no-invalid-meta.js @@ -18,10 +18,8 @@ */ function getPropertyFromObject(propertyName, node) { if (node && node.type === 'ObjectExpression') { - const properties = node.properties - - for (const property of properties) { - if (property.key.name === propertyName) { + for (const property of node.properties) { + if (property.type === 'Property' && property.key.name === propertyName) { return property } } diff --git a/lib/configs/essential.js b/lib/configs/essential.js index 970473a4d..33cfe64e0 100644 --- a/lib/configs/essential.js +++ b/lib/configs/essential.js @@ -9,14 +9,18 @@ module.exports = { 'vue/multi-word-component-names': 'error', 'vue/no-arrow-functions-in-watch': 'error', 'vue/no-async-in-computed-properties': 'error', + 'vue/no-child-content': 'error', 'vue/no-computed-properties-in-data': 'error', 'vue/no-custom-modifiers-on-v-model': 'error', 'vue/no-dupe-keys': 'error', 'vue/no-dupe-v-else-if': 'error', 'vue/no-duplicate-attributes': 'error', + 'vue/no-export-in-script-setup': 'error', 'vue/no-multiple-template-root': 'error', 'vue/no-mutating-props': 'error', 'vue/no-parsing-error': 'error', + 'vue/no-ref-as-operand': 'error', + 'vue/no-reserved-component-names': 'error', 'vue/no-reserved-keys': 'error', 'vue/no-reserved-props': [ 'error', @@ -24,23 +28,30 @@ module.exports = { vueVersion: 2 } ], + 'vue/no-setup-props-destructure': 'error', 'vue/no-shared-component-data': 'error', 'vue/no-side-effects-in-computed-properties': 'error', 'vue/no-template-key': 'error', 'vue/no-textarea-mustache': 'error', 'vue/no-unused-components': 'error', 'vue/no-unused-vars': 'error', + 'vue/no-use-computed-property-like-method': 'error', 'vue/no-use-v-if-with-v-for': 'error', 'vue/no-useless-template-attributes': 'error', 'vue/no-v-for-template-key': 'error', 'vue/no-v-model-argument': 'error', + 'vue/no-v-text-v-html-on-component': 'error', 'vue/require-component-is': 'error', 'vue/require-prop-type-constructor': 'error', 'vue/require-render-return': 'error', 'vue/require-v-for-key': 'error', 'vue/require-valid-default-prop': 'error', 'vue/return-in-computed-property': 'error', + 'vue/return-in-emits-validator': 'error', 'vue/use-v-on-exact': 'error', + 'vue/valid-define-emits': 'error', + 'vue/valid-define-props': 'error', + 'vue/valid-model-definition': 'error', 'vue/valid-next-tick': 'error', 'vue/valid-template-root': 'error', 'vue/valid-v-bind-sync': 'error', diff --git a/lib/configs/vue3-essential.js b/lib/configs/vue3-essential.js index 155774649..1a1507335 100644 --- a/lib/configs/vue3-essential.js +++ b/lib/configs/vue3-essential.js @@ -9,6 +9,7 @@ module.exports = { 'vue/multi-word-component-names': 'error', 'vue/no-arrow-functions-in-watch': 'error', 'vue/no-async-in-computed-properties': 'error', + 'vue/no-child-content': 'error', 'vue/no-computed-properties-in-data': 'error', 'vue/no-deprecated-data-object-declaration': 'error', 'vue/no-deprecated-destroyed-lifecycle': 'error', @@ -33,10 +34,12 @@ module.exports = { 'vue/no-dupe-v-else-if': 'error', 'vue/no-duplicate-attributes': 'error', 'vue/no-export-in-script-setup': 'error', + 'vue/no-expose-after-await': 'error', 'vue/no-lifecycle-after-await': 'error', 'vue/no-mutating-props': 'error', 'vue/no-parsing-error': 'error', 'vue/no-ref-as-operand': 'error', + 'vue/no-reserved-component-names': 'error', 'vue/no-reserved-keys': 'error', 'vue/no-reserved-props': 'error', 'vue/no-setup-props-destructure': 'error', @@ -46,10 +49,13 @@ module.exports = { 'vue/no-textarea-mustache': 'error', 'vue/no-unused-components': 'error', 'vue/no-unused-vars': 'error', + 'vue/no-use-computed-property-like-method': 'error', 'vue/no-use-v-if-with-v-for': 'error', 'vue/no-useless-template-attributes': 'error', 'vue/no-v-for-template-key-on-child': 'error', + 'vue/no-v-text-v-html-on-component': 'error', 'vue/no-watch-after-await': 'error', + 'vue/prefer-import-from-vue': 'error', 'vue/require-component-is': 'error', 'vue/require-prop-type-constructor': 'error', 'vue/require-render-return': 'error', diff --git a/lib/index.js b/lib/index.js index 39f91b515..08423c75e 100644 --- a/lib/index.js +++ b/lib/index.js @@ -202,6 +202,7 @@ module.exports = { 'v-slot-style': require('./rules/v-slot-style'), 'valid-define-emits': require('./rules/valid-define-emits'), 'valid-define-props': require('./rules/valid-define-props'), + 'valid-model-definition': require('./rules/valid-model-definition'), 'valid-next-tick': require('./rules/valid-next-tick'), 'valid-template-root': require('./rules/valid-template-root'), 'valid-v-bind-sync': require('./rules/valid-v-bind-sync'), diff --git a/lib/rules/no-child-content.js b/lib/rules/no-child-content.js index 138a425c1..1eb7fbdfe 100644 --- a/lib/rules/no-child-content.js +++ b/lib/rules/no-child-content.js @@ -79,7 +79,7 @@ module.exports = { docs: { description: "disallow element's child contents which would be overwritten by a directive like `v-html` or `v-text`", - categories: undefined, + categories: ['vue3-essential', 'essential'], url: 'https://eslint.vuejs.org/rules/no-child-content.html' }, fixable: null, diff --git a/lib/rules/no-export-in-script-setup.js b/lib/rules/no-export-in-script-setup.js index fe825fdd7..0105d52f4 100644 --- a/lib/rules/no-export-in-script-setup.js +++ b/lib/rules/no-export-in-script-setup.js @@ -17,7 +17,7 @@ module.exports = { type: 'problem', docs: { description: 'disallow `export` in ` + + + ` + }, + { + filename: 'test.vue', + code: ` + + + + ` + }, + { + filename: 'test.vue', + code: ` + + + + ` + }, + { + filename: 'test.vue', + code: ` + + + + ` + }, + { + filename: 'test.vue', + code: ` + + + + ` + }, + + // Resolve component name + { + filename: 'test.vue', + code: ` + + + + ` + }, + { + filename: 'test.vue', + code: ` + + + + ` + }, + + // TopLevel await + { + filename: 'test.vue', + code: ` + + + + `, + parserOptions: { + ecmaVersion: 2022, + sourceType: 'module' + } + }, + + // ref + { + filename: 'test.vue', + code: ` + + + + ` + }, + + //style vars + { + filename: 'test.vue', + code: ` + + + + ` + }, + // ns + { + filename: 'test.vue', + code: ` + + + + ` + } + ], + + invalid: [ + { + filename: 'test.vue', + code: ` + + + + `, + errors: [ + { + message: "'Bar' is defined but never used.", + line: 5 + }, + { + message: "'baz' is assigned a value but never used.", + line: 18 + } + ] + }, + + // Resolve component name + { + filename: 'test.vue', + code: ` + + + + `, + errors: [ + { + message: "'camelCase' is defined but never used.", + line: 3 + } + ] + }, + + // Scope tests + { + filename: 'test.vue', + code: ` + + + + `, + errors: [ + { + message: "'msg' is assigned a value but never used.", + line: 4 + } + ] + }, + { + filename: 'test.vue', + code: ` + + + + `, + errors: [ + { + message: "'i' is assigned a value but never used.", + line: 3 + } + ] + }, + + // Not ` + + + `, + errors: [ + { + message: "'msg' is assigned a value but never used.", + line: 3 + } + ] + }, + + //style vars + { + filename: 'test.vue', + code: ` + + + + `, + errors: ["'color' is assigned a value but never used."] + } + ] + }) + + ruleTester.run('no-undef', ruleNoUndef, { + valid: [ + { + filename: 'test.vue', + code: ` + + ` + } + ], + invalid: [ + { + filename: 'test.vue', + code: ` + + `, + errors: [ + { + message: "'defineUnknown' is not defined.", + line: 3 + }, + { + message: "'defineUnknown' is not defined.", + line: 7 + } + ] + } + ] + }) +}) diff --git a/tools/update-lib-index.js b/tools/update-lib-index.js index b9a5dcbd9..f57ae1e58 100644 --- a/tools/update-lib-index.js +++ b/tools/update-lib-index.js @@ -39,6 +39,8 @@ module.exports = { '.vue': require('./processor') }, environments: { + // TODO Remove in the next major version + /** @deprecated */ 'setup-compiler-macros': { globals: { defineProps: 'readonly', From a0cf01843154ef4ca3621d8e51d9992fd4d74bc7 Mon Sep 17 00:00:00 2001 From: Flo Edelmann Date: Thu, 12 May 2022 13:57:47 +0200 Subject: [PATCH 16/22] Remove deprecated rules (#1881) * Remove deprecated rules * Remove no longer needed `script-setup` test * Restore docs from removed rules * Mention in rule docs that they are removed * Add "Removed" section to rules list in docs * Use different emoji for removed rules * Remove no longer necessary warning block Co-authored-by: yosuke ota --- docs/rules/README.md | 15 +- docs/rules/experimental-script-setup-vars.md | 6 +- docs/rules/name-property-casing.md | 3 +- docs/rules/no-confusing-v-for-v-if.md | 2 +- docs/rules/no-unregistered-components.md | 2 +- lib/index.js | 7 +- lib/removed-rules.js | 37 + lib/rules/experimental-script-setup-vars.js | 230 ------ lib/rules/name-property-casing.js | 72 -- lib/rules/no-confusing-v-for-v-if.js | 69 -- lib/rules/no-unregistered-components.js | 195 ----- .../rules/experimental-script-setup-vars.js | 58 -- tests/lib/rules/name-property-casing.js | 213 ----- tests/lib/rules/no-confusing-v-for-v-if.js | 59 -- tests/lib/rules/no-unregistered-components.js | 777 ------------------ tests/lib/script-setup.js | 49 -- tools/update-docs-rules-index.js | 28 + tools/update-docs.js | 35 +- 18 files changed, 115 insertions(+), 1742 deletions(-) create mode 100644 lib/removed-rules.js delete mode 100644 lib/rules/experimental-script-setup-vars.js delete mode 100644 lib/rules/name-property-casing.js delete mode 100644 lib/rules/no-confusing-v-for-v-if.js delete mode 100644 lib/rules/no-unregistered-components.js delete mode 100644 tests/lib/rules/experimental-script-setup-vars.js delete mode 100644 tests/lib/rules/name-property-casing.js delete mode 100644 tests/lib/rules/no-confusing-v-for-v-if.js delete mode 100644 tests/lib/rules/no-unregistered-components.js delete mode 100644 tests/lib/script-setup.js diff --git a/docs/rules/README.md b/docs/rules/README.md index 4492988e4..0f39db375 100644 --- a/docs/rules/README.md +++ b/docs/rules/README.md @@ -315,9 +315,16 @@ The following rules extend the rules provided by ESLint itself and apply them to | Rule ID | Replaced by | |:--------|:------------| -| [vue/experimental-script-setup-vars](./experimental-script-setup-vars.md) | (no replacement) | -| [vue/name-property-casing](./name-property-casing.md) | [vue/component-definition-name-casing](./component-definition-name-casing.md) | -| [vue/no-confusing-v-for-v-if](./no-confusing-v-for-v-if.md) | [vue/no-use-v-if-with-v-for](./no-use-v-if-with-v-for.md) | | [vue/no-invalid-model-keys](./no-invalid-model-keys.md) | [vue/valid-model-definition](./valid-model-definition.md) | -| [vue/no-unregistered-components](./no-unregistered-components.md) | [vue/no-undef-components](./no-undef-components.md) | | [vue/script-setup-uses-vars](./script-setup-uses-vars.md) | (no replacement) | + +## Removed + +- :no_entry_sign: These rules have been removed in a previous major release, after they have been deprecated for a while. + +| Rule ID | Replaced by | Deprecated in version | Removed in version | +|:--------|:------------|:-----------------------|:-------------------| +| [vue/experimental-script-setup-vars](./experimental-script-setup-vars.md) | (no replacement) | [v7.13.0](https://github.com/vuejs/eslint-plugin-vue/releases/tag/v7.13.0) | [v9.0.0](https://github.com/vuejs/eslint-plugin-vue/releases/tag/v9.0.0) | +| [vue/name-property-casing](./name-property-casing.md) | [vue/component-definition-name-casing](./component-definition-name-casing.md) | [v7.0.0](https://github.com/vuejs/eslint-plugin-vue/releases/tag/v7.0.0) | [v9.0.0](https://github.com/vuejs/eslint-plugin-vue/releases/tag/v9.0.0) | +| [vue/no-confusing-v-for-v-if](./no-confusing-v-for-v-if.md) | [vue/no-use-v-if-with-v-for](./no-use-v-if-with-v-for.md) | [v5.0.0](https://github.com/vuejs/eslint-plugin-vue/releases/tag/v5.0.0) | [v9.0.0](https://github.com/vuejs/eslint-plugin-vue/releases/tag/v9.0.0) | +| [vue/no-unregistered-components](./no-unregistered-components.md) | [vue/no-undef-components](./no-undef-components.md) | [v8.4.0](https://github.com/vuejs/eslint-plugin-vue/releases/tag/v8.4.0) | [v9.0.0](https://github.com/vuejs/eslint-plugin-vue/releases/tag/v9.0.0) | diff --git a/docs/rules/experimental-script-setup-vars.md b/docs/rules/experimental-script-setup-vars.md index d13710246..7a77d941d 100644 --- a/docs/rules/experimental-script-setup-vars.md +++ b/docs/rules/experimental-script-setup-vars.md @@ -9,11 +9,7 @@ since: v7.0.0 > prevent variables defined in ``, - ` - `, - ` - `, - ` - ` - ], - invalid: [ - { - code: ` - - `, - errors: [ - { - message: 'Parsing error.', - line: 2 - } - ] - } - ] -}) diff --git a/tests/lib/rules/name-property-casing.js b/tests/lib/rules/name-property-casing.js deleted file mode 100644 index a7a8d41cc..000000000 --- a/tests/lib/rules/name-property-casing.js +++ /dev/null @@ -1,213 +0,0 @@ -/** - * @fileoverview Define a style for the name property casing for consistency purposes - * @author Armano - */ -'use strict' - -// ------------------------------------------------------------------------------ -// Requirements -// ------------------------------------------------------------------------------ - -const rule = require('../../../lib/rules/name-property-casing') -const RuleTester = require('eslint').RuleTester - -// ------------------------------------------------------------------------------ -// Tests -// ------------------------------------------------------------------------------ - -const parserOptions = { - ecmaVersion: 2018, - sourceType: 'module' -} - -const ruleTester = new RuleTester() -ruleTester.run('name-property-casing', rule, { - valid: [ - { - filename: 'test.vue', - code: ` - export default { - } - `, - parserOptions - }, - { - filename: 'test.vue', - code: ` - export default { - ...name - } - `, - parserOptions - }, - { - filename: 'test.vue', - code: ` - export default { - name: 'FooBar' - } - `, - parserOptions - }, - { - filename: 'test.vue', - code: ` - export default { - name: 'FooBar' - } - `, - options: ['PascalCase'], - parserOptions - }, - { - filename: 'test.vue', - code: ` - export default { - name: 'foo-bar' - } - `, - options: ['kebab-case'], - parserOptions - } - ], - - invalid: [ - { - filename: 'test.vue', - code: ` - export default { - name: 'foo-bar' - } - `, - output: ` - export default { - name: 'FooBar' - } - `, - parserOptions, - errors: [ - { - message: 'Property name "foo-bar" is not PascalCase.', - type: 'Literal', - line: 3 - } - ] - }, - { - filename: 'test.vue', - code: ` - export default { - name: 'foo bar' - } - `, - output: null, - parserOptions, - errors: [ - { - message: 'Property name "foo bar" is not PascalCase.', - type: 'Literal', - line: 3 - } - ] - }, - { - filename: 'test.vue', - code: ` - export default { - name: 'foo!bar' - } - `, - output: null, - parserOptions, - errors: [ - { - message: 'Property name "foo!bar" is not PascalCase.', - type: 'Literal', - line: 3 - } - ] - }, - { - filename: 'test.js', - code: ` - new Vue({ - name: 'foo!bar' - }) - `, - output: null, - parserOptions: { ecmaVersion: 6 }, - errors: [ - { - message: 'Property name "foo!bar" is not PascalCase.', - type: 'Literal', - line: 3 - } - ] - }, - { - filename: 'test.vue', - code: ` - export default { - name: 'foo_bar' - } - `, - output: ` - export default { - name: 'FooBar' - } - `, - parserOptions, - errors: [ - { - message: 'Property name "foo_bar" is not PascalCase.', - type: 'Literal', - line: 3 - } - ] - }, - { - filename: 'test.vue', - code: ` - export default { - name: 'foo_bar' - } - `, - output: ` - export default { - name: 'FooBar' - } - `, - options: ['PascalCase'], - parserOptions, - errors: [ - { - message: 'Property name "foo_bar" is not PascalCase.', - type: 'Literal', - line: 3 - } - ] - }, - { - filename: 'test.vue', - code: ` - export default { - name: 'foo_bar' - } - `, - output: ` - export default { - name: 'foo-bar' - } - `, - options: ['kebab-case'], - parserOptions, - errors: [ - { - message: 'Property name "foo_bar" is not kebab-case.', - type: 'Literal', - line: 3 - } - ] - } - ] -}) diff --git a/tests/lib/rules/no-confusing-v-for-v-if.js b/tests/lib/rules/no-confusing-v-for-v-if.js deleted file mode 100644 index 47959aeb4..000000000 --- a/tests/lib/rules/no-confusing-v-for-v-if.js +++ /dev/null @@ -1,59 +0,0 @@ -/** - * @author Toru Nagashima - * @copyright 2017 Toru Nagashima. All rights reserved. - * See LICENSE file in root directory for full license. - */ -'use strict' - -// ------------------------------------------------------------------------------ -// Requirements -// ------------------------------------------------------------------------------ - -const RuleTester = require('eslint').RuleTester -const rule = require('../../../lib/rules/no-confusing-v-for-v-if') - -// ------------------------------------------------------------------------------ -// Tests -// ------------------------------------------------------------------------------ - -const tester = new RuleTester({ - parser: require.resolve('vue-eslint-parser'), - parserOptions: { ecmaVersion: 2015 } -}) - -tester.run('no-confusing-v-for-v-if', rule, { - valid: [ - { - filename: 'test.vue', - code: '' - }, - { - filename: 'test.vue', - code: '' - }, - { - filename: 'test.vue', - code: '' - }, - { - filename: 'test.vue', - code: '' - }, - { - filename: 'test.vue', - code: '' - } - ], - invalid: [ - { - filename: 'test.vue', - code: '', - errors: ["This 'v-if' should be moved to the wrapper element."] - }, - { - filename: 'test.vue', - code: '', - errors: ["This 'v-if' should be moved to the wrapper element."] - } - ] -}) diff --git a/tests/lib/rules/no-unregistered-components.js b/tests/lib/rules/no-unregistered-components.js deleted file mode 100644 index f120cde81..000000000 --- a/tests/lib/rules/no-unregistered-components.js +++ /dev/null @@ -1,777 +0,0 @@ -/** - * @fileoverview Report used components that are not registered - * @author Jesús Ángel González Novez - */ -'use strict' - -// ------------------------------------------------------------------------------ -// Requirements -// ------------------------------------------------------------------------------ - -const RuleTester = require('eslint').RuleTester -const rule = require('../../../lib/rules/no-unregistered-components') - -// ------------------------------------------------------------------------------ -// Tests -// ------------------------------------------------------------------------------ - -const tester = new RuleTester({ - parser: require.resolve('vue-eslint-parser'), - parserOptions: { - ecmaVersion: 2018, - sourceType: 'module' - } -}) - -tester.run('no-unregistered-components', rule, { - valid: [ - { - filename: 'test.vue', - code: ` - - ` - }, - { - filename: 'test.vue', - code: ` - - `, - options: [ - { - ignorePatterns: ['custom(\\-\\w+)+'] - } - ] - }, - { - filename: 'test.vue', - code: ` - - `, - options: [ - { - ignorePatterns: ['custom(\\-\\w+)+'] - } - ] - }, - { - filename: 'test.vue', - code: ` - - `, - options: [ - { - ignorePatterns: ['custom(\\-\\w+)+'] - } - ] - }, - { - filename: 'test.vue', - code: ` - - `, - options: [ - { - ignorePatterns: ['Custom(\\w+)+'] - } - ] - }, - { - filename: 'test.vue', - code: ` - - `, - options: [ - { - ignorePatterns: ['Custom(\\w+)+'] - } - ] - }, - { - filename: 'test.vue', - code: ` - - `, - options: [ - { - ignorePatterns: ['Custom(\\w+)+'] - } - ] - }, - { - filename: 'test.vue', - code: ` - - `, - options: [ - { - ignorePatterns: ['Custom(\\w+)+', 'Warm(\\w+)+', 'InfoBtn(\\w+)+'] - } - ] - }, - { - filename: 'test.vue', - code: ` - - - ` - }, - { - filename: 'test.vue', - code: ` - - - ` - }, - { - filename: 'test.vue', - code: ` - - - ` - }, - { - filename: 'test.vue', - code: ` - - - ` - }, - { - filename: 'test.vue', - code: ` - - - ` - }, - { - filename: 'test.vue', - code: ` - - - ` - }, - { - filename: 'test.vue', - code: ` - - ` - }, - { - filename: 'test.vue', - code: ` - - ` - }, - { - filename: 'test.vue', - code: ` - - ` - }, - { - filename: 'test.vue', - code: ` - - ` - }, - { - filename: 'test.vue', - code: ` - - ` - }, - { - filename: 'test.vue', - code: ` - - - ` - }, - { - filename: 'test.vue', - code: ` - - - ` - }, - { - filename: 'test.vue', - code: ` - - ` - }, - { - filename: 'test.vue', - code: ` - - ` - }, - { - filename: 'test.vue', - code: ` - - - ` - }, - { - filename: 'test.vue', - code: ` - - ` - }, - { - filename: 'test.vue', - code: ` - - ` - }, - { - filename: 'test.vue', - code: ` - - - ` - }, - { - filename: 'test.vue', - code: ` - - - ` - }, - { - filename: 'test.vue', - code: ` - - - ` - }, - { - filename: 'test.vue', - code: ` - - - ` - }, - { - filename: 'test.vue', - code: ` - - - ` - }, - { - filename: 'test.vue', - code: ` - - - ` - }, - { - filename: 'test.vue', - code: ` - - - ` - }, - { - filename: 'test.vue', - code: ` - - - ` - }, - { - filename: 'test.vue', - code: ` - - - ` - } - ], - invalid: [ - { - filename: 'test.vue', - code: ` - - `, - errors: [ - { - message: - 'The "CustomComponent" component has been used but not registered.', - line: 3 - } - ] - }, - { - filename: 'test.vue', - code: ` - - `, - options: [ - { - ignorePatterns: ['custom(\\-\\w+)+'] - } - ], - errors: [ - { - message: - 'The "WarmButton" component has been used but not registered.', - line: 3 - } - ] - }, - { - filename: 'test.vue', - code: ` - - `, - errors: [ - { - message: - 'The "CustomComponent" component has been used but not registered.', - line: 3 - } - ] - }, - { - filename: 'test.vue', - code: ` - - `, - options: [ - { - ignorePatterns: ['custom(\\-\\w+)+'] - } - ], - errors: [ - { - message: - 'The "WarmButton" component has been used but not registered.', - line: 3 - } - ] - }, - { - filename: 'test.vue', - code: ` - - - `, - errors: [ - { - message: - 'The "CustomComponent" component has been used but not registered.', - line: 3 - } - ] - }, - { - filename: 'test.vue', - code: ` - - - `, - errors: [ - { - message: - 'The "CustomComponent" component has been used but not registered.', - line: 3 - } - ] - }, - { - filename: 'test.vue', - code: ` - - - `, - errors: [ - { - message: - 'The "CustomComponent" component has been used but not registered.', - line: 3 - } - ] - }, - { - filename: 'test.vue', - code: ` - - - `, - errors: [ - { - message: - 'The "CustomComponent" component has been used but not registered.', - line: 3 - } - ] - }, - { - filename: 'test.vue', - code: ` - - - `, - errors: [ - { - message: - 'The "CustomComponent" component has been used but not registered.', - line: 3 - } - ] - }, - { - filename: 'test.vue', - code: ` - - - `, - errors: [ - { - message: - 'The "CustomComponent" component has been used but not registered.', - line: 3 - } - ] - }, - { - filename: 'test.vue', - code: ` - - `, - errors: [ - { - message: - 'The "CustomComponentWithNamedSlots" component has been used but not registered.', - line: 3 - } - ] - }, - { - filename: 'test.vue', - code: ` - - - `, - errors: [ - { - message: 'The "foo" component has been used but not registered.', - line: 3 - } - ] - }, - { - filename: 'test.vue', - code: ` - - - `, - errors: [ - { - message: 'The "foo" component has been used but not registered.', - line: 3 - } - ] - } - ] -}) diff --git a/tests/lib/script-setup.js b/tests/lib/script-setup.js deleted file mode 100644 index ad7409dcb..000000000 --- a/tests/lib/script-setup.js +++ /dev/null @@ -1,49 +0,0 @@ -/** - * @author Yosuke Ota - * See LICENSE file in root directory for full license. - */ -'use strict' - -const Linter = require('eslint').Linter -const parser = require('vue-eslint-parser') -const assert = require('assert') -const experimentalScriptSetupVars = require('../../lib/rules/experimental-script-setup-vars') - -const baseConfig = { - parser: 'vue-eslint-parser', - parserOptions: { - ecmaVersion: 2020, - sourceType: 'module' - } -} - -describe('script-setup test cases', () => { - const linter = new Linter() - linter.defineParser('vue-eslint-parser', parser) - linter.defineRule( - 'vue/experimental-script-setup-vars', - experimentalScriptSetupVars - ) - - describe('temporary supports.', () => { - const config = Object.assign({}, baseConfig, { - globals: { console: false }, - rules: { - 'vue/experimental-script-setup-vars': 'error', - 'no-undef': 'error' - } - }) - - it('should not be marked.', () => { - const code = ` - ` - const messages = linter.verify(code, config, 'test.vue') - assert.deepStrictEqual(messages, []) - }) - }) -}) diff --git a/tools/update-docs-rules-index.js b/tools/update-docs-rules-index.js index 27ab06979..3db26c36b 100644 --- a/tools/update-docs-rules-index.js +++ b/tools/update-docs-rules-index.js @@ -8,6 +8,7 @@ const fs = require('fs') const path = require('path') const rules = require('./lib/rules') const { getPresetIds, formatItems } = require('./lib/utils') +const removedRules = require('../lib/removed-rules') const VUE3_EMOJI = ':three:' const VUE2_EMOJI = ':two:' @@ -62,6 +63,22 @@ function toDeprecatedRuleRow(rule) { return `| ${link} | ${replacedBy || '(no replacement)'} |` } +function toRemovedRuleRow({ + ruleName, + replacedBy, + deprecatedInVersion, + removedInVersion +}) { + const link = `[vue/${ruleName}](./${ruleName}.md)` + const replacement = + replacedBy.map((name) => `[vue/${name}](./${name}.md)`).join(', ') || + '(no replacement)' + const deprecatedVersionLink = `[${deprecatedInVersion}](https://github.com/vuejs/eslint-plugin-vue/releases/tag/${deprecatedInVersion})` + const removedVersionLink = `[${removedInVersion}](https://github.com/vuejs/eslint-plugin-vue/releases/tag/${removedInVersion})` + + return `| ${link} | ${replacement} | ${deprecatedVersionLink} | ${removedVersionLink} |` +} + const categoryGroups = [ { title: 'Base Rules (Enabling Correct ESLint Parsing)', @@ -217,6 +234,17 @@ ${deprecatedRules.map(toDeprecatedRuleRow).join('\n')} ` } +// ----------------------------------------------------------------------------- +rulesTableContent += ` +## Removed + +- :no_entry_sign: These rules have been removed in a previous major release, after they have been deprecated for a while. + +| Rule ID | Replaced by | Deprecated in version | Removed in version | +|:--------|:------------|:-----------------------|:-------------------| +${removedRules.map(toRemovedRuleRow).join('\n')} +` + // ----------------------------------------------------------------------------- const readmeFilePath = path.resolve(__dirname, '../docs/rules/README.md') fs.writeFileSync( diff --git a/tools/update-docs.js b/tools/update-docs.js index 80211d278..75f6dab6d 100644 --- a/tools/update-docs.js +++ b/tools/update-docs.js @@ -21,6 +21,7 @@ For example: const fs = require('fs') const path = require('path') const rules = require('./lib/rules') +const removedRules = require('../lib/removed-rules') const { getPresetIds, formatItems } = require('./lib/utils') const ROOT = path.resolve(__dirname, '../docs/rules') @@ -82,10 +83,28 @@ class DocFile { updateHeader() { const { ruleId, meta } = this.rule - const title = `# ${ruleId}\n\n> ${meta.docs.description}` + const description = meta.docs + ? meta.docs.description + : this.content.match(/^description: (.*)$/m)[1] + const title = `# ${ruleId}\n\n> ${description}` const notes = [] - if (meta.deprecated) { + if (meta.removedInVersion) { + if (meta.replacedBy.length > 0) { + const replacedRules = meta.replacedBy.map( + (name) => `[vue/${name}](${name}.md) rule` + ) + notes.push( + `- :no_entry_sign: This rule was **removed** in eslint-plugin-vue ${ + meta.removedInVersion + } and replaced by ${formatItems(replacedRules)}.` + ) + } else { + notes.push( + `- :no_entry_sign: This rule was **removed** in eslint-plugin-vue ${meta.removedInVersion}.` + ) + } + } else if (meta.deprecated) { if (meta.replacedBy) { const replacedRules = meta.replacedBy.map( (name) => `[vue/${name}](${name}.md) rule` @@ -255,3 +274,15 @@ for (const rule of rules) { .checkHeadings() .checkGoodBadSymbols() } + +for (const { ruleName, replacedBy, removedInVersion } of removedRules) { + const rule = { + name: ruleName, + ruleId: `vue/${ruleName}`, + meta: { + replacedBy, + removedInVersion + } + } + DocFile.read(rule).updateHeader().write() +} From 52accc9aa4ec4031073863de347de90fbcde585a Mon Sep 17 00:00:00 2001 From: Yosuke Ota Date: Thu, 12 May 2022 20:58:01 +0900 Subject: [PATCH 17/22] Update `vue/no-expose-after-await` rule to support ` +``` + + + ## :wrench: Options Nothing. diff --git a/lib/rules/attribute-hyphenation.js b/lib/rules/attribute-hyphenation.js index 0502ae3bc..1c097763c 100644 --- a/lib/rules/attribute-hyphenation.js +++ b/lib/rules/attribute-hyphenation.js @@ -107,9 +107,7 @@ module.exports = { * @param {string} value */ function isIgnoredAttribute(value) { - const isIgnored = ignoredAttributes.some((attr) => { - return value.includes(attr) - }) + const isIgnored = ignoredAttributes.some((attr) => value.includes(attr)) if (isIgnored) { return true diff --git a/lib/rules/no-dupe-v-else-if.js b/lib/rules/no-dupe-v-else-if.js index d0d74cfed..1500d7d24 100644 --- a/lib/rules/no-dupe-v-else-if.js +++ b/lib/rules/no-dupe-v-else-if.js @@ -167,11 +167,10 @@ module.exports = { for (const condition of listToCheck) { const operands = (condition.operands = condition.operands.filter( - (orOperand) => { - return !currentOrOperands.operands.some((currentOrOperand) => + (orOperand) => + !currentOrOperands.operands.some((currentOrOperand) => isSubset(currentOrOperand, orOperand) ) - } )) if (operands.length === 0) { context.report({ diff --git a/lib/rules/no-expose-after-await.js b/lib/rules/no-expose-after-await.js index 9d753ab81..fdfa10869 100644 --- a/lib/rules/no-expose-after-await.js +++ b/lib/rules/no-expose-after-await.js @@ -46,7 +46,7 @@ module.exports = { fixable: null, schema: [], messages: { - forbidden: 'The `expose` after `await` expression are forbidden.' + forbidden: '`{{name}}` is forbidden after an `await` expression.' } }, /** @param {RuleContext} context */ @@ -55,147 +55,192 @@ module.exports = { * @typedef {object} SetupScopeData * @property {boolean} afterAwait * @property {[number,number]} range - * @property {Set} exposeReferenceIds - * @property {Set} contextReferenceIds + * @property {(node: Identifier, callNode: CallExpression) => boolean} isExposeReferenceId + * @property {(node: Identifier) => boolean} isContextReferenceId */ /** * @typedef {object} ScopeStack * @property {ScopeStack | null} upper - * @property {FunctionDeclaration | FunctionExpression | ArrowFunctionExpression} scopeNode + * @property {FunctionDeclaration | FunctionExpression | ArrowFunctionExpression | Program} scopeNode */ - /** @type {Map} */ + /** @type {Map} */ const setupScopes = new Map() /** @type {ScopeStack | null} */ let scopeStack = null - return utils.defineVueVisitor(context, { - onSetupFunctionEnter(node) { - const contextParam = node.params[1] - if (!contextParam) { - // no arguments - return - } - if (contextParam.type === 'RestElement') { - // cannot check - return - } - if (contextParam.type === 'ArrayPattern') { - // cannot check - return + return utils.compositingVisitors( + { + /** + * @param {Program} node + */ + Program(node) { + scopeStack = { + upper: scopeStack, + scopeNode: node + } } - /** @type {Set} */ - const contextReferenceIds = new Set() - /** @type {Set} */ - const exposeReferenceIds = new Set() - if (contextParam.type === 'ObjectPattern') { - const exposeProperty = utils.findAssignmentProperty( - contextParam, - 'expose' - ) - if (!exposeProperty) { - return + }, + { + /** + * @param {FunctionExpression | FunctionDeclaration | ArrowFunctionExpression} node + */ + ':function'(node) { + scopeStack = { + upper: scopeStack, + scopeNode: node } - const exposeParam = exposeProperty.value - // `setup(props, {emit})` - const variable = - exposeParam.type === 'Identifier' - ? findVariable(context.getScope(), exposeParam) - : null - if (!variable) { + }, + ':function:exit'() { + scopeStack = scopeStack && scopeStack.upper + }, + /** @param {AwaitExpression} node */ + AwaitExpression(node) { + if (!scopeStack) { return } - for (const reference of variable.references) { - if (!reference.isRead()) { - continue - } - exposeReferenceIds.add(reference.identifier) + const setupScope = setupScopes.get(scopeStack.scopeNode) + if (!setupScope || !utils.inRange(setupScope.range, node)) { + return } - } else if (contextParam.type === 'Identifier') { - // `setup(props, context)` - const variable = findVariable(context.getScope(), contextParam) - if (!variable) { + setupScope.afterAwait = true + }, + /** @param {CallExpression} node */ + CallExpression(node) { + if (!scopeStack) { return } - for (const reference of variable.references) { - if (!reference.isRead()) { - continue - } - contextReferenceIds.add(reference.identifier) + const setupScope = setupScopes.get(scopeStack.scopeNode) + if ( + !setupScope || + !setupScope.afterAwait || + !utils.inRange(setupScope.range, node) + ) { + return } - } - setupScopes.set(node, { - afterAwait: false, - range: node.range, - exposeReferenceIds, - contextReferenceIds - }) - }, - /** - * @param {FunctionExpression | FunctionDeclaration | ArrowFunctionExpression} node - */ - ':function'(node) { - scopeStack = { - upper: scopeStack, - scopeNode: node - } - }, - ':function:exit'() { - scopeStack = scopeStack && scopeStack.upper - }, - /** @param {AwaitExpression} node */ - AwaitExpression(node) { - if (!scopeStack) { - return - } - const setupScope = setupScopes.get(scopeStack.scopeNode) - if (!setupScope || !utils.inRange(setupScope.range, node)) { - return - } - setupScope.afterAwait = true - }, - /** @param {CallExpression} node */ - CallExpression(node) { - if (!scopeStack) { - return - } - const setupScope = setupScopes.get(scopeStack.scopeNode) - if ( - !setupScope || - !setupScope.afterAwait || - !utils.inRange(setupScope.range, node) - ) { - return - } - const { contextReferenceIds, exposeReferenceIds } = setupScope - if ( - node.callee.type === 'Identifier' && - exposeReferenceIds.has(node.callee) - ) { - // setup(props,{expose}) {expose()} - context.report({ - node, - messageId: 'forbidden' - }) - } else { - const expose = getCalleeMemberNode(node) + const { isContextReferenceId, isExposeReferenceId } = setupScope if ( - expose && - expose.name === 'expose' && - expose.member.object.type === 'Identifier' && - contextReferenceIds.has(expose.member.object) + node.callee.type === 'Identifier' && + isExposeReferenceId(node.callee, node) ) { - // setup(props,context) {context.emit()} + // setup(props,{expose}) {expose()} context.report({ node, - messageId: 'forbidden' + messageId: 'forbidden', + data: { + name: node.callee.name + } }) + } else { + const expose = getCalleeMemberNode(node) + if ( + expose && + expose.name === 'expose' && + expose.member.object.type === 'Identifier' && + isContextReferenceId(expose.member.object) + ) { + // setup(props,context) {context.emit()} + context.report({ + node, + messageId: 'forbidden', + data: { + name: expose.name + } + }) + } } } }, - onSetupFunctionExit(node) { - setupScopes.delete(node) - } - }) + (() => { + const scriptSetup = utils.getScriptSetupElement(context) + if (!scriptSetup) { + return {} + } + return { + /** + * @param {Program} node + */ + Program(node) { + context + .getScope() + .references.find((ref) => ref.identifier.name === 'defineExpose') + setupScopes.set(node, { + afterAwait: false, + range: scriptSetup.range, + isExposeReferenceId: (_id, callNode) => + callNode.parent.type === 'ExpressionStatement' && + callNode.parent.parent === node, + isContextReferenceId: () => false + }) + } + } + })(), + utils.defineVueVisitor(context, { + onSetupFunctionEnter(node) { + const contextParam = node.params[1] + if (!contextParam) { + // no arguments + return + } + if (contextParam.type === 'RestElement') { + // cannot check + return + } + if (contextParam.type === 'ArrayPattern') { + // cannot check + return + } + /** @type {Set} */ + const contextReferenceIds = new Set() + /** @type {Set} */ + const exposeReferenceIds = new Set() + if (contextParam.type === 'ObjectPattern') { + const exposeProperty = utils.findAssignmentProperty( + contextParam, + 'expose' + ) + if (!exposeProperty) { + return + } + const exposeParam = exposeProperty.value + // `setup(props, {emit})` + const variable = + exposeParam.type === 'Identifier' + ? findVariable(context.getScope(), exposeParam) + : null + if (!variable) { + return + } + for (const reference of variable.references) { + if (!reference.isRead()) { + continue + } + exposeReferenceIds.add(reference.identifier) + } + } else if (contextParam.type === 'Identifier') { + // `setup(props, context)` + const variable = findVariable(context.getScope(), contextParam) + if (!variable) { + return + } + for (const reference of variable.references) { + if (!reference.isRead()) { + continue + } + contextReferenceIds.add(reference.identifier) + } + } + setupScopes.set(node, { + afterAwait: false, + range: node.range, + isExposeReferenceId: (id) => exposeReferenceIds.has(id), + isContextReferenceId: (id) => contextReferenceIds.has(id) + }) + }, + onSetupFunctionExit(node) { + setupScopes.delete(node) + } + }) + ) } } diff --git a/lib/rules/no-lifecycle-after-await.js b/lib/rules/no-lifecycle-after-await.js index d38a97a59..bb097910f 100644 --- a/lib/rules/no-lifecycle-after-await.js +++ b/lib/rules/no-lifecycle-after-await.js @@ -35,7 +35,7 @@ module.exports = { fixable: null, schema: [], messages: { - forbidden: 'The lifecycle hooks after `await` expression are forbidden.' + forbidden: 'Lifecycle hooks are forbidden after an `await` expression.' } }, /** @param {RuleContext} context */ diff --git a/lib/rules/no-restricted-call-after-await.js b/lib/rules/no-restricted-call-after-await.js index 7b6ff74e7..f4f3b6d38 100644 --- a/lib/rules/no-restricted-call-after-await.js +++ b/lib/rules/no-restricted-call-after-await.js @@ -169,9 +169,9 @@ module.exports = { local[ReferenceTracker.CALL] = true const message = option.message || - `The \`${[`import("${module}")`, ...paths].join( + `\`${[`import("${module}")`, ...paths].join( '.' - )}\` after \`await\` expression are forbidden.` + )}\` is forbidden after an \`await\` expression.` for (const { node } of tracker.iterateEsmReferences(traceMap)) { restrictedCallNodes.set(node, message) diff --git a/lib/rules/no-restricted-v-bind.js b/lib/rules/no-restricted-v-bind.js index 2fdb5b744..f9bf5462b 100644 --- a/lib/rules/no-restricted-v-bind.js +++ b/lib/rules/no-restricted-v-bind.js @@ -69,9 +69,9 @@ function parseOption(option) { if (!argTest(key)) { return false } - return /** @type {string[]} */ (option.modifiers).every((modName) => { - return key.modifiers.some((mid) => mid.name === modName) - }) + return /** @type {string[]} */ (option.modifiers).every((modName) => + key.modifiers.some((mid) => mid.name === modName) + ) } parsed.modifiers = option.modifiers } diff --git a/lib/rules/no-side-effects-in-computed-properties.js b/lib/rules/no-side-effects-in-computed-properties.js index 6e43266fb..7f7697b50 100644 --- a/lib/rules/no-side-effects-in-computed-properties.js +++ b/lib/rules/no-side-effects-in-computed-properties.js @@ -74,14 +74,13 @@ module.exports = { const computedProperty = ( info ? computedPropertiesMap.get(info.node) || [] : [] - ).find((cp) => { - return ( + ).find( + (cp) => cp.value && cp.value.range[0] <= node.range[0] && node.range[1] <= cp.value.range[1] && targetBody === cp.value - ) - }) + ) if (computedProperty) { const mem = node.parent if (mem.object !== node) { diff --git a/lib/rules/no-watch-after-await.js b/lib/rules/no-watch-after-await.js index 6ecc4dd75..ae88fd51a 100644 --- a/lib/rules/no-watch-after-await.js +++ b/lib/rules/no-watch-after-await.js @@ -51,7 +51,7 @@ module.exports = { fixable: null, schema: [], messages: { - forbidden: 'The `watch` after `await` expression are forbidden.' + forbidden: '`watch` is forbidden after an `await` expression.' } }, /** @param {RuleContext} context */ diff --git a/lib/rules/order-in-components.js b/lib/rules/order-in-components.js index 83e814413..1e0b517f8 100644 --- a/lib/rules/order-in-components.js +++ b/lib/rules/order-in-components.js @@ -259,15 +259,13 @@ module.exports = { function checkOrder(propertiesNodes) { const properties = propertiesNodes .filter(utils.isProperty) - .map((property) => { - return { - node: property, - name: - utils.getStaticPropertyName(property) || - (property.key.type === 'Identifier' && property.key.name) || - '' - } - }) + .map((property) => ({ + node: property, + name: + utils.getStaticPropertyName(property) || + (property.key.type === 'Identifier' && property.key.name) || + '' + })) for (const [i, property] of properties.entries()) { const orderPos = getOrderPosition(property.name) diff --git a/lib/rules/static-class-names-order.js b/lib/rules/static-class-names-order.js index bee820fa2..3e5e9a631 100644 --- a/lib/rules/static-class-names-order.js +++ b/lib/rules/static-class-names-order.js @@ -25,8 +25,8 @@ module.exports = { schema: [] }, /** @param {RuleContext} context */ - create: (context) => { - return defineTemplateBodyVisitor(context, { + create: (context) => + defineTemplateBodyVisitor(context, { /** @param {VAttribute} node */ "VAttribute[directive=false][key.name='class']"(node) { const value = node.value @@ -57,5 +57,4 @@ module.exports = { } } }) - } } diff --git a/lib/rules/v-on-event-hyphenation.js b/lib/rules/v-on-event-hyphenation.js index 16ea753ab..7060ab620 100644 --- a/lib/rules/v-on-event-hyphenation.js +++ b/lib/rules/v-on-event-hyphenation.js @@ -74,9 +74,7 @@ module.exports = { autofix && // It cannot be converted in snake_case. !name.includes('_') - ? (fixer) => { - return fixer.replaceText(argument, caseConverter(name)) - } + ? (fixer) => fixer.replaceText(argument, caseConverter(name)) : null }) } @@ -85,9 +83,7 @@ module.exports = { * @param {string} value */ function isIgnoredAttribute(value) { - const isIgnored = ignoredAttributes.some((attr) => { - return value.includes(attr) - }) + const isIgnored = ignoredAttributes.some((attr) => value.includes(attr)) if (isIgnored) { return true diff --git a/lib/utils/index.js b/lib/utils/index.js index 63a838bca..5a423faa4 100644 --- a/lib/utils/index.js +++ b/lib/utils/index.js @@ -675,13 +675,10 @@ module.exports = { * @param {ESNode} p * @returns {p is (Property & { key: Identifier & {name: 'components'}, value: ObjectExpression })} */ - (p) => { - return ( - p.type === 'Property' && - getStaticPropertyName(p) === 'components' && - p.value.type === 'ObjectExpression' - ) - } + (p) => + p.type === 'Property' && + getStaticPropertyName(p) === 'components' && + p.value.type === 'ObjectExpression' ) if (!componentsNode) { @@ -913,13 +910,10 @@ module.exports = { * @param {ESNode} p * @returns {p is (Property & { key: Identifier & {name: 'computed'}, value: ObjectExpression })} */ - (p) => { - return ( - p.type === 'Property' && - getStaticPropertyName(p) === 'computed' && - p.value.type === 'ObjectExpression' - ) - } + (p) => + p.type === 'Property' && + getStaticPropertyName(p) === 'computed' && + p.value.type === 'ObjectExpression' ) if (!computedPropertiesNode) { @@ -2705,14 +2699,11 @@ function getAttribute(node, name, value) { * @param {VAttribute | VDirective} node * @returns {node is VAttribute} */ - (node) => { - return ( - !node.directive && - node.key.name === name && - (value === undefined || - (node.value != null && node.value.value === value)) - ) - } + (node) => + !node.directive && + node.key.name === name && + (value === undefined || + (node.value != null && node.value.value === value)) ) || null ) } @@ -2731,9 +2722,7 @@ function getDirectives(node, name) { * @param {VAttribute | VDirective} node * @returns {node is VDirective} */ - (node) => { - return node.directive && node.key.name.name === name - } + (node) => node.directive && node.key.name.name === name ) } /** @@ -2750,16 +2739,13 @@ function getDirective(node, name, argument) { * @param {VAttribute | VDirective} node * @returns {node is VDirective} */ - (node) => { - return ( - node.directive && - node.key.name.name === name && - (argument === undefined || - (node.key.argument && - node.key.argument.type === 'VIdentifier' && - node.key.argument.name) === argument) - ) - } + (node) => + node.directive && + node.key.name.name === name && + (argument === undefined || + (node.key.argument && + node.key.argument.type === 'VIdentifier' && + node.key.argument.name) === argument) ) || null ) } @@ -2851,9 +2837,7 @@ function getComponentPropsFromOptions(componentObject) { * @param {ESNode} p * @returns {p is (Property & { key: Identifier & {name: 'props'} })} */ - (p) => { - return p.type === 'Property' && getStaticPropertyName(p) === 'props' - } + (p) => p.type === 'Property' && getStaticPropertyName(p) === 'props' ) if (!propsNode) { @@ -2888,9 +2872,7 @@ function getComponentEmitsFromOptions(componentObject) { * @param {ESNode} p * @returns {p is (Property & { key: Identifier & {name: 'emits'} })} */ - (p) => { - return p.type === 'Property' && getStaticPropertyName(p) === 'emits' - } + (p) => p.type === 'Property' && getStaticPropertyName(p) === 'emits' ) if (!emitsNode) { diff --git a/lib/utils/property-references.js b/lib/utils/property-references.js index e89457230..beb3118d6 100644 --- a/lib/utils/property-references.js +++ b/lib/utils/property-references.js @@ -533,9 +533,9 @@ function definePropertyReferenceExtractor(context) { // unknown name return ANY } - return extractFromName(refName, nameNode, () => { - return extractFromExpression(node, false).getNest('value') - }) + return extractFromName(refName, nameNode, () => + extractFromExpression(node, false).getNest('value') + ) } /** diff --git a/lib/utils/selector.js b/lib/utils/selector.js index a1b18bc83..862f695fb 100644 --- a/lib/utils/selector.js +++ b/lib/utils/selector.js @@ -373,9 +373,7 @@ function pseudoNodeToVElementMatcher(selector) { case ':not': { // https://developer.mozilla.org/en-US/docs/Web/CSS/:not const selectors = selectorsToVElementMatcher(selector.nodes) - return (element, subject) => { - return !selectors(element, subject) - } + return (element, subject) => !selectors(element, subject) } case ':is': case ':where': diff --git a/tests/lib/rules/no-expose-after-await.js b/tests/lib/rules/no-expose-after-await.js index 2f33c58e6..d335da64a 100644 --- a/tests/lib/rules/no-expose-after-await.js +++ b/tests/lib/rules/no-expose-after-await.js @@ -101,7 +101,19 @@ tester.run('no-expose-after-await', rule, { + `, + parserOptions: { ecmaVersion: 2022 } + }, + { + filename: 'test.vue', + code: ` + `, parserOptions: { ecmaVersion: 2022 } @@ -122,7 +134,7 @@ tester.run('no-expose-after-await', rule, { `, errors: [ { - message: 'The `expose` after `await` expression are forbidden.', + message: '`expose` is forbidden after an `await` expression.', line: 6, column: 11 } @@ -142,11 +154,28 @@ tester.run('no-expose-after-await', rule, { `, errors: [ { - message: 'The `expose` after `await` expression are forbidden.', + message: '`expose` is forbidden after an `await` expression.', line: 6, column: 11 } ] + }, + { + filename: 'test.vue', + code: ` + + `, + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { + message: '`defineExpose` is forbidden after an `await` expression.', + line: 4, + column: 7 + } + ] } ] }) diff --git a/tests/lib/rules/no-lifecycle-after-await.js b/tests/lib/rules/no-lifecycle-after-await.js index bb7414682..e75995a5f 100644 --- a/tests/lib/rules/no-lifecycle-after-await.js +++ b/tests/lib/rules/no-lifecycle-after-await.js @@ -174,8 +174,7 @@ tester.run('no-lifecycle-after-await', rule, { `, errors: [ { - message: - 'The lifecycle hooks after `await` expression are forbidden.', + message: 'Lifecycle hooks are forbidden after an `await` expression.', line: 8, column: 11, endLine: 8, diff --git a/tests/lib/rules/no-reserved-component-names.js b/tests/lib/rules/no-reserved-component-names.js index 126123dd4..4b39c0858 100644 --- a/tests/lib/rules/no-reserved-component-names.js +++ b/tests/lib/rules/no-reserved-component-names.js @@ -536,212 +536,188 @@ ruleTester.run('no-reserved-component-names', rule, { code: `fn1(component.data)`, parserOptions }, - ...vue2BuiltInComponents.map((name) => { - return { - filename: `${name}.vue`, - code: ` + ...vue2BuiltInComponents.map((name) => ({ + filename: `${name}.vue`, + code: ` export default { name: '${name}' } `, - parserOptions - } - }), - ...vue3BuiltInComponents.map((name) => { - return { - filename: `${name}.vue`, - code: ` + parserOptions + })), + ...vue3BuiltInComponents.map((name) => ({ + filename: `${name}.vue`, + code: ` export default { name: '${name}' } `, - parserOptions - } - }), - ...vue3BuiltInComponents.map((name) => { - return { - filename: `${name}.vue`, - code: ` + parserOptions + })), + ...vue3BuiltInComponents.map((name) => ({ + filename: `${name}.vue`, + code: ` export default { name: '${name}' } `, - parserOptions, - options: [{ disallowVueBuiltInComponents: true }] - } - }) + parserOptions, + options: [{ disallowVueBuiltInComponents: true }] + })) ], invalid: [ - ...invalidElements.map((name) => { - return { - filename: `${name}.vue`, - code: ` + ...invalidElements.map((name) => ({ + filename: `${name}.vue`, + code: ` export default { name: '${name}' } `, - parserOptions, - errors: [ - { - messageId: RESERVED_NAMES_IN_HTML.has(name) - ? 'reservedInHtml' - : 'reserved', - data: { name }, - type: 'Literal', - line: 3 - } - ] - } - }), - ...invalidElements.map((name) => { - return { - filename: 'test.vue', - code: `Vue.component('${name}', component)`, - parserOptions, - errors: [ - { - messageId: RESERVED_NAMES_IN_HTML.has(name) - ? 'reservedInHtml' - : 'reserved', - data: { name }, - type: 'Literal', - line: 1 - } - ] - } - }), - ...invalidElements.map((name) => { - return { - filename: 'test.vue', - code: `app.component('${name}', component)`, - parserOptions, - errors: [ - { - messageId: RESERVED_NAMES_IN_HTML.has(name) - ? 'reservedInHtml' - : 'reserved', - data: { name }, - type: 'Literal', - line: 1 - } - ] - } - }), - ...invalidElements.map((name) => { - return { - filename: 'test.vue', - code: `Vue.component(\`${name}\`, {})`, - parserOptions, - errors: [ - { - messageId: RESERVED_NAMES_IN_HTML.has(name) - ? 'reservedInHtml' - : 'reserved', - data: { name }, - type: 'TemplateLiteral', - line: 1 - } - ] - } - }), - ...invalidElements.map((name) => { - return { - filename: 'test.vue', - code: `app.component(\`${name}\`, {})`, - parserOptions, - errors: [ - { - messageId: RESERVED_NAMES_IN_HTML.has(name) - ? 'reservedInHtml' - : 'reserved', - data: { name }, - type: 'TemplateLiteral', - line: 1 - } - ] - } - }), - ...invalidElements.map((name) => { - return { - filename: 'test.vue', - code: `export default { + parserOptions, + errors: [ + { + messageId: RESERVED_NAMES_IN_HTML.has(name) + ? 'reservedInHtml' + : 'reserved', + data: { name }, + type: 'Literal', + line: 3 + } + ] + })), + ...invalidElements.map((name) => ({ + filename: 'test.vue', + code: `Vue.component('${name}', component)`, + parserOptions, + errors: [ + { + messageId: RESERVED_NAMES_IN_HTML.has(name) + ? 'reservedInHtml' + : 'reserved', + data: { name }, + type: 'Literal', + line: 1 + } + ] + })), + ...invalidElements.map((name) => ({ + filename: 'test.vue', + code: `app.component('${name}', component)`, + parserOptions, + errors: [ + { + messageId: RESERVED_NAMES_IN_HTML.has(name) + ? 'reservedInHtml' + : 'reserved', + data: { name }, + type: 'Literal', + line: 1 + } + ] + })), + ...invalidElements.map((name) => ({ + filename: 'test.vue', + code: `Vue.component(\`${name}\`, {})`, + parserOptions, + errors: [ + { + messageId: RESERVED_NAMES_IN_HTML.has(name) + ? 'reservedInHtml' + : 'reserved', + data: { name }, + type: 'TemplateLiteral', + line: 1 + } + ] + })), + ...invalidElements.map((name) => ({ + filename: 'test.vue', + code: `app.component(\`${name}\`, {})`, + parserOptions, + errors: [ + { + messageId: RESERVED_NAMES_IN_HTML.has(name) + ? 'reservedInHtml' + : 'reserved', + data: { name }, + type: 'TemplateLiteral', + line: 1 + } + ] + })), + ...invalidElements.map((name) => ({ + filename: 'test.vue', + code: `export default { components: { '${name}': {}, } }`, - parserOptions, - errors: [ - { - messageId: RESERVED_NAMES_IN_HTML.has(name) - ? 'reservedInHtml' - : 'reserved', - data: { name }, - type: 'Property', - line: 3 - } - ] - } - }), - ...vue2BuiltInComponents.map((name) => { - return { - filename: `${name}.vue`, - code: ` + parserOptions, + errors: [ + { + messageId: RESERVED_NAMES_IN_HTML.has(name) + ? 'reservedInHtml' + : 'reserved', + data: { name }, + type: 'Property', + line: 3 + } + ] + })), + ...vue2BuiltInComponents.map((name) => ({ + filename: `${name}.vue`, + code: ` export default { name: '${name}' } `, - parserOptions, - options: [{ disallowVueBuiltInComponents: true }], - errors: [ - { - messageId: 'reservedInVue', - data: { name }, - type: 'Literal', - line: 3 - } - ] - } - }), - ...vue2BuiltInComponents.map((name) => { - return { - filename: `${name}.vue`, - code: ` + parserOptions, + options: [{ disallowVueBuiltInComponents: true }], + errors: [ + { + messageId: 'reservedInVue', + data: { name }, + type: 'Literal', + line: 3 + } + ] + })), + ...vue2BuiltInComponents.map((name) => ({ + filename: `${name}.vue`, + code: ` export default { name: '${name}' } `, - parserOptions, - options: [{ disallowVue3BuiltInComponents: true }], - errors: [ - { - messageId: 'reservedInVue', - data: { name }, - type: 'Literal', - line: 3 - } - ] - } - }), - ...vue3BuiltInComponents.map((name) => { - return { - filename: `${name}.vue`, - code: ` + parserOptions, + options: [{ disallowVue3BuiltInComponents: true }], + errors: [ + { + messageId: 'reservedInVue', + data: { name }, + type: 'Literal', + line: 3 + } + ] + })), + ...vue3BuiltInComponents.map((name) => ({ + filename: `${name}.vue`, + code: ` export default { name: '${name}' } `, - parserOptions, - options: [{ disallowVue3BuiltInComponents: true }], - errors: [ - { - messageId: 'reservedInVue3', - data: { name }, - type: 'Literal', - line: 3 - } - ] - } - }) + parserOptions, + options: [{ disallowVue3BuiltInComponents: true }], + errors: [ + { + messageId: 'reservedInVue3', + data: { name }, + type: 'Literal', + line: 3 + } + ] + })) ] }) diff --git a/tests/lib/rules/no-restricted-call-after-await.js b/tests/lib/rules/no-restricted-call-after-await.js index 0eef6713b..69eaf3a42 100644 --- a/tests/lib/rules/no-restricted-call-after-await.js +++ b/tests/lib/rules/no-restricted-call-after-await.js @@ -170,7 +170,7 @@ tester.run('no-restricted-call-after-await', rule, { errors: [ { message: - 'The `import("vue-i18n").useI18n` after `await` expression are forbidden.', + '`import("vue-i18n").useI18n` is forbidden after an `await` expression.', line: 7, column: 25, endLine: 7, @@ -195,7 +195,7 @@ tester.run('no-restricted-call-after-await', rule, { errors: [ { message: - 'The `import("foo").bar.baz` after `await` expression are forbidden.', + '`import("foo").bar.baz` is forbidden after an `await` expression.', line: 7 } ] @@ -222,12 +222,12 @@ tester.run('no-restricted-call-after-await', rule, { errors: [ { message: - 'The `import("vue-i18n").useI18n` after `await` expression are forbidden.', + '`import("vue-i18n").useI18n` is forbidden after an `await` expression.', line: 8 }, { message: - 'The `import("foo").bar.baz` after `await` expression are forbidden.', + '`import("foo").bar.baz` is forbidden after an `await` expression.', line: 9 } ] @@ -249,7 +249,7 @@ tester.run('no-restricted-call-after-await', rule, { errors: [ { message: - 'The `import("foo").default` after `await` expression are forbidden.', + '`import("foo").default` is forbidden after an `await` expression.', line: 7 } ] @@ -271,7 +271,7 @@ tester.run('no-restricted-call-after-await', rule, { errors: [ { message: - 'The `import("foo").default` after `await` expression are forbidden.', + '`import("foo").default` is forbidden after an `await` expression.', line: 7 } ] @@ -293,7 +293,7 @@ tester.run('no-restricted-call-after-await', rule, { errors: [ { message: - 'The `import("vue-i18n").useI18n` after `await` expression are forbidden.', + '`import("vue-i18n").useI18n` is forbidden after an `await` expression.', line: 7 } ] @@ -324,17 +324,17 @@ tester.run('no-restricted-call-after-await', rule, { errors: [ { message: - 'The `import("./foo").bar` after `await` expression are forbidden.', + '`import("./foo").bar` is forbidden after an `await` expression.', line: 10 }, { message: - 'The `import("./baz").qux` after `await` expression are forbidden.', + '`import("./baz").qux` is forbidden after an `await` expression.', line: 11 }, { message: - 'The `import("vue-i18n").useI18n` after `await` expression are forbidden.', + '`import("vue-i18n").useI18n` is forbidden after an `await` expression.', line: 12 } ] @@ -365,17 +365,17 @@ tester.run('no-restricted-call-after-await', rule, { errors: [ { message: - 'The `import("./foo").bar` after `await` expression are forbidden.', + '`import("./foo").bar` is forbidden after an `await` expression.', line: 10 }, { message: - 'The `import("./baz").qux` after `await` expression are forbidden.', + '`import("./baz").qux` is forbidden after an `await` expression.', line: 11 }, { message: - 'The `import("vue-i18n").useI18n` after `await` expression are forbidden.', + '`import("vue-i18n").useI18n` is forbidden after an `await` expression.', line: 12 } ] @@ -397,7 +397,7 @@ tester.run('no-restricted-call-after-await', rule, { errors: [ { message: - 'The `import("..").foo` after `await` expression are forbidden.', + '`import("..").foo` is forbidden after an `await` expression.', line: 7 } ] diff --git a/tests/lib/rules/no-watch-after-await.js b/tests/lib/rules/no-watch-after-await.js index e97c78c63..c54c36eb7 100644 --- a/tests/lib/rules/no-watch-after-await.js +++ b/tests/lib/rules/no-watch-after-await.js @@ -188,7 +188,7 @@ tester.run('no-watch-after-await', rule, { `, errors: [ { - message: 'The `watch` after `await` expression are forbidden.', + message: '`watch` is forbidden after an `await` expression.', line: 8, column: 11, endLine: 8, From 07a783305a2d97e915e039fdfec0570db7a2ae10 Mon Sep 17 00:00:00 2001 From: Flo Edelmann Date: Sat, 14 May 2022 00:42:37 +0200 Subject: [PATCH 18/22] Bump devDependencies to their newest versions (#1890) * Bump `eslint-plugin-import` from v2.20.2 to v2.26.0 * Bump `eslint-plugin-jsonc` from v1.4.0 to v2.2.1 * Bump `eslint-plugin-prettier` from v3.1.3 to v4.0.0 * Bump `eslint-config-prettier` from v6.11.0 to v8.5.0 * Bump `eslint` from v8.0.0 to v8.15.0 * Bump `eslint-plugin-unicorn` from v40.1.0 to v42.0.0 * Bump `@types/eslint` from v7.28.1 to v8.4.2 * Bump `@typescript-eslint/parser` from v5.5.0 to v5.23.0 * Bump `@types/natural-compare` from v1.4.0 to v1.4.1 * Bump `@types/semver` from v7.2.0 to v7.3.9 * Bump `@vuepress/plugin-pwa` from v1.4.1 to v1.9.2 * Bump `acorn` from v8.5.0 to v8.7.1 * Bump `espree` from v9.0.0 to v9.3.2 * Bump `mocha` from v7.1.2 to v8.4.0 * Bump `mocha` from v8.4.0 to v9.2.2 * Bump `mocha` from v9.2.2 to v10.0.0 * Bump `prettier` from v2.4.1 to v2.6.2 * Bump `typescript` from v4.5.0 to v4.6.4 * Bump `vuepress` from v1.8.2 to v1.9.7 --- package.json | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/package.json b/package.json index 632cc0bb8..5a910309b 100644 --- a/package.json +++ b/package.json @@ -62,31 +62,31 @@ "vue-eslint-parser": "^9.0.1" }, "devDependencies": { - "@types/eslint": "^7.28.1", + "@types/eslint": "^8.4.2", "@types/eslint-visitor-keys": "^1.0.0", - "@types/natural-compare": "^1.4.0", + "@types/natural-compare": "^1.4.1", "@types/node": "^13.13.5", - "@types/semver": "^7.2.0", - "@typescript-eslint/parser": "^5.5.0", - "@vuepress/plugin-pwa": "^1.4.1", - "acorn": "^8.5.0", + "@types/semver": "^7.3.9", + "@typescript-eslint/parser": "^5.23.0", + "@vuepress/plugin-pwa": "^1.9.7", + "acorn": "^8.7.1", "env-cmd": "^10.1.0", - "eslint": "^8.0.0", - "eslint-config-prettier": "^6.11.0", + "eslint": "^8.15.0", + "eslint-config-prettier": "^8.5.0", "eslint-plugin-eslint-plugin": "^3.5.3", - "eslint-plugin-import": "^2.20.2", - "eslint-plugin-jsonc": "^1.4.0", + "eslint-plugin-import": "^2.26.0", + "eslint-plugin-jsonc": "^2.2.1", "eslint-plugin-node-dependencies": ">=0.5.0 <1.0.0", - "eslint-plugin-prettier": "^3.1.3", - "eslint-plugin-unicorn": "^40.1.0", + "eslint-plugin-prettier": "^4.0.0", + "eslint-plugin-unicorn": "^42.0.0", "eslint-plugin-vue": "file:.", - "espree": "^9.0.0", + "espree": "^9.3.2", "markdownlint-cli": "^0.31.1", - "mocha": "^7.1.2", + "mocha": "^10.0.0", "nyc": "^15.1.0", - "prettier": "^2.4.1", - "typescript": "^4.5.0", + "prettier": "^2.6.2", + "typescript": "^4.6.4", "vue-eslint-editor": "^1.1.0", - "vuepress": "^1.8.2" + "vuepress": "^1.9.7" } } From b0639d725861e4ed6ea6bb15499411e2b872e9de Mon Sep 17 00:00:00 2001 From: Deisling Eduard <41228762+edikdeisling@users.noreply.github.com> Date: Sat, 14 May 2022 04:23:14 +0300 Subject: [PATCH 19/22] Improve auto-fix for `vue/define-macros-order` rule (#1863) * vue/define-macros-order improvements * vue/define-macros-order improvements * Add test for corner case --- lib/rules/define-macros-order.js | 26 ++++++++++++++++++-------- tests/lib/rules/define-macros-order.js | 23 ++++++++++++++++------- 2 files changed, 34 insertions(+), 15 deletions(-) diff --git a/lib/rules/define-macros-order.js b/lib/rules/define-macros-order.js index 1db259fa1..007b17027 100644 --- a/lib/rules/define-macros-order.js +++ b/lib/rules/define-macros-order.js @@ -232,13 +232,12 @@ function create(context) { const targetComment = sourceCode.getTokenAfter(beforeTargetToken, { includeComments: true }) - const textSpace = getTextBetweenTokens(beforeTargetToken, targetComment) // make insert text: comments + node + space before target const textNode = sourceCode.getText( node, node.range[0] - nodeComment.range[0] ) - const insertText = textNode + textSpace + const insertText = getInsertText(textNode, target) return [ fixer.insertTextBefore(targetComment, insertText), @@ -247,17 +246,28 @@ function create(context) { } /** - * @param {ASTNode} tokenBefore - * @param {ASTNode} tokenAfter + * Get result text to insert + * @param {string} textNode + * @param {ASTNode} target */ - function getTextBetweenTokens(tokenBefore, tokenAfter) { - return sourceCode.text.slice(tokenBefore.range[1], tokenAfter.range[0]) + function getInsertText(textNode, target) { + const afterTargetComment = sourceCode.getTokenAfter(target, { + includeComments: true + }) + const afterText = sourceCode.text.slice( + target.range[1], + afterTargetComment.range[0] + ) + // handle case when a();b() -> b()a(); + const invalidResult = !textNode.endsWith(';') && !afterText.includes('\n') + + return textNode + afterText + (invalidResult ? ';' : '') } /** * Get position of the beginning of the token's line(or prevToken end if no line) - * @param {ASTNode} token - * @param {ASTNode} prevToken + * @param {ASTNode|Token} token + * @param {ASTNode|Token} prevToken */ function getLineStartIndex(token, prevToken) { // if we have next token on the same line - get index right before that token diff --git a/tests/lib/rules/define-macros-order.js b/tests/lib/rules/define-macros-order.js index 65a07887f..90e6748d6 100644 --- a/tests/lib/rules/define-macros-order.js +++ b/tests/lib/rules/define-macros-order.js @@ -155,9 +155,7 @@ tester.run('define-macros-order', rule, { defineProps({ test: Boolean }) - defineEmits(['update:test']) - console.log('test1') console.log('test2') console.log('test3') @@ -192,7 +190,6 @@ tester.run('define-macros-order', rule, { defineProps({ test: Boolean }) - defineEmits(['update:test']) console.log('test1') @@ -261,7 +258,6 @@ tester.run('define-macros-order', rule, { } const emit = defineEmits<{(e: 'update:test'): void}>() - const props = withDefaults(defineProps(), { msg: 'hello', labels: () => ['one', 'two'] @@ -377,8 +373,7 @@ tester.run('define-macros-order', rule, { `, output: ` + defineEmits(['update:test']);const props = defineProps({ test: Boolean }); `, options: optionsEmitsFirst, errors: [ @@ -413,7 +408,6 @@ tester.run('define-macros-order', rule, { import 'test' const props = defineProps({ test: Boolean }); - defineEmits(['update:test']) `, @@ -423,6 +417,21 @@ tester.run('define-macros-order', rule, { line: 11 } ] + }, + { + filename: 'test.vue', + code: ` + + `, + output: ` + + `, + errors: [ + { + message: message('defineProps'), + line: 2 + } + ] } ] }) From ab85fd6ff6a0f4b5b36bd61eadb1750a7f54d747 Mon Sep 17 00:00:00 2001 From: Douglas Wade Date: Tue, 17 May 2022 03:48:40 -0700 Subject: [PATCH 20/22] Add `vue/no-invalid-attribute-name` rule (#1851) * Fix #1373: Add rule no-invalid-attribute-name * Remove stray newline * Apply suggestions from code review Co-authored-by: Flo Edelmann * #1373 Use xml-name-validator * Fix linting error * remove stray newline * refactor test code * Update lib/rules/no-invalid-attribute-name.js Co-authored-by: Flo Edelmann * fix bad commit from github ui * fix typechecking error * Respond to PR feedback * Include the added types in package.json * check v-bind directives * Update tests/lib/rules/no-invalid-attribute-name.js Co-authored-by: Flo Edelmann * Fix failing unit test * Update lib/rules/no-invalid-attribute-name.js * Update lib/rules/no-invalid-attribute-name.js * Update tests/lib/rules/no-invalid-attribute-name.js * Update tests/lib/rules/no-invalid-attribute-name.js Co-authored-by: Flo Edelmann Co-authored-by: Yosuke Ota --- docs/rules/README.md | 1 + docs/rules/no-invalid-attribute-name.md | 43 ++++++ lib/index.js | 1 + lib/rules/no-invalid-attribute-name.js | 69 +++++++++ package.json | 4 +- tests/lib/rules/no-invalid-attribute-name.js | 146 +++++++++++++++++++ 6 files changed, 263 insertions(+), 1 deletion(-) create mode 100644 docs/rules/no-invalid-attribute-name.md create mode 100644 lib/rules/no-invalid-attribute-name.js create mode 100644 tests/lib/rules/no-invalid-attribute-name.js diff --git a/docs/rules/README.md b/docs/rules/README.md index 0f39db375..869a0cee0 100644 --- a/docs/rules/README.md +++ b/docs/rules/README.md @@ -224,6 +224,7 @@ For example: | [vue/no-boolean-default](./no-boolean-default.md) | disallow boolean defaults | :wrench: | :hammer: | | [vue/no-duplicate-attr-inheritance](./no-duplicate-attr-inheritance.md) | enforce `inheritAttrs` to be set to `false` when using `v-bind="$attrs"` | | :hammer: | | [vue/no-empty-component-block](./no-empty-component-block.md) | disallow the `