diff --git a/docs/src/extend/custom-rules.md b/docs/src/extend/custom-rules.md index 9b99121d22e..fdb58a9d661 100644 --- a/docs/src/extend/custom-rules.md +++ b/docs/src/extend/custom-rules.md @@ -60,6 +60,8 @@ The source file for a rule exports an object with the following properties. Both * `schema`: (`object | array | false`) Specifies the [options](#options-schemas) so ESLint can prevent invalid [rule configurations](../use/configure/rules). Mandatory when the rule has options. +* `defaultOptions`: (`array`) Specifies [default options](#option-defaults) for the rule. If present, any user-provided options in their config will be merged on top of them recursively. + * `deprecated`: (`boolean`) Indicates whether the rule has been deprecated. You may omit the `deprecated` property if the rule has not been deprecated. * `replacedBy`: (`array`) In the case of a deprecated rule, specify replacement rule(s). @@ -796,6 +798,51 @@ module.exports = { To learn more about JSON Schema, we recommend looking at some examples on the [JSON Schema website](https://json-schema.org/learn/miscellaneous-examples), or reading the free [Understanding JSON Schema](https://json-schema.org/understanding-json-schema/) ebook. +#### Option Defaults + +Rules may specify a `meta.defaultOptions` array of default values for any options. +When the rule is enabled in a user configuration, ESLint will recursively merge any user-provided option elements on top of the default elements. + +For example, given the following defaults: + +```js +export default { + meta: { + defaultOptions: [{ + alias: "basic", + }], + schema: { + type: "object", + properties: { + alias: { + type: "string" + } + }, + additionalProperties: false + } + }, + create(context) { + const [{ alias }] = context.options; + + return { /* ... */ }; + } +} +``` + +The rule would have a runtime `alias` value of `"basic"` unless the user configuration specifies a different value, such as with `["error", { alias: "complex" }]`. + +Each element of the options array is merged according to the following rules: + +* Any missing value or explicit user-provided `undefined` will fall back to a default option +* User-provided arrays and primitive values other than `undefined` override a default option +* User-provided objects will merge into a default option object and replace a non-object default otherwise + +Option defaults will also be validated against the rule's `meta.schema`. + +**Note:** ESLint internally uses [Ajv](https://ajv.js.org) for schema validation with its [`useDefaults` option](https://ajv.js.org/guide/modifying-data.html#assigning-defaults) enabled. +Both user-provided and `meta.defaultOptions` options will override any defaults specified in a rule's schema. +ESLint may disable Ajv's `useDefaults` in a future major version. + ### Accessing Shebangs [Shebangs (#!)](https://en.wikipedia.org/wiki/Shebang_(Unix)) are represented by the unique tokens of type `"Shebang"`. They are treated as comments and can be accessed by the methods outlined in the [Accessing Comments](#accessing-comments) section, such as `sourceCode.getAllComments()`. diff --git a/lib/config/flat-config-schema.js b/lib/config/flat-config-schema.js index ce86229c429..472288e697c 100644 --- a/lib/config/flat-config-schema.js +++ b/lib/config/flat-config-schema.js @@ -588,5 +588,6 @@ const flatConfigSchema = { module.exports = { flatConfigSchema, + assertIsRuleOptions, assertIsRuleSeverity }; diff --git a/lib/config/rule-validator.js b/lib/config/rule-validator.js index 3b4ea6122cb..73ec2ad1828 100644 --- a/lib/config/rule-validator.js +++ b/lib/config/rule-validator.js @@ -10,6 +10,7 @@ //----------------------------------------------------------------------------- const ajvImport = require("../shared/ajv"); +const { deepMergeArrays } = require("../shared/deep-merge-arrays"); const ajv = ajvImport(); const { parseRuleId, @@ -164,7 +165,10 @@ class RuleValidator { if (validateRule) { - validateRule(ruleOptions.slice(1)); + const slicedOptions = ruleOptions.slice(1); + const mergedOptions = deepMergeArrays(rule.meta?.defaultOptions, slicedOptions); + + validateRule(mergedOptions); if (validateRule.errors) { throw new Error(`Key "rules": Key "${ruleId}":\n${ @@ -186,6 +190,10 @@ class RuleValidator { ).join("") }`); } + + if (mergedOptions.length) { + config.rules[ruleId] = [ruleOptions[0], ...mergedOptions]; + } } } } diff --git a/lib/linter/linter.js b/lib/linter/linter.js index 8ae8ad367a3..38386c78b1e 100644 --- a/lib/linter/linter.js +++ b/lib/linter/linter.js @@ -42,8 +42,9 @@ const const { getRuleFromConfig } = require("../config/flat-config-helpers"); const { FlatConfigArray } = require("../config/flat-config-array"); const { RuleValidator } = require("../config/rule-validator"); -const { assertIsRuleSeverity } = require("../config/flat-config-schema"); +const { assertIsRuleOptions, assertIsRuleSeverity } = require("../config/flat-config-schema"); const { normalizeSeverityToString } = require("../shared/severity"); +const { deepMergeArrays } = require("../shared/deep-merge-arrays"); const debug = require("debug")("eslint:linter"); const MAX_AUTOFIX_PASSES = 10; const DEFAULT_PARSER_NAME = "espree"; @@ -828,14 +829,31 @@ function stripUnicodeBOM(text) { /** * Get the options for a rule (not including severity), if any * @param {Array|number} ruleConfig rule configuration + * @param {Object|undefined} defaultOptions rule.meta.defaultOptions * @returns {Array} of rule options, empty Array if none */ -function getRuleOptions(ruleConfig) { +function getRuleOptions(ruleConfig, defaultOptions) { if (Array.isArray(ruleConfig)) { - return ruleConfig.slice(1); + return deepMergeArrays(defaultOptions, ruleConfig.slice(1)); } - return []; + return defaultOptions ?? []; +} + +/** + * Get the options for a rule's inline comment, including severity + * @param {string} ruleId Rule name being configured. + * @param {Array|number} ruleValue rule severity and options, if any + * @param {Object|undefined} defaultOptions rule.meta.defaultOptions + * @returns {Array} of rule options, empty Array if none + */ +function getRuleOptionsInline(ruleId, ruleValue, defaultOptions) { + assertIsRuleOptions(ruleId, ruleValue); + + const [ruleSeverity, ...ruleConfig] = Array.isArray(ruleValue) ? ruleValue : [ruleValue]; + assertIsRuleSeverity(ruleId, ruleSeverity); + + return [ruleSeverity, ...deepMergeArrays(defaultOptions, ruleConfig)]; } /** @@ -982,6 +1000,7 @@ function createRuleListeners(rule, ruleContext) { * @param {LanguageOptions} languageOptions The options for parsing the code. * @param {Object} settings The settings that were enabled in the config * @param {string} filename The reported filename of the code + * @param {boolean} applyDefaultOptions If true, apply rules' meta.defaultOptions in computing their config options. * @param {boolean} disableFixes If true, it doesn't make `fix` properties. * @param {string | undefined} cwd cwd of the cli * @param {string} physicalFilename The full path of the file on disk without any code block information @@ -989,7 +1008,20 @@ function createRuleListeners(rule, ruleContext) { * @returns {LintMessage[]} An array of reported problems * @throws {Error} If traversal into a node fails. */ -function runRules(sourceCode, configuredRules, ruleMapper, parserName, languageOptions, settings, filename, disableFixes, cwd, physicalFilename, ruleFilter) { +function runRules( + sourceCode, + configuredRules, + ruleMapper, + parserName, + languageOptions, + settings, + filename, + applyDefaultOptions, + disableFixes, + cwd, + physicalFilename, + ruleFilter +) { const emitter = createEmitter(); // must happen first to assign all node.parent properties @@ -1047,7 +1079,7 @@ function runRules(sourceCode, configuredRules, ruleMapper, parserName, languageO Object.create(sharedTraversalContext), { id: ruleId, - options: getRuleOptions(configuredRules[ruleId]), + options: getRuleOptions(configuredRules[ruleId], applyDefaultOptions && rule.meta?.defaultOptions), report(...args) { /* @@ -1395,6 +1427,7 @@ class Linter { languageOptions, settings, options.filename, + true, options.disableFixes, slots.cwd, providedOptions.physicalFilename, @@ -1723,9 +1756,7 @@ class Linter { try { - let ruleOptions = Array.isArray(ruleValue) ? ruleValue : [ruleValue]; - - assertIsRuleSeverity(ruleId, ruleOptions[0]); + let ruleOptions = getRuleOptionsInline(ruleId, ruleValue, rule.meta?.defaultOptions); /* * If the rule was already configured, inline rule configuration that @@ -1838,6 +1869,7 @@ class Linter { languageOptions, settings, options.filename, + false, options.disableFixes, slots.cwd, providedOptions.physicalFilename, diff --git a/lib/rules/accessor-pairs.js b/lib/rules/accessor-pairs.js index f97032895df..90c71950f42 100644 --- a/lib/rules/accessor-pairs.js +++ b/lib/rules/accessor-pairs.js @@ -139,6 +139,12 @@ module.exports = { meta: { type: "suggestion", + defaultOptions: [{ + enforceForClassMembers: true, + getWithoutSet: false, + setWithoutGet: true + }], + docs: { description: "Enforce getter and setter pairs in objects and classes", recommended: false, @@ -149,16 +155,13 @@ module.exports = { type: "object", properties: { getWithoutSet: { - type: "boolean", - default: false + type: "boolean" }, setWithoutGet: { - type: "boolean", - default: true + type: "boolean" }, enforceForClassMembers: { - type: "boolean", - default: true + type: "boolean" } }, additionalProperties: false @@ -174,7 +177,7 @@ module.exports = { } }, create(context) { - const config = context.options[0] || {}; + const [config] = context.options; const checkGetWithoutSet = config.getWithoutSet === true; const checkSetWithoutGet = config.setWithoutGet !== false; const enforceForClassMembers = config.enforceForClassMembers !== false; diff --git a/lib/rules/array-bracket-spacing.js b/lib/rules/array-bracket-spacing.js index 31ace985a20..c032de47f13 100644 --- a/lib/rules/array-bracket-spacing.js +++ b/lib/rules/array-bracket-spacing.js @@ -18,6 +18,8 @@ module.exports = { replacedBy: [], type: "layout", + defaultOptions: ["never"], + docs: { description: "Enforce consistent spacing inside array brackets", recommended: false, diff --git a/lib/rules/array-callback-return.js b/lib/rules/array-callback-return.js index 6d8f258fa14..974fea8cdd1 100644 --- a/lib/rules/array-callback-return.js +++ b/lib/rules/array-callback-return.js @@ -215,6 +215,12 @@ module.exports = { meta: { type: "problem", + defaultOptions: [{ + allowImplicit: false, + checkForEach: false, + allowVoid: false + }], + docs: { description: "Enforce `return` statements in callbacks of array methods", recommended: false, @@ -229,16 +235,13 @@ module.exports = { type: "object", properties: { allowImplicit: { - type: "boolean", - default: false + type: "boolean" }, checkForEach: { - type: "boolean", - default: false + type: "boolean" }, allowVoid: { - type: "boolean", - default: false + type: "boolean" } }, additionalProperties: false @@ -256,8 +259,7 @@ module.exports = { }, create(context) { - - const options = context.options[0] || { allowImplicit: false, checkForEach: false, allowVoid: false }; + const [options] = context.options; const sourceCode = context.sourceCode; let funcInfo = { diff --git a/lib/rules/array-element-newline.js b/lib/rules/array-element-newline.js index 504fe04a0b8..673ee693b6d 100644 --- a/lib/rules/array-element-newline.js +++ b/lib/rules/array-element-newline.js @@ -19,6 +19,8 @@ module.exports = { replacedBy: [], type: "layout", + defaultOptions: ["always"], + docs: { description: "Enforce line breaks after each array element", recommended: false, diff --git a/lib/rules/arrow-body-style.js b/lib/rules/arrow-body-style.js index 759070454c4..a5947e500c2 100644 --- a/lib/rules/arrow-body-style.js +++ b/lib/rules/arrow-body-style.js @@ -19,6 +19,8 @@ module.exports = { meta: { type: "suggestion", + defaultOptions: ["as-needed"], + docs: { description: "Require braces around arrow function bodies", recommended: false, @@ -71,7 +73,7 @@ module.exports = { create(context) { const options = context.options; const always = options[0] === "always"; - const asNeeded = !options[0] || options[0] === "as-needed"; + const asNeeded = options[0] === "as-needed"; const never = options[0] === "never"; const requireReturnForObjectLiteral = options[1] && options[1].requireReturnForObjectLiteral; const sourceCode = context.sourceCode; diff --git a/lib/rules/arrow-parens.js b/lib/rules/arrow-parens.js index 2206d8ce2bf..93964c755ce 100644 --- a/lib/rules/arrow-parens.js +++ b/lib/rules/arrow-parens.js @@ -35,6 +35,8 @@ module.exports = { replacedBy: [], type: "layout", + defaultOptions: ["always"], + docs: { description: "Require parentheses around arrow function arguments", recommended: false, @@ -51,8 +53,7 @@ module.exports = { type: "object", properties: { requireForBlockBody: { - type: "boolean", - default: false + type: "boolean" } }, additionalProperties: false diff --git a/lib/rules/arrow-spacing.js b/lib/rules/arrow-spacing.js index 2b7d464ffcf..5c3bb1b6f30 100644 --- a/lib/rules/arrow-spacing.js +++ b/lib/rules/arrow-spacing.js @@ -22,6 +22,11 @@ module.exports = { replacedBy: [], type: "layout", + defaultOptions: [{ + after: true, + before: true + }], + docs: { description: "Enforce consistent spacing before and after the arrow in arrow functions", recommended: false, @@ -35,12 +40,10 @@ module.exports = { type: "object", properties: { before: { - type: "boolean", - default: true + type: "boolean" }, after: { - type: "boolean", - default: true + type: "boolean" } }, additionalProperties: false @@ -57,13 +60,7 @@ module.exports = { }, create(context) { - - // merge rules with default - const rule = Object.assign({}, context.options[0]); - - rule.before = rule.before !== false; - rule.after = rule.after !== false; - + const [options] = context.options; const sourceCode = context.sourceCode; /** @@ -104,7 +101,7 @@ module.exports = { const tokens = getTokens(node); const countSpace = countSpaces(tokens); - if (rule.before) { + if (options.before) { // should be space(s) before arrow if (countSpace.before === 0) { @@ -130,7 +127,7 @@ module.exports = { } } - if (rule.after) { + if (options.after) { // should be space(s) after arrow if (countSpace.after === 0) { diff --git a/lib/rules/block-spacing.js b/lib/rules/block-spacing.js index 9ca461158d9..76050dd3c6e 100644 --- a/lib/rules/block-spacing.js +++ b/lib/rules/block-spacing.js @@ -19,6 +19,8 @@ module.exports = { replacedBy: [], type: "layout", + defaultOptions: ["always"], + docs: { description: "Disallow or enforce spaces inside of blocks after opening block and before closing block", recommended: false, diff --git a/lib/rules/brace-style.js b/lib/rules/brace-style.js index 0fb4c65e68d..d51f308118d 100644 --- a/lib/rules/brace-style.js +++ b/lib/rules/brace-style.js @@ -19,6 +19,8 @@ module.exports = { replacedBy: [], type: "layout", + defaultOptions: ["1tbs", { allowSingleLine: false }], + docs: { description: "Enforce consistent brace style for blocks", recommended: false, @@ -33,8 +35,7 @@ module.exports = { type: "object", properties: { allowSingleLine: { - type: "boolean", - default: false + type: "boolean" } }, additionalProperties: false @@ -54,8 +55,8 @@ module.exports = { }, create(context) { - const style = context.options[0] || "1tbs", - params = context.options[1] || {}, + const style = context.options[0], + params = context.options[1], sourceCode = context.sourceCode; //-------------------------------------------------------------------------- diff --git a/lib/rules/camelcase.js b/lib/rules/camelcase.js index 51bb4122df0..5e75df2e8f7 100644 --- a/lib/rules/camelcase.js +++ b/lib/rules/camelcase.js @@ -20,6 +20,14 @@ module.exports = { meta: { type: "suggestion", + defaultOptions: [{ + allow: [], + ignoreDestructuring: false, + ignoreGlobals: false, + ignoreImports: false, + properties: "always" + }], + docs: { description: "Enforce camelcase naming convention", recommended: false, @@ -31,16 +39,13 @@ module.exports = { type: "object", properties: { ignoreDestructuring: { - type: "boolean", - default: false + type: "boolean" }, ignoreImports: { - type: "boolean", - default: false + type: "boolean" }, ignoreGlobals: { - type: "boolean", - default: false + type: "boolean" }, properties: { enum: ["always", "never"] @@ -67,12 +72,13 @@ module.exports = { }, create(context) { - const options = context.options[0] || {}; - const properties = options.properties === "never" ? "never" : "always"; - const ignoreDestructuring = options.ignoreDestructuring; - const ignoreImports = options.ignoreImports; - const ignoreGlobals = options.ignoreGlobals; - const allow = options.allow || []; + const [{ + allow, + ignoreDestructuring, + ignoreGlobals, + ignoreImports, + properties + }] = context.options; const sourceCode = context.sourceCode; //-------------------------------------------------------------------------- diff --git a/lib/rules/class-methods-use-this.js b/lib/rules/class-methods-use-this.js index 9cf8a1b8a86..11054630328 100644 --- a/lib/rules/class-methods-use-this.js +++ b/lib/rules/class-methods-use-this.js @@ -20,6 +20,11 @@ module.exports = { meta: { type: "suggestion", + defaultOptions: [{ + enforceForClassFields: true, + exceptMethods: [] + }], + docs: { description: "Enforce that class methods utilize `this`", recommended: false, @@ -36,8 +41,7 @@ module.exports = { } }, enforceForClassFields: { - type: "boolean", - default: true + type: "boolean" } }, additionalProperties: false @@ -48,9 +52,9 @@ module.exports = { } }, create(context) { - const config = Object.assign({}, context.options[0]); - const enforceForClassFields = config.enforceForClassFields !== false; - const exceptMethods = new Set(config.exceptMethods || []); + const [options] = context.options; + const { enforceForClassFields } = options; + const exceptMethods = new Set(options.exceptMethods); const stack = []; diff --git a/lib/rules/comma-dangle.js b/lib/rules/comma-dangle.js index c24c1475444..5e0cfcc2044 100644 --- a/lib/rules/comma-dangle.js +++ b/lib/rules/comma-dangle.js @@ -16,14 +16,6 @@ const astUtils = require("./utils/ast-utils"); // Helpers //------------------------------------------------------------------------------ -const DEFAULT_OPTIONS = Object.freeze({ - arrays: "never", - objects: "never", - imports: "never", - exports: "never", - functions: "never" -}); - /** * Checks whether or not a trailing comma is allowed in a given node. * If the `lastItem` is `RestElement` or `RestProperty`, it disallows trailing commas. @@ -54,17 +46,8 @@ function normalizeOptions(optionValue, ecmaVersion) { functions: ecmaVersion < 2017 ? "ignore" : optionValue }; } - if (typeof optionValue === "object" && optionValue !== null) { - return { - arrays: optionValue.arrays || DEFAULT_OPTIONS.arrays, - objects: optionValue.objects || DEFAULT_OPTIONS.objects, - imports: optionValue.imports || DEFAULT_OPTIONS.imports, - exports: optionValue.exports || DEFAULT_OPTIONS.exports, - functions: optionValue.functions || DEFAULT_OPTIONS.functions - }; - } - return DEFAULT_OPTIONS; + return optionValue; } //------------------------------------------------------------------------------ @@ -78,6 +61,14 @@ module.exports = { replacedBy: [], type: "layout", + defaultOptions: [{ + arrays: "never", + objects: "never", + imports: "never", + exports: "never", + functions: "never" + }], + docs: { description: "Require or disallow trailing commas", recommended: false, diff --git a/lib/rules/comma-spacing.js b/lib/rules/comma-spacing.js index e266de4a9c3..44c1689459e 100644 --- a/lib/rules/comma-spacing.js +++ b/lib/rules/comma-spacing.js @@ -18,6 +18,8 @@ module.exports = { replacedBy: [], type: "layout", + defaultOptions: [{ before: false, after: true }], + docs: { description: "Enforce consistent spacing before and after commas", recommended: false, @@ -53,11 +55,7 @@ module.exports = { const sourceCode = context.sourceCode; const tokensAndComments = sourceCode.tokensAndComments; - - const options = { - before: context.options[0] ? context.options[0].before : false, - after: context.options[0] ? context.options[0].after : true - }; + const [options] = context.options; //-------------------------------------------------------------------------- // Helpers diff --git a/lib/rules/comma-style.js b/lib/rules/comma-style.js index d632a3bae56..9f68308ec95 100644 --- a/lib/rules/comma-style.js +++ b/lib/rules/comma-style.js @@ -19,6 +19,19 @@ module.exports = { replacedBy: [], type: "layout", + defaultOptions: ["last", { + exceptions: { + ArrayPattern: true, + ArrowFunctionExpression: true, + CallExpression: true, + FunctionDeclaration: true, + FunctionExpression: true, + ImportDeclaration: true, + ObjectPattern: true, + NewExpression: true + } + }], + docs: { description: "Enforce consistent comma style", recommended: false, @@ -53,26 +66,8 @@ module.exports = { }, create(context) { - const style = context.options[0] || "last", + const [style, { exceptions }] = context.options, sourceCode = context.sourceCode; - const exceptions = { - ArrayPattern: true, - ArrowFunctionExpression: true, - CallExpression: true, - FunctionDeclaration: true, - FunctionExpression: true, - ImportDeclaration: true, - ObjectPattern: true, - NewExpression: true - }; - - if (context.options.length === 2 && Object.hasOwn(context.options[1], "exceptions")) { - const keys = Object.keys(context.options[1].exceptions); - - for (let i = 0; i < keys.length; i++) { - exceptions[keys[i]] = context.options[1].exceptions[keys[i]]; - } - } //-------------------------------------------------------------------------- // Helpers diff --git a/lib/rules/complexity.js b/lib/rules/complexity.js index 647de7b4d3b..9c6cbc12cfb 100644 --- a/lib/rules/complexity.js +++ b/lib/rules/complexity.js @@ -17,11 +17,15 @@ const { upperCaseFirst } = require("../shared/string-utils"); // Rule Definition //------------------------------------------------------------------------------ +const THRESHOLD_DEFAULT = 20; + /** @type {import('../shared/types').Rule} */ module.exports = { meta: { type: "suggestion", + defaultOptions: [THRESHOLD_DEFAULT], + docs: { description: "Enforce a maximum cyclomatic complexity allowed in a program", recommended: false, @@ -60,15 +64,15 @@ module.exports = { create(context) { const option = context.options[0]; - let THRESHOLD = 20; + let threshold = THRESHOLD_DEFAULT; if ( typeof option === "object" && (Object.hasOwn(option, "maximum") || Object.hasOwn(option, "max")) ) { - THRESHOLD = option.maximum || option.max; + threshold = option.maximum || option.max; } else if (typeof option === "number") { - THRESHOLD = option; + threshold = option; } //-------------------------------------------------------------------------- @@ -150,7 +154,7 @@ module.exports = { return; } - if (complexity > THRESHOLD) { + if (complexity > threshold) { let name; if (codePath.origin === "class-field-initializer") { @@ -167,7 +171,7 @@ module.exports = { data: { name: upperCaseFirst(name), complexity, - max: THRESHOLD + max: threshold } }); } diff --git a/lib/rules/computed-property-spacing.js b/lib/rules/computed-property-spacing.js index 2852877fddf..b7b0b4b5168 100644 --- a/lib/rules/computed-property-spacing.js +++ b/lib/rules/computed-property-spacing.js @@ -42,6 +42,8 @@ module.exports = { } ], + defaultOptions: ["never", { enforceForClassMembers: true }], + messages: { unexpectedSpaceBefore: "There should be no space before '{{tokenValue}}'.", unexpectedSpaceAfter: "There should be no space after '{{tokenValue}}'.", diff --git a/lib/rules/consistent-return.js b/lib/rules/consistent-return.js index 304e924b14a..f31c2e10025 100644 --- a/lib/rules/consistent-return.js +++ b/lib/rules/consistent-return.js @@ -62,13 +62,14 @@ module.exports = { type: "object", properties: { treatUndefinedAsUnspecified: { - type: "boolean", - default: false + type: "boolean" } }, additionalProperties: false }], + defaultOptions: [{ treatUndefinedAsUnspecified: false }], + messages: { missingReturn: "Expected to return a value at the end of {{name}}.", missingReturnValue: "{{name}} expected a return value.", @@ -77,8 +78,7 @@ module.exports = { }, create(context) { - const options = context.options[0] || {}; - const treatUndefinedAsUnspecified = options.treatUndefinedAsUnspecified === true; + const [{ treatUndefinedAsUnspecified }] = context.options; let funcInfo = null; /** diff --git a/lib/rules/consistent-this.js b/lib/rules/consistent-this.js index 658957ae25b..9a76e7a49a8 100644 --- a/lib/rules/consistent-this.js +++ b/lib/rules/consistent-this.js @@ -28,6 +28,8 @@ module.exports = { uniqueItems: true }, + defaultOptions: ["that"], + messages: { aliasNotAssignedToThis: "Designated alias '{{name}}' is not assigned to 'this'.", unexpectedAlias: "Unexpected alias '{{name}}' for 'this'." @@ -35,15 +37,9 @@ module.exports = { }, create(context) { - let aliases = []; + const aliases = context.options; const sourceCode = context.sourceCode; - if (context.options.length === 0) { - aliases.push("that"); - } else { - aliases = context.options; - } - /** * Reports that a variable declarator or assignment expression is assigning * a non-'this' value to the specified alias. diff --git a/lib/rules/curly.js b/lib/rules/curly.js index 35408247a19..f0c1b215d8e 100644 --- a/lib/rules/curly.js +++ b/lib/rules/curly.js @@ -53,6 +53,8 @@ module.exports = { ] }, + defaultOptions: ["all"], + fixable: "code", messages: { @@ -64,7 +66,6 @@ module.exports = { }, create(context) { - const multiOnly = (context.options[0] === "multi"); const multiLine = (context.options[0] === "multi-line"); const multiOrNest = (context.options[0] === "multi-or-nest"); diff --git a/lib/rules/default-case.js b/lib/rules/default-case.js index 1f83cef3b11..9fa7acd3048 100644 --- a/lib/rules/default-case.js +++ b/lib/rules/default-case.js @@ -15,6 +15,8 @@ module.exports = { meta: { type: "suggestion", + defaultOptions: [{}], + docs: { description: "Require `default` cases in `switch` statements", recommended: false, @@ -37,7 +39,7 @@ module.exports = { }, create(context) { - const options = context.options[0] || {}; + const [options] = context.options; const commentPattern = options.commentPattern ? new RegExp(options.commentPattern, "u") : DEFAULT_COMMENT_PATTERN; diff --git a/lib/rules/dot-notation.js b/lib/rules/dot-notation.js index 21cba54e2a5..e3e3a9f601a 100644 --- a/lib/rules/dot-notation.js +++ b/lib/rules/dot-notation.js @@ -25,6 +25,8 @@ module.exports = { meta: { type: "suggestion", + defaultOptions: [{}], + docs: { description: "Enforce dot notation whenever possible", recommended: false, @@ -57,7 +59,7 @@ module.exports = { }, create(context) { - const options = context.options[0] || {}; + const [options] = context.options; const allowKeywords = options.allowKeywords === void 0 || options.allowKeywords; const sourceCode = context.sourceCode; diff --git a/lib/rules/func-names.js b/lib/rules/func-names.js index b180580e114..6990f0c7ba5 100644 --- a/lib/rules/func-names.js +++ b/lib/rules/func-names.js @@ -29,6 +29,8 @@ module.exports = { meta: { type: "suggestion", + defaultOptions: ["always", {}], + docs: { description: "Require or disallow named `function` expressions", recommended: false, @@ -68,7 +70,6 @@ module.exports = { }, create(context) { - const sourceCode = context.sourceCode; /** @@ -79,13 +80,12 @@ module.exports = { function getConfigForNode(node) { if ( node.generator && - context.options.length > 1 && context.options[1].generators ) { return context.options[1].generators; } - return context.options[0] || "always"; + return context.options[0]; } /** diff --git a/lib/rules/func-style.js b/lib/rules/func-style.js index ab83772ef5f..6bb0ff07ccb 100644 --- a/lib/rules/func-style.js +++ b/lib/rules/func-style.js @@ -13,6 +13,8 @@ module.exports = { meta: { type: "suggestion", + defaultOptions: ["expression", {}], + docs: { description: "Enforce the consistent use of either `function` declarations or expressions", recommended: false, @@ -27,8 +29,7 @@ module.exports = { type: "object", properties: { allowArrowFunctions: { - type: "boolean", - default: false + type: "boolean" } }, additionalProperties: false @@ -42,11 +43,9 @@ module.exports = { }, create(context) { - - const style = context.options[0], - allowArrowFunctions = context.options[1] && context.options[1].allowArrowFunctions, - enforceDeclarations = (style === "declaration"), - stack = []; + const [style, { allowArrowFunctions }] = context.options; + const enforceDeclarations = (style === "declaration"); + const stack = []; const nodesToCheck = { FunctionDeclaration(node) { diff --git a/lib/rules/generator-star-spacing.js b/lib/rules/generator-star-spacing.js index c633f979f84..65538c82374 100644 --- a/lib/rules/generator-star-spacing.js +++ b/lib/rules/generator-star-spacing.js @@ -62,6 +62,11 @@ module.exports = { } ], + defaultOptions: [{ + after: false, + before: true + }], + messages: { missingBefore: "Missing space before *.", missingAfter: "Missing space after *.", diff --git a/lib/rules/getter-return.js b/lib/rules/getter-return.js index 79ebf3e0902..843dfa2d4d6 100644 --- a/lib/rules/getter-return.js +++ b/lib/rules/getter-return.js @@ -42,6 +42,8 @@ module.exports = { meta: { type: "problem", + defaultOptions: [{}], + docs: { description: "Enforce `return` statements in getters", recommended: true, @@ -55,8 +57,7 @@ module.exports = { type: "object", properties: { allowImplicit: { - type: "boolean", - default: false + type: "boolean" } }, additionalProperties: false @@ -70,8 +71,7 @@ module.exports = { }, create(context) { - - const options = context.options[0] || { allowImplicit: false }; + const [{ allowImplicit }] = context.options; const sourceCode = context.sourceCode; let funcInfo = { @@ -184,7 +184,7 @@ module.exports = { funcInfo.hasReturn = true; // if allowImplicit: false, should also check node.argument - if (!options.allowImplicit && !node.argument) { + if (!allowImplicit && !node.argument) { context.report({ node, messageId: "expected", diff --git a/lib/rules/grouped-accessor-pairs.js b/lib/rules/grouped-accessor-pairs.js index 9556f475682..cd627b479ba 100644 --- a/lib/rules/grouped-accessor-pairs.js +++ b/lib/rules/grouped-accessor-pairs.js @@ -95,6 +95,8 @@ module.exports = { meta: { type: "suggestion", + defaultOptions: ["anyOrder"], + docs: { description: "Require grouped accessor pairs in object literals and classes", recommended: false, @@ -114,7 +116,7 @@ module.exports = { }, create(context) { - const order = context.options[0] || "anyOrder"; + const [order] = context.options; const sourceCode = context.sourceCode; /** diff --git a/lib/rules/id-denylist.js b/lib/rules/id-denylist.js index baaa65fe01a..747192da569 100644 --- a/lib/rules/id-denylist.js +++ b/lib/rules/id-denylist.js @@ -98,6 +98,8 @@ module.exports = { meta: { type: "suggestion", + defaultOptions: [], + docs: { description: "Disallow specified identifiers", recommended: false, @@ -118,7 +120,6 @@ module.exports = { }, create(context) { - const denyList = new Set(context.options); const reportedNodes = new Set(); const sourceCode = context.sourceCode; diff --git a/lib/rules/id-length.js b/lib/rules/id-length.js index 97bc0e43006..ee1d80528b3 100644 --- a/lib/rules/id-length.js +++ b/lib/rules/id-length.js @@ -21,6 +21,12 @@ module.exports = { meta: { type: "suggestion", + defaultOptions: [{ + exceptionPatterns: [], + exceptions: [], + min: 2 + }], + docs: { description: "Enforce minimum and maximum identifier lengths", recommended: false, @@ -32,8 +38,7 @@ module.exports = { type: "object", properties: { min: { - type: "integer", - default: 2 + type: "integer" }, max: { type: "integer" @@ -68,12 +73,11 @@ module.exports = { }, create(context) { - const options = context.options[0] || {}; - const minLength = typeof options.min !== "undefined" ? options.min : 2; - const maxLength = typeof options.max !== "undefined" ? options.max : Infinity; + const [options] = context.options; + const { max: maxLength = Infinity, min: minLength } = options; const properties = options.properties !== "never"; const exceptions = new Set(options.exceptions); - const exceptionPatterns = (options.exceptionPatterns || []).map(pattern => new RegExp(pattern, "u")); + const exceptionPatterns = options.exceptionPatterns.map(pattern => new RegExp(pattern, "u")); const reportedNodes = new Set(); /** diff --git a/lib/rules/id-match.js b/lib/rules/id-match.js index e225454e771..820429abce8 100644 --- a/lib/rules/id-match.js +++ b/lib/rules/id-match.js @@ -14,6 +14,8 @@ module.exports = { meta: { type: "suggestion", + defaultOptions: ["^.+$", {}], + docs: { description: "Require identifiers to match a specified regular expression", recommended: false, @@ -28,20 +30,16 @@ module.exports = { type: "object", properties: { properties: { - type: "boolean", - default: false + type: "boolean" }, classFields: { - type: "boolean", - default: false + type: "boolean" }, onlyDeclarations: { - type: "boolean", - default: false + type: "boolean" }, ignoreDestructuring: { - type: "boolean", - default: false + type: "boolean" } }, additionalProperties: false @@ -58,14 +56,13 @@ module.exports = { //-------------------------------------------------------------------------- // Options //-------------------------------------------------------------------------- - const pattern = context.options[0] || "^.+$", - regexp = new RegExp(pattern, "u"); - - const options = context.options[1] || {}, - checkProperties = !!options.properties, - checkClassFields = !!options.classFields, - onlyDeclarations = !!options.onlyDeclarations, - ignoreDestructuring = !!options.ignoreDestructuring; + const [pattern, { + classFields: checkClassFields, + ignoreDestructuring, + onlyDeclarations, + properties: checkProperties + }] = context.options; + const regexp = new RegExp(pattern, "u"); const sourceCode = context.sourceCode; let globalScope; diff --git a/lib/rules/indent.js b/lib/rules/indent.js index bc812b13c3e..77886781c5b 100644 --- a/lib/rules/indent.js +++ b/lib/rules/indent.js @@ -605,12 +605,10 @@ module.exports = { ObjectExpression: ELEMENT_LIST_SCHEMA, ImportDeclaration: ELEMENT_LIST_SCHEMA, flatTernaryExpressions: { - type: "boolean", - default: false + type: "boolean" }, offsetTernaryExpressions: { - type: "boolean", - default: false + type: "boolean" }, ignoredNodes: { type: "array", @@ -622,13 +620,15 @@ module.exports = { } }, ignoreComments: { - type: "boolean", - default: false + type: "boolean" } }, additionalProperties: false } ], + + defaultOptions: [4], + messages: { wrongIndentation: "Expected indentation of {{expected}} but found {{actual}}." } diff --git a/lib/rules/lines-around-comment.js b/lib/rules/lines-around-comment.js index 2a6e472f9a0..61ea347212e 100644 --- a/lib/rules/lines-around-comment.js +++ b/lib/rules/lines-around-comment.js @@ -70,28 +70,22 @@ module.exports = { type: "object", properties: { beforeBlockComment: { - type: "boolean", - default: true + type: "boolean" }, afterBlockComment: { - type: "boolean", - default: false + type: "boolean" }, beforeLineComment: { - type: "boolean", - default: false + type: "boolean" }, afterLineComment: { - type: "boolean", - default: false + type: "boolean" }, allowBlockStart: { - type: "boolean", - default: false + type: "boolean" }, allowBlockEnd: { - type: "boolean", - default: false + type: "boolean" }, allowClassStart: { type: "boolean" @@ -118,13 +112,18 @@ module.exports = { type: "boolean" }, afterHashbangComment: { - type: "boolean", - default: false + type: "boolean" } }, additionalProperties: false } ], + + defaultOptions: [{ + applyDefaultIgnorePatterns: true, + beforeBlockComment: true + }], + messages: { after: "Expected line after comment.", before: "Expected line before comment." @@ -132,14 +131,10 @@ module.exports = { }, create(context) { - - const options = Object.assign({}, context.options[0]); - const ignorePattern = options.ignorePattern; + const [options] = context.options; + const { applyDefaultIgnorePatterns, ignorePattern } = options; const defaultIgnoreRegExp = astUtils.COMMENTS_IGNORE_PATTERN; const customIgnoreRegExp = new RegExp(ignorePattern, "u"); - const applyDefaultIgnorePatterns = options.applyDefaultIgnorePatterns !== false; - - options.beforeBlockComment = typeof options.beforeBlockComment !== "undefined" ? options.beforeBlockComment : true; const sourceCode = context.sourceCode; diff --git a/lib/rules/new-cap.js b/lib/rules/new-cap.js index 638e64c0a22..0b8efd995c8 100644 --- a/lib/rules/new-cap.js +++ b/lib/rules/new-cap.js @@ -92,12 +92,10 @@ module.exports = { type: "object", properties: { newIsCap: { - type: "boolean", - default: true + type: "boolean" }, capIsNew: { - type: "boolean", - default: true + type: "boolean" }, newIsCapExceptions: { type: "array", @@ -118,13 +116,19 @@ module.exports = { type: "string" }, properties: { - type: "boolean", - default: true + type: "boolean" } }, additionalProperties: false } ], + + defaultOptions: [{ + capIsNew: true, + newIsCap: true, + properties: true + }], + messages: { upper: "A function with a name starting with an uppercase letter should only be used as a constructor.", lower: "A constructor name should not start with a lowercase letter." @@ -132,11 +136,7 @@ module.exports = { }, create(context) { - - const config = Object.assign({}, context.options[0]); - - config.newIsCap = config.newIsCap !== false; - config.capIsNew = config.capIsNew !== false; + const [config] = context.options; const skipProperties = config.properties === false; const newIsCapExceptions = checkArray(config, "newIsCapExceptions", []).reduce(invert, {}); diff --git a/lib/rules/no-bitwise.js b/lib/rules/no-bitwise.js index d90992b2064..f904f994cfb 100644 --- a/lib/rules/no-bitwise.js +++ b/lib/rules/no-bitwise.js @@ -25,6 +25,8 @@ module.exports = { meta: { type: "suggestion", + defaultOptions: [{ allow: [] }], + docs: { description: "Disallow bitwise operators", recommended: false, @@ -43,8 +45,7 @@ module.exports = { uniqueItems: true }, int32Hint: { - type: "boolean", - default: false + type: "boolean" } }, additionalProperties: false @@ -57,9 +58,7 @@ module.exports = { }, create(context) { - const options = context.options[0] || {}; - const allowed = options.allow || []; - const int32Hint = options.int32Hint === true; + const [{ allow: allowed, int32Hint }] = context.options; /** * Reports an unexpected use of a bitwise operator. diff --git a/lib/rules/no-cond-assign.js b/lib/rules/no-cond-assign.js index 952920215aa..6d4135d08ec 100644 --- a/lib/rules/no-cond-assign.js +++ b/lib/rules/no-cond-assign.js @@ -33,6 +33,8 @@ module.exports = { meta: { type: "problem", + defaultOptions: ["except-parens"], + docs: { description: "Disallow assignment operators in conditional expressions", recommended: true, @@ -54,9 +56,7 @@ module.exports = { }, create(context) { - - const prohibitAssign = (context.options[0] || "except-parens"); - + const [prohibitAssign] = context.options; const sourceCode = context.sourceCode; /** diff --git a/lib/rules/no-console.js b/lib/rules/no-console.js index d20477c5d9a..62dc1693836 100644 --- a/lib/rules/no-console.js +++ b/lib/rules/no-console.js @@ -20,6 +20,8 @@ module.exports = { meta: { type: "suggestion", + defaultOptions: [{}], + docs: { description: "Disallow the use of `console`", recommended: false, @@ -52,8 +54,7 @@ module.exports = { }, create(context) { - const options = context.options[0] || {}; - const allowed = options.allow || []; + const [{ allow: allowed = [] }] = context.options; const sourceCode = context.sourceCode; /** diff --git a/lib/rules/no-constant-condition.js b/lib/rules/no-constant-condition.js index 24abe363280..f6570d32352 100644 --- a/lib/rules/no-constant-condition.js +++ b/lib/rules/no-constant-condition.js @@ -20,6 +20,8 @@ module.exports = { meta: { type: "problem", + defaultOptions: [{ checkLoops: true }], + docs: { description: "Disallow constant expressions in conditions", recommended: true, @@ -31,8 +33,7 @@ module.exports = { type: "object", properties: { checkLoops: { - type: "boolean", - default: true + type: "boolean" } }, additionalProperties: false @@ -45,9 +46,8 @@ module.exports = { }, create(context) { - const options = context.options[0] || {}, - checkLoops = options.checkLoops !== false, - loopSetStack = []; + const [{ checkLoops }] = context.options; + const loopSetStack = []; const sourceCode = context.sourceCode; let loopsInCurrentScope = new Set(); diff --git a/lib/rules/no-duplicate-imports.js b/lib/rules/no-duplicate-imports.js index 25c07b7500d..7d584e22b42 100644 --- a/lib/rules/no-duplicate-imports.js +++ b/lib/rules/no-duplicate-imports.js @@ -232,6 +232,8 @@ module.exports = { meta: { type: "problem", + defaultOptions: [{}], + docs: { description: "Disallow duplicate module imports", recommended: false, @@ -243,8 +245,7 @@ module.exports = { type: "object", properties: { includeExports: { - type: "boolean", - default: false + type: "boolean" } }, additionalProperties: false @@ -260,8 +261,8 @@ module.exports = { }, create(context) { - const includeExports = (context.options[0] || {}).includeExports, - modules = new Map(); + const [{ includeExports }] = context.options; + const modules = new Map(); const handlers = { ImportDeclaration: handleImportsExports( context, diff --git a/lib/rules/no-else-return.js b/lib/rules/no-else-return.js index 6e6bf476dd8..d456181b594 100644 --- a/lib/rules/no-else-return.js +++ b/lib/rules/no-else-return.js @@ -21,6 +21,8 @@ module.exports = { meta: { type: "suggestion", + defaultOptions: [{ allowElseIf: true }], + docs: { description: "Disallow `else` blocks after `return` statements in `if` statements", recommended: false, @@ -31,8 +33,7 @@ module.exports = { type: "object", properties: { allowElseIf: { - type: "boolean", - default: true + type: "boolean" } }, additionalProperties: false @@ -46,7 +47,7 @@ module.exports = { }, create(context) { - + const [{ allowElseIf }] = context.options; const sourceCode = context.sourceCode; //-------------------------------------------------------------------------- @@ -389,8 +390,6 @@ module.exports = { } } - const allowElseIf = !(context.options[0] && context.options[0].allowElseIf === false); - //-------------------------------------------------------------------------- // Public API //-------------------------------------------------------------------------- diff --git a/lib/rules/no-empty-function.js b/lib/rules/no-empty-function.js index b7dee94c4ea..16e611bb138 100644 --- a/lib/rules/no-empty-function.js +++ b/lib/rules/no-empty-function.js @@ -94,6 +94,8 @@ module.exports = { meta: { type: "suggestion", + defaultOptions: [{ allow: [] }], + docs: { description: "Disallow empty functions", recommended: false, @@ -120,9 +122,7 @@ module.exports = { }, create(context) { - const options = context.options[0] || {}; - const allowed = options.allow || []; - + const [{ allow }] = context.options; const sourceCode = context.sourceCode; /** @@ -144,7 +144,7 @@ module.exports = { filter: astUtils.isCommentToken }); - if (!allowed.includes(kind) && + if (!allow.includes(kind) && node.body.type === "BlockStatement" && node.body.body.length === 0 && innerComments.length === 0 diff --git a/lib/rules/no-empty-pattern.js b/lib/rules/no-empty-pattern.js index fb75f6d25b3..a9756ef5f0e 100644 --- a/lib/rules/no-empty-pattern.js +++ b/lib/rules/no-empty-pattern.js @@ -15,6 +15,8 @@ module.exports = { meta: { type: "problem", + defaultOptions: [{}], + docs: { description: "Disallow empty destructuring patterns", recommended: true, @@ -26,8 +28,7 @@ module.exports = { type: "object", properties: { allowObjectPatternsAsParameters: { - type: "boolean", - default: false + type: "boolean" } }, additionalProperties: false @@ -40,8 +41,7 @@ module.exports = { }, create(context) { - const options = context.options[0] || {}, - allowObjectPatternsAsParameters = options.allowObjectPatternsAsParameters || false; + const [{ allowObjectPatternsAsParameters }] = context.options; return { ObjectPattern(node) { diff --git a/lib/rules/no-empty.js b/lib/rules/no-empty.js index 1c157963e9d..01119cfb86b 100644 --- a/lib/rules/no-empty.js +++ b/lib/rules/no-empty.js @@ -20,6 +20,8 @@ module.exports = { hasSuggestions: true, type: "suggestion", + defaultOptions: [{}], + docs: { description: "Disallow empty block statements", recommended: true, @@ -31,8 +33,7 @@ module.exports = { type: "object", properties: { allowEmptyCatch: { - type: "boolean", - default: false + type: "boolean" } }, additionalProperties: false @@ -46,9 +47,7 @@ module.exports = { }, create(context) { - const options = context.options[0] || {}, - allowEmptyCatch = options.allowEmptyCatch || false; - + const [{ allowEmptyCatch }] = context.options; const sourceCode = context.sourceCode; return { diff --git a/lib/rules/no-eval.js b/lib/rules/no-eval.js index a059526a68b..30e3860931f 100644 --- a/lib/rules/no-eval.js +++ b/lib/rules/no-eval.js @@ -42,6 +42,8 @@ module.exports = { meta: { type: "suggestion", + defaultOptions: [{}], + docs: { description: "Disallow the use of `eval()`", recommended: false, @@ -52,7 +54,7 @@ module.exports = { { type: "object", properties: { - allowIndirect: { type: "boolean", default: false } + allowIndirect: { type: "boolean" } }, additionalProperties: false } @@ -64,10 +66,7 @@ module.exports = { }, create(context) { - const allowIndirect = Boolean( - context.options[0] && - context.options[0].allowIndirect - ); + const [{ allowIndirect }] = context.options; const sourceCode = context.sourceCode; let funcInfo = null; diff --git a/lib/rules/no-extend-native.js b/lib/rules/no-extend-native.js index a69dd690039..7164c091c9f 100644 --- a/lib/rules/no-extend-native.js +++ b/lib/rules/no-extend-native.js @@ -20,6 +20,8 @@ module.exports = { meta: { type: "suggestion", + defaultOptions: [{ exceptions: [] }], + docs: { description: "Disallow extending native types", recommended: false, @@ -48,10 +50,8 @@ module.exports = { }, create(context) { - - const config = context.options[0] || {}; const sourceCode = context.sourceCode; - const exceptions = new Set(config.exceptions || []); + const exceptions = new Set(context.options[0].exceptions); const modifiedBuiltins = new Set( Object.keys(astUtils.ECMASCRIPT_GLOBALS) .filter(builtin => builtin[0].toUpperCase() === builtin[0]) diff --git a/lib/rules/no-extra-boolean-cast.js b/lib/rules/no-extra-boolean-cast.js index f342533bfc9..dacaa0eecb8 100644 --- a/lib/rules/no-extra-boolean-cast.js +++ b/lib/rules/no-extra-boolean-cast.js @@ -23,6 +23,8 @@ module.exports = { meta: { type: "suggestion", + defaultOptions: [{}], + docs: { description: "Disallow unnecessary boolean casts", recommended: true, @@ -33,8 +35,7 @@ module.exports = { type: "object", properties: { enforceForLogicalOperands: { - type: "boolean", - default: false + type: "boolean" } }, additionalProperties: false @@ -49,6 +50,7 @@ module.exports = { create(context) { const sourceCode = context.sourceCode; + const [{ enforceForLogicalOperands }] = context.options; // Node types which have a test which will coerce values to booleans. const BOOLEAN_NODE_TYPES = new Set([ @@ -80,7 +82,7 @@ module.exports = { function isLogicalContext(node) { return node.type === "LogicalExpression" && (node.operator === "||" || node.operator === "&&") && - (context.options.length && context.options[0].enforceForLogicalOperands === true); + (enforceForLogicalOperands === true); } diff --git a/lib/rules/no-fallthrough.js b/lib/rules/no-fallthrough.js index 4c98ddde525..e8925e451f2 100644 --- a/lib/rules/no-fallthrough.js +++ b/lib/rules/no-fallthrough.js @@ -90,6 +90,8 @@ module.exports = { meta: { type: "problem", + defaultOptions: [{}], + docs: { description: "Disallow fallthrough of `case` statements", recommended: true, @@ -101,16 +103,13 @@ module.exports = { type: "object", properties: { commentPattern: { - type: "string", - default: "" + type: "string" }, allowEmptyCase: { - type: "boolean", - default: false + type: "boolean" }, reportUnusedFallthroughComment: { - type: "boolean", - default: false + type: "boolean" } }, additionalProperties: false @@ -124,25 +123,20 @@ module.exports = { }, create(context) { - const options = context.options[0] || {}; const codePathSegments = []; let currentCodePathSegments = new Set(); const sourceCode = context.sourceCode; - const allowEmptyCase = options.allowEmptyCase || false; - const reportUnusedFallthroughComment = options.reportUnusedFallthroughComment || false; + const [{ allowEmptyCase, commentPattern, reportUnusedFallthroughComment }] = context.options; + const fallthroughCommentPattern = commentPattern + ? new RegExp(commentPattern, "u") + : DEFAULT_FALLTHROUGH_COMMENT; /* * We need to use leading comments of the next SwitchCase node because * trailing comments is wrong if semicolons are omitted. */ let previousCase = null; - let fallthroughCommentPattern = null; - if (options.commentPattern) { - fallthroughCommentPattern = new RegExp(options.commentPattern, "u"); - } else { - fallthroughCommentPattern = DEFAULT_FALLTHROUGH_COMMENT; - } return { onCodePathStart() { diff --git a/lib/rules/no-global-assign.js b/lib/rules/no-global-assign.js index 99ae7a2ee5e..0a6f65eb56e 100644 --- a/lib/rules/no-global-assign.js +++ b/lib/rules/no-global-assign.js @@ -14,6 +14,8 @@ module.exports = { meta: { type: "suggestion", + defaultOptions: [{ exceptions: [] }], + docs: { description: "Disallow assignments to native objects or read-only global variables", recommended: true, @@ -40,9 +42,8 @@ module.exports = { }, create(context) { - const config = context.options[0]; const sourceCode = context.sourceCode; - const exceptions = (config && config.exceptions) || []; + const [{ exceptions }] = context.options; /** * Reports write references. diff --git a/lib/rules/no-implicit-coercion.js b/lib/rules/no-implicit-coercion.js index 3f8a7c0f941..e82638fddb6 100644 --- a/lib/rules/no-implicit-coercion.js +++ b/lib/rules/no-implicit-coercion.js @@ -14,21 +14,6 @@ const astUtils = require("./utils/ast-utils"); const INDEX_OF_PATTERN = /^(?:i|lastI)ndexOf$/u; const ALLOWABLE_OPERATORS = ["~", "!!", "+", "- -", "-", "*"]; -/** - * Parses and normalizes an option object. - * @param {Object} options An option object to parse. - * @returns {Object} The parsed and normalized option object. - */ -function parseOptions(options) { - return { - boolean: "boolean" in options ? options.boolean : true, - number: "number" in options ? options.number : true, - string: "string" in options ? options.string : true, - disallowTemplateShorthand: "disallowTemplateShorthand" in options ? options.disallowTemplateShorthand : false, - allow: options.allow || [] - }; -} - /** * Checks whether or not a node is a double logical negating. * @param {ASTNode} node An UnaryExpression node to check. @@ -203,20 +188,16 @@ module.exports = { type: "object", properties: { boolean: { - type: "boolean", - default: true + type: "boolean" }, number: { - type: "boolean", - default: true + type: "boolean" }, string: { - type: "boolean", - default: true + type: "boolean" }, disallowTemplateShorthand: { - type: "boolean", - default: false + type: "boolean" }, allow: { type: "array", @@ -229,6 +210,14 @@ module.exports = { additionalProperties: false }], + defaultOptions: [{ + allow: [], + boolean: true, + disallowTemplateShorthand: false, + number: true, + string: true + }], + messages: { implicitCoercion: "Unexpected implicit coercion encountered. Use `{{recommendation}}` instead.", useRecommendation: "Use `{{recommendation}}` instead." @@ -236,7 +225,7 @@ module.exports = { }, create(context) { - const options = parseOptions(context.options[0] || {}); + const [options] = context.options; const sourceCode = context.sourceCode; /** diff --git a/lib/rules/no-implicit-globals.js b/lib/rules/no-implicit-globals.js index 2a182477c0e..a4efa0eb56c 100644 --- a/lib/rules/no-implicit-globals.js +++ b/lib/rules/no-implicit-globals.js @@ -14,6 +14,8 @@ module.exports = { meta: { type: "suggestion", + defaultOptions: [{}], + docs: { description: "Disallow declarations in the global scope", recommended: false, @@ -24,8 +26,7 @@ module.exports = { type: "object", properties: { lexicalBindings: { - type: "boolean", - default: false + type: "boolean" } }, additionalProperties: false @@ -41,8 +42,7 @@ module.exports = { }, create(context) { - - const checkLexicalBindings = context.options[0] && context.options[0].lexicalBindings === true; + const [{ lexicalBindings: checkLexicalBindings }] = context.options; const sourceCode = context.sourceCode; /** diff --git a/lib/rules/no-inline-comments.js b/lib/rules/no-inline-comments.js index d96e6472d13..439418c7b11 100644 --- a/lib/rules/no-inline-comments.js +++ b/lib/rules/no-inline-comments.js @@ -15,6 +15,8 @@ module.exports = { meta: { type: "suggestion", + defaultOptions: [{}], + docs: { description: "Disallow inline comments after code", recommended: false, @@ -40,12 +42,8 @@ module.exports = { create(context) { const sourceCode = context.sourceCode; - const options = context.options[0]; - let customIgnoreRegExp; - - if (options && options.ignorePattern) { - customIgnoreRegExp = new RegExp(options.ignorePattern, "u"); - } + const [{ ignorePattern }] = context.options; + const customIgnoreRegExp = ignorePattern && new RegExp(ignorePattern, "u"); /** * Will check that comments are not on lines starting with or ending with code diff --git a/lib/rules/no-inner-declarations.js b/lib/rules/no-inner-declarations.js index a690d95bb9c..e31ddd13fe7 100644 --- a/lib/rules/no-inner-declarations.js +++ b/lib/rules/no-inner-declarations.js @@ -47,6 +47,8 @@ module.exports = { meta: { type: "problem", + defaultOptions: ["functions"], + docs: { description: "Disallow variable or `function` declarations in nested blocks", recommended: false, @@ -74,6 +76,7 @@ module.exports = { }, create(context) { + const both = context.options[0] === "both"; const sourceCode = context.sourceCode; const ecmaVersion = context.languageOptions.ecmaVersion; @@ -107,7 +110,6 @@ module.exports = { }); } - return { FunctionDeclaration(node) { @@ -120,7 +122,7 @@ module.exports = { check(node); }, VariableDeclaration(node) { - if (context.options[0] === "both" && node.kind === "var") { + if (both && node.kind === "var") { check(node); } } diff --git a/lib/rules/no-invalid-regexp.js b/lib/rules/no-invalid-regexp.js index 99fa6295af2..caa5fd52237 100644 --- a/lib/rules/no-invalid-regexp.js +++ b/lib/rules/no-invalid-regexp.js @@ -22,6 +22,8 @@ module.exports = { meta: { type: "problem", + defaultOptions: [{}], + docs: { description: "Disallow invalid regular expression strings in `RegExp` constructors", recommended: true, @@ -47,17 +49,10 @@ module.exports = { }, create(context) { - - const options = context.options[0]; - let allowedFlags = null; - - if (options && options.allowConstructorFlags) { - const temp = options.allowConstructorFlags.join("").replace(validFlags, ""); - - if (temp) { - allowedFlags = new RegExp(`[${temp}]`, "gu"); - } - } + const [{ allowConstructorFlags }] = context.options; + const allowedFlags = allowConstructorFlags + ? new RegExp(`[${allowConstructorFlags.join("").replace(validFlags, "")}]`, "gu") + : null; /** * Reports error with the provided message. diff --git a/lib/rules/no-invalid-this.js b/lib/rules/no-invalid-this.js index 4ea22889852..0a7b9e408bc 100644 --- a/lib/rules/no-invalid-this.js +++ b/lib/rules/no-invalid-this.js @@ -35,6 +35,8 @@ module.exports = { meta: { type: "suggestion", + defaultOptions: [{ capIsConstructor: true }], + docs: { description: "Disallow use of `this` in contexts where the value of `this` is `undefined`", recommended: false, @@ -46,8 +48,7 @@ module.exports = { type: "object", properties: { capIsConstructor: { - type: "boolean", - default: true + type: "boolean" } }, additionalProperties: false @@ -60,8 +61,7 @@ module.exports = { }, create(context) { - const options = context.options[0] || {}; - const capIsConstructor = options.capIsConstructor !== false; + const [{ capIsConstructor }] = context.options; const stack = [], sourceCode = context.sourceCode; diff --git a/lib/rules/no-irregular-whitespace.js b/lib/rules/no-irregular-whitespace.js index ab7ccac54e4..5a647a9b4ed 100644 --- a/lib/rules/no-irregular-whitespace.js +++ b/lib/rules/no-irregular-whitespace.js @@ -30,6 +30,8 @@ module.exports = { meta: { type: "problem", + defaultOptions: [{ skipStrings: true }], + docs: { description: "Disallow irregular whitespace", recommended: true, @@ -41,24 +43,19 @@ module.exports = { type: "object", properties: { skipComments: { - type: "boolean", - default: false + type: "boolean" }, skipStrings: { - type: "boolean", - default: true + type: "boolean" }, skipTemplates: { - type: "boolean", - default: false + type: "boolean" }, skipRegExps: { - type: "boolean", - default: false + type: "boolean" }, skipJSXText: { - type: "boolean", - default: false + type: "boolean" } }, additionalProperties: false @@ -71,21 +68,20 @@ module.exports = { }, create(context) { - - // Module store of errors that we have found - let errors = []; - - // Lookup the `skipComments` option, which defaults to `false`. - const options = context.options[0] || {}; - const skipComments = !!options.skipComments; - const skipStrings = options.skipStrings !== false; - const skipRegExps = !!options.skipRegExps; - const skipTemplates = !!options.skipTemplates; - const skipJSXText = !!options.skipJSXText; + const [{ + skipComments, + skipStrings, + skipRegExps, + skipTemplates, + skipJSXText + }] = context.options; const sourceCode = context.sourceCode; const commentNodes = sourceCode.getAllComments(); + // Module store of errors that we have found + let errors = []; + /** * Removes errors that occur inside the given node * @param {ASTNode} node to check for matching errors. diff --git a/lib/rules/no-labels.js b/lib/rules/no-labels.js index d991a0a8062..cbd69a2b3bf 100644 --- a/lib/rules/no-labels.js +++ b/lib/rules/no-labels.js @@ -19,6 +19,8 @@ module.exports = { meta: { type: "suggestion", + defaultOptions: [{}], + docs: { description: "Disallow labeled statements", recommended: false, @@ -30,12 +32,10 @@ module.exports = { type: "object", properties: { allowLoop: { - type: "boolean", - default: false + type: "boolean" }, allowSwitch: { - type: "boolean", - default: false + type: "boolean" } }, additionalProperties: false @@ -50,9 +50,7 @@ module.exports = { }, create(context) { - const options = context.options[0]; - const allowLoop = options && options.allowLoop; - const allowSwitch = options && options.allowSwitch; + const [{ allowLoop, allowSwitch }] = context.options; let scopeInfo = null; /** diff --git a/lib/rules/no-multi-assign.js b/lib/rules/no-multi-assign.js index a7a50c19495..32deab5596e 100644 --- a/lib/rules/no-multi-assign.js +++ b/lib/rules/no-multi-assign.js @@ -15,6 +15,8 @@ module.exports = { meta: { type: "suggestion", + defaultOptions: [{}], + docs: { description: "Disallow use of chained assignment expressions", recommended: false, @@ -25,8 +27,7 @@ module.exports = { type: "object", properties: { ignoreNonDeclaration: { - type: "boolean", - default: false + type: "boolean" } }, additionalProperties: false @@ -38,19 +39,13 @@ module.exports = { }, create(context) { - - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - const options = context.options[0] || { - ignoreNonDeclaration: false - }; + const [{ ignoreNonDeclaration }] = context.options; const selectors = [ "VariableDeclarator > AssignmentExpression.init", "PropertyDefinition > AssignmentExpression.value" ]; - if (!options.ignoreNonDeclaration) { + if (!ignoreNonDeclaration) { selectors.push("AssignmentExpression > AssignmentExpression.right"); } diff --git a/lib/rules/no-multi-spaces.js b/lib/rules/no-multi-spaces.js index bc90ee5b5b0..dbb0396312f 100644 --- a/lib/rules/no-multi-spaces.js +++ b/lib/rules/no-multi-spaces.js @@ -41,14 +41,15 @@ module.exports = { additionalProperties: false }, ignoreEOLComments: { - type: "boolean", - default: false + type: "boolean" } }, additionalProperties: false } ], + defaultOptions: [{ ignoreEOLComments: false, exceptions: { Property: true } }], + messages: { multipleSpaces: "Multiple spaces found before '{{displayValue}}'." } @@ -56,9 +57,7 @@ module.exports = { create(context) { const sourceCode = context.sourceCode; - const options = context.options[0] || {}; - const ignoreEOLComments = options.ignoreEOLComments; - const exceptions = Object.assign({ Property: true }, options.exceptions); + const [{ exceptions, ignoreEOLComments }] = context.options; const hasExceptions = Object.keys(exceptions).some(key => exceptions[key]); /** diff --git a/lib/rules/no-plusplus.js b/lib/rules/no-plusplus.js index 22a6fd01350..9d473ab2b35 100644 --- a/lib/rules/no-plusplus.js +++ b/lib/rules/no-plusplus.js @@ -50,6 +50,8 @@ module.exports = { meta: { type: "suggestion", + defaultOptions: [{}], + docs: { description: "Disallow the unary operators `++` and `--`", recommended: false, @@ -61,8 +63,7 @@ module.exports = { type: "object", properties: { allowForLoopAfterthoughts: { - type: "boolean", - default: false + type: "boolean" } }, additionalProperties: false @@ -75,13 +76,7 @@ module.exports = { }, create(context) { - - const config = context.options[0]; - let allowForLoopAfterthoughts = false; - - if (typeof config === "object") { - allowForLoopAfterthoughts = config.allowForLoopAfterthoughts === true; - } + const [{ allowForLoopAfterthoughts }] = context.options; return { diff --git a/lib/rules/no-promise-executor-return.js b/lib/rules/no-promise-executor-return.js index b27e440729c..0b4714be85e 100644 --- a/lib/rules/no-promise-executor-return.js +++ b/lib/rules/no-promise-executor-return.js @@ -141,6 +141,8 @@ module.exports = { meta: { type: "problem", + defaultOptions: [{}], + docs: { description: "Disallow returning values from Promise executor functions", recommended: false, @@ -153,8 +155,7 @@ module.exports = { type: "object", properties: { allowVoid: { - type: "boolean", - default: false + type: "boolean" } }, additionalProperties: false @@ -172,12 +173,9 @@ module.exports = { }, create(context) { - let funcInfo = null; const sourceCode = context.sourceCode; - const { - allowVoid = false - } = context.options[0] || {}; + const [{ allowVoid }] = context.options; return { diff --git a/lib/rules/no-redeclare.js b/lib/rules/no-redeclare.js index 8a4877e8a3c..94a3c212472 100644 --- a/lib/rules/no-redeclare.js +++ b/lib/rules/no-redeclare.js @@ -20,6 +20,8 @@ module.exports = { meta: { type: "suggestion", + defaultOptions: [{ builtinGlobals: true }], + docs: { description: "Disallow variable redeclaration", recommended: true, @@ -36,7 +38,7 @@ module.exports = { { type: "object", properties: { - builtinGlobals: { type: "boolean", default: true } + builtinGlobals: { type: "boolean" } }, additionalProperties: false } @@ -44,12 +46,7 @@ module.exports = { }, create(context) { - const options = { - builtinGlobals: Boolean( - context.options.length === 0 || - context.options[0].builtinGlobals - ) - }; + const [{ builtinGlobals }] = context.options; const sourceCode = context.sourceCode; /** @@ -58,7 +55,7 @@ module.exports = { * @returns {IterableIterator<{type:string,node:ASTNode,loc:SourceLocation}>} The declarations. */ function *iterateDeclarations(variable) { - if (options.builtinGlobals && ( + if (builtinGlobals && ( variable.eslintImplicitGlobalSetting === "readonly" || variable.eslintImplicitGlobalSetting === "writable" )) { diff --git a/lib/rules/no-return-assign.js b/lib/rules/no-return-assign.js index 73caf0e6bd3..7c1f00ec9a2 100644 --- a/lib/rules/no-return-assign.js +++ b/lib/rules/no-return-assign.js @@ -25,6 +25,8 @@ module.exports = { meta: { type: "suggestion", + defaultOptions: ["except-parens"], + docs: { description: "Disallow assignment operators in `return` statements", recommended: false, @@ -44,7 +46,7 @@ module.exports = { }, create(context) { - const always = (context.options[0] || "except-parens") !== "except-parens"; + const always = context.options[0] !== "except-parens"; const sourceCode = context.sourceCode; return { diff --git a/lib/rules/no-self-assign.js b/lib/rules/no-self-assign.js index 33ac8fb5085..91b928ea112 100644 --- a/lib/rules/no-self-assign.js +++ b/lib/rules/no-self-assign.js @@ -129,6 +129,8 @@ module.exports = { meta: { type: "problem", + defaultOptions: [{ props: true }], + docs: { description: "Disallow assignments where both sides are exactly the same", recommended: true, @@ -140,8 +142,7 @@ module.exports = { type: "object", properties: { props: { - type: "boolean", - default: true + type: "boolean" } }, additionalProperties: false @@ -155,7 +156,7 @@ module.exports = { create(context) { const sourceCode = context.sourceCode; - const [{ props = true } = {}] = context.options; + const [{ props }] = context.options; /** * Reports a given node as self assignments. diff --git a/lib/rules/no-sequences.js b/lib/rules/no-sequences.js index 5822a24542f..5843b7a0934 100644 --- a/lib/rules/no-sequences.js +++ b/lib/rules/no-sequences.js @@ -15,9 +15,6 @@ const astUtils = require("./utils/ast-utils"); // Helpers //------------------------------------------------------------------------------ -const DEFAULT_OPTIONS = { - allowInParentheses: true -}; //------------------------------------------------------------------------------ // Rule Definition @@ -38,20 +35,23 @@ module.exports = { type: "object", properties: { allowInParentheses: { - type: "boolean", - default: true + type: "boolean" } }, additionalProperties: false }], + defaultOptions: [{ + allowInParentheses: true + }], + messages: { unexpectedCommaExpression: "Unexpected use of comma operator." } }, create(context) { - const options = Object.assign({}, DEFAULT_OPTIONS, context.options[0]); + const [{ allowInParentheses }] = context.options; const sourceCode = context.sourceCode; /** @@ -117,7 +117,7 @@ module.exports = { } // Wrapping a sequence in extra parens indicates intent - if (options.allowInParentheses) { + if (allowInParentheses) { if (requiresExtraParens(node)) { if (isParenthesisedTwice(node)) { return; diff --git a/lib/rules/no-shadow.js b/lib/rules/no-shadow.js index 3e4d99822a8..b9e85845b80 100644 --- a/lib/rules/no-shadow.js +++ b/lib/rules/no-shadow.js @@ -29,6 +29,11 @@ module.exports = { meta: { type: "suggestion", + defaultOptions: [{ + allow: [], + hoist: "functions" + }], + docs: { description: "Disallow variable declarations from shadowing variables declared in the outer scope", recommended: false, @@ -39,7 +44,7 @@ module.exports = { { type: "object", properties: { - builtinGlobals: { type: "boolean", default: false }, + builtinGlobals: { type: "boolean" }, hoist: { enum: ["all", "functions", "never"], default: "functions" }, allow: { type: "array", @@ -47,7 +52,7 @@ module.exports = { type: "string" } }, - ignoreOnInitialization: { type: "boolean", default: false } + ignoreOnInitialization: { type: "boolean" } }, additionalProperties: false } @@ -60,13 +65,12 @@ module.exports = { }, create(context) { - - const options = { - builtinGlobals: context.options[0] && context.options[0].builtinGlobals, - hoist: (context.options[0] && context.options[0].hoist) || "functions", - allow: (context.options[0] && context.options[0].allow) || [], - ignoreOnInitialization: context.options[0] && context.options[0].ignoreOnInitialization - }; + const [{ + builtinGlobals, + hoist, + allow, + ignoreOnInitialization + }] = context.options; const sourceCode = context.sourceCode; /** @@ -174,7 +178,7 @@ module.exports = { * @returns {boolean} Whether or not the variable name is allowed. */ function isAllowed(variable) { - return options.allow.includes(variable.name); + return allow.includes(variable.name); } /** @@ -269,7 +273,7 @@ module.exports = { inner[1] < outer[0] && // Excepts FunctionDeclaration if is {"hoist":"function"}. - (options.hoist !== "functions" || !outerDef || outerDef.node.type !== "FunctionDeclaration") + (hoist !== "functions" || !outerDef || outerDef.node.type !== "FunctionDeclaration") ); } @@ -296,10 +300,10 @@ module.exports = { const shadowed = astUtils.getVariableByName(scope.upper, variable.name); if (shadowed && - (shadowed.identifiers.length > 0 || (options.builtinGlobals && "writeable" in shadowed)) && + (shadowed.identifiers.length > 0 || (builtinGlobals && "writeable" in shadowed)) && !isOnInitializer(variable, shadowed) && - !(options.ignoreOnInitialization && isInitPatternNode(variable, shadowed)) && - !(options.hoist !== "all" && isInTdz(variable, shadowed)) + !(ignoreOnInitialization && isInitPatternNode(variable, shadowed)) && + !(hoist !== "all" && isInTdz(variable, shadowed)) ) { const location = getDeclaredLocation(shadowed); const messageId = location.global ? "noShadowGlobal" : "noShadow"; diff --git a/lib/rules/no-undef.js b/lib/rules/no-undef.js index fe470286c04..e6ec5fbe14e 100644 --- a/lib/rules/no-undef.js +++ b/lib/rules/no-undef.js @@ -28,6 +28,8 @@ module.exports = { meta: { type: "problem", + defaultOptions: [{}], + docs: { description: "Disallow the use of undeclared variables unless mentioned in `/*global */` comments", recommended: true, @@ -39,8 +41,7 @@ module.exports = { type: "object", properties: { typeof: { - type: "boolean", - default: false + type: "boolean" } }, additionalProperties: false @@ -52,8 +53,7 @@ module.exports = { }, create(context) { - const options = context.options[0]; - const considerTypeOf = options && options.typeof === true || false; + const [{ typeof: considerTypeOf }] = context.options; const sourceCode = context.sourceCode; return { diff --git a/lib/rules/no-underscore-dangle.js b/lib/rules/no-underscore-dangle.js index a0e05c6c1cc..da230792fa0 100644 --- a/lib/rules/no-underscore-dangle.js +++ b/lib/rules/no-underscore-dangle.js @@ -14,6 +14,13 @@ module.exports = { meta: { type: "suggestion", + defaultOptions: [{ + allow: [], + allowFunctionParams: true, + allowInArrayDestructuring: true, + allowInObjectDestructuring: true + }], + docs: { description: "Disallow dangling underscores in identifiers", recommended: false, @@ -31,36 +38,28 @@ module.exports = { } }, allowAfterThis: { - type: "boolean", - default: false + type: "boolean" }, allowAfterSuper: { - type: "boolean", - default: false + type: "boolean" }, allowAfterThisConstructor: { - type: "boolean", - default: false + type: "boolean" }, enforceInMethodNames: { - type: "boolean", - default: false + type: "boolean" }, allowFunctionParams: { - type: "boolean", - default: true + type: "boolean" }, enforceInClassFields: { - type: "boolean", - default: false + type: "boolean" }, allowInArrayDestructuring: { - type: "boolean", - default: true + type: "boolean" }, allowInObjectDestructuring: { - type: "boolean", - default: true + type: "boolean" } }, additionalProperties: false @@ -73,17 +72,17 @@ module.exports = { }, create(context) { - - const options = context.options[0] || {}; - const ALLOWED_VARIABLES = options.allow ? options.allow : []; - const allowAfterThis = typeof options.allowAfterThis !== "undefined" ? options.allowAfterThis : false; - const allowAfterSuper = typeof options.allowAfterSuper !== "undefined" ? options.allowAfterSuper : false; - const allowAfterThisConstructor = typeof options.allowAfterThisConstructor !== "undefined" ? options.allowAfterThisConstructor : false; - const enforceInMethodNames = typeof options.enforceInMethodNames !== "undefined" ? options.enforceInMethodNames : false; - const enforceInClassFields = typeof options.enforceInClassFields !== "undefined" ? options.enforceInClassFields : false; - const allowFunctionParams = typeof options.allowFunctionParams !== "undefined" ? options.allowFunctionParams : true; - const allowInArrayDestructuring = typeof options.allowInArrayDestructuring !== "undefined" ? options.allowInArrayDestructuring : true; - const allowInObjectDestructuring = typeof options.allowInObjectDestructuring !== "undefined" ? options.allowInObjectDestructuring : true; + const [{ + allow, + allowAfterSuper, + allowAfterThis, + allowAfterThisConstructor, + allowFunctionParams, + allowInArrayDestructuring, + allowInObjectDestructuring, + enforceInClassFields, + enforceInMethodNames + }] = context.options; const sourceCode = context.sourceCode; //------------------------------------------------------------------------- @@ -97,7 +96,7 @@ module.exports = { * @private */ function isAllowed(identifier) { - return ALLOWED_VARIABLES.includes(identifier); + return allow.includes(identifier); } /** diff --git a/lib/rules/no-unneeded-ternary.js b/lib/rules/no-unneeded-ternary.js index 9bff7fb6da4..3d3dad0f61b 100644 --- a/lib/rules/no-unneeded-ternary.js +++ b/lib/rules/no-unneeded-ternary.js @@ -28,6 +28,8 @@ module.exports = { meta: { type: "suggestion", + defaultOptions: [{ defaultAssignment: true }], + docs: { description: "Disallow ternary operators when simpler alternatives exist", recommended: false, @@ -39,8 +41,7 @@ module.exports = { type: "object", properties: { defaultAssignment: { - type: "boolean", - default: true + type: "boolean" } }, additionalProperties: false @@ -56,8 +57,7 @@ module.exports = { }, create(context) { - const options = context.options[0] || {}; - const defaultAssignment = options.defaultAssignment !== false; + const [{ defaultAssignment }] = context.options; const sourceCode = context.sourceCode; /** diff --git a/lib/rules/no-unreachable-loop.js b/lib/rules/no-unreachable-loop.js index 577d39ac7c7..f5507ecc957 100644 --- a/lib/rules/no-unreachable-loop.js +++ b/lib/rules/no-unreachable-loop.js @@ -74,6 +74,8 @@ module.exports = { meta: { type: "problem", + defaultOptions: [{ ignore: [] }], + docs: { description: "Disallow loops with a body that allows only one iteration", recommended: false, @@ -100,8 +102,8 @@ module.exports = { }, create(context) { - const ignoredLoopTypes = context.options[0] && context.options[0].ignore || [], - loopTypesToCheck = getDifference(allLoopTypes, ignoredLoopTypes), + const [{ ignore: ignoredLoopTypes }] = context.options; + const loopTypesToCheck = getDifference(allLoopTypes, ignoredLoopTypes), loopSelector = loopTypesToCheck.join(","), loopsByTargetSegments = new Map(), loopsToReport = new Set(); diff --git a/lib/rules/no-unsafe-negation.js b/lib/rules/no-unsafe-negation.js index cabd7e2ccc2..3bc824b2627 100644 --- a/lib/rules/no-unsafe-negation.js +++ b/lib/rules/no-unsafe-negation.js @@ -51,6 +51,8 @@ module.exports = { meta: { type: "problem", + defaultOptions: [{}], + docs: { description: "Disallow negating the left operand of relational operators", recommended: true, @@ -64,8 +66,7 @@ module.exports = { type: "object", properties: { enforceForOrderingRelations: { - type: "boolean", - default: false + type: "boolean" } }, additionalProperties: false @@ -83,8 +84,7 @@ module.exports = { create(context) { const sourceCode = context.sourceCode; - const options = context.options[0] || {}; - const enforceForOrderingRelations = options.enforceForOrderingRelations === true; + const [{ enforceForOrderingRelations }] = context.options; return { BinaryExpression(node) { diff --git a/lib/rules/no-unsafe-optional-chaining.js b/lib/rules/no-unsafe-optional-chaining.js index fa787939649..d3df4f64b75 100644 --- a/lib/rules/no-unsafe-optional-chaining.js +++ b/lib/rules/no-unsafe-optional-chaining.js @@ -23,6 +23,8 @@ module.exports = { meta: { type: "problem", + defaultOptions: [{}], + docs: { description: "Disallow use of optional chaining in contexts where the `undefined` value is not allowed", recommended: true, @@ -32,8 +34,7 @@ module.exports = { type: "object", properties: { disallowArithmeticOperators: { - type: "boolean", - default: false + type: "boolean" } }, additionalProperties: false @@ -46,8 +47,7 @@ module.exports = { }, create(context) { - const options = context.options[0] || {}; - const disallowArithmeticOperators = (options.disallowArithmeticOperators) || false; + const [{ disallowArithmeticOperators }] = context.options; /** * Reports unsafe usage of optional chaining diff --git a/lib/rules/no-unused-expressions.js b/lib/rules/no-unused-expressions.js index 46bb7baac22..fd1437c1606 100644 --- a/lib/rules/no-unused-expressions.js +++ b/lib/rules/no-unused-expressions.js @@ -42,37 +42,41 @@ module.exports = { type: "object", properties: { allowShortCircuit: { - type: "boolean", - default: false + type: "boolean" }, allowTernary: { - type: "boolean", - default: false + type: "boolean" }, allowTaggedTemplates: { - type: "boolean", - default: false + type: "boolean" }, enforceForJSX: { - type: "boolean", - default: false + type: "boolean" } }, additionalProperties: false } ], + defaultOptions: [{ + allowShortCircuit: false, + allowTernary: false, + allowTaggedTemplates: false, + enforceForJSX: false + }], + messages: { unusedExpression: "Expected an assignment or function call and instead saw an expression." } }, create(context) { - const config = context.options[0] || {}, - allowShortCircuit = config.allowShortCircuit || false, - allowTernary = config.allowTernary || false, - allowTaggedTemplates = config.allowTaggedTemplates || false, - enforceForJSX = config.enforceForJSX || false; + const [{ + allowShortCircuit, + allowTernary, + allowTaggedTemplates, + enforceForJSX + }] = context.options; /** * Has AST suggesting a directive. diff --git a/lib/rules/no-use-before-define.js b/lib/rules/no-use-before-define.js index 9d6b043404a..6e4510d2df0 100644 --- a/lib/rules/no-use-before-define.js +++ b/lib/rules/no-use-before-define.js @@ -18,21 +18,16 @@ const FOR_IN_OF_TYPE = /^For(?:In|Of)Statement$/u; * @returns {Object} The parsed options. */ function parseOptions(options) { - let functions = true; - let classes = true; - let variables = true; - let allowNamedExports = false; - - if (typeof options === "string") { - functions = (options !== "nofunc"); - } else if (typeof options === "object" && options !== null) { - functions = options.functions !== false; - classes = options.classes !== false; - variables = options.variables !== false; - allowNamedExports = !!options.allowNamedExports; + if (typeof options === "object" && options !== null) { + return options; } - return { functions, classes, variables, allowNamedExports }; + const functions = + typeof options === "string" + ? options !== "nofunc" + : true; + + return { functions, classes: true, variables: true, allowNamedExports: false }; } /** @@ -251,6 +246,12 @@ module.exports = { } ], + defaultOptions: [{ + classes: true, + functions: true, + variables: true + }], + messages: { usedBeforeDefined: "'{{name}}' was used before it was defined." } diff --git a/lib/rules/no-useless-computed-key.js b/lib/rules/no-useless-computed-key.js index 5cc652bea26..3ca600a2b5f 100644 --- a/lib/rules/no-useless-computed-key.js +++ b/lib/rules/no-useless-computed-key.js @@ -90,6 +90,10 @@ module.exports = { meta: { type: "suggestion", + defaultOptions: [{ + enforceForClassMembers: true + }], + docs: { description: "Disallow unnecessary computed property keys in objects and classes", recommended: false, @@ -100,8 +104,7 @@ module.exports = { type: "object", properties: { enforceForClassMembers: { - type: "boolean", - default: true + type: "boolean" } }, additionalProperties: false diff --git a/lib/rules/no-useless-rename.js b/lib/rules/no-useless-rename.js index 0c818fb2c2f..4195947b7a5 100644 --- a/lib/rules/no-useless-rename.js +++ b/lib/rules/no-useless-rename.js @@ -20,6 +20,8 @@ module.exports = { meta: { type: "suggestion", + defaultOptions: [{}], + docs: { description: "Disallow renaming import, export, and destructured assignments to the same name", recommended: false, @@ -32,9 +34,9 @@ module.exports = { { type: "object", properties: { - ignoreDestructuring: { type: "boolean", default: false }, - ignoreImport: { type: "boolean", default: false }, - ignoreExport: { type: "boolean", default: false } + ignoreDestructuring: { type: "boolean" }, + ignoreImport: { type: "boolean" }, + ignoreExport: { type: "boolean" } }, additionalProperties: false } @@ -46,11 +48,8 @@ module.exports = { }, create(context) { - const sourceCode = context.sourceCode, - options = context.options[0] || {}, - ignoreDestructuring = options.ignoreDestructuring === true, - ignoreImport = options.ignoreImport === true, - ignoreExport = options.ignoreExport === true; + const sourceCode = context.sourceCode; + const [{ ignoreDestructuring, ignoreImport, ignoreExport }] = context.options; //-------------------------------------------------------------------------- // Helpers diff --git a/lib/rules/no-void.js b/lib/rules/no-void.js index 9546d7a62c3..25a123dc6e7 100644 --- a/lib/rules/no-void.js +++ b/lib/rules/no-void.js @@ -13,6 +13,8 @@ module.exports = { meta: { type: "suggestion", + defaultOptions: [{}], + docs: { description: "Disallow `void` operators", recommended: false, @@ -28,8 +30,7 @@ module.exports = { type: "object", properties: { allowAsStatement: { - type: "boolean", - default: false + type: "boolean" } }, additionalProperties: false @@ -38,8 +39,7 @@ module.exports = { }, create(context) { - const allowAsStatement = - context.options[0] && context.options[0].allowAsStatement; + const [{ allowAsStatement }] = context.options; //-------------------------------------------------------------------------- // Public diff --git a/lib/rules/no-warning-comments.js b/lib/rules/no-warning-comments.js index c415bee7a7b..628f5a2ac51 100644 --- a/lib/rules/no-warning-comments.js +++ b/lib/rules/no-warning-comments.js @@ -19,6 +19,11 @@ module.exports = { meta: { type: "suggestion", + defaultOptions: [{ + location: "start", + terms: ["todo", "fixme", "xxx"] + }], + docs: { description: "Disallow specified warning terms in comments", recommended: false, @@ -58,12 +63,10 @@ module.exports = { }, create(context) { - const sourceCode = context.sourceCode, - configuration = context.options[0] || {}, - warningTerms = configuration.terms || ["todo", "fixme", "xxx"], - location = configuration.location || "start", - decoration = [...configuration.decoration || []].join(""), - selfConfigRegEx = /\bno-warning-comments\b/u; + const sourceCode = context.sourceCode; + const [{ decoration, location, terms: warningTerms }] = context.options; + const escapedDecoration = escapeRegExp(decoration ? decoration.join("") : ""); + const selfConfigRegEx = /\bno-warning-comments\b/u; /** * Convert a warning term into a RegExp which will match a comment containing that whole word in the specified @@ -74,7 +77,6 @@ module.exports = { */ function convertToRegExp(term) { const escaped = escapeRegExp(term); - const escapedDecoration = escapeRegExp(decoration); /* * When matching at the start, ignore leading whitespace, and diff --git a/lib/rules/operator-assignment.js b/lib/rules/operator-assignment.js index f71d73be75d..412c97f66e0 100644 --- a/lib/rules/operator-assignment.js +++ b/lib/rules/operator-assignment.js @@ -62,6 +62,8 @@ module.exports = { meta: { type: "suggestion", + defaultOptions: ["always"], + docs: { description: "Require or disallow assignment operator shorthand where possible", recommended: false, @@ -82,7 +84,7 @@ module.exports = { }, create(context) { - + const never = context.options[0] === "never"; const sourceCode = context.sourceCode; /** @@ -202,7 +204,7 @@ module.exports = { } return { - AssignmentExpression: context.options[0] !== "never" ? verify : prohibit + AssignmentExpression: !never ? verify : prohibit }; } diff --git a/lib/rules/prefer-arrow-callback.js b/lib/rules/prefer-arrow-callback.js index b23696dd64c..98ac84a21d1 100644 --- a/lib/rules/prefer-arrow-callback.js +++ b/lib/rules/prefer-arrow-callback.js @@ -150,6 +150,8 @@ module.exports = { meta: { type: "suggestion", + defaultOptions: [{ allowUnboundThis: true }], + docs: { description: "Require using arrow functions for callbacks", recommended: false, @@ -161,12 +163,10 @@ module.exports = { type: "object", properties: { allowNamedFunctions: { - type: "boolean", - default: false + type: "boolean" }, allowUnboundThis: { - type: "boolean", - default: true + type: "boolean" } }, additionalProperties: false @@ -181,10 +181,7 @@ module.exports = { }, create(context) { - const options = context.options[0] || {}; - - const allowUnboundThis = options.allowUnboundThis !== false; // default to true - const allowNamedFunctions = options.allowNamedFunctions; + const [{ allowNamedFunctions, allowUnboundThis }] = context.options; const sourceCode = context.sourceCode; /* diff --git a/lib/rules/prefer-const.js b/lib/rules/prefer-const.js index b43975e9bae..d86c79c39a8 100644 --- a/lib/rules/prefer-const.js +++ b/lib/rules/prefer-const.js @@ -331,6 +331,8 @@ module.exports = { meta: { type: "suggestion", + defaultOptions: [{ destructuring: "any" }], + docs: { description: "Require `const` declarations for variables that are never reassigned after declared", recommended: false, @@ -343,8 +345,8 @@ module.exports = { { type: "object", properties: { - destructuring: { enum: ["any", "all"], default: "any" }, - ignoreReadBeforeAssign: { type: "boolean", default: false } + destructuring: { enum: ["any", "all"] }, + ignoreReadBeforeAssign: { type: "boolean" } }, additionalProperties: false } @@ -355,7 +357,7 @@ module.exports = { }, create(context) { - const options = context.options[0] || {}; + const [options] = context.options; const sourceCode = context.sourceCode; const shouldMatchAnyDestructuredVariable = options.destructuring !== "all"; const ignoreReadBeforeAssign = options.ignoreReadBeforeAssign === true; diff --git a/lib/rules/prefer-promise-reject-errors.js b/lib/rules/prefer-promise-reject-errors.js index e990265e99f..9715d0af7b8 100644 --- a/lib/rules/prefer-promise-reject-errors.js +++ b/lib/rules/prefer-promise-reject-errors.js @@ -15,6 +15,8 @@ module.exports = { meta: { type: "suggestion", + defaultOptions: [{}], + docs: { description: "Require using Error objects as Promise rejection reasons", recommended: false, @@ -27,7 +29,7 @@ module.exports = { { type: "object", properties: { - allowEmptyReject: { type: "boolean", default: false } + allowEmptyReject: { type: "boolean" } }, additionalProperties: false } @@ -40,7 +42,7 @@ module.exports = { create(context) { - const ALLOW_EMPTY_REJECT = context.options.length && context.options[0].allowEmptyReject; + const [{ allowEmptyReject }] = context.options; const sourceCode = context.sourceCode; //---------------------------------------------------------------------- @@ -53,7 +55,7 @@ module.exports = { * @returns {void} */ function checkRejectCall(callExpression) { - if (!callExpression.arguments.length && ALLOW_EMPTY_REJECT) { + if (!callExpression.arguments.length && allowEmptyReject) { return; } if ( diff --git a/lib/rules/prefer-regex-literals.js b/lib/rules/prefer-regex-literals.js index dd18f7ec2e3..684ebe51b68 100644 --- a/lib/rules/prefer-regex-literals.js +++ b/lib/rules/prefer-regex-literals.js @@ -112,6 +112,8 @@ module.exports = { meta: { type: "suggestion", + defaultOptions: [{}], + docs: { description: "Disallow use of the `RegExp` constructor in favor of regular expression literals", recommended: false, @@ -125,8 +127,7 @@ module.exports = { type: "object", properties: { disallowRedundantWrapping: { - type: "boolean", - default: false + type: "boolean" } }, additionalProperties: false @@ -144,7 +145,7 @@ module.exports = { }, create(context) { - const [{ disallowRedundantWrapping = false } = {}] = context.options; + const [{ disallowRedundantWrapping }] = context.options; const sourceCode = context.sourceCode; /** diff --git a/lib/rules/radix.js b/lib/rules/radix.js index efae749690a..6d37d18df76 100644 --- a/lib/rules/radix.js +++ b/lib/rules/radix.js @@ -79,6 +79,8 @@ module.exports = { meta: { type: "suggestion", + defaultOptions: [MODE_ALWAYS], + docs: { description: "Enforce the consistent use of the radix argument when using `parseInt()`", recommended: false, @@ -103,7 +105,7 @@ module.exports = { }, create(context) { - const mode = context.options[0] || MODE_ALWAYS; + const [mode] = context.options; const sourceCode = context.sourceCode; /** diff --git a/lib/rules/require-atomic-updates.js b/lib/rules/require-atomic-updates.js index 7e397ceb1cf..92c33a6a27d 100644 --- a/lib/rules/require-atomic-updates.js +++ b/lib/rules/require-atomic-updates.js @@ -170,6 +170,8 @@ module.exports = { meta: { type: "problem", + defaultOptions: [{}], + docs: { description: "Disallow assignments that can lead to race conditions due to usage of `await` or `yield`", recommended: false, @@ -182,8 +184,7 @@ module.exports = { type: "object", properties: { allowProperties: { - type: "boolean", - default: false + type: "boolean" } }, additionalProperties: false @@ -196,7 +197,7 @@ module.exports = { }, create(context) { - const allowProperties = !!context.options[0] && context.options[0].allowProperties; + const [{ allowProperties }] = context.options; const sourceCode = context.sourceCode; const assignmentReferences = new Map(); diff --git a/lib/rules/sort-imports.js b/lib/rules/sort-imports.js index 9deaf1d4c97..a842e79d80f 100644 --- a/lib/rules/sort-imports.js +++ b/lib/rules/sort-imports.js @@ -14,6 +14,10 @@ module.exports = { meta: { type: "suggestion", + defaultOptions: [{ + memberSyntaxSortOrder: ["none", "all", "multiple", "single"] + }], + docs: { description: "Enforce sorted import declarations within modules", recommended: false, @@ -25,8 +29,7 @@ module.exports = { type: "object", properties: { ignoreCase: { - type: "boolean", - default: false + type: "boolean" }, memberSyntaxSortOrder: { type: "array", @@ -38,16 +41,13 @@ module.exports = { maxItems: 4 }, ignoreDeclarationSort: { - type: "boolean", - default: false + type: "boolean" }, ignoreMemberSort: { - type: "boolean", - default: false + type: "boolean" }, allowSeparatedGroups: { - type: "boolean", - default: false + type: "boolean" } }, additionalProperties: false @@ -64,14 +64,14 @@ module.exports = { }, create(context) { - - const configuration = context.options[0] || {}, - ignoreCase = configuration.ignoreCase || false, - ignoreDeclarationSort = configuration.ignoreDeclarationSort || false, - ignoreMemberSort = configuration.ignoreMemberSort || false, - memberSyntaxSortOrder = configuration.memberSyntaxSortOrder || ["none", "all", "multiple", "single"], - allowSeparatedGroups = configuration.allowSeparatedGroups || false, - sourceCode = context.sourceCode; + const [{ + ignoreCase, + ignoreDeclarationSort, + ignoreMemberSort, + memberSyntaxSortOrder, + allowSeparatedGroups + }] = context.options; + const sourceCode = context.sourceCode; let previousDeclaration = null; /** diff --git a/lib/rules/sort-keys.js b/lib/rules/sort-keys.js index e355e8afdc8..642f2d3a885 100644 --- a/lib/rules/sort-keys.js +++ b/lib/rules/sort-keys.js @@ -80,6 +80,8 @@ module.exports = { meta: { type: "suggestion", + defaultOptions: ["asc", { caseSensitive: true, minKeys: 2 }], + docs: { description: "Require object keys to be sorted", recommended: false, @@ -94,21 +96,17 @@ module.exports = { type: "object", properties: { caseSensitive: { - type: "boolean", - default: true + type: "boolean" }, natural: { - type: "boolean", - default: false + type: "boolean" }, minKeys: { type: "integer", - minimum: 2, - default: 2 + minimum: 2 }, allowLineSeparatedGroups: { - type: "boolean", - default: false + type: "boolean" } }, additionalProperties: false @@ -121,14 +119,8 @@ module.exports = { }, create(context) { - - // Parse options. - const order = context.options[0] || "asc"; - const options = context.options[1]; - const insensitive = options && options.caseSensitive === false; - const natural = options && options.natural; - const minKeys = options && options.minKeys; - const allowLineSeparatedGroups = options && options.allowLineSeparatedGroups || false; + const [order, { caseSensitive, natural, minKeys, allowLineSeparatedGroups }] = context.options; + const insensitive = !caseSensitive; const isValidOrder = isValidOrders[ order + (insensitive ? "I" : "") + (natural ? "N" : "") ]; diff --git a/lib/rules/sort-vars.js b/lib/rules/sort-vars.js index 21bfb88e8dd..a8cf84157e9 100644 --- a/lib/rules/sort-vars.js +++ b/lib/rules/sort-vars.js @@ -14,6 +14,8 @@ module.exports = { meta: { type: "suggestion", + defaultOptions: [{}], + docs: { description: "Require variables within the same declaration block to be sorted", recommended: false, @@ -41,10 +43,8 @@ module.exports = { }, create(context) { - - const configuration = context.options[0] || {}, - ignoreCase = configuration.ignoreCase || false, - sourceCode = context.sourceCode; + const [{ ignoreCase }] = context.options; + const sourceCode = context.sourceCode; return { VariableDeclaration(node) { diff --git a/lib/rules/strict.js b/lib/rules/strict.js index fd970f279e1..198bf8521b7 100644 --- a/lib/rules/strict.js +++ b/lib/rules/strict.js @@ -68,6 +68,8 @@ module.exports = { meta: { type: "suggestion", + defaultOptions: ["safe"], + docs: { description: "Require or disallow strict mode directives", recommended: false, @@ -96,11 +98,10 @@ module.exports = { }, create(context) { - const ecmaFeatures = context.parserOptions.ecmaFeatures || {}, scopes = [], classScopes = []; - let mode = context.options[0] || "safe"; + let [mode] = context.options; if (ecmaFeatures.impliedStrict) { mode = "implied"; diff --git a/lib/rules/unicode-bom.js b/lib/rules/unicode-bom.js index 09971d26e30..15c7ad77a51 100644 --- a/lib/rules/unicode-bom.js +++ b/lib/rules/unicode-bom.js @@ -13,6 +13,8 @@ module.exports = { meta: { type: "layout", + defaultOptions: ["never"], + docs: { description: "Require or disallow Unicode byte order mark (BOM)", recommended: false, @@ -43,8 +45,8 @@ module.exports = { Program: function checkUnicodeBOM(node) { const sourceCode = context.sourceCode, - location = { column: 0, line: 1 }, - requireBOM = context.options[0] || "never"; + location = { column: 0, line: 1 }; + const [requireBOM] = context.options; if (!sourceCode.hasBOM && (requireBOM === "always")) { context.report({ diff --git a/lib/rules/use-isnan.js b/lib/rules/use-isnan.js index 5c0b65feefb..968cec7884e 100644 --- a/lib/rules/use-isnan.js +++ b/lib/rules/use-isnan.js @@ -56,18 +56,21 @@ module.exports = { type: "object", properties: { enforceForSwitchCase: { - type: "boolean", - default: true + type: "boolean" }, enforceForIndexOf: { - type: "boolean", - default: false + type: "boolean" } }, additionalProperties: false } ], + defaultOptions: [{ + enforceForIndexOf: false, + enforceForSwitchCase: true + }], + messages: { comparisonWithNaN: "Use the isNaN function to compare with NaN.", switchNaN: "'switch(NaN)' can never match a case clause. Use Number.isNaN instead of the switch.", diff --git a/lib/rules/valid-typeof.js b/lib/rules/valid-typeof.js index 3818dafeae9..34d9228ff20 100644 --- a/lib/rules/valid-typeof.js +++ b/lib/rules/valid-typeof.js @@ -19,6 +19,8 @@ module.exports = { meta: { type: "problem", + defaultOptions: [{}], + docs: { description: "Enforce comparing `typeof` expressions against valid strings", recommended: true, @@ -47,11 +49,10 @@ module.exports = { }, create(context) { - const VALID_TYPES = new Set(["symbol", "undefined", "object", "boolean", "number", "string", "function", "bigint"]), OPERATORS = new Set(["==", "===", "!=", "!=="]); const sourceCode = context.sourceCode; - const requireStringLiterals = context.options[0] && context.options[0].requireStringLiterals; + const [{ requireStringLiterals }] = context.options; let globalScope; diff --git a/lib/rules/yoda.js b/lib/rules/yoda.js index af8f525182e..7f192b7fd7a 100644 --- a/lib/rules/yoda.js +++ b/lib/rules/yoda.js @@ -111,6 +111,8 @@ module.exports = { meta: { type: "suggestion", + defaultOptions: ["never", {}], + docs: { description: 'Require or disallow "Yoda" conditions', recommended: false, @@ -125,12 +127,10 @@ module.exports = { type: "object", properties: { exceptRange: { - type: "boolean", - default: false + type: "boolean" }, onlyEquality: { - type: "boolean", - default: false + type: "boolean" } }, additionalProperties: false @@ -145,14 +145,8 @@ module.exports = { }, create(context) { - - // Default to "never" (!always) if no option - const always = context.options[0] === "always"; - const exceptRange = - context.options[1] && context.options[1].exceptRange; - const onlyEquality = - context.options[1] && context.options[1].onlyEquality; - + const [when, { exceptRange, onlyEquality }] = context.options; + const always = when === "always"; const sourceCode = context.sourceCode; /** diff --git a/lib/shared/deep-merge-arrays.js b/lib/shared/deep-merge-arrays.js new file mode 100644 index 00000000000..a7a2deef338 --- /dev/null +++ b/lib/shared/deep-merge-arrays.js @@ -0,0 +1,60 @@ +/** + * @fileoverview Applies default rule options + * @author JoshuaKGoldberg + */ + +"use strict"; + +/** + * Check if the variable contains an object strictly rejecting arrays + * @param {unknown} value an object + * @returns {boolean} Whether value is an object + */ +function isObjectNotArray(value) { + return typeof value === "object" && value !== null && value !== void 0 && !Array.isArray(value); +} + +/** + * Deeply merges second on top of first, creating a new {} object if needed. + * @param {T} first Base, default value. + * @param {U} second User-specified value. + * @returns {T | U | (T & U)} Merged equivalent of second on top of first. + */ +function deepMergeObjects(first, second) { + if (second === void 0) { + return first; + } + + if (!isObjectNotArray(first) || !isObjectNotArray(second)) { + return second; + } + + const result = { ...first, ...second }; + + for (const key of Object.keys(second)) { + if (Object.prototype.propertyIsEnumerable.call(first, key)) { + result[key] = deepMergeObjects(first[key], second[key]); + } + } + + return result; +} + +/** + * Deeply merges second on top of first, creating a new [] array if needed. + * @param {T[]} first Base, default values. + * @param {U[]} second User-specified values. + * @returns {(T | U | (T & U))[]} Merged equivalent of second on top of first. + */ +function deepMergeArrays(first, second) { + if (!first || !second) { + return second || first || []; + } + + return [ + ...first.map((value, i) => deepMergeObjects(value, i < second.length ? second[i] : void 0)), + ...second.slice(first.length) + ]; +} + +module.exports = { deepMergeArrays }; diff --git a/lib/shared/types.js b/lib/shared/types.js index 15666d1c23f..9a5f3562c78 100644 --- a/lib/shared/types.js +++ b/lib/shared/types.js @@ -148,6 +148,7 @@ module.exports = {}; /** * @typedef {Object} RuleMeta * @property {boolean} [deprecated] If `true` then the rule has been deprecated. + * @property {Array} [defaultOptions] Default options for the rule. * @property {RuleMetaDocs} docs The document information of the rule. * @property {"code"|"whitespace"} [fixable] The autofix type. * @property {boolean} [hasSuggestions] If `true` then the rule provides suggestions. diff --git a/tests/lib/config/flat-config-array.js b/tests/lib/config/flat-config-array.js index a8bff24417a..ebe709149ad 100644 --- a/tests/lib/config/flat-config-array.js +++ b/tests/lib/config/flat-config-array.js @@ -2328,9 +2328,11 @@ describe("FlatConfigArray", () => { assert.deepStrictEqual(config.rules, { camelcase: [2, { + allow: [], ignoreDestructuring: false, ignoreGlobals: false, - ignoreImports: false + ignoreImports: false, + properties: "always" }], "default-case": [2, {}] }); diff --git a/tests/lib/linter/linter.js b/tests/lib/linter/linter.js index 6e98b93f3a1..883fe9abf12 100644 --- a/tests/lib/linter/linter.js +++ b/tests/lib/linter/linter.js @@ -1458,6 +1458,29 @@ describe("Linter", () => { assert.strictEqual(suppressedMessages.length, 0); }); + it("rules should apply meta.defaultOptions", () => { + const config = { rules: {} }; + const codeA = "/*eslint no-constant-condition: error */ while (true) {}"; + const messages = linter.verify(codeA, config, filename, false); + + assert.deepStrictEqual( + messages, + [ + { + severity: 2, + ruleId: "no-constant-condition", + message: "Unexpected constant condition.", + messageId: "unexpected", + nodeType: "Literal", + line: 1, + column: 49, + endLine: 1, + endColumn: 53 + } + ] + ); + }); + describe("when the rule was already configured", () => { beforeEach(() => { @@ -6932,6 +6955,52 @@ var a = "test2"; }); }); + describe("options", () => { + it("rules should apply meta.defaultOptions and ignore schema defaults", () => { + linter.defineRule("my-rule", { + meta: { + defaultOptions: [{ + inBoth: "from-default-options", + inDefaultOptions: "from-default-options" + }], + schema: { + type: "object", + properties: { + inBoth: { default: "from-schema", type: "string" }, + inDefaultOptions: { type: "string" }, + inSchema: { default: "from-schema", type: "string" } + }, + additionalProperties: false + } + }, + create(context) { + return { + Program(node) { + context.report({ + message: JSON.stringify(context.options[0]), + node + }); + } + }; + } + }); + + const config = { + rules: { + "my-rule": "error" + } + }; + + const code = ""; + const messages = linter.verify(code, config); + + assert.deepStrictEqual( + JSON.parse(messages[0].message), + { inBoth: "from-default-options", inDefaultOptions: "from-default-options" } + ); + }); + }); + describe("processors", () => { let receivedFilenames = []; let receivedPhysicalFilenames = []; @@ -15806,6 +15875,56 @@ var a = "test2"; }); }); + describe("options", () => { + it("rules should apply meta.defaultOptions on top of schema defaults", () => { + const config = { + plugins: { + test: { + rules: { + checker: { + meta: { + defaultOptions: [{ + inBoth: "from-default-options", + inDefaultOptions: "from-default-options" + }], + schema: [{ + type: "object", + properties: { + inBoth: { default: "from-schema", type: "string" }, + inDefaultOptions: { type: "string" }, + inSchema: { default: "from-schema", type: "string" } + }, + additionalProperties: false + }] + }, + create(context) { + return { + Program(node) { + context.report({ + message: JSON.stringify(context.options[0]), + node + }); + } + }; + } + } + } + } + }, + rules: { + "test/checker": "error" + } + }; + + const messages = linter.verify("foo", config, filename); + + assert.deepStrictEqual( + JSON.parse(messages[0].message), + { inBoth: "from-default-options", inDefaultOptions: "from-default-options", inSchema: "from-schema" } + ); + }); + }); + describe("processors", () => { let receivedFilenames = []; let receivedPhysicalFilenames = []; diff --git a/tests/lib/shared/deep-merge-arrays.js b/tests/lib/shared/deep-merge-arrays.js new file mode 100644 index 00000000000..c374ae7bf03 --- /dev/null +++ b/tests/lib/shared/deep-merge-arrays.js @@ -0,0 +1,138 @@ +/* eslint-disable no-undefined -- `null` and `undefined` are different in options */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const assert = require("assert"); +const { deepMergeArrays } = require("../../../lib/shared/deep-merge-arrays"); + +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + +/** + * Turns a value into its string equivalent for a test name. + * @param {unknown} value Value to be stringified. + * @returns {string} String equivalent of the value. + */ +function toTestCaseName(value) { + return typeof value === "object" ? JSON.stringify(value) : `${value}`; +} + +describe("deepMerge", () => { + for (const [first, second, result] of [ + [[], undefined, []], + [["abc"], undefined, ["abc"]], + [undefined, ["abc"], ["abc"]], + [[], ["abc"], ["abc"]], + [[undefined], ["abc"], ["abc"]], + [[undefined, undefined], ["abc"], ["abc", undefined]], + [[undefined, undefined], ["abc", "def"], ["abc", "def"]], + [[undefined, null], ["abc"], ["abc", null]], + [[undefined, null], ["abc", "def"], ["abc", "def"]], + [[null], ["abc"], ["abc"]], + [[123], [undefined], [123]], + [[123], [null], [null]], + [[123], [{ a: 0 }], [{ a: 0 }]], + [["abc"], [undefined], ["abc"]], + [["abc"], [null], [null]], + [["abc"], ["def"], ["def"]], + [["abc"], [{ a: 0 }], [{ a: 0 }]], + [[["abc"]], [null], [null]], + [[["abc"]], ["def"], ["def"]], + [[["abc"]], [{ a: 0 }], [{ a: 0 }]], + [[{ abc: true }], ["def"], ["def"]], + [[{ abc: true }], [["def"]], [["def"]]], + [[null], [{ abc: true }], [{ abc: true }]], + [[{ a: undefined }], [{ a: 0 }], [{ a: 0 }]], + [[{ a: null }], [{ a: 0 }], [{ a: 0 }]], + [[{ a: null }], [{ a: { b: 0 } }], [{ a: { b: 0 } }]], + [[{ a: 0 }], [{ a: 1 }], [{ a: 1 }]], + [[{ a: 0 }], [{ a: null }], [{ a: null }]], + [[{ a: 0 }], [{ a: undefined }], [{ a: 0 }]], + [[{ a: 0 }], ["abc"], ["abc"]], + [[{ a: 0 }], [123], [123]], + [[[{ a: 0 }]], [123], [123]], + [ + [{ a: ["b"] }], + [{ a: ["c"] }], + [{ a: ["c"] }] + ], + [ + [{ a: [{ b: "c" }] }], + [{ a: [{ d: "e" }] }], + [{ a: [{ d: "e" }] }] + ], + [ + [{ a: { b: "c" }, d: true }], + [{ a: { e: "f" } }], + [{ a: { b: "c", e: "f" }, d: true }] + ], + [ + [{ a: { b: "c" } }], + [{ a: { e: "f" }, d: true }], + [{ a: { b: "c", e: "f" }, d: true }] + ], + [ + [{ a: { b: "c" } }, { d: true }], + [{ a: { e: "f" } }, { f: 123 }], + [{ a: { b: "c", e: "f" } }, { d: true, f: 123 }] + ], + [ + [{ hasOwnProperty: true }], + [{}], + [{ hasOwnProperty: true }] + ], + [ + [{ hasOwnProperty: false }], + [{}], + [{ hasOwnProperty: false }] + ], + [ + [{ hasOwnProperty: null }], + [{}], + [{ hasOwnProperty: null }] + ], + [ + [{ hasOwnProperty: undefined }], + [{}], + [{ hasOwnProperty: undefined }] + ], + [ + [{}], + [{ hasOwnProperty: null }], + [{ hasOwnProperty: null }] + ], + [ + [{}], + [{ hasOwnProperty: undefined }], + [{ hasOwnProperty: undefined }] + ], + [ + [{ + allow: [], + ignoreDestructuring: false, + ignoreGlobals: false, + ignoreImports: false, + properties: "always" + }], + [], + [{ + allow: [], + ignoreDestructuring: false, + ignoreGlobals: false, + ignoreImports: false, + properties: "always" + }] + ] + ]) { + it(`${toTestCaseName(first)}, ${toTestCaseName(second)}`, () => { + assert.deepStrictEqual(deepMergeArrays(first, second), result); + }); + } +}); + +/* eslint-enable no-undefined -- `null` and `undefined` are different in options */