Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ import (
"github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/prefer_reduce_type_parameter"
"github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/prefer_return_this_type"
"github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/prefer_string_starts_ends_with"
"github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/prefer_ts_expect_error"
"github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/promise_function_async"
"github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/related_getter_setter_pairs"
"github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/require_array_sort_compare"
Expand Down Expand Up @@ -448,6 +449,7 @@ func registerAllTypeScriptEslintPluginRules() {
GlobalRuleRegistry.Register("@typescript-eslint/prefer-reduce-type-parameter", prefer_reduce_type_parameter.PreferReduceTypeParameterRule)
GlobalRuleRegistry.Register("@typescript-eslint/prefer-return-this-type", prefer_return_this_type.PreferReturnThisTypeRule)
GlobalRuleRegistry.Register("@typescript-eslint/prefer-string-starts-ends-with", prefer_string_starts_ends_with.PreferStringStartsEndsWithRule)
GlobalRuleRegistry.Register("@typescript-eslint/prefer-ts-expect-error", prefer_ts_expect_error.PreferTsExpectErrorRule)
GlobalRuleRegistry.Register("@typescript-eslint/promise-function-async", promise_function_async.PromiseFunctionAsyncRule)
GlobalRuleRegistry.Register("@typescript-eslint/related-getter-setter-pairs", related_getter_setter_pairs.RelatedGetterSetterPairsRule)
GlobalRuleRegistry.Register("@typescript-eslint/require-array-sort-compare", require_array_sort_compare.RequireArraySortCompareRule)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
package prefer_ts_expect_error

import (
"strings"

"github.com/microsoft/typescript-go/shim/ast"
"github.com/microsoft/typescript-go/shim/core"
"github.com/web-infra-dev/rslint/internal/rule"
"github.com/web-infra-dev/rslint/internal/utils"
)

const tsIgnoreDirective = "@ts-ignore"
const tsExpectErrorDirective = "@ts-expect-error"

func buildPreferExpectErrorMessage() rule.RuleMessage {
return rule.RuleMessage{
Id: "preferExpectErrorComment",
Description: "Use @ts-expect-error instead of @ts-ignore.",
}
}

func findDirectiveInLineComment(commentText string) (int, int, bool) {
if len(commentText) < 2 || commentText[0] != '/' || commentText[1] != '/' {
return 0, 0, false
}
idx := 2
for idx < len(commentText) && (commentText[idx] == ' ' || commentText[idx] == '\t') {
idx++
}
if idx < len(commentText) && commentText[idx] == '/' {
idx++
}
for idx < len(commentText) && (commentText[idx] == ' ' || commentText[idx] == '\t') {
idx++
}
if hasTsIgnoreDirectiveAt(commentText, idx) {
return idx, idx + len(tsIgnoreDirective), true
}
return 0, 0, false
}

func isDirectiveBoundaryChar(ch byte) bool {
return (ch < 'a' || ch > 'z') &&
(ch < 'A' || ch > 'Z') &&
(ch < '0' || ch > '9') &&
ch != '_' &&
ch != '$'
}

func hasTsIgnoreDirectiveAt(text string, idx int) bool {
if idx < 0 || idx >= len(text) || !strings.HasPrefix(text[idx:], tsIgnoreDirective) {
return false
}
end := idx + len(tsIgnoreDirective)
if end >= len(text) {
return true
}
return isDirectiveBoundaryChar(text[end])
}

func findDirectiveInBlockComment(commentText string) (int, int, bool) {
if len(commentText) < 4 {
return 0, 0, false
}

contentStart := 2
contentEnd := len(commentText) - 2
if contentEnd <= contentStart {
return 0, 0, false
}
content := commentText[contentStart:contentEnd]

lastLineStart := strings.LastIndexByte(content, '\n')
if lastLineStart == -1 {
lastLineStart = 0
} else {
lastLineStart++
}

line := content[lastLineStart:]
idx := 0
for idx < len(line) && (line[idx] == ' ' || line[idx] == '\t' || line[idx] == '\r') {
idx++
}
for idx < len(line) && (line[idx] == '/' || line[idx] == '*') {
idx++
}
for idx < len(line) && (line[idx] == ' ' || line[idx] == '\t') {
idx++
}
if idx < len(line) && hasTsIgnoreDirectiveAt(line, idx) {
start := contentStart + lastLineStart + idx
return start, start + len(tsIgnoreDirective), true
}

return 0, 0, false
}

func findTsIgnoreDirective(commentText string, kind ast.Kind) (int, int, bool) {
switch kind {
case ast.KindSingleLineCommentTrivia:
return findDirectiveInLineComment(commentText)
case ast.KindMultiLineCommentTrivia:
return findDirectiveInBlockComment(commentText)
}
return 0, 0, false
}

var PreferTsExpectErrorRule = rule.CreateRule(rule.Rule{
Name: "prefer-ts-expect-error",
Run: func(ctx rule.RuleContext, options any) rule.RuleListeners {
fullText := ctx.SourceFile.Text()
utils.ForEachComment(ctx.SourceFile.AsNode(), func(comment *ast.CommentRange) {
if comment == nil {
return
}
commentText := fullText[comment.Pos():comment.End()]
start, end, ok := findTsIgnoreDirective(commentText, comment.Kind)
if !ok {
return
}

fixRange := core.NewTextRange(comment.Pos()+start, comment.Pos()+end)
fix := rule.RuleFixReplaceRange(fixRange, tsExpectErrorDirective)
commentRange := core.NewTextRange(comment.Pos(), comment.End())
ctx.ReportRangeWithFixes(commentRange, buildPreferExpectErrorMessage(), fix)
}, ctx.SourceFile)

return rule.RuleListeners{}
},
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# prefer-ts-expect-error

## Rule Details

Prefer `@ts-expect-error` over `@ts-ignore` in TypeScript directive comments.

Examples of **incorrect** code for this rule:

```typescript
// @ts-ignore
```

Examples of **correct** code for this rule:

```typescript
// @ts-expect-error
```

## Original Documentation

https://typescript-eslint.io/rules/prefer-ts-expect-error
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
package prefer_ts_expect_error

import (
"testing"

"github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/fixtures"
"github.com/web-infra-dev/rslint/internal/rule_tester"
)

func TestPreferTsExpectErrorRule(t *testing.T) {
rule_tester.RunRuleTester(fixtures.GetRootDir(), "tsconfig.json", t, &PreferTsExpectErrorRule, []rule_tester.ValidTestCase{
{Code: `// @ts-nocheck`},
{Code: `// @ts-check`},
{Code: `// just a comment containing @ts-ignore somewhere`},
{Code: `// @ts-ignorefoo`},
{Code: `/* @ts-ignorefoo */`},
{
Code: `
{
/*
just a comment containing @ts-ignore somewhere in a block
*/
}
`,
},
{Code: `// @ts-expect-error`},
{
Code: `
if (false) {
// @ts-expect-error: Unreachable code error
console.log('hello');
}
`,
},
{
Code: `
/**
* Explaining comment
*
* @ts-expect-error
*
* Not last line
* */
`,
},
}, []rule_tester.InvalidTestCase{
{
Code: `// @ts-ignore`,
Output: []string{
`// @ts-expect-error`,
},
Errors: []rule_tester.InvalidTestCaseError{
{MessageId: "preferExpectErrorComment", Line: 1, Column: 1, EndLine: 1, EndColumn: 14},
},
},
{
Code: `// @ts-ignore: Suppress next line`,
Output: []string{
`// @ts-expect-error: Suppress next line`,
},
Errors: []rule_tester.InvalidTestCaseError{
{MessageId: "preferExpectErrorComment", Line: 1, Column: 1, EndLine: 1, EndColumn: 34},
},
},
{
Code: `///@ts-ignore: Suppress next line`,
Output: []string{
`///@ts-expect-error: Suppress next line`,
},
Errors: []rule_tester.InvalidTestCaseError{
{MessageId: "preferExpectErrorComment", Line: 1, Column: 1, EndLine: 1, EndColumn: 34},
},
},
{
Code: `/// @ts-ignore: Suppress next line`,
Output: []string{
`/// @ts-expect-error: Suppress next line`,
},
Errors: []rule_tester.InvalidTestCaseError{
{MessageId: "preferExpectErrorComment", Line: 1, Column: 1, EndLine: 1, EndColumn: 35},
},
},
{
Code: `
if (false) {
// @ts-ignore: Unreachable code error
console.log('hello');
}
`,
Output: []string{
`
if (false) {
// @ts-expect-error: Unreachable code error
console.log('hello');
}
`,
},
Errors: []rule_tester.InvalidTestCaseError{
{MessageId: "preferExpectErrorComment", Line: 3, Column: 3, EndLine: 3, EndColumn: 40},
},
},
{
Code: `/* @ts-ignore */`,
Output: []string{
`/* @ts-expect-error */`,
},
Errors: []rule_tester.InvalidTestCaseError{
{MessageId: "preferExpectErrorComment", Line: 1, Column: 1, EndLine: 1, EndColumn: 17},
},
},
{
Code: `
/**
* Explaining comment
*
* @ts-ignore */
`,
Output: []string{
`
/**
* Explaining comment
*
* @ts-expect-error */
`,
},
Errors: []rule_tester.InvalidTestCaseError{
{MessageId: "preferExpectErrorComment", Line: 2, Column: 1, EndLine: 5, EndColumn: 17},
},
},
{
Code: `/* @ts-ignore in a single block */`,
Output: []string{
`/* @ts-expect-error in a single block */`,
},
Errors: []rule_tester.InvalidTestCaseError{
{MessageId: "preferExpectErrorComment", Line: 1, Column: 1, EndLine: 1, EndColumn: 35},
},
},
{
Code: `
/*
// @ts-ignore in a block with single line comments */
`,
Output: []string{
`
/*
// @ts-expect-error in a block with single line comments */
`,
},
Errors: []rule_tester.InvalidTestCaseError{
{MessageId: "preferExpectErrorComment", Line: 2, Column: 1, EndLine: 3, EndColumn: 54},
},
},
})
}
2 changes: 1 addition & 1 deletion packages/rslint-test-tools/rstest.config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ export default defineConfig({
// './tests/typescript-eslint/rules/prefer-regexp-exec.test.ts',
'./tests/typescript-eslint/rules/prefer-return-this-type.test.ts',
'./tests/typescript-eslint/rules/prefer-string-starts-ends-with.test.ts',
// './tests/typescript-eslint/rules/prefer-ts-expect-error.test.ts',
'./tests/typescript-eslint/rules/prefer-ts-expect-error.test.ts',
// './tests/typescript-eslint/rules/promise-function-async.test.ts',
'./tests/typescript-eslint/rules/related-getter-setter-pairs.test.ts',
// './tests/typescript-eslint/rules/require-array-sort-compare.test.ts',
Expand Down
Loading
Loading