Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
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 @@ -369,6 +370,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,112 @@
package ban_tslint_comment

import (
"regexp"
"strings"

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

// Regular expression to match TSLint directive comments
// Matches patterns like:
// - tslint:disable
// - tslint:enable
// - tslint:disable-line
// - tslint:disable-next-line
// - tslint:enable-line
var tslintCommentRegex = regexp.MustCompile(`^\s*tslint:(enable|disable)(?:-(line|next-line))?(?::|s|$)`)

// BanTslintCommentRule implements the ban-tslint-comment rule
// Bans // tslint:<rule flag> comments
var BanTslintCommentRule = rule.CreateRule(rule.Rule{
Name: "ban-tslint-comment",
Run: run,
})

func run(ctx rule.RuleContext, options any) rule.RuleListeners {
// Get the full text of the source file
text := ctx.SourceFile.Text()

// Process the text to find TSLint comments
processComments(ctx, text)

return rule.RuleListeners{}
}

// processComments scans the source text for comments and checks for TSLint directives
func processComments(ctx rule.RuleContext, text string) {
pos := 0
length := len(text)

for pos < length {
// Skip to next potential comment
if pos+1 < length {
if text[pos] == '/' && text[pos+1] == '/' {
// Single-line comment
commentStart := pos
pos += 2
lineEnd := pos
for lineEnd < length && text[lineEnd] != '\n' && text[lineEnd] != '\r' {
lineEnd++
}
commentText := text[commentStart:lineEnd]
checkComment(ctx, commentText, commentStart, false)
pos = lineEnd
} else if text[pos] == '/' && text[pos+1] == '*' {
// Multi-line comment
commentStart := pos
pos += 2
commentEnd := pos
for commentEnd+1 < length {
if text[commentEnd] == '*' && text[commentEnd+1] == '/' {
commentEnd += 2
break
}
commentEnd++
}
commentText := text[commentStart:commentEnd]
checkComment(ctx, commentText, commentStart, true)
pos = commentEnd
} else {
pos++
}
} else {
pos++
}
}
}

// checkComment checks a single comment for TSLint directives
func checkComment(ctx rule.RuleContext, commentText string, commentStart int, isMultiLine bool) {
var contentToCheck string

if isMultiLine {
// For multi-line comments, remove /* and */ and check the content
contentToCheck = commentText
if strings.HasPrefix(contentToCheck, "/*") {

Check failure on line 87 in internal/plugins/typescript/rules/ban_tslint_comment/ban_tslint_comment.go

View workflow job for this annotation

GitHub Actions / Lint&Check

S1017: should replace this if statement with an unconditional strings.TrimPrefix (staticcheck)
contentToCheck = contentToCheck[2:]
}
if strings.HasSuffix(contentToCheck, "*/") {

Check failure on line 90 in internal/plugins/typescript/rules/ban_tslint_comment/ban_tslint_comment.go

View workflow job for this annotation

GitHub Actions / Lint&Check

S1017: should replace this if statement with an unconditional strings.TrimSuffix (staticcheck)
contentToCheck = contentToCheck[:len(contentToCheck)-2]
}
} else {
// For single-line comments, remove // and check the content
contentToCheck = commentText
if strings.HasPrefix(contentToCheck, "//") {

Check failure on line 96 in internal/plugins/typescript/rules/ban_tslint_comment/ban_tslint_comment.go

View workflow job for this annotation

GitHub Actions / Lint&Check

S1017: should replace this if statement with an unconditional strings.TrimPrefix (staticcheck)
contentToCheck = contentToCheck[2:]
}
}

// Check if the content matches TSLint directive pattern
if tslintCommentRegex.MatchString(contentToCheck) {
// Report the TSLint directive
ctx.ReportRange(
core.NewTextRange(commentStart, commentStart+len(commentText)),
rule.RuleMessage{
Id: "commentDetected",
Description: "tslint comment detected: \"" + strings.TrimSpace(commentText) + "\"",
},
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
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,
// Valid cases - comments that should NOT be flagged
[]rule_tester.ValidTestCase{
// Valid TypeScript code
{Code: `let a: readonly any[] = [];`},
{Code: `let a = new Array();`},

// Regular comments mentioning tslint (not directives)
{Code: `// some other comment`},
{Code: `// TODO: this is a comment that mentions tslint`},
{Code: `/* another comment that mentions tslint */`},
{Code: `// This project used to use tslint`},
{Code: `/* We migrated from tslint to eslint */`},

// Comments that don't match the directive pattern
{Code: `// tslint is deprecated`},
{Code: `/* tslint was a linter */`},
{Code: `// about tslint:disable`},
{Code: `/* discussing tslint:enable */`},
},
// Invalid cases - TSLint directives that should be flagged
[]rule_tester.InvalidTestCase{
// Basic tslint:disable
{
Code: `/* tslint:disable */`,
Errors: []rule_tester.InvalidTestCaseError{
{MessageId: "commentDetected", Line: 1, Column: 1},
},
},

// Basic tslint:enable
{
Code: `/* tslint:enable */`,
Errors: []rule_tester.InvalidTestCaseError{
{MessageId: "commentDetected", Line: 1, Column: 1},
},
},

// tslint:disable with specific rules
{
Code: `/* tslint:disable:rule1 rule2 rule3... */`,
Errors: []rule_tester.InvalidTestCaseError{
{MessageId: "commentDetected", Line: 1, Column: 1},
},
},

// tslint:enable with specific rules
{
Code: `/* tslint:enable:rule1 rule2 rule3... */`,
Errors: []rule_tester.InvalidTestCaseError{
{MessageId: "commentDetected", Line: 1, Column: 1},
},
},

// Single-line comment: tslint:disable-next-line
{
Code: `// tslint:disable-next-line`,
Errors: []rule_tester.InvalidTestCaseError{
{MessageId: "commentDetected", Line: 1, Column: 1},
},
},

// Inline tslint:disable-line
{
Code: `someCode(); // tslint:disable-line`,
Errors: []rule_tester.InvalidTestCaseError{
{MessageId: "commentDetected", Line: 1, Column: 13},
},
},

// tslint:disable-next-line with specific rules
{
Code: `// tslint:disable-next-line:rule1 rule2 rule3...`,
Errors: []rule_tester.InvalidTestCaseError{
{MessageId: "commentDetected", Line: 1, Column: 1},
},
},

// Multi-line code with tslint:disable-line
{
Code: `if (true) {
console.log("test");
}
// tslint:disable-line
const x = 1;`,
Errors: []rule_tester.InvalidTestCaseError{
{MessageId: "commentDetected", Line: 4, Column: 1},
},
},

// tslint:enable-line
{
Code: `// tslint:enable-line`,
Errors: []rule_tester.InvalidTestCaseError{
{MessageId: "commentDetected", Line: 1, Column: 1},
},
},

// Multiple spaces before directive
{
Code: `// tslint:disable`,
Errors: []rule_tester.InvalidTestCaseError{
{MessageId: "commentDetected", Line: 1, Column: 1},
},
},

// Block comment with spaces
{
Code: `/* tslint:disable */`,
Errors: []rule_tester.InvalidTestCaseError{
{MessageId: "commentDetected", Line: 1, Column: 1},
},
},

// tslint:disable with colon separator
{
Code: `// tslint:disable:no-console`,
Errors: []rule_tester.InvalidTestCaseError{
{MessageId: "commentDetected", Line: 1, Column: 1},
},
},

// tslint:enable with colon separator
{
Code: `// tslint:enable:no-console`,
Errors: []rule_tester.InvalidTestCaseError{
{MessageId: "commentDetected", Line: 1, Column: 1},
},
},

// Tab character before directive
{
Code: "//\ttslint:disable",
Errors: []rule_tester.InvalidTestCaseError{
{MessageId: "commentDetected", Line: 1, Column: 1},
},
},

// Multiple tslint comments in one file
{
Code: `// tslint:disable
const x = 1;
// tslint:enable`,
Errors: []rule_tester.InvalidTestCaseError{
{MessageId: "commentDetected", Line: 1, Column: 1},
{MessageId: "commentDetected", Line: 3, Column: 1},
},
},

// tslint:disable-next-line before code
{
Code: `// tslint:disable-next-line
const value = "test";`,
Errors: []rule_tester.InvalidTestCaseError{
{MessageId: "commentDetected", Line: 1, Column: 1},
},
},

// Block comment tslint:disable-next-line
{
Code: `/* tslint:disable-next-line */
const value = "test";`,
Errors: []rule_tester.InvalidTestCaseError{
{MessageId: "commentDetected", Line: 1, Column: 1},
},
},

// tslint directive with 's' suffix (alternative format)
{
Code: `// tslint:disables`,
Errors: []rule_tester.InvalidTestCaseError{
{MessageId: "commentDetected", Line: 1, Column: 1},
},
},

// tslint directive with 's' suffix for enable
{
Code: `// tslint:enables`,
Errors: []rule_tester.InvalidTestCaseError{
{MessageId: "commentDetected", Line: 1, Column: 1},
},
},
},
)
}
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 @@ -33,7 +33,7 @@ export default defineConfig({
// Additional tests (commented out)
// typescript-eslint - additional rules
// './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-methods-use-this/class-methods-use-this-core.test.ts',
// './tests/typescript-eslint/rules/class-methods-use-this/class-methods-use-this.test.ts',
// './tests/typescript-eslint/rules/consistent-generic-constructors.test.ts',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
import { RuleTester } from '@typescript-eslint/rule-tester';

import { getFixturesRootDir } from '../RuleTester';


const ruleTester = new RuleTester();
const rootDir = getFixturesRootDir();
const ruleTester = new RuleTester({
languageOptions: {
parserOptions: {
project: './tsconfig.json',
tsconfigRootDir: rootDir,
},
},
});

ruleTester.run('ban-tslint-comment', {
valid: [
Expand All @@ -21,6 +29,24 @@ ruleTester.run('ban-tslint-comment', {
{
code: '/* another comment that mentions tslint */',
},
{
code: '// This project used to use tslint',
},
{
code: '/* We migrated from tslint to eslint */',
},
{
code: '// tslint is deprecated',
},
{
code: '/* tslint was a linter */',
},
{
code: '// about tslint:disable',
},
{
code: '/* discussing tslint:enable */',
},
],
invalid: [
{
Expand Down
Loading