Skip to content
Closed
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 @@ -365,6 +366,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
14 changes: 14 additions & 0 deletions internal/linter/linter.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,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,
})
},
ReportRangeWithSuggestions: func(textRange core.TextRange, msg rule.RuleMessage, suggestions ...rule.RuleSuggestion) {
// Check if rule is disabled at this position
if disableManager.IsRuleDisabled(r.Name, textRange.Pos()) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
package ban_tslint_comment

import (
"regexp"

"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, // tslint:enable, // tslint:disable-next-line, // tslint:disable-line
singleLineTslintRegex = regexp.MustCompile(`^\/\/\s*tslint:(disable|enable|disable-next-line|disable-line)`)

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

// BanTslintCommentRule implements the ban-tslint-comment rule
// Bans tslint:<directive> 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++
}

// Find the start of the line
lineStart := commentStart
for lineStart > 0 && text[lineStart-1] != '\n' && text[lineStart-1] != '\r' {
lineStart--
}

commentText := text[commentStart:lineEnd]
checkComment(ctx, text, commentText, commentStart, lineStart, lineEnd, 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, text, commentText, commentStart, commentStart, commentEnd, true)
pos = commentEnd
} else {
pos++
}
} else {
pos++
}
}
}

// checkComment checks a single comment for tslint directives
func checkComment(ctx rule.RuleContext, fullText string, commentText string, commentStart int, lineStart int, commentEnd int, isMultiLine bool) {
var matches []string

if isMultiLine {
match := multiLineTslintRegex.FindStringSubmatch(commentText)
if match != nil {
matches = match
}
} else {
match := singleLineTslintRegex.FindStringSubmatch(commentText)
if match != nil {
matches = match
}
}

if matches == nil {
return
}

// Determine what to remove
// Check if there's code before the comment on the same line
lineBeforeComment := fullText[lineStart:commentStart]
hasCodeBefore := false
for _, ch := range lineBeforeComment {
if ch != ' ' && ch != '\t' {
hasCodeBefore = true
break
}
}

var fix rule.RuleFix
if hasCodeBefore {
// For inline comments, only remove the comment (starting with optional space before comment)
// Find the last non-whitespace character before the comment
removeStart := commentStart
for removeStart > lineStart && (fullText[removeStart-1] == ' ' || fullText[removeStart-1] == '\t') {
removeStart--
}
fix = rule.RuleFixRemoveRange(core.NewTextRange(removeStart, commentEnd))
} else {
// For standalone comments, remove the whole line including newline
removeEnd := commentEnd
// Include trailing newlines
if removeEnd < len(fullText) && (fullText[removeEnd] == '\n' || fullText[removeEnd] == '\r') {
removeEnd++
// Handle \r\n
if removeEnd < len(fullText) && fullText[removeEnd-1] == '\r' && fullText[removeEnd] == '\n' {
removeEnd++
}
}
fix = rule.RuleFixRemoveRange(core.NewTextRange(lineStart, removeEnd))
}

// Create the diagnostic with fix
ctx.ReportRangeWithFixes(
core.NewTextRange(commentStart, commentEnd),
rule.RuleMessage{
Id: "commentDetected",
Description: "tslint is deprecated. Please remove this comment.",
},
fix,
)
}
1 change: 1 addition & 0 deletions internal/rule/rule.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ type RuleContext struct {
TypeChecker *checker.Checker
DisableManager *DisableManager
ReportRange func(textRange core.TextRange, msg RuleMessage)
ReportRangeWithFixes func(textRange core.TextRange, msg RuleMessage, fixes ...RuleFix)
ReportRangeWithSuggestions func(textRange core.TextRange, msg RuleMessage, suggestions ...RuleSuggestion)
ReportNode func(node *ast.Node, msg RuleMessage)
ReportNodeWithFixes func(node *ast.Node, msg RuleMessage, fixes ...RuleFix)
Expand Down
1 change: 1 addition & 0 deletions packages/rslint-test-tools/rstest.config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export default defineConfig({
'./tests/typescript-eslint/rules/adjacent-overload-signatures.test.ts',
'./tests/typescript-eslint/rules/array-type.test.ts',
'./tests/typescript-eslint/rules/await-thenable.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/dot-notation.test.ts',
'./tests/typescript-eslint/rules/no-array-delete.test.ts',
Expand Down
Loading