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: improve rule schemas, add test to validate schemas, add tooling to generate schema types #6899

Merged
merged 18 commits into from
Apr 20, 2023
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
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
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ jobs:
'eslint-plugin-tslint',
'parser',
'repo-tools',
'rule-schema-to-typescript-types',
'scope-manager',
'type-utils',
'typescript-estree',
Expand Down
36 changes: 36 additions & 0 deletions docs/linting/Troubleshooting.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,42 @@ See our docs on [type aware linting](./Typed_Linting.mdx) for more information.
You're using an outdated version of `@typescript-eslint/parser`.
Update to the latest version to see a more informative version of this error message, explained [above](#i-get-errors-telling-me-eslint-was-configured-to-run--however-that-tsconfig-does-not--none-of-those-tsconfigs-include-this-file 'backlink to I get errors telling me ESLint was configured to run ...').

## How do I turn on a `@typescript-eslint` rule?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Meant for another PR?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Intentionally added as part of cleaning up the holistic docs update in this PR.
We've seen this asked a few times and the ESLint team doesn't think their docs are unclear, so as part of improving the rule config docs - this FAQ article to quick-link people to.


First make sure you've read the docs and understand ESLint configuration files:

- [Read our getting started guide](../Getting_Started.mdx) to ensure your config is properly setup to start configuring our rules.
- [Checkout ESLint's documentation on configuring rules](https://eslint.org/docs/latest/use/configure/rules) to ensure you understand how to configure rules.

Our [rule docs](/rules) detail the options each rule supports under the "Options" heading.
We use TypeScript types to describe an `Options` tuple type for the rule which you can use to configure the a rule.
In your config file the keys of the `rules` object are the names of the rules you wish to configure and the values follow the following form:

```ts
type Severity = 'off' | 'warn' | 'error';
type RuleConfig =
| Severity
| [Severity]
| [
Severiy,
// Options is the tuple type from the rule docs
...Options,
];
```

Some examples

```js title=".eslintrc.js"
module.exports = {
rules: {
// turns a rule on with no configuration (i.e. uses the default configuration)
'@typescript-eslint/array-type': 'error',
// turns on a rule with configuration
'@typescript-eslint/no-explicit-any': ['warn', { ignoreRestArgs: true }],
},
};
```

## I use a framework (like Vue) that requires custom file extensions, and I get errors like "You should add `parserOptions.extraFileExtensions` to your config"

You can use `parserOptions.extraFileExtensions` to specify an array of non-TypeScript extensions to allow, for example:
Expand Down
2 changes: 2 additions & 0 deletions packages/eslint-plugin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,9 @@
"@types/marked": "*",
"@types/natural-compare-lite": "^1.4.0",
"@types/prettier": "*",
"@typescript-eslint/rule-schema-to-typescript-types": "5.59.0",
"cross-fetch": "*",
"jest-specific-snapshot": "*",
"json-schema": "*",
"markdown-table": "^3.0.2",
"marked": "^4.0.15",
Expand Down
39 changes: 18 additions & 21 deletions packages/eslint-plugin/src/rules/array-type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,30 +105,27 @@ export default util.createRule<Options, MessageIds>({
errorStringGenericSimple:
"Array type using '{{readonlyPrefix}}{{type}}[]' is forbidden for non-simple types. Use '{{className}}<{{type}}>' instead.",
},
schema: {
$defs: {
arrayOption: {
enum: ['array', 'generic', 'array-simple'],
schema: [
{
$defs: {
arrayOption: {
enum: ['array', 'generic', 'array-simple'],
},
},
},
prefixItems: [
{
properties: {
default: {
$ref: '#/$defs/arrayOption',
description: 'The array type expected for mutable cases...',
},
readonly: {
$ref: '#/$defs/arrayOption',
description:
'The array type expected for readonly cases. If omitted, the value for `default` will be used.',
},
properties: {
default: {
$ref: '#/items/0/$defs/arrayOption',
description: 'The array type expected for mutable cases.',
},
readonly: {
$ref: '#/items/0/$defs/arrayOption',
description:
'The array type expected for readonly cases. If omitted, the value for `default` will be used.',
},
type: 'object',
},
],
type: 'array',
},
type: 'object',
},
],
},
defaultOptions: [
{
Expand Down
66 changes: 31 additions & 35 deletions packages/eslint-plugin/src/rules/ban-ts-comment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,45 +39,41 @@ export default util.createRule<[Options], MessageIds>({
tsDirectiveCommentDescriptionNotMatchPattern:
'The description for the "@ts-{{directive}}" directive must match the {{format}} format.',
},
schema: {
$defs: {
directiveConfigSchema: {
oneOf: [
{
type: 'boolean',
default: true,
},
{
enum: ['allow-with-description'],
},
{
type: 'object',
properties: {
descriptionFormat: { type: 'string' },
schema: [
{
$defs: {
directiveConfigSchema: {
oneOf: [
{
type: 'boolean',
default: true,
},
},
],
{
enum: ['allow-with-description'],
},
{
type: 'object',
properties: {
descriptionFormat: { type: 'string' },
},
},
],
},
},
},
prefixItems: [
{
properties: {
'ts-expect-error': {
$ref: '#/$defs/directiveConfigSchema',
},
'ts-ignore': { $ref: '#/$defs/directiveConfigSchema' },
'ts-nocheck': { $ref: '#/$defs/directiveConfigSchema' },
'ts-check': { $ref: '#/$defs/directiveConfigSchema' },
minimumDescriptionLength: {
type: 'number',
default: defaultMinimumDescriptionLength,
},
properties: {
'ts-expect-error': { $ref: '#/items/0/$defs/directiveConfigSchema' },
'ts-ignore': { $ref: '#/items/0/$defs/directiveConfigSchema' },
'ts-nocheck': { $ref: '#/items/0/$defs/directiveConfigSchema' },
'ts-check': { $ref: '#/items/0/$defs/directiveConfigSchema' },
minimumDescriptionLength: {
type: 'number',
default: defaultMinimumDescriptionLength,
},
additionalProperties: false,
},
],
type: 'array',
},
type: 'object',
additionalProperties: false,
},
],
},
defaultOptions: [
{
Expand Down
69 changes: 49 additions & 20 deletions packages/eslint-plugin/src/rules/ban-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import * as util from '../util';
type Types = Record<
string,
| null
| false
| boolean
| string
| {
message: string;
Expand Down Expand Up @@ -35,9 +35,9 @@ function stringifyNode(
}

function getCustomMessage(
bannedType: null | string | { message?: string; fixWith?: string },
bannedType: null | true | string | { message?: string; fixWith?: string },
): string {
if (bannedType == null) {
if (bannedType == null || bannedType === true) {
bradzacher marked this conversation as resolved.
Show resolved Hide resolved
return '';
}

Expand Down Expand Up @@ -140,28 +140,57 @@ export default util.createRule<Options, MessageIds>({
},
schema: [
{
$defs: {
banConfig: {
oneOf: [
{
type: 'null',
description: 'Bans the type with the default message',
},
{
enum: [false],
description:
'Un-bans the type (useful when paired with `extendDefaults`)',
},
{
enum: [true],
description: 'Bans the type with the default message',
},
{
type: 'string',
description: 'Bans the type with a custom message',
},
{
type: 'object',
description: 'Bans a type',
properties: {
message: {
type: 'string',
description: 'Custom error message',
},
fixWith: {
type: 'string',
description:
'Type to autofix replace with. Note that autofixers can be applied automatically - so you need to be careful with this option.',
},
suggest: {
type: 'array',
items: { type: 'string' },
description: 'Types to suggest replacing with.',
additionalItems: false,
},
},
additionalProperties: false,
},
],
},
},
type: 'object',
properties: {
types: {
type: 'object',
additionalProperties: {
oneOf: [
{ type: 'null' },
{ type: 'boolean' },
{ type: 'string' },
{
type: 'object',
properties: {
message: { type: 'string' },
fixWith: { type: 'string' },
suggest: {
type: 'array',
items: { type: 'string' },
},
},
additionalProperties: false,
},
],
$ref: '#/items/0/$defs/banConfig',
},
},
extendDefaults: {
Expand Down
89 changes: 42 additions & 47 deletions packages/eslint-plugin/src/rules/explicit-member-accessibility.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,23 +27,6 @@ type MessageIds =
| 'missingAccessibility'
| 'addExplicitAccessibility';

const accessibilityLevel = {
oneOf: [
{
const: 'explicit',
description: 'Always require an accessor.',
},
{
const: 'no-public',
description: 'Require an accessor except when public.',
},
{
const: 'off',
description: 'Never check whether there is an accessor.',
},
],
};

export default util.createRule<Options, MessageIds>({
name: 'explicit-member-accessibility',
meta: {
Expand All @@ -62,41 +45,53 @@ export default util.createRule<Options, MessageIds>({
'Public accessibility modifier on {{type}} {{name}}.',
addExplicitAccessibility: "Add '{{ type }}' accessibility modifier",
},
schema: {
$defs: {
accessibilityLevel,
},
prefixItems: [
{
type: 'object',
properties: {
accessibility: { $ref: '#/$defs/accessibilityLevel' },
overrides: {
type: 'object',
properties: {
accessors: { $ref: '#/$defs/accessibilityLevel' },
constructors: { $ref: '#/$defs/accessibilityLevel' },
methods: { $ref: '#/$defs/accessibilityLevel' },
properties: { $ref: '#/$defs/accessibilityLevel' },
parameterProperties: {
$ref: '#/$defs/accessibilityLevel',
},
schema: [
{
$defs: {
accessibilityLevel: {
oneOf: [
{
enum: ['explicit'],
description: 'Always require an accessor.',
},

additionalProperties: false,
},
ignoredMethodNames: {
type: 'array',
items: {
type: 'string',
{
enum: ['no-public'],
description: 'Require an accessor except when public.',
},
{
enum: ['off'],
description: 'Never check whether there is an accessor.',
},
],
},
},
type: 'object',
properties: {
accessibility: { $ref: '#/items/0/$defs/accessibilityLevel' },
overrides: {
type: 'object',
properties: {
accessors: { $ref: '#/items/0/$defs/accessibilityLevel' },
constructors: { $ref: '#/items/0/$defs/accessibilityLevel' },
methods: { $ref: '#/items/0/$defs/accessibilityLevel' },
properties: { $ref: '#/items/0/$defs/accessibilityLevel' },
parameterProperties: {
$ref: '#/items/0/$defs/accessibilityLevel',
},
},

additionalProperties: false,
},
ignoredMethodNames: {
type: 'array',
items: {
type: 'string',
},
},
additionalProperties: false,
},
],
type: 'array',
},
additionalProperties: false,
},
],
},
defaultOptions: [{ accessibility: 'explicit' }],
create(context, [option]) {
Expand Down