-
-
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
no-meta-schema-default
rule (#503)
* Add new `no-meta-schema-default` rule * Disable rule locally * Compare `type` rather than `key` Co-authored-by: 唯然 <[email protected]> --------- Co-authored-by: 唯然 <[email protected]>
- Loading branch information
1 parent
8de68ec
commit dbd96b2
Showing
5 changed files
with
449 additions
and
0 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,83 @@ | ||
# Disallow rules `meta.schema` properties to include defaults (`eslint-plugin/no-meta-schema-default`) | ||
|
||
<!-- end auto-generated rule header --> | ||
|
||
Since ESLint v9.15.0, rules' default options are supported using `meta.defaultOptions`. Additionally defining them using the `default` property in `meta.schema` is confusing, error-prone, and can be ambiguous for complex schemas. | ||
|
||
## Rule Details | ||
|
||
This rule disallows the `default` property in rules' `meta.schema`. | ||
|
||
Examples of **incorrect** code for this rule: | ||
|
||
```js | ||
/* eslint eslint-plugin/no-meta-schema-default: error */ | ||
|
||
module.exports = { | ||
meta: { | ||
schema: [ | ||
{ | ||
elements: { type: 'string' }, | ||
type: 'array', | ||
default: [], | ||
}, | ||
], | ||
}, | ||
create() {}, | ||
}; | ||
|
||
module.exports = { | ||
meta: { | ||
schema: { | ||
type: 'object', | ||
properties: { | ||
foo: { type: 'string', default: 'bar' }, | ||
baz: { type: 'number', default: 42 }, | ||
}, | ||
}, | ||
}, | ||
create() {}, | ||
}; | ||
``` | ||
|
||
Examples of **correct** code for this rule: | ||
|
||
```js | ||
/* eslint eslint-plugin/no-meta-schema-default: error */ | ||
|
||
module.exports = { | ||
meta: { | ||
schema: [ | ||
{ | ||
elements: { type: 'string' }, | ||
type: 'array', | ||
}, | ||
], | ||
defaultOptions: [[]], | ||
}, | ||
create() {}, | ||
}; | ||
|
||
module.exports = { | ||
meta: { | ||
schema: { | ||
type: 'object', | ||
properties: { | ||
foo: { type: 'string' }, | ||
baz: { type: 'number' }, | ||
}, | ||
}, | ||
defaultOptions: [{ foo: 'bar', baz: 42 }], | ||
}, | ||
create() {}, | ||
}; | ||
``` | ||
|
||
## When Not To Use It | ||
|
||
When using [`eslint-doc-generator`](https://github.com/bmish/eslint-doc-generator) to generate documentation for your rules, you may want to disable this rule to include the `default` property in the generated documentation. This is because `eslint-doc-generator` does not yet support `meta.defaultOptions`, see [bmish/eslint-doc-generator#513](https://github.com/bmish/eslint-doc-generator/issues/513). | ||
|
||
## 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
'use strict'; | ||
|
||
const { getStaticValue } = require('@eslint-community/eslint-utils'); | ||
const utils = require('../utils'); | ||
|
||
// ------------------------------------------------------------------------------ | ||
// Rule Definition | ||
// ------------------------------------------------------------------------------ | ||
|
||
/** @type {import('eslint').Rule.RuleModule} */ | ||
module.exports = { | ||
meta: { | ||
type: 'suggestion', | ||
docs: { | ||
description: | ||
'disallow rules `meta.schema` properties to include defaults', | ||
category: 'Rules', | ||
recommended: false, | ||
url: 'https://github.com/eslint-community/eslint-plugin-eslint-plugin/tree/HEAD/docs/rules/no-meta-schema-default.md', | ||
}, | ||
schema: [], | ||
messages: { | ||
foundDefault: 'Disallowed default value in schema.', | ||
}, | ||
}, | ||
|
||
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 schemaNode = utils.getMetaSchemaNode(ruleInfo.meta, scopeManager); | ||
if (!schemaNode) { | ||
return {}; | ||
} | ||
|
||
const schemaProperty = utils.getMetaSchemaNodeProperty( | ||
schemaNode, | ||
scopeManager, | ||
); | ||
|
||
if (schemaProperty?.type === 'ObjectExpression') { | ||
checkSchemaElement(schemaProperty, true); | ||
} else if (schemaProperty?.type === 'ArrayExpression') { | ||
for (const element of schemaProperty.elements) { | ||
checkSchemaElement(element, true); | ||
} | ||
} | ||
|
||
return {}; | ||
|
||
function checkSchemaElement(node) { | ||
if (node.type !== 'ObjectExpression') { | ||
return; | ||
} | ||
|
||
for (const { type, key, value } of node.properties) { | ||
if (type !== 'Property') { | ||
continue; | ||
} | ||
const staticKey = | ||
key.type === 'Identifier' ? { value: key.name } : getStaticValue(key); | ||
if (!staticKey?.value) { | ||
continue; | ||
} | ||
|
||
switch (key.name ?? key.value) { | ||
case 'allOf': | ||
case 'anyOf': | ||
case 'oneOf': { | ||
if (value.type === 'ArrayExpression') { | ||
for (const element of value.elements) { | ||
checkSchemaElement(element); | ||
} | ||
} | ||
|
||
break; | ||
} | ||
|
||
case 'properties': { | ||
if (Array.isArray(value.properties)) { | ||
for (const property of value.properties) { | ||
if (property.value?.type === 'ObjectExpression') { | ||
checkSchemaElement(property.value); | ||
} | ||
} | ||
} | ||
|
||
break; | ||
} | ||
|
||
case 'elements': { | ||
checkSchemaElement(value); | ||
|
||
break; | ||
} | ||
|
||
case 'default': { | ||
context.report({ | ||
messageId: 'foundDefault', | ||
node: key, | ||
}); | ||
|
||
break; | ||
} | ||
} | ||
} | ||
} | ||
}, | ||
}; |
Oops, something went wrong.