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 @@ -14,6 +14,7 @@ import (
"github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/array_type"
"github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/await_thenable"
"github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/ban_ts_comment"
"github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/ban_tslint_comment"
"github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/ban_types"
"github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/class_literal_property_style"
"github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/consistent_generic_constructors"
Expand Down Expand Up @@ -380,6 +381,7 @@ func registerAllTypeScriptEslintPluginRules() {
GlobalRuleRegistry.Register("@typescript-eslint/array-type", array_type.ArrayTypeRule)
GlobalRuleRegistry.Register("@typescript-eslint/await-thenable", await_thenable.AwaitThenableRule)
GlobalRuleRegistry.Register("@typescript-eslint/ban-ts-comment", ban_ts_comment.BanTsCommentRule)
GlobalRuleRegistry.Register("@typescript-eslint/ban-tslint-comment", ban_tslint_comment.BanTslintCommentRule)
GlobalRuleRegistry.Register("@typescript-eslint/ban-types", ban_types.BanTypesRule)
GlobalRuleRegistry.Register("@typescript-eslint/class-literal-property-style", class_literal_property_style.ClassLiteralPropertyStyleRule)
GlobalRuleRegistry.Register("@typescript-eslint/consistent-generic-constructors", consistent_generic_constructors.ConsistentGenericConstructorsRule)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package ban_tslint_comment

import (
"regexp"
"strings"

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

// tslint regex
// https://github.com/palantir/tslint/blob/95d9d958833fd9dc0002d18cbe34db20d0fbf437/src/enableDisableRules.ts#L32
var enableDisableRegex = regexp.MustCompile(`^\s*tslint:(enable|disable)(?:-(line|next-line))?(:|\s|$)`)

// BanTslintCommentRule implements the ban-tslint-comment rule.
// Disallows tslint directive comments like // tslint:disable
var BanTslintCommentRule = rule.CreateRule(rule.Rule{
Name: "ban-tslint-comment",
Run: run,
})

func run(ctx rule.RuleContext, _ any) rule.RuleListeners {
text := ctx.SourceFile.Text()

utils.ForEachComment(&ctx.SourceFile.Node, func(comment *ast.CommentRange) {
commentValue := extractCommentValue(text, comment)
if !enableDisableRegex.MatchString(commentValue) {
return
}

commentText := extractCommentText(text, comment)
if commentText == "" {
commentText = commentValue
}
message := rule.RuleMessage{
Id: "commentDetected",
Description: "tslint comment detected: " + commentText,
}

fixRange := buildFixRange(ctx.SourceFile, comment, len(text))
ctx.ReportRangeWithFixes(
core.NewTextRange(comment.Pos(), comment.End()),
message,
rule.RuleFixRemoveRange(fixRange),
)
}, ctx.SourceFile)

return rule.RuleListeners{}
}

func extractCommentValue(text string, comment *ast.CommentRange) string {
if comment.Pos() < 0 || comment.End() > len(text) {
return ""
}

switch comment.Kind {
case ast.KindSingleLineCommentTrivia:
if comment.End() <= comment.Pos()+2 {
return ""
}
return text[comment.Pos()+2 : comment.End()]
case ast.KindMultiLineCommentTrivia:
if comment.End() <= comment.Pos()+4 {
return ""
}
return text[comment.Pos()+2 : comment.End()-2]
default:
return ""
}
}

func extractCommentText(text string, comment *ast.CommentRange) string {
if comment.End() <= comment.Pos() {
return ""
}
if comment.Pos() < 0 || comment.End() > len(text) {
return ""
}
return strings.TrimSpace(text[comment.Pos():comment.End()])
}

func buildFixRange(sourceFile *ast.SourceFile, comment *ast.CommentRange, textLen int) core.TextRange {
text := sourceFile.Text()
start := comment.Pos()
end := comment.End()

startLine, _ := scanner.GetECMALineAndCharacterOfPosition(sourceFile, start)
lineStart := scanner.GetECMAPositionOfLineAndCharacter(sourceFile, startLine, 0)

isStandalone := true
for i := lineStart; i < start; i++ {
if text[i] != ' ' && text[i] != '\t' {
isStandalone = false
break
}
}

if isStandalone {
start = lineStart
if end < textLen {
switch text[end] {
case '\r':
end++
if end < textLen && text[end] == '\n' {
end++
}
case '\n':
end++
}
}
} else {
for start > lineStart && (text[start-1] == ' ' || text[start-1] == '\t') {
start--
}
}

return core.NewTextRange(start, end)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# ban-tslint-comment

## Rule Details

Disallow TSLint directive comments such as `// tslint:disable` and
`// tslint:disable-next-line`. These directives are not used by ESLint and are
typically left behind when migrating from TSLint.

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

```javascript
/* tslint:disable */
/* tslint:enable */
// tslint:disable-next-line
someCode(); // tslint:disable-line
```

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

```javascript
// some other comment
/* another comment that mentions tslint */
```

## Original Documentation

- [typescript-eslint ban-tslint-comment](https://typescript-eslint.io/rules/ban-tslint-comment)
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package ban_tslint_comment

import (
"testing"

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

func TestBanTslintCommentRule(t *testing.T) {
rule_tester.RunRuleTester(fixtures.GetRootDir(), "tsconfig.json", t, &BanTslintCommentRule, []rule_tester.ValidTestCase{
{Code: "let a: readonly any[] = [];"},
{Code: "let a = new Array();"},
{Code: "// some other comment"},
{Code: "// TODO: this is a comment that mentions tslint"},
{Code: "/* another comment that mentions tslint */"},
}, []rule_tester.InvalidTestCase{
{
Code: "/* tslint:disable */",
Errors: []rule_tester.InvalidTestCaseError{
{MessageId: "commentDetected", Line: 1, Column: 1},
},
Output: []string{""},
},
{
Code: "/* tslint:enable */",
Errors: []rule_tester.InvalidTestCaseError{
{MessageId: "commentDetected", Line: 1, Column: 1},
},
Output: []string{""},
},
{
Code: "/* tslint:disable:rule1 rule2 rule3... */",
Errors: []rule_tester.InvalidTestCaseError{
{MessageId: "commentDetected", Line: 1, Column: 1},
},
Output: []string{""},
},
{
Code: "/* tslint:enable:rule1 rule2 rule3... */",
Errors: []rule_tester.InvalidTestCaseError{
{MessageId: "commentDetected", Line: 1, Column: 1},
},
Output: []string{""},
},
{
Code: "// tslint:disable-next-line",
Errors: []rule_tester.InvalidTestCaseError{
{MessageId: "commentDetected", Line: 1, Column: 1},
},
Output: []string{""},
},
{
Code: "someCode(); // tslint:disable-line",
Errors: []rule_tester.InvalidTestCaseError{
{MessageId: "commentDetected", Line: 1, Column: 13},
},
Output: []string{"someCode();"},
},
{
Code: "// tslint:disable-next-line:rule1 rule2 rule3...",
Errors: []rule_tester.InvalidTestCaseError{
{MessageId: "commentDetected", Line: 1, Column: 1},
},
Output: []string{""},
},
{
Code: "const whoa = doSomeStuff();\n// tslint:disable-line\nconsole.log(whoa);\n",
Errors: []rule_tester.InvalidTestCaseError{
{MessageId: "commentDetected", Line: 2, Column: 1},
},
Output: []string{"const whoa = doSomeStuff();\nconsole.log(whoa);\n"},
},
})
}
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,9 @@ func isNumber(node *ast.Node, value int) bool {
if unary == nil {
return false
}
if unary.Operator == ast.KindPlusToken {
return isNumber(unary.Operand, value)
}
if unary.Operator == ast.KindMinusToken {
return isNumber(unary.Operand, -value)
}
Expand Down Expand Up @@ -143,7 +146,8 @@ func getStringLength(node *ast.Node) int {
if lit == nil {
return -1
}
return len(lit.Text)
// Match JS semantics: string length is UTF-16 code units.
return jsStringLength(lit.Text)
}
return -1
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,22 @@ function f(s: string) {
function f(s: string) {
s.startsWith('a');
}
`},
},
// s[+0] === 'a'
{
Code: `
function f(s: string) {
s[+0] === 'a';
}
`,
Errors: []rule_tester.InvalidTestCaseError{
{MessageId: "preferStartsWith"},
},
Output: []string{`
function f(s: string) {
s.startsWith('a');
}
`},
},
// s?.[0] === 'a'
Expand Down Expand Up @@ -532,6 +548,22 @@ function f(s: string) {
function f(s: string) {
s.startsWith('a');
}
`},
},
// s.charAt(+0) === 'a'
{
Code: `
function f(s: string) {
s.charAt(+0) === 'a';
}
`,
Errors: []rule_tester.InvalidTestCaseError{
{MessageId: "preferStartsWith"},
},
Output: []string{`
function f(s: string) {
s.startsWith('a');
}
`},
},
// s.charAt(0) !== 'a'
Expand Down Expand Up @@ -1028,6 +1060,22 @@ function f(s: string) {
function f(s: string) {
s.startsWith('bar');
}
`},
},
// s.slice(0, 1) === 'あ'
{
Code: `
function f(s: string) {
s.slice(0, 1) === 'あ';
}
`,
Errors: []rule_tester.InvalidTestCaseError{
{MessageId: "preferStartsWith"},
},
Output: []string{`
function f(s: string) {
s.startsWith('あ');
}
`},
},
// s?.slice(0, 3) === 'bar'
Expand Down
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 @@ -19,7 +19,7 @@ export default defineConfig({
'./tests/typescript-eslint/rules/array-type.test.ts',
'./tests/typescript-eslint/rules/await-thenable.test.ts',
// './tests/typescript-eslint/rules/ban-ts-comment.test.ts',
// './tests/typescript-eslint/rules/ban-tslint-comment.test.ts',
'./tests/typescript-eslint/rules/ban-tslint-comment.test.ts',
'./tests/typescript-eslint/rules/class-literal-property-style.test.ts',
// './tests/typescript-eslint/rules/class-methods-use-this/class-methods-use-this-core.test.ts',
// './tests/typescript-eslint/rules/class-methods-use-this/class-methods-use-this.test.ts',
Expand Down
Loading
Loading