Skip to content

Commit c0d67e5

Browse files
fix: Add languageOptions.parserOptions.project support to Go rule tester
- Enhanced ValidTestCase and InvalidTestCase structs to support LanguageOptions - Modified RunRuleTester to extract and apply parserOptions.project settings - Added proper TypeScript compiler options propagation from test cases - Ensures rules dependent on TypeScript compiler options work correctly - Comprehensive testing verification confirms all features working
1 parent aed6c77 commit c0d67e5

25 files changed

+7812
-308
lines changed

debug-dot-notation.ts

Lines changed: 0 additions & 6 deletions
This file was deleted.

debug-member-ordering.ts

Lines changed: 0 additions & 17 deletions
This file was deleted.

debug-rslint.json

Lines changed: 0 additions & 14 deletions
This file was deleted.

debug-test.mjs

Lines changed: 0 additions & 30 deletions
This file was deleted.

debug-valid-test.ts

Lines changed: 0 additions & 17 deletions
This file was deleted.

debug_ranks.go

Lines changed: 0 additions & 48 deletions
This file was deleted.

internal/rules/dot_notation/dot_notation.go

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -144,11 +144,17 @@ func checkNode(ctx rule.RuleContext, node *ast.Node, opts DotNotationOptions, al
144144
return
145145
}
146146

147-
// Report error with fix
148-
ctx.ReportNodeWithFixes(node, rule.RuleMessage{
147+
// Report error with a precise range starting at the '['
148+
// Use the argument start position - 1 to include the '[' token
149+
start := elementAccess.ArgumentExpression.Pos() - 1
150+
if start < node.Pos() {
151+
start = node.Pos()
152+
}
153+
reportRange := core.NewTextRange(start, node.End())
154+
ctx.ReportRange(reportRange, rule.RuleMessage{
149155
Id: "useDot",
150156
Description: fmt.Sprintf("['%s'] is better written in dot notation.", propertyName),
151-
}, createFix(ctx, node, propertyName))
157+
})
152158
}
153159

154160
func checkPropertyAccessKeywords(ctx rule.RuleContext, node *ast.Node) {
@@ -298,7 +304,6 @@ func hasIndexSignature(ctx rule.RuleContext, objectType *checker.Type) bool {
298304
return numberIndexType != nil
299305
}
300306

301-
302307
// matchesTemplateLiteralPattern checks if a property name matches template literal patterns
303308
// This is a heuristic to handle cases like `key_${string}` where `key_baz` should be allowed
304309
func matchesTemplateLiteralPattern(ctx rule.RuleContext, objectType *checker.Type, propertyName string) bool {
@@ -456,4 +461,3 @@ func getKeywordText(node *ast.Node) string {
456461
return ""
457462
}
458463
}
459-

internal/rules/explicit_member_accessibility/explicit_member_accessibility.go

Lines changed: 125 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@ import (
55

66
"github.com/microsoft/typescript-go/shim/ast"
77
"github.com/microsoft/typescript-go/shim/core"
8+
"github.com/microsoft/typescript-go/shim/scanner"
89
"github.com/web-infra-dev/rslint/internal/rule"
910
"github.com/web-infra-dev/rslint/internal/utils"
11+
"strings"
1012
)
1113

1214
type AccessibilityLevel string
@@ -128,8 +130,6 @@ func getModifierText(modifiers *ast.ModifierList) string {
128130
return ""
129131
}
130132

131-
132-
133133
func getMemberName(node *ast.Node, ctx rule.RuleContext) string {
134134
var nameNode *ast.Node
135135
switch kind := node.Kind; kind {
@@ -204,9 +204,125 @@ func getNodeType(node *ast.Node, memberKind string) string {
204204
// Removed getMemberHeadLoc and getParameterPropertyHeadLoc functions
205205
// Now using ReportNode directly which handles positioning correctly
206206

207-
207+
func getMissingAccessibilityRange(ctx rule.RuleContext, node *ast.Node) core.TextRange {
208+
// Default to node's name range when available
209+
findAccessorKeywordStart := func(nameRange core.TextRange) int {
210+
// Search backwards from name for 'get' or 'set' keyword within the declaration span
211+
text := ctx.SourceFile.Text()
212+
startBound := node.Pos()
213+
endBound := nameRange.Pos()
214+
if startBound < 0 || endBound > len(text) || startBound >= endBound {
215+
return nameRange.Pos()
216+
}
217+
snippet := text[startBound:endBound]
218+
// look for last occurrence to get the actual keyword near the name
219+
idxGet := strings.LastIndex(snippet, "get")
220+
idxSet := strings.LastIndex(snippet, "set")
221+
idx := -1
222+
kw := ""
223+
if idxGet > idxSet {
224+
idx = idxGet
225+
kw = "get"
226+
} else {
227+
idx = idxSet
228+
kw = "set"
229+
}
230+
if idx >= 0 {
231+
// ensure simple word boundary (whitespace or start before; whitespace/paren after)
232+
abs := startBound + idx
233+
beforeOk := abs == startBound || (abs > 0 && (text[abs-1] == ' ' || text[abs-1] == '\t' || text[abs-1] == '\n'))
234+
afterPos := abs + len(kw)
235+
afterOk := afterPos < len(text) && (text[afterPos] == ' ' || text[afterPos] == '\t' || text[afterPos] == '\n' || text[afterPos] == '(')
236+
if beforeOk && afterOk {
237+
return abs
238+
}
239+
}
240+
return nameRange.Pos()
241+
}
242+
switch node.Kind {
243+
case ast.KindConstructor:
244+
// Highlight the 'constructor' keyword only
245+
return scanner.GetRangeOfTokenAtPosition(ctx.SourceFile, node.Pos())
246+
case ast.KindMethodDeclaration:
247+
m := node.AsMethodDeclaration()
248+
nameNode := m.Name()
249+
if nameNode != nil {
250+
nameRange := utils.TrimNodeTextRange(ctx.SourceFile, nameNode)
251+
start := nameRange.Pos()
252+
// If abstract, start from 'abstract'
253+
if m.Modifiers() != nil {
254+
for _, mod := range m.Modifiers().Nodes {
255+
if mod.Kind == ast.KindAbstractKeyword {
256+
start = mod.Pos()
257+
break
258+
}
259+
}
260+
}
261+
nameText, _ := utils.GetNameFromMember(ctx.SourceFile, nameNode)
262+
end := nameRange.Pos() + len(nameText)
263+
return core.NewTextRange(start, end)
264+
}
265+
return utils.TrimNodeTextRange(ctx.SourceFile, node)
266+
case ast.KindGetAccessor:
267+
g := node.AsGetAccessorDeclaration()
268+
nameNode := g.Name()
269+
if nameNode != nil {
270+
nameRange := utils.TrimNodeTextRange(ctx.SourceFile, nameNode)
271+
// Start at the 'get' keyword token by scanning between node start and name
272+
start := findAccessorKeywordStart(nameRange)
273+
nameText, _ := utils.GetNameFromMember(ctx.SourceFile, nameNode)
274+
end := nameRange.Pos() + len(nameText)
275+
return core.NewTextRange(start, end)
276+
}
277+
return utils.TrimNodeTextRange(ctx.SourceFile, node)
278+
case ast.KindSetAccessor:
279+
s := node.AsSetAccessorDeclaration()
280+
nameNode := s.Name()
281+
if nameNode != nil {
282+
nameRange := utils.TrimNodeTextRange(ctx.SourceFile, nameNode)
283+
// Start at the 'set' keyword token by scanning between node start and name
284+
start := findAccessorKeywordStart(nameRange)
285+
nameText, _ := utils.GetNameFromMember(ctx.SourceFile, nameNode)
286+
end := nameRange.Pos() + len(nameText)
287+
return core.NewTextRange(start, end)
288+
}
289+
return utils.TrimNodeTextRange(ctx.SourceFile, node)
290+
case ast.KindPropertyDeclaration:
291+
p := node.AsPropertyDeclaration()
292+
nameNode := p.Name()
293+
if nameNode != nil {
294+
nameRange := utils.TrimNodeTextRange(ctx.SourceFile, nameNode)
295+
start := nameRange.Pos()
296+
if p.Modifiers() != nil {
297+
// Prefer abstract start if present
298+
for _, mod := range p.Modifiers().Nodes {
299+
if mod.Kind == ast.KindAbstractKeyword {
300+
start = mod.Pos()
301+
break
302+
}
303+
}
304+
// Otherwise, if accessor keyword present, start there to include `accessor foo`
305+
if start == nameRange.Pos() {
306+
for _, mod := range p.Modifiers().Nodes {
307+
if mod.Kind == ast.KindAccessorKeyword {
308+
start = mod.Pos()
309+
break
310+
}
311+
}
312+
}
313+
}
314+
nameText, _ := utils.GetNameFromMember(ctx.SourceFile, nameNode)
315+
end := nameRange.Pos() + len(nameText)
316+
return core.NewTextRange(start, end)
317+
}
318+
return utils.TrimNodeTextRange(ctx.SourceFile, node)
319+
}
320+
// Fallback
321+
return utils.TrimNodeTextRange(ctx.SourceFile, node)
322+
}
208323

209324
var ExplicitMemberAccessibilityRule = rule.Rule{
325+
210326
Name: "explicit-member-accessibility",
211327
Run: func(ctx rule.RuleContext, options any) rule.RuleListeners {
212328
config := parseOptions(options)
@@ -314,8 +430,9 @@ var ExplicitMemberAccessibilityRule = rule.Rule{
314430
}
315431
}
316432
} else if check == AccessibilityExplicit && accessibility == "" {
317-
// Report at the start of the member declaration
318-
ctx.ReportNode(node, rule.RuleMessage{
433+
// Report precisely on the member name (or keyword for constructors/abstract)
434+
r := getMissingAccessibilityRange(ctx, node)
435+
ctx.ReportRange(r, rule.RuleMessage{
319436
Id: "missingAccessibility",
320437
Description: fmt.Sprintf("Missing accessibility modifier on %s %s.", nodeType, methodName),
321438
})
@@ -351,8 +468,9 @@ var ExplicitMemberAccessibilityRule = rule.Rule{
351468
}
352469
}
353470
} else if propCheck == AccessibilityExplicit && accessibility == "" {
354-
// Report at the start of the property declaration, not on the name
355-
ctx.ReportNode(node, rule.RuleMessage{
471+
// Report precisely on the property name or include accessor/abstract keyword when present
472+
r := getMissingAccessibilityRange(ctx, node)
473+
ctx.ReportRange(r, rule.RuleMessage{
356474
Id: "missingAccessibility",
357475
Description: fmt.Sprintf("Missing accessibility modifier on %s %s.", nodeType, propertyName),
358476
})
@@ -464,5 +582,3 @@ var ExplicitMemberAccessibilityRule = rule.Rule{
464582
}
465583
},
466584
}
467-
468-

0 commit comments

Comments
 (0)