-
-
Notifications
You must be signed in to change notification settings - Fork 29
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Add new
require-meta-default-options
rule (#502)
* Add new `require-meta-default-options` rule * Add `defaultOptions` to all rules * Improve rule description * Allow array root schemas to have empty `defaultOptions`
- Loading branch information
1 parent
334aec3
commit 13e625a
Showing
15 changed files
with
436 additions
and
23 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
# Require only rules with options to implement a `meta.defaultOptions` property (`eslint-plugin/require-meta-default-options`) | ||
|
||
🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). | ||
|
||
<!-- end auto-generated rule header --> | ||
|
||
Defining default options declaratively in a rule's `meta.defaultOptions` property enables ESLint v9.15.0+ to merge any user-provided options with the default options, simplifying the rule's implementation. It can also be useful for other tools like [eslint-doc-generator](https://github.com/bmish/eslint-doc-generator) to generate documentation for the rule's options. | ||
|
||
## Rule Details | ||
|
||
This rule requires ESLint rules to have a valid `meta.defaultOptions` property if and only if the rule has options defined in its `meta.schema` property. | ||
|
||
Examples of **incorrect** code for this rule: | ||
|
||
```js | ||
/* eslint eslint-plugin/require-meta-default-options: error */ | ||
|
||
module.exports = { | ||
meta: { | ||
schema: [ | ||
{ | ||
type: 'object', | ||
/* ... */ | ||
}, | ||
], | ||
// defaultOptions is missing | ||
}, | ||
create(context) { | ||
/* ... */ | ||
}, | ||
}; | ||
|
||
module.exports = { | ||
meta: { | ||
schema: [], | ||
defaultOptions: [{}], // defaultOptions is not needed when schema is empty | ||
}, | ||
create(context) { | ||
/* ... */ | ||
}, | ||
}; | ||
|
||
module.exports = { | ||
meta: { | ||
schema: [ | ||
{ | ||
/* ... */ | ||
}, | ||
], | ||
defaultOptions: {}, // defaultOptions should be an array | ||
}, | ||
create(context) { | ||
/* ... */ | ||
}, | ||
}; | ||
|
||
module.exports = { | ||
meta: { | ||
schema: [ | ||
{ | ||
/* ... */ | ||
}, | ||
], | ||
defaultOptions: [], // defaultOptions should not be empty | ||
}, | ||
create(context) { | ||
/* ... */ | ||
}, | ||
}; | ||
``` | ||
|
||
Examples of **correct** code for this rule: | ||
|
||
```js | ||
/* eslint eslint-plugin/require-meta-default-options: error */ | ||
|
||
module.exports = { | ||
meta: { schema: [] }, // no defaultOptions needed when schema is empty | ||
create(context) { | ||
/* ... */ | ||
}, | ||
}; | ||
|
||
module.exports = { | ||
meta: { | ||
schema: [ | ||
{ | ||
type: 'object', | ||
properties: { | ||
exceptRange: { | ||
type: 'boolean', | ||
}, | ||
}, | ||
additionalProperties: false, | ||
}, | ||
], | ||
defaultOptions: [{ exceptRange: false }], | ||
}, | ||
create(context) { | ||
/* ... */ | ||
}, | ||
}; | ||
``` | ||
|
||
## Further Reading | ||
|
||
- [ESLint rule docs: Option Defaults](https://eslint.org/docs/latest/extend/custom-rules#option-defaults) | ||
- [RFC introducing `meta.defaultOptions`](https://github.com/eslint/rfcs/blob/main/designs/2023-rule-options-defaults/README.md) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
'use strict'; | ||
|
||
const utils = require('../utils'); | ||
|
||
/** @type {import('eslint').Rule.RuleModule} */ | ||
module.exports = { | ||
meta: { | ||
type: 'suggestion', | ||
docs: { | ||
description: | ||
'require only rules with options to implement a `meta.defaultOptions` property', | ||
category: 'Rules', | ||
recommended: false, | ||
url: 'https://github.com/eslint-community/eslint-plugin-eslint-plugin/tree/HEAD/docs/rules/require-meta-default-options.md', | ||
}, | ||
fixable: 'code', | ||
schema: [], | ||
messages: { | ||
missingDefaultOptions: | ||
'Rule with non-empty schema is missing a `meta.defaultOptions` property.', | ||
unnecessaryDefaultOptions: | ||
'Rule with empty schema should not have a `meta.defaultOptions` property.', | ||
defaultOptionsMustBeArray: 'Default options must be an array.', | ||
defaultOptionsMustNotBeEmpty: 'Default options must not be empty.', | ||
}, | ||
}, | ||
|
||
create(context) { | ||
const sourceCode = context.sourceCode || context.getSourceCode(); // TODO: just use context.sourceCode when dropping eslint < v9 | ||
const { scopeManager } = sourceCode; | ||
const ruleInfo = utils.getRuleInfo(sourceCode); | ||
if (!ruleInfo) { | ||
return {}; | ||
} | ||
|
||
const metaNode = ruleInfo.meta; | ||
|
||
const schemaNode = utils.getMetaSchemaNode(metaNode, scopeManager); | ||
const schemaProperty = utils.getMetaSchemaNodeProperty( | ||
schemaNode, | ||
scopeManager, | ||
); | ||
if (!schemaProperty) { | ||
return {}; | ||
} | ||
|
||
const metaDefaultOptions = utils | ||
.evaluateObjectProperties(metaNode, scopeManager) | ||
.find( | ||
(p) => | ||
p.type === 'Property' && utils.getKeyName(p) === 'defaultOptions', | ||
); | ||
|
||
if ( | ||
schemaProperty.type === 'ArrayExpression' && | ||
schemaProperty.elements.length === 0 | ||
) { | ||
if (metaDefaultOptions) { | ||
context.report({ | ||
node: metaDefaultOptions, | ||
messageId: 'unnecessaryDefaultOptions', | ||
fix(fixer) { | ||
return fixer.remove(metaDefaultOptions); | ||
}, | ||
}); | ||
} | ||
return {}; | ||
} | ||
|
||
if (!metaDefaultOptions) { | ||
context.report({ | ||
node: metaNode, | ||
messageId: 'missingDefaultOptions', | ||
fix(fixer) { | ||
return fixer.insertTextAfter(schemaProperty, ', defaultOptions: []'); | ||
}, | ||
}); | ||
return {}; | ||
} | ||
|
||
if (metaDefaultOptions.value.type !== 'ArrayExpression') { | ||
context.report({ | ||
node: metaDefaultOptions.value, | ||
messageId: 'defaultOptionsMustBeArray', | ||
}); | ||
return {}; | ||
} | ||
|
||
const isArrayRootSchema = | ||
schemaProperty.type === 'ObjectExpression' && | ||
schemaProperty.properties.find((property) => property.key.name === 'type') | ||
?.value.value === 'array'; | ||
|
||
if (metaDefaultOptions.value.elements.length === 0 && !isArrayRootSchema) { | ||
context.report({ | ||
node: metaDefaultOptions.value, | ||
messageId: 'defaultOptionsMustNotBeEmpty', | ||
}); | ||
return {}; | ||
} | ||
|
||
return {}; | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.