Skip to content
Draft
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 @@ -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,108 @@
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
contentToCheck = strings.TrimPrefix(contentToCheck, "/*")
contentToCheck = strings.TrimSuffix(contentToCheck, "*/")
contentToCheck = strings.TrimSpace(contentToCheck)
} else {
// For single-line comments, remove // and check the content
contentToCheck = commentText
contentToCheck = strings.TrimPrefix(contentToCheck, "//")
contentToCheck = strings.TrimSpace(contentToCheck)
}

// 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