Skip to content

Commit

Permalink
docs: Expand on and clarify how rule configuration schemas are applie…
Browse files Browse the repository at this point in the history
…d, explicitly calling out the behaviour and potential pitfalls of the different styles, along with expanded examples.
  • Loading branch information
matwilko committed May 18, 2023
1 parent fde0727 commit 9caec8b
Showing 1 changed file with 87 additions and 4 deletions.
91 changes: 87 additions & 4 deletions docs/src/extend/custom-rules.md
Expand Up @@ -614,17 +614,40 @@ You can also access comments through many of `sourceCode`'s methods using the `i

Rules may export a `schema` property, which is a [JSON Schema](https://json-schema.org/) format description of a rule's options which will be used by ESLint to validate configuration options and prevent invalid or unexpected inputs before they are passed to the rule in `context.options`.

There are two formats for a rule's exported `schema`:
If `schema` is not specified, then any options (besides severity) supplied to the rule are passed on verbatim without validation.

When validating a rule's options, there are 3 main preparation/validation steps:

1. If the options are not an array, the value is wrapped into an array (e.g. `'off'` becomes `['off']`)
2. ESLint will validate the first element of the options array as a severity (`off`, `warn`, `error`, `0`, `1`, `2`)
3. If the rule is enabled, then the remaining elements of the array are sliced and the rule's schema validation is run on this sliced options array (e.g. `['warn', 'never', { someOption: 5 }]` results in the schema being applied to `['never', { someOption: 5 }]`)

Note that this means that the rule schema cannot see or validate the severity, it only sees the array elements _after_ the severity.

1. A full JSON Schema object describing all possible options the rule accepts.
2. An array of JSON Schema objects for each optional positional argument.
There are two formats for a rule's exported `schema`:

In both cases, these should exclude the [severity](../use/configure/rules#rule-severities), as ESLint automatically validates this first.
* An array of JSON Schema objects
* Each element will be checked against the same position in the sliced options array
* If the sliced options array has fewer elements than there are schemas, then the unmatched schemas are ignored
* If the sliced options array has more elements than there are schemas, the validation fails
* Two important consequences:
* Empty rule options beyond severity are always valid (e.g. `['error']` will be a valid rule option)
* An empty array _enforces no options_ for the rule
* A full JSON Schema object that will validate the sliced options array
* Be very careful that you are _always_ validating against an array, even if you only expect one options value/object to be supplied
* The schema can be arbitrarily complex, so you can validate completely different sets of potential options via `oneOf` or `if`/`then`/`else` etc.

For example, the `yoda` rule accepts a primary mode argument of `"always"` or `"never"`, as well as an extra options object with an optional property `exceptRange`:

```js
// Valid options:
// "yoda": "warn"
// "yoda": ["error"]
// "yoda": ["error", "always"]
// "yoda": ["error", "never", { "exceptRange": true }]
// Invalid options:
// "yoda": ["warn", "never", { "exceptRange": true }, 5]
// "yoda": ["error", { "exceptRange": true }, "never"]
module.exports = {
meta: {
schema: [
Expand All @@ -645,6 +668,66 @@ module.exports = {
};
```

And as an example for the full JSON Schema object form:

```js
// Valid options:
// "yoda": ["error", 5]
// "yoda": ["warn", 10, { someNonOptionalProperty: true }]
// Invalid options:
// "yoda": "warn"
// "yoda": ["error"]
// "yoda": ["warn", 15]
// "yoda": ["warn", 7, { }]
// "yoda": ["warn", 7, { someOtherProperty: 5 }]
// "yoda": ["warn", 7, { someNonOptionalProperty: false, someOtherProperty: 5 }]
module.exports = {
meta: {
schema: {
type: "array",
minItems: 1,
items: [
{
type: "number",
minimum: 0,
maximum: 10
},
{
type: "object",
properties: {
someNonOptionalProperty: {
type: "boolean"
}
},
required: ['someNonOptionalProperty'],
additionalProperties: false
}
]
}
}
}
```

And note the easy pitfall of specifying a schema that will _always_ fail because it doesn't account for the array being validated:

```js
// Possibly trying to validate ["error", { someOptionalProperty: true }], but:
// THERE ARE NO POSSIBLE VALID OPTIONS FOR THIS SCHEMA WHEN THE RULE IS ENABLED
module.exports = {
meta: {
schema: {
type: "object",
properties: {
someOptionalProperty: {
type: "boolean"
}
},
additionalProperties: false
}
}
}
```

**Note:** If your rule schema uses JSON schema [`$ref`](https://json-schema.org/understanding-json-schema/structuring.html#ref) properties, you must use the full JSON Schema object rather than the array of positional property schemas. This is because ESLint transforms the array shorthand into a single schema without updating references that makes them incorrect (they are ignored).

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.
Expand Down

0 comments on commit 9caec8b

Please sign in to comment.