Skip to content
6 changes: 6 additions & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@ 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/class_methods_use_this"
"github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/consistent_generic_constructors"
"github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/consistent_indexed_object_style"
"github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/consistent_return"
Expand All @@ -24,6 +26,7 @@ import (
"github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/consistent_type_exports"
"github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/consistent_type_imports"
"github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/default_param_last"
"github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/explicit_function_return_type"
"github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/no_array_delete"
"github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/no_base_to_string"
"github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/no_confusing_void_expression"
Expand Down Expand Up @@ -365,8 +368,10 @@ 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/class-methods-use-this", class_methods_use_this.ClassMethodsUseThisRule)
GlobalRuleRegistry.Register("@typescript-eslint/consistent-generic-constructors", consistent_generic_constructors.ConsistentGenericConstructorsRule)
GlobalRuleRegistry.Register("@typescript-eslint/consistent-indexed-object-style", consistent_indexed_object_style.ConsistentIndexedObjectStyleRule)
GlobalRuleRegistry.Register("@typescript-eslint/consistent-return", consistent_return.ConsistentReturnRule)
Expand All @@ -376,6 +381,7 @@ func registerAllTypeScriptEslintPluginRules() {
GlobalRuleRegistry.Register("@typescript-eslint/consistent-type-imports", consistent_type_imports.ConsistentTypeImportsRule)
GlobalRuleRegistry.Register("@typescript-eslint/default-param-last", default_param_last.DefaultParamLastRule)
GlobalRuleRegistry.Register("@typescript-eslint/dot-notation", dot_notation.DotNotationRule)
GlobalRuleRegistry.Register("@typescript-eslint/explicit-function-return-type", explicit_function_return_type.ExplicitFunctionReturnTypeRule)
GlobalRuleRegistry.Register("@typescript-eslint/no-array-delete", no_array_delete.NoArrayDeleteRule)
GlobalRuleRegistry.Register("@typescript-eslint/no-base-to-string", no_base_to_string.NoBaseToStringRule)
GlobalRuleRegistry.Register("@typescript-eslint/no-confusing-void-expression", no_confusing_void_expression.NoConfusingVoidExpressionRule)
Expand Down
14 changes: 14 additions & 0 deletions internal/linter/linter.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,20 @@ func RunLinterInProgram(program *compiler.Program, allowFiles []string, skipFile
Severity: r.Severity,
})
},
ReportRangeWithFixes: func(textRange core.TextRange, msg rule.RuleMessage, fixes ...rule.RuleFix) {
// Check if rule is disabled at this position
if disableManager.IsRuleDisabled(r.Name, textRange.Pos()) {
return
}
onDiagnostic(rule.RuleDiagnostic{
RuleName: r.Name,
Range: textRange,
Message: msg,
FixesPtr: &fixes,
SourceFile: file,
Severity: r.Severity,
})
},
ReportNode: func(node *ast.Node, msg rule.RuleMessage) {
// Check if rule is disabled at this position
if disableManager.IsRuleDisabled(r.Name, node.Pos()) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
package ban_tslint_comment

import (
"regexp"
"strings"

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

// Regular expressions for matching TSLint directives
var (
// Matches single-line comments: // tslint:disable or // tslint:enable
singleLineTslintRegex = regexp.MustCompile(`^//\s*tslint:(disable|enable)`)

// Matches multi-line comments: /* tslint:disable */ or /* tslint:enable */
multiLineTslintRegex = regexp.MustCompile(`^/\*\s*tslint:(disable|enable)`)
)

// 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 tslint comments
func processComments(ctx rule.RuleContext, text string) {
pos := 0
length := len(text)
lineStarts := calculateLineStarts(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]

// Check if this is a tslint comment
if singleLineTslintRegex.MatchString(commentText) {
reportTslintComment(ctx, commentText, commentStart, lineEnd, lineStarts, text)
}

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]

// Check if this is a tslint comment
if multiLineTslintRegex.MatchString(commentText) {
reportTslintComment(ctx, commentText, commentStart, commentEnd, lineStarts, text)
}

pos = commentEnd
} else {
pos++
}
} else {
pos++
}
}
}

// calculateLineStarts returns the starting positions of each line
func calculateLineStarts(text string) []int {
lineStarts := []int{0}
for i := 0; i < len(text); i++ {

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

View workflow job for this annotation

GitHub Actions / Lint&Check

for loop can be changed to use an integer range (Go 1.22+) (intrange)
if text[i] == '\n' {
lineStarts = append(lineStarts, i+1)
}
}
return lineStarts
}

// getLineAndColumn returns the line and column numbers for a given position
func getLineAndColumn(pos int, lineStarts []int) (line, column int) {
for i := len(lineStarts) - 1; i >= 0; i-- {
if pos >= lineStarts[i] {
line = i + 1
column = pos - lineStarts[i] + 1
return
}
}
return 1, 1
}

// reportTslintComment reports a tslint comment with autofix
func reportTslintComment(ctx rule.RuleContext, commentText string, start, end int, lineStarts []int, fullText string) {
line, column := getLineAndColumn(start, lineStarts)

// Create the fix
fix := createFix(start, end, fullText)

ctx.ReportRangeWithFixes(
core.NewTextRange(start, end),
rule.RuleMessage{
Id: "commentDetected",
Description: "tslint is deprecated and you should stop using it",
},
*fix,
)

_ = line
_ = column
}

// createFix creates a fix that removes the tslint comment
func createFix(start, end int, fullText string) *rule.RuleFix {
// Check if we need to remove the entire line or just the comment

// Look backwards to see if there's any non-whitespace before the comment
hasContentBefore := false
lineStart := start
for lineStart > 0 && fullText[lineStart-1] != '\n' && fullText[lineStart-1] != '\r' {
lineStart--
if !isWhitespace(fullText[lineStart]) {
hasContentBefore = true
}
}

// Look forwards to see if there's any non-whitespace after the comment (on the same line)
hasContentAfter := false
lineEnd := end
for lineEnd < len(fullText) && fullText[lineEnd] != '\n' && fullText[lineEnd] != '\r' {
if !isWhitespace(fullText[lineEnd]) {
hasContentAfter = true
break
}
lineEnd++
}

// Skip the newline characters if removing the entire line
if !hasContentBefore && !hasContentAfter {
// Include the newline in the removal
if lineEnd < len(fullText) && fullText[lineEnd] == '\r' {
lineEnd++
}
if lineEnd < len(fullText) && fullText[lineEnd] == '\n' {
lineEnd++
}

return &rule.RuleFix{
Range: core.NewTextRange(lineStart, lineEnd),
Text: "",
}
}

// If there's content before the comment (e.g., "someCode(); // tslint:disable-line")
if hasContentBefore {
// Remove just the comment, preserving whitespace before it but removing the comment
// Find where the actual code ends
codeEnd := start
for codeEnd > lineStart && isWhitespace(fullText[codeEnd-1]) {
codeEnd--
}

return &rule.RuleFix{
Range: core.NewTextRange(codeEnd, end),
Text: "",
}
}

// Otherwise, just remove the comment
return &rule.RuleFix{
Range: core.NewTextRange(start, end),
Text: "",
}
}

// isWhitespace checks if a character is whitespace
func isWhitespace(ch byte) bool {
return ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n'
}

// trimTrailingWhitespace removes trailing whitespace and newlines
func trimTrailingWhitespace(s string) string {

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

View workflow job for this annotation

GitHub Actions / Lint&Check

func trimTrailingWhitespace is unused (unused)
return strings.TrimRight(s, " \t\r\n")
}
Loading
Loading