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): [ban-ts-comments] suggest ts-expect-error over ts-ignore #7849

Merged
merged 2 commits into from
Oct 30, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
47 changes: 40 additions & 7 deletions packages/eslint-plugin/src/rules/ban-ts-comment.ts
@@ -1,4 +1,4 @@
import { AST_TOKEN_TYPES } from '@typescript-eslint/utils';
import { AST_TOKEN_TYPES, type TSESLint } from '@typescript-eslint/utils';

import { createRule, getStringLength } from '../util';

Expand All @@ -19,8 +19,10 @@ export const defaultMinimumDescriptionLength = 3;

type MessageIds =
| 'tsDirectiveComment'
| 'tsIgnoreInsteadOfExpectError'
| 'tsDirectiveCommentDescriptionNotMatchPattern'
| 'tsDirectiveCommentRequiresDescription';
| 'tsDirectiveCommentRequiresDescription'
| 'replaceTsIgnoreWithTsExpectError';

export default createRule<[Options], MessageIds>({
name: 'ban-ts-comment',
Expand All @@ -34,11 +36,16 @@ export default createRule<[Options], MessageIds>({
messages: {
tsDirectiveComment:
'Do not use "@ts-{{directive}}" because it alters compilation errors.',
tsIgnoreInsteadOfExpectError:
'Use "@ts-expect-error" instead of "@ts-ignore", as "@ts-ignore" will do nothing if the following line is error-free.',
tsDirectiveCommentRequiresDescription:
'Include a description after the "@ts-{{directive}}" directive to explain why the @ts-{{directive}} is necessary. The description must be {{minimumDescriptionLength}} characters or longer.',
tsDirectiveCommentDescriptionNotMatchPattern:
'The description for the "@ts-{{directive}}" directive must match the {{format}} format.',
replaceTsIgnoreWithTsExpectError:
'Replace "@ts-ignore" with "@ts-expect-error".',
},
hasSuggestions: true,
schema: [
{
$defs: {
Expand Down Expand Up @@ -130,11 +137,37 @@ export default createRule<[Options], MessageIds>({

const option = options[fullDirective];
if (option === true) {
context.report({
data: { directive },
node: comment,
messageId: 'tsDirectiveComment',
});
if (directive === 'ignore' && options['ts-expect-error'] !== true) {
// Special case to suggest @ts-expect-error instead of @ts-ignore,
// as long as @ts-expect-error is banned outright.
NotWoods marked this conversation as resolved.
Show resolved Hide resolved
context.report({
node: comment,
messageId: 'tsIgnoreInsteadOfExpectError',
suggest: [
{
messageId: 'replaceTsIgnoreWithTsExpectError',
fix(fixer): TSESLint.RuleFix {
const commentText = comment.value.replace(
/@ts-ignore/,
'@ts-expect-error',
);
return fixer.replaceText(
comment,
comment.type === AST_TOKEN_TYPES.Line
? `//${commentText}`
: `/*${commentText}*/`,
);
},
},
],
});
} else {
context.report({
data: { directive },
node: comment,
messageId: 'tsDirectiveComment',
});
}
}

if (
Expand Down
112 changes: 102 additions & 10 deletions packages/eslint-plugin/tests/rules/ban-ts-comment.test.ts
Expand Up @@ -303,7 +303,7 @@ ruleTester.run('ts-ignore', rule, {
invalid: [
{
code: '// @ts-ignore',
options: [{ 'ts-ignore': true }],
options: [{ 'ts-ignore': true, 'ts-expect-error': true }],
errors: [
{
data: { directive: 'ignore' },
Expand All @@ -315,18 +315,36 @@ ruleTester.run('ts-ignore', rule, {
},
{
code: '// @ts-ignore',
options: [
{ 'ts-ignore': true, 'ts-expect-error': 'allow-with-description' },
],
errors: [
{
data: { directive: 'ignore' },
messageId: 'tsDirectiveComment',
messageId: 'tsIgnoreInsteadOfExpectError',
line: 1,
column: 1,
suggestions: [
{
messageId: 'replaceTsIgnoreWithTsExpectError',
output: '// @ts-expect-error',
},
],
},
],
},
{
code: '// @ts-ignore',
errors: [
{
messageId: 'tsIgnoreInsteadOfExpectError',
line: 1,
column: 1,
},
],
},
{
code: '/* @ts-ignore */',
options: [{ 'ts-ignore': true }],
options: [{ 'ts-ignore': true, 'ts-expect-error': true }],
errors: [
{
data: { directive: 'ignore' },
Expand All @@ -336,13 +354,30 @@ ruleTester.run('ts-ignore', rule, {
},
],
},
{
code: '/* @ts-ignore */',
options: [{ 'ts-ignore': true, 'ts-expect-error': false }],
errors: [
{
messageId: 'tsIgnoreInsteadOfExpectError',
line: 1,
column: 1,
suggestions: [
{
messageId: 'replaceTsIgnoreWithTsExpectError',
output: '/* @ts-expect-error */',
},
],
},
],
},
{
code: `
/*
@ts-ignore
*/
`,
options: [{ 'ts-ignore': true }],
options: [{ 'ts-ignore': true, 'ts-expect-error': true }],
errors: [
{
data: { directive: 'ignore' },
Expand All @@ -353,8 +388,33 @@ ruleTester.run('ts-ignore', rule, {
],
},
{
code: '/** @ts-ignore */',
code: `
/*
@ts-ignore
*/
`,
options: [{ 'ts-ignore': true }],
errors: [
{
messageId: 'tsIgnoreInsteadOfExpectError',
line: 2,
column: 1,
suggestions: [
{
messageId: 'replaceTsIgnoreWithTsExpectError',
output: `
/*
@ts-expect-error
*/
`,
},
],
},
],
},
{
code: '/** @ts-ignore */',
options: [{ 'ts-ignore': true, 'ts-expect-error': true }],
errors: [
{
data: { directive: 'ignore' },
Expand All @@ -366,6 +426,23 @@ ruleTester.run('ts-ignore', rule, {
},
{
code: '// @ts-ignore: Suppress next line',
errors: [
{
messageId: 'tsIgnoreInsteadOfExpectError',
line: 1,
column: 1,
suggestions: [
{
messageId: 'replaceTsIgnoreWithTsExpectError',
output: '// @ts-expect-error: Suppress next line',
},
],
},
],
},
{
code: '// @ts-ignore: Suppress next line',
options: [{ 'ts-ignore': true, 'ts-expect-error': true }],
errors: [
{
data: { directive: 'ignore' },
Expand All @@ -379,10 +456,15 @@ ruleTester.run('ts-ignore', rule, {
code: '/////@ts-ignore: Suppress next line',
errors: [
{
data: { directive: 'ignore' },
messageId: 'tsDirectiveComment',
messageId: 'tsIgnoreInsteadOfExpectError',
line: 1,
column: 1,
suggestions: [
{
messageId: 'replaceTsIgnoreWithTsExpectError',
output: '/////@ts-expect-error: Suppress next line',
},
],
},
],
},
Expand All @@ -395,10 +477,20 @@ if (false) {
`,
errors: [
{
data: { directive: 'ignore' },
messageId: 'tsDirectiveComment',
messageId: 'tsIgnoreInsteadOfExpectError',
line: 3,
column: 3,
suggestions: [
{
messageId: 'replaceTsIgnoreWithTsExpectError',
output: `
if (false) {
// @ts-expect-error: Unreachable code error
console.log('hello');
}
`,
},
],
},
],
},
Expand Down