From b670c4897c246c402f6f0b7c1c447609ccb9370d Mon Sep 17 00:00:00 2001 From: Flo Edelmann Date: Tue, 19 Apr 2022 22:53:28 +0200 Subject: [PATCH 1/2] Install `eslint-plugin-unicorn` --- .eslintrc.js | 19 ++++++++++++++++--- package.json | 1 + 2 files changed, 17 insertions(+), 3 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/package.json b/package.json index 9ddbb72a4..4e300e5f7 100644 --- a/package.json +++ b/package.json @@ -78,6 +78,7 @@ "eslint-plugin-jsonc": "^1.4.0", "eslint-plugin-node-dependencies": ">=0.5.0 <1.0.0", "eslint-plugin-prettier": "^3.1.3", + "eslint-plugin-unicorn": "^40.1.0", "eslint-plugin-vue": "file:.", "espree": "^9.0.0", "lodash": "^4.17.21", From 2293b9a73cf581a2146816600ac52a9624ae034e Mon Sep 17 00:00:00 2001 From: Flo Edelmann Date: Wed, 20 Apr 2022 00:02:19 +0200 Subject: [PATCH 2/2] Fix new lint issues --- 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 +-- 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 +++-- 122 files changed, 2181 insertions(+), 2148 deletions(-) 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) {