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(eslint-plugin): add meta.docs.recommended setting for strict config options #8364

25 changes: 22 additions & 3 deletions packages/eslint-plugin/src/configs/strict-type-checked-only.ts
Expand Up @@ -15,7 +15,7 @@ export = {
'@typescript-eslint/no-base-to-string': 'error',
'@typescript-eslint/no-confusing-void-expression': 'error',
'@typescript-eslint/no-duplicate-type-constituents': 'error',
'@typescript-eslint/no-floating-promises': 'error',
'@typescript-eslint/no-floating-promises': ['error', { ignoreVoid: false }],
'@typescript-eslint/no-for-in-array': 'error',
'no-implied-eval': 'off',
'@typescript-eslint/no-implied-eval': 'error',
Expand Down Expand Up @@ -43,8 +43,27 @@ export = {
'@typescript-eslint/prefer-return-this-type': 'error',
'require-await': 'off',
'@typescript-eslint/require-await': 'error',
'@typescript-eslint/restrict-plus-operands': 'error',
'@typescript-eslint/restrict-template-expressions': 'error',
'@typescript-eslint/restrict-plus-operands': [
'error',
{
allowAny: false,
allowBoolean: false,
allowNullish: false,
allowNumberAndString: false,
allowRegExp: false,
},
],
'@typescript-eslint/restrict-template-expressions': [
'error',
{
allowAny: false,
allowBoolean: false,
allowNullish: false,
allowNumber: false,
allowRegExp: false,
allowNever: false,
},
],
'@typescript-eslint/unbound-method': 'error',
},
} satisfies ClassicConfig.Config;
30 changes: 26 additions & 4 deletions packages/eslint-plugin/src/configs/strict-type-checked.ts
Expand Up @@ -11,7 +11,10 @@ export = {
extends: ['./configs/base', './configs/eslint-recommended'],
rules: {
'@typescript-eslint/await-thenable': 'error',
'@typescript-eslint/ban-ts-comment': 'error',
'@typescript-eslint/ban-ts-comment': [
'error',
{ minimumDescriptionLength: 10 },
],
'@typescript-eslint/ban-types': 'error',
'no-array-constructor': 'off',
'@typescript-eslint/no-array-constructor': 'error',
Expand All @@ -24,7 +27,7 @@ export = {
'@typescript-eslint/no-explicit-any': 'error',
'@typescript-eslint/no-extra-non-null-assertion': 'error',
'@typescript-eslint/no-extraneous-class': 'error',
'@typescript-eslint/no-floating-promises': 'error',
'@typescript-eslint/no-floating-promises': ['error', { ignoreVoid: false }],
'@typescript-eslint/no-for-in-array': 'error',
'no-implied-eval': 'off',
'@typescript-eslint/no-implied-eval': 'error',
Expand Down Expand Up @@ -71,8 +74,27 @@ export = {
'@typescript-eslint/prefer-ts-expect-error': 'error',
'require-await': 'off',
'@typescript-eslint/require-await': 'error',
'@typescript-eslint/restrict-plus-operands': 'error',
'@typescript-eslint/restrict-template-expressions': 'error',
'@typescript-eslint/restrict-plus-operands': [
'error',
{
allowAny: false,
allowBoolean: false,
allowNullish: false,
allowNumberAndString: false,
allowRegExp: false,
},
],
'@typescript-eslint/restrict-template-expressions': [
'error',
{
allowAny: false,
allowBoolean: false,
allowNullish: false,
allowNumber: false,
allowRegExp: false,
allowNever: false,
},
],
'@typescript-eslint/triple-slash-reference': 'error',
'@typescript-eslint/unbound-method': 'error',
'@typescript-eslint/unified-signatures': 'error',
Expand Down
5 changes: 4 additions & 1 deletion packages/eslint-plugin/src/configs/strict.ts
Expand Up @@ -10,7 +10,10 @@ import type { ClassicConfig } from '@typescript-eslint/utils/ts-eslint';
export = {
extends: ['./configs/base', './configs/eslint-recommended'],
rules: {
'@typescript-eslint/ban-ts-comment': 'error',
'@typescript-eslint/ban-ts-comment': [
'error',
{ minimumDescriptionLength: 10 },
],
'@typescript-eslint/ban-types': 'error',
'no-array-constructor': 'off',
'@typescript-eslint/no-array-constructor': 'error',
Expand Down
5 changes: 4 additions & 1 deletion packages/eslint-plugin/src/rules/ban-ts-comment.ts
Expand Up @@ -32,7 +32,10 @@ export default createRule<[Options], MessageIds>({
docs: {
description:
'Disallow `@ts-<directive>` comments or require descriptions after directives',
recommended: 'recommended',
recommended: {
recommended: true,
strict: [{ minimumDescriptionLength: 10 }],
},
},
messages: {
tsDirectiveComment:
Expand Down
5 changes: 4 additions & 1 deletion packages/eslint-plugin/src/rules/no-floating-promises.ts
Expand Up @@ -50,7 +50,10 @@ export default createRule<Options, MessageId>({
docs: {
description:
'Require Promise-like statements to be handled appropriately',
recommended: 'recommended',
recommended: {
recommended: true,
strict: [{ ignoreVoid: false }],
},
requiresTypeChecking: true,
},
hasSuggestions: true,
Expand Down
13 changes: 12 additions & 1 deletion packages/eslint-plugin/src/rules/restrict-plus-operands.ts
Expand Up @@ -31,7 +31,18 @@ export default createRule<Options, MessageIds>({
docs: {
description:
'Require both operands of addition to be the same type and be `bigint`, `number`, or `string`',
recommended: 'recommended',
recommended: {
recommended: true,
strict: [
{
allowAny: false,
allowBoolean: false,
allowNullish: false,
allowNumberAndString: false,
allowRegExp: false,
},
],
},
requiresTypeChecking: true,
},
messages: {
Expand Down
Expand Up @@ -62,7 +62,19 @@ export default createRule<Options, MessageId>({
docs: {
description:
'Enforce template literal expressions to be of `string` type',
recommended: 'recommended',
recommended: {
recommended: true,
strict: [
{
allowAny: false,
allowBoolean: false,
allowNullish: false,
allowNumber: false,
Copy link
Contributor

@llllvvuu llllvvuu Mar 19, 2024

Choose a reason for hiding this comment

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

Is allowNumber: false intended? If so, should this doc be updated?

This rule reports on values used in a template literal string that aren't strings, numbers, or BigInts, optionally allowing other data types that provide useful stringification results.

Copy link
Member Author

Choose a reason for hiding this comment

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

If you think there's a small typo in the docs like that, we'd welcome a PR :) thanks

allowRegExp: false,
allowNever: false,
},
],
},
requiresTypeChecking: true,
},
messages: {
Expand Down
47 changes: 37 additions & 10 deletions packages/eslint-plugin/tests/configs.test.ts
Expand Up @@ -23,7 +23,9 @@ function entriesToObject<T = unknown>(value: [string, T][]): Record<string, T> {
}, {});
}

function filterRules(values: Record<string, string>): [string, string][] {
function filterRules(
values: Record<string, string | unknown[]>,
): [string, string | unknown[]][] {
return Object.entries(values).filter(([name]) =>
name.startsWith(RULE_NAME_PREFIX),
);
Expand All @@ -39,7 +41,7 @@ function filterAndMapRuleConfigs({
excludeDeprecated,
typeChecked,
recommendations,
}: FilterAndMapRuleConfigsSettings = {}): [string, string][] {
}: FilterAndMapRuleConfigsSettings = {}): [string, unknown][] {
let result = Object.entries(rules);

if (excludeDeprecated) {
Expand All @@ -55,16 +57,41 @@ function filterAndMapRuleConfigs({
}

if (recommendations) {
result = result.filter(([, rule]) =>
recommendations.includes(rule.meta.docs?.recommended),
);
result = result.filter(([, rule]) => {
switch (typeof rule.meta.docs?.recommended) {
case 'undefined':
return false;
case 'object':
return Object.keys(rule.meta.docs.recommended).some(recommended =>
recommendations.includes(recommended as RuleRecommendation),
);
case 'string':
return recommendations.includes(rule.meta.docs.recommended);
}
});
}

return result.map(([name]) => [`${RULE_NAME_PREFIX}${name}`, 'error']);
const highestRecommendation = recommendations?.filter(Boolean).at(-1);

return result.map(([name, rule]) => {
const customRecommendation =
highestRecommendation &&
typeof rule.meta.docs?.recommended === 'object' &&
rule.meta.docs.recommended[
highestRecommendation as 'recommended' | 'strict'
];

return [
`${RULE_NAME_PREFIX}${name}`,
customRecommendation && typeof customRecommendation !== 'boolean'
? ['error', customRecommendation[0]]
: 'error',
];
});
}

function itHasBaseRulesOverriden(
unfilteredConfigRules: Record<string, string>,
unfilteredConfigRules: Record<string, string | unknown[]>,
): void {
it('has the base rules overriden by the appropriate extension rules', () => {
const ruleNames = new Set(Object.keys(unfilteredConfigRules));
Expand Down Expand Up @@ -166,7 +193,7 @@ describe('recommended-type-checked-only.ts', () => {
});

describe('strict.ts', () => {
const unfilteredConfigRules: Record<string, string> =
const unfilteredConfigRules: Record<string, string | unknown[]> =
plugin.configs.strict.rules;

it('contains all strict rules, excluding type checked ones', () => {
Expand All @@ -185,7 +212,7 @@ describe('strict.ts', () => {
});

describe('strict-type-checked.ts', () => {
const unfilteredConfigRules: Record<string, string> =
const unfilteredConfigRules: Record<string, string | unknown[]> =
plugin.configs['strict-type-checked'].rules;

it('contains all strict rules', () => {
Expand Down Expand Up @@ -221,7 +248,7 @@ describe('strict-type-checked-only.ts', () => {
});

describe('stylistic.ts', () => {
const unfilteredConfigRules: Record<string, string> =
const unfilteredConfigRules: Record<string, string | unknown[]> =
plugin.configs.stylistic.rules;

it('contains all stylistic rules, excluding deprecated or type checked ones', () => {
Expand Down