From 1a508c01eabcf71c25b39fcb07e9aa06d30b7324 Mon Sep 17 00:00:00 2001 From: Flo Edelmann Date: Thu, 19 Dec 2024 16:18:03 +0100 Subject: [PATCH 1/3] Enable `eslint-plugin/require-meta-default-options` rule --- eslint.config.js | 1 - 1 file changed, 1 deletion(-) diff --git a/eslint.config.js b/eslint.config.js index 73c3ba2a4..7d3f4d858 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -45,7 +45,6 @@ module.exports = [ // turn off some rules from shared configs in all files { rules: { - 'eslint-plugin/require-meta-default-options': 'off', // TODO: enable when all rules have defaultOptions 'eslint-plugin/require-meta-docs-recommended': 'off', // use `categories` instead 'eslint-plugin/require-meta-schema-description': 'off', From 33ffc9189730f6ed59753f838a7255c42cb5e07b Mon Sep 17 00:00:00 2001 From: Flo Edelmann Date: Fri, 25 Oct 2024 13:18:39 +0200 Subject: [PATCH 2/3] Add internal rule that enforces valid default options --- .../no-invalid-meta-default-options.js | 156 ++++++++++++++++++ eslint.config.js | 3 + 2 files changed, 159 insertions(+) create mode 100644 eslint-internal-rules/no-invalid-meta-default-options.js diff --git a/eslint-internal-rules/no-invalid-meta-default-options.js b/eslint-internal-rules/no-invalid-meta-default-options.js new file mode 100644 index 000000000..919d8fa56 --- /dev/null +++ b/eslint-internal-rules/no-invalid-meta-default-options.js @@ -0,0 +1,156 @@ +/** + * @fileoverview Internal rule to enforce valid default options. + * @author Flo Edelmann + */ + +'use strict' + +const Ajv = require('ajv') +const metaSchema = require('ajv/lib/refs/json-schema-draft-04.json') + +// from https://github.com/eslint/eslint/blob/main/lib/shared/ajv.js +const ajv = new Ajv({ + meta: false, + useDefaults: true, + validateSchema: false, + missingRefs: 'ignore', + verbose: true, + schemaId: 'auto' +}) +ajv.addMetaSchema(metaSchema) +ajv._opts.defaultMeta = metaSchema.id + +// from https://github.com/eslint/eslint/blob/main/lib/config/flat-config-helpers.js +const noOptionsSchema = Object.freeze({ + type: 'array', + minItems: 0, + maxItems: 0 +}) +function getRuleOptionsSchema(schema) { + if (schema === false || typeof schema !== 'object' || schema === null) { + return null + } + + if (!Array.isArray(schema)) { + return schema + } + + if (schema.length === 0) { + return { ...noOptionsSchema } + } + + return { + type: 'array', + items: schema, + minItems: 0, + maxItems: schema.length + } +} + +/** + * @param {RuleContext} context + * @param {ASTNode} node + * @returns {any} + */ +function getNodeValue(context, node) { + try { + // eslint-disable-next-line no-eval + return eval(context.getSourceCode().getText(node)) + } catch (error) { + return undefined + } +} + +/** + * Gets the property of the Object node passed in that has the name specified. + * + * @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(propertyName, node) { + if (node && node.type === 'ObjectExpression') { + for (const property of node.properties) { + if (property.type === 'Property' && property.key.name === propertyName) { + return property + } + } + } + return null +} + +module.exports = { + meta: { + type: 'problem', + docs: { + description: 'enforce correct use of `meta` property in core rules', + categories: ['Internal'] + }, + schema: [], + messages: { + defaultOptionsNotMatchingSchema: + 'Default options do not match the schema.' + } + }, + + create(context) { + /** @type {ASTNode} */ + let exportsNode + + return { + AssignmentExpression(node) { + if ( + node.left && + node.right && + node.left.type === 'MemberExpression' && + node.left.object.name === 'module' && + node.left.property.name === 'exports' + ) { + exportsNode = node.right + } + }, + + 'Program:exit'() { + const metaProperty = getPropertyFromObject('meta', exportsNode) + if (!metaProperty) { + return + } + + const metaSchema = getPropertyFromObject('schema', metaProperty.value) + const metaDefaultOptions = getPropertyFromObject( + 'defaultOptions', + metaProperty.value + ) + + if ( + !metaSchema || + !metaDefaultOptions || + metaDefaultOptions.value.type !== 'ArrayExpression' + ) { + return + } + + const defaultOptions = getNodeValue(context, metaDefaultOptions.value) + const schema = getNodeValue(context, metaSchema.value) + + if (!defaultOptions || !schema) { + return + } + + let validate + try { + validate = ajv.compile(getRuleOptionsSchema(schema)) + } catch (error) { + return + } + + if (!validate(defaultOptions)) { + context.report({ + node: metaDefaultOptions.value, + messageId: 'defaultOptionsNotMatchingSchema' + }) + } + } + } + } +} diff --git a/eslint.config.js b/eslint.config.js index 7d3f4d858..925c9577c 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -35,6 +35,7 @@ module.exports = [ internal: { rules: { 'no-invalid-meta': require('./eslint-internal-rules/no-invalid-meta'), + 'no-invalid-meta-default-options': require('./eslint-internal-rules/no-invalid-meta-default-options'), 'no-invalid-meta-docs-categories': require('./eslint-internal-rules/no-invalid-meta-docs-categories'), 'require-eslint-community': require('./eslint-internal-rules/require-eslint-community') } @@ -224,6 +225,7 @@ module.exports = [ { pattern: 'https://eslint.vuejs.org/rules/{{name}}.html' } ], 'internal/no-invalid-meta': 'error', + 'internal/no-invalid-meta-default-options': 'error', 'internal/no-invalid-meta-docs-categories': 'error' } }, @@ -232,6 +234,7 @@ module.exports = [ rules: { 'eslint-plugin/require-meta-docs-url': 'off', 'internal/no-invalid-meta': 'error', + 'internal/no-invalid-meta-default-options': 'error', 'internal/no-invalid-meta-docs-categories': 'error' } }, From 2ecbd76b884002fc9d09154ce4d66d00d59ccf10 Mon Sep 17 00:00:00 2001 From: Flo Edelmann Date: Fri, 25 Oct 2024 19:50:53 +0200 Subject: [PATCH 3/3] Add `defaultOptions` to all rules --- lib/rules/attribute-hyphenation.js | 7 +++++++ lib/rules/attributes-order.js | 18 ++++++++++++++++++ lib/rules/block-lang.js | 7 +++++++ lib/rules/block-order.js | 5 +++++ lib/rules/block-tag-newline.js | 8 ++++++++ lib/rules/comment-directive.js | 1 + lib/rules/component-api-style.js | 1 + lib/rules/component-definition-name-casing.js | 1 + .../component-name-in-template-casing.js | 8 ++++++++ lib/rules/component-options-name-casing.js | 1 + lib/rules/custom-event-name-casing.js | 4 ++-- lib/rules/define-emits-declaration.js | 1 + lib/rules/define-macros-order.js | 1 + lib/rules/define-props-declaration.js | 1 + lib/rules/enforce-style-attribute.js | 1 + lib/rules/first-attribute-linebreak.js | 1 + lib/rules/html-button-has-type.js | 1 + lib/rules/html-closing-bracket-newline.js | 7 +++++++ lib/rules/html-closing-bracket-spacing.js | 7 +++++++ lib/rules/html-comment-content-newline.js | 7 +++++++ lib/rules/html-comment-content-spacing.js | 1 + lib/rules/html-comment-indent.js | 1 + lib/rules/html-indent.js | 15 +++++++++++++++ lib/rules/html-quotes.js | 1 + lib/rules/html-self-closing.js | 11 +++++++++++ lib/rules/match-component-file-name.js | 1 + lib/rules/max-attributes-per-line.js | 6 ++++++ lib/rules/max-len.js | 19 +++++++++++++++++++ lib/rules/max-lines-per-block.js | 8 ++++++++ lib/rules/max-props.js | 1 + lib/rules/max-template-depth.js | 1 + lib/rules/multi-word-component-names.js | 1 + .../multiline-html-element-content-newline.js | 7 +++++++ lib/rules/mustache-interpolation-spacing.js | 1 + .../new-line-between-multi-line-property.js | 1 + lib/rules/next-tick-style.js | 1 + lib/rules/no-bare-strings-in-template.js | 7 +++++++ lib/rules/no-boolean-default.js | 1 + lib/rules/no-child-content.js | 2 ++ lib/rules/no-deprecated-model-definition.js | 1 + .../no-deprecated-router-link-tag-prop.js | 1 + lib/rules/no-deprecated-slot-attribute.js | 1 + lib/rules/no-dupe-keys.js | 1 + lib/rules/no-duplicate-attr-inheritance.js | 1 + lib/rules/no-duplicate-attributes.js | 1 + lib/rules/no-irregular-whitespace.js | 10 ++++++++++ lib/rules/no-lone-template.js | 1 + lib/rules/no-multi-spaces.js | 1 + lib/rules/no-mutating-props.js | 1 + lib/rules/no-parsing-error.js | 1 + .../no-potential-component-option-typo.js | 7 +++++++ lib/rules/no-required-prop-with-default.js | 1 + lib/rules/no-reserved-component-names.js | 7 +++++++ lib/rules/no-reserved-keys.js | 1 + lib/rules/no-reserved-props.js | 1 + lib/rules/no-restricted-block.js | 2 +- lib/rules/no-restricted-call-after-await.js | 1 + lib/rules/no-restricted-class.js | 1 + lib/rules/no-restricted-component-names.js | 1 + lib/rules/no-restricted-component-options.js | 2 +- lib/rules/no-restricted-custom-event.js | 2 +- lib/rules/no-restricted-html-elements.js | 1 + lib/rules/no-restricted-props.js | 2 +- lib/rules/no-restricted-static-attribute.js | 2 +- lib/rules/no-restricted-v-bind.js | 8 +++++++- lib/rules/no-restricted-v-on.js | 1 + lib/rules/no-static-inline-styles.js | 1 + lib/rules/no-template-shadow.js | 1 + lib/rules/no-template-target-blank.js | 1 + lib/rules/no-undef-components.js | 1 + lib/rules/no-undef-properties.js | 1 + lib/rules/no-unsupported-features.js | 1 + lib/rules/no-unused-components.js | 1 + lib/rules/no-unused-properties.js | 8 ++++++++ lib/rules/no-unused-vars.js | 1 + lib/rules/no-use-v-if-with-v-for.js | 1 + lib/rules/no-useless-mustaches.js | 3 +++ lib/rules/no-useless-v-bind.js | 3 +++ lib/rules/no-v-text-v-html-on-component.js | 6 ++++++ lib/rules/order-in-components.js | 1 + lib/rules/padding-line-between-blocks.js | 1 + lib/rules/padding-line-between-tags.js | 1 + .../padding-lines-in-component-definition.js | 1 + lib/rules/prefer-true-attribute-shorthand.js | 1 + lib/rules/prop-name-casing.js | 1 + lib/rules/require-direct-export.js | 5 +++++ lib/rules/require-explicit-emits.js | 1 + lib/rules/require-macro-variable-name.js | 1 + lib/rules/require-prop-comment.js | 1 + lib/rules/require-toggle-inside-transition.js | 1 + lib/rules/restricted-component-names.js | 1 + lib/rules/return-in-computed-property.js | 1 + lib/rules/script-indent.js | 3 ++- ...singleline-html-element-content-newline.js | 8 ++++++++ lib/rules/slot-name-casing.js | 1 + lib/rules/sort-keys.js | 16 ++++++++++++++++ lib/rules/this-in-template.js | 1 + lib/rules/v-bind-style.js | 1 + lib/rules/v-for-delimiter-style.js | 1 + lib/rules/v-on-event-hyphenation.js | 8 ++++++++ lib/rules/v-on-function-call.js | 1 + lib/rules/v-on-handler-style.js | 4 ++++ lib/rules/v-on-style.js | 1 + lib/rules/v-slot-style.js | 7 +++++++ lib/rules/valid-v-on.js | 1 + lib/rules/valid-v-slot.js | 1 + 106 files changed, 325 insertions(+), 9 deletions(-) diff --git a/lib/rules/attribute-hyphenation.js b/lib/rules/attribute-hyphenation.js index 65d096cd4..95b001f2f 100644 --- a/lib/rules/attribute-hyphenation.js +++ b/lib/rules/attribute-hyphenation.js @@ -68,6 +68,13 @@ module.exports = { additionalProperties: false } ], + defaultOptions: [ + 'always', + { + ignore: [], + ignoreTags: [] + } + ], messages: { mustBeHyphenated: "Attribute '{{text}}' must be hyphenated.", cannotBeHyphenated: "Attribute '{{text}}' can't be hyphenated." diff --git a/lib/rules/attributes-order.js b/lib/rules/attributes-order.js index 50c9b452c..c51ccb4b8 100644 --- a/lib/rules/attributes-order.js +++ b/lib/rules/attributes-order.js @@ -455,6 +455,24 @@ module.exports = { additionalProperties: false } ], + defaultOptions: [ + { + order: [ + ATTRS.DEFINITION, + ATTRS.LIST_RENDERING, + ATTRS.CONDITIONALS, + ATTRS.RENDER_MODIFIERS, + ATTRS.GLOBAL, + [ATTRS.UNIQUE, ATTRS.SLOT], + ATTRS.TWO_WAY_BINDING, + ATTRS.OTHER_DIRECTIVES, + [ATTRS.ATTR_DYNAMIC, ATTRS.ATTR_STATIC, ATTRS.ATTR_SHORTHAND_BOOL], + ATTRS.EVENTS, + ATTRS.CONTENT + ], + alphabetical: false + } + ], messages: { expectedOrder: `Attribute "{{currentNode}}" should go before "{{prevNode}}".` } diff --git a/lib/rules/block-lang.js b/lib/rules/block-lang.js index 2190b17cb..483a887a7 100644 --- a/lib/rules/block-lang.js +++ b/lib/rules/block-lang.js @@ -151,6 +151,13 @@ module.exports = { additionalProperties: false } ], + defaultOptions: [ + { + script: { allowNoLang: true }, + template: { allowNoLang: true }, + style: { allowNoLang: true } + } + ], messages: { expected: "Only {{allows}} can be used for the 'lang' attribute of '<{{tag}}>'.", diff --git a/lib/rules/block-order.js b/lib/rules/block-order.js index 03b3d4a5f..6d93309f2 100644 --- a/lib/rules/block-order.js +++ b/lib/rules/block-order.js @@ -61,6 +61,11 @@ module.exports = { additionalProperties: false } ], + defaultOptions: [ + { + order: [['script', 'template'], 'style'] + } + ], messages: { unexpected: "'<{{elementName}}{{elementAttributes}}>' should be above '<{{firstUnorderedName}}{{firstUnorderedAttributes}}>' on line {{line}}." diff --git a/lib/rules/block-tag-newline.js b/lib/rules/block-tag-newline.js index 22fb12870..fc7f45a7b 100644 --- a/lib/rules/block-tag-newline.js +++ b/lib/rules/block-tag-newline.js @@ -71,6 +71,14 @@ module.exports = { additionalProperties: false } ], + defaultOptions: [ + { + singleline: 'consistent', + multiline: 'always', + maxEmptyLines: 0, + blocks: {} + } + ], messages: { unexpectedOpeningLinebreak: "There should be no line break after '<{{tag}}>'.", diff --git a/lib/rules/comment-directive.js b/lib/rules/comment-directive.js index 655e222bd..d6a6b1bb5 100644 --- a/lib/rules/comment-directive.js +++ b/lib/rules/comment-directive.js @@ -289,6 +289,7 @@ module.exports = { additionalProperties: false } ], + defaultOptions: [{ reportUnusedDisableDirectives: false }], messages: { disableBlock: '--block {{key}}', enableBlock: '++block', diff --git a/lib/rules/component-api-style.js b/lib/rules/component-api-style.js index 550eebfed..c925305a8 100644 --- a/lib/rules/component-api-style.js +++ b/lib/rules/component-api-style.js @@ -216,6 +216,7 @@ module.exports = { minItems: 1 } ], + defaultOptions: [['script-setup', 'composition']], messages: { disallowScriptSetup: '`