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

Draft
wants to merge 31 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
31 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
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
61 changes: 61 additions & 0 deletions lib/linter/deep-merge.js
@@ -0,0 +1,61 @@
/* eslint-disable eqeqeq, no-undefined -- `null` and `undefined` are different in options */
/**
* @fileoverview Applies default rule options
* @author JoshuaKGoldberg
*/

"use strict";

/**
* Check if the variable contains an object strictly rejecting arrays
* @param {unknown} obj an object
* @returns {boolean} Whether obj is an object
*/
function isObjectNotArray(obj) {
return typeof obj === "object" && obj != null && !Array.isArray(obj);
}

/**
* 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 deepMerge(first, second) {
if (second === null || (second !== undefined && typeof second !== "object")) {
return second;
}

if (typeof first !== "object" && typeof second === "object" && second !== null) {
return second;
}

if (first === null || Array.isArray(first) || second === undefined) {
return first;
}

const keysUnion = new Set(Object.keys(first).concat(Object.keys(second)));

return Array.from(keysUnion).reduce((acc, key) => {
const firstValue = first[key];
const secondValue = second[key];

if (firstValue !== undefined && secondValue !== undefined) {
if (isObjectNotArray(firstValue) && isObjectNotArray(secondValue)) {
acc[key] = deepMerge(firstValue, secondValue);
} else {
acc[key] = secondValue;
}
} else if (firstValue !== undefined) {
acc[key] = firstValue;
} else {
acc[key] = secondValue;
}

return acc;
}, {});
}

module.exports = { deepMerge };

/* eslint-enable eqeqeq, no-undefined -- `null` and `undefined` are different in options */
48 changes: 48 additions & 0 deletions lib/linter/get-rule-options.js
@@ -0,0 +1,48 @@
/**
* @fileoverview Applies default rule options
* @author JoshuaKGoldberg
*/

"use strict";

const { deepMerge } = require("./deep-merge");

/**
* Creates rule options by merging a user config on top of any default options.
* @param {Array|undefined} defaultOptions Default options from a rule's meta.
* @param {Array} ruleConfig User-specified rule configuration.
* @returns {Array} Rule options, factoring in user config and any defaults.
*/
function getMergedRuleOptions(defaultOptions, ruleConfig) {
if (!defaultOptions) {
return ruleConfig;
}

const options = [];
const sharedLength = Math.min(defaultOptions.length, ruleConfig.length);
let i;

for (i = 0; i < sharedLength; i += 1) {
options.push(deepMerge(defaultOptions[i], ruleConfig[i]));
}

options.push(...defaultOptions.slice(i));
options.push(...ruleConfig.slice(i));

return options;
}

/**
* Get the options for a rule (not including severity), if any, factoring in defaults
* @param {Array|undefined} defaultOptions default options from rule's meta.
* @param {Array|number} ruleConfig rule configuration
* @returns {Array} of rule options, empty Array if none
*/
function getRuleOptions(defaultOptions, ruleConfig) {
if (Array.isArray(ruleConfig)) {
return getMergedRuleOptions(defaultOptions, ruleConfig.slice(1));
}
return defaultOptions || [];
}

module.exports = { getRuleOptions };
16 changes: 2 additions & 14 deletions lib/linter/linter.js
Expand Up @@ -33,6 +33,7 @@ const
CodePathAnalyzer = require("./code-path-analysis/code-path-analyzer"),
applyDisableDirectives = require("./apply-disable-directives"),
ConfigCommentParser = require("./config-comment-parser"),
{ getRuleOptions } = require("./get-rule-options"),
NodeEventGenerator = require("./node-event-generator"),
createReportTranslator = require("./report-translator"),
Rules = require("./rules"),
Expand Down Expand Up @@ -772,19 +773,6 @@ function stripUnicodeBOM(text) {
return text;
}

/**
* Get the options for a rule (not including severity), if any
* @param {Array|number} ruleConfig rule configuration
* @returns {Array} of rule options, empty Array if none
*/
function getRuleOptions(ruleConfig) {
if (Array.isArray(ruleConfig)) {
return ruleConfig.slice(1);
}
return [];

}

/**
* Analyze scope of the given AST.
* @param {ASTNode} ast The `Program` node to analyze.
Expand Down Expand Up @@ -1037,7 +1025,7 @@ function runRules(sourceCode, configuredRules, ruleMapper, parserName, languageO
Object.create(sharedTraversalContext),
{
id: ruleId,
options: getRuleOptions(configuredRules[ruleId]),
options: getRuleOptions(rule.meta && rule.meta.defaultOptions, configuredRules[ruleId]),
JoshuaKGoldberg marked this conversation as resolved.
Show resolved Hide resolved
report(...args) {

/*
Expand Down
17 changes: 10 additions & 7 deletions lib/rules/accessor-pairs.js
Expand Up @@ -139,6 +139,12 @@ module.exports = {
meta: {
type: "suggestion",

defaultOptions: [{
enforceForClassMembers: true,
getWithoutGet: 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
3 changes: 3 additions & 0 deletions lib/rules/array-bracket-spacing.js
Expand Up @@ -15,6 +15,8 @@ module.exports = {
meta: {
type: "layout",

defaultOptions: ["never"],

docs: {
description: "Enforce consistent spacing inside array brackets",
recommended: false,
Expand Down Expand Up @@ -55,6 +57,7 @@ module.exports = {
const spaced = context.options[0] === "always",
sourceCode = context.sourceCode;


/**
* Determines whether an option is set, relative to the spacing option.
* If spaced is "always", then check whether option is set to false.
Expand Down
17 changes: 10 additions & 7 deletions lib/rules/array-callback-return.js
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 @@ -257,7 +260,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
Expand Up @@ -16,6 +16,8 @@ module.exports = {
meta: {
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
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
Expand Up @@ -32,6 +32,8 @@ module.exports = {
meta: {
type: "layout",

defaultOptions: ["always"],

docs: {
description: "Require parentheses around arrow function arguments",
recommended: false,
Expand All @@ -48,8 +50,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
Expand Up @@ -19,6 +19,11 @@ module.exports = {
meta: {
type: "layout",

defaultOptions: [{
after: true,
before: true
}],

docs: {
description: "Enforce consistent spacing before and after the arrow in arrow functions",
recommended: false,
Expand All @@ -32,12 +37,10 @@ module.exports = {
type: "object",
properties: {
before: {
type: "boolean",
default: true
type: "boolean"
},
after: {
type: "boolean",
default: true
type: "boolean"
}
},
additionalProperties: false
Expand All @@ -54,13 +57,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 @@ -101,7 +98,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 @@ -127,7 +124,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
Expand Up @@ -16,6 +16,8 @@ module.exports = {
meta: {
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
Expand Up @@ -16,6 +16,8 @@ module.exports = {
meta: {
type: "layout",

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

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