Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add meta.defaultOptions #17656

Open
wants to merge 58 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
9b70e84
[Reference] feat: add meta.defaultOptions
JoshuaKGoldberg Oct 16, 2023
90504b7
Removed optionsRaw
JoshuaKGoldberg Oct 19, 2023
7a61675
computed-property-spacing: defaultOptions
JoshuaKGoldberg Oct 26, 2023
13f6270
Merge branch 'main' into rule-meta-default-options
JoshuaKGoldberg Nov 21, 2023
e493311
fix: handle object type mismatches in merging
JoshuaKGoldberg Nov 21, 2023
1bb2568
Validate arrays in flat-config-array
JoshuaKGoldberg Nov 21, 2023
629f691
Fix rule defaultOptions typos
JoshuaKGoldberg Nov 21, 2023
6276b71
Put back getRuleOptions as before
JoshuaKGoldberg Nov 21, 2023
1f36e93
Apply deep merging in config-validator and rule-validator
JoshuaKGoldberg Nov 21, 2023
859594f
Merge branch 'main' into rule-meta-default-options
JoshuaKGoldberg Dec 16, 2023
ec021b5
Converted remaining rules. Note: inline comments still need to have d…
JoshuaKGoldberg Dec 18, 2023
f9b435f
Fixes around inline comments
JoshuaKGoldberg Dec 19, 2023
e04072f
Extract to a getRuleOptionsInline
JoshuaKGoldberg Dec 19, 2023
22b9fd6
Merge branch 'main' (with a few test failures)
JoshuaKGoldberg Jan 6, 2024
3e4f310
nit: new extra line
JoshuaKGoldberg Jan 6, 2024
f5e284d
Test fix: meta.defaultOptions in a comment
JoshuaKGoldberg Jan 6, 2024
2b2f99a
Refactor-level review feedback
JoshuaKGoldberg Jan 10, 2024
79d3eb0
Used a recommended rule in linter.js test
JoshuaKGoldberg Jan 10, 2024
efa307a
Added custom-rules.md docs
JoshuaKGoldberg Jan 10, 2024
de24526
Update docs/src/extend/custom-rules.md
JoshuaKGoldberg Jan 11, 2024
6b18f8a
Clarified undefined point
JoshuaKGoldberg Jan 11, 2024
7f3e808
Adjusted for edge cases per review
JoshuaKGoldberg Feb 2, 2024
753bf8d
Refactored per review
JoshuaKGoldberg Feb 2, 2024
9bc927b
Removed lint disable in source
JoshuaKGoldberg Feb 2, 2024
7d25a7b
Added Linter test for meta.defaultOptions
JoshuaKGoldberg Feb 2, 2024
ce75fa2
Documented useDefaults from Ajv
JoshuaKGoldberg Feb 2, 2024
6b9b8ac
Merge branch 'main'
JoshuaKGoldberg Feb 2, 2024
0dc4810
Set up meta+schema merging unit tests for flat (passing) and legacy (…
JoshuaKGoldberg Feb 15, 2024
adb7972
Potential solution: boolean applyDefaultOptions param for runRules
JoshuaKGoldberg Feb 15, 2024
2b8659b
Merge branch 'main'
JoshuaKGoldberg Mar 5, 2024
6657263
Merge branch 'main'
JoshuaKGoldberg Mar 23, 2024
73868f4
Merge branch 'main'
JoshuaKGoldberg Jun 8, 2024
a64b798
chore: node:assert
JoshuaKGoldberg Jun 8, 2024
310fd73
Update lib/shared/deep-merge-arrays.js
JoshuaKGoldberg Jul 28, 2024
09ce777
Merge branch 'main'
JoshuaKGoldberg Aug 14, 2024
3109906
Made tests more explicit on defaulting behavior
JoshuaKGoldberg Aug 14, 2024
c555a05
Merge branch 'main' into rule-meta-default-options
JoshuaKGoldberg Aug 14, 2024
9b60110
Merge branch 'main'
JoshuaKGoldberg Sep 12, 2024
a3bf8b6
Merge branch 'main'
JoshuaKGoldberg Oct 9, 2024
9c984b3
Handled defaultOptions and option-less inline comments
JoshuaKGoldberg Oct 9, 2024
299d44f
Added explicit tests for mismatched comment options and comment optio…
JoshuaKGoldberg Oct 9, 2024
220b0eb
Try out configToValidate approach
JoshuaKGoldberg Oct 19, 2024
dc9a5ab
Add in unit tests
JoshuaKGoldberg Oct 21, 2024
ae3b5a0
Always apply defaultOptions, even with meta.schema: false
JoshuaKGoldberg Oct 21, 2024
3b4a7bd
Filled in some falsy values
JoshuaKGoldberg Oct 21, 2024
1071122
Fix a few lint complaints
JoshuaKGoldberg Oct 21, 2024
23ff934
That's right, Infinity is not allowed
JoshuaKGoldberg Oct 22, 2024
075b852
Update lib/config/rule-validator.js
JoshuaKGoldberg Oct 22, 2024
19911f0
Update docs/src/extend/custom-rules.md
JoshuaKGoldberg Oct 22, 2024
10696f7
Update docs/src/extend/custom-rules.md
JoshuaKGoldberg Oct 22, 2024
d61cf14
Revert deprecated rules
JoshuaKGoldberg Oct 22, 2024
db22b97
Bring in eslintrc#factor-in-default-options
JoshuaKGoldberg Oct 22, 2024
cd0dcda
Add index.d.ts types
JoshuaKGoldberg Oct 22, 2024
47c38a4
Merge branch 'main'
JoshuaKGoldberg Oct 22, 2024
9996427
Update lib/linter/linter.js
JoshuaKGoldberg Oct 22, 2024
1a46e35
Apply suggestions from code review
JoshuaKGoldberg Oct 29, 2024
4201ca3
Moved config changing outside of validation
JoshuaKGoldberg Nov 6, 2024
903e022
Merge branch 'main'
JoshuaKGoldberg Nov 6, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions docs/src/extend/custom-rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down Expand Up @@ -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/), or reading the free [Understanding JSON Schema](https://json-schema.org/understanding-json-schema/) ebook.

#### Option Defaults
JoshuaKGoldberg marked this conversation as resolved.
Show resolved Hide resolved
JoshuaKGoldberg marked this conversation as resolved.
Show resolved Hide resolved

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
}
JoshuaKGoldberg marked this conversation as resolved.
Show resolved Hide resolved
},
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()`.
Expand Down
10 changes: 9 additions & 1 deletion lib/config/rule-validator.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
//-----------------------------------------------------------------------------

const ajvImport = require("../shared/ajv");
const { deepMergeArrays } = require("../shared/deep-merge-arrays");
const ajv = ajvImport();
const {
parseRuleId,
Expand Down Expand Up @@ -164,7 +165,10 @@ class RuleValidator {

if (validateRule) {

validateRule(ruleOptions.slice(1));
const slicedOptions = ruleOptions.slice(1);
const mergedOptions = deepMergeArrays(rule.meta && rule.meta.defaultOptions, slicedOptions);

validateRule(mergedOptions);
JoshuaKGoldberg marked this conversation as resolved.
Show resolved Hide resolved

if (validateRule.errors) {
throw new Error(`Key "rules": Key "${ruleId}": ${
Expand All @@ -173,6 +177,10 @@ class RuleValidator {
).join("")
}`);
}

if (mergedOptions.length) {
config.rules[ruleId] = [ruleOptions[0], ...mergedOptions];
}
}
}
}
Expand Down
31 changes: 23 additions & 8 deletions lib/linter/linter.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ const { FlatConfigArray } = require("../config/flat-config-array");
const { RuleValidator } = require("../config/rule-validator");
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";
Expand Down Expand Up @@ -778,14 +779,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));
JoshuaKGoldberg marked this conversation as resolved.
Show resolved Hide resolved
}
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)];
}

/**
Expand Down Expand Up @@ -1006,7 +1024,7 @@ function runRules(sourceCode, configuredRules, ruleMapper, parserName, languageO
Object.create(sharedTraversalContext),
{
id: ruleId,
options: getRuleOptions(configuredRules[ruleId]),
options: getRuleOptions(configuredRules[ruleId], rule.meta?.defaultOptions),
report(...args) {

/*
Expand Down Expand Up @@ -1713,10 +1731,7 @@ class Linter {

try {

const ruleOptions = Array.isArray(ruleValue) ? ruleValue : [ruleValue];

assertIsRuleOptions(ruleId, ruleValue);
assertIsRuleSeverity(ruleId, ruleOptions[0]);
const ruleOptions = getRuleOptionsInline(ruleId, ruleValue, rule.meta?.defaultOptions);

ruleValidator.validate({
plugins: config.plugins,
Expand Down
17 changes: 10 additions & 7 deletions lib/rules/accessor-pairs.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
Expand All @@ -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;
Expand Down
2 changes: 2 additions & 0 deletions lib/rules/array-bracket-spacing.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ module.exports = {
replacedBy: [],
type: "layout",

defaultOptions: ["never"],

docs: {
description: "Enforce consistent spacing inside array brackets",
recommended: false,
Expand Down
18 changes: 10 additions & 8 deletions lib/rules/array-callback-return.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
Expand All @@ -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 = {
Expand Down
2 changes: 2 additions & 0 deletions lib/rules/array-element-newline.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ module.exports = {
replacedBy: [],
type: "layout",

defaultOptions: ["always"],

docs: {
description: "Enforce line breaks after each array element",
recommended: false,
Expand Down
4 changes: 3 additions & 1 deletion lib/rules/arrow-body-style.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ module.exports = {
meta: {
type: "suggestion",

defaultOptions: ["as-needed"],

docs: {
description: "Require braces around arrow function bodies",
recommended: false,
Expand Down Expand Up @@ -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;
Expand Down
5 changes: 3 additions & 2 deletions lib/rules/arrow-parens.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ module.exports = {
replacedBy: [],
type: "layout",

defaultOptions: ["always"],

docs: {
description: "Require parentheses around arrow function arguments",
recommended: false,
Expand All @@ -51,8 +53,7 @@ module.exports = {
type: "object",
properties: {
requireForBlockBody: {
type: "boolean",
default: false
type: "boolean"
}
},
additionalProperties: false
Expand Down
23 changes: 10 additions & 13 deletions lib/rules/arrow-spacing.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
Expand All @@ -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;

/**
Expand Down Expand Up @@ -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) {
Expand All @@ -130,7 +127,7 @@ module.exports = {
}
}

if (rule.after) {
if (options.after) {

// should be space(s) after arrow
if (countSpace.after === 0) {
Expand Down
2 changes: 2 additions & 0 deletions lib/rules/block-spacing.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
9 changes: 5 additions & 4 deletions lib/rules/brace-style.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ module.exports = {
replacedBy: [],
type: "layout",

defaultOptions: ["1tbs", { allowSingleLine: false }],

docs: {
description: "Enforce consistent brace style for blocks",
recommended: false,
Expand All @@ -33,8 +35,7 @@ module.exports = {
type: "object",
properties: {
allowSingleLine: {
type: "boolean",
default: false
type: "boolean"
}
},
additionalProperties: false
Expand All @@ -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;

//--------------------------------------------------------------------------
Expand Down
Loading