@@ -5,8 +5,10 @@ import (
5
5
6
6
"github.com/microsoft/typescript-go/shim/ast"
7
7
"github.com/microsoft/typescript-go/shim/core"
8
+ "github.com/microsoft/typescript-go/shim/scanner"
8
9
"github.com/web-infra-dev/rslint/internal/rule"
9
10
"github.com/web-infra-dev/rslint/internal/utils"
11
+ "strings"
10
12
)
11
13
12
14
type AccessibilityLevel string
@@ -128,8 +130,6 @@ func getModifierText(modifiers *ast.ModifierList) string {
128
130
return ""
129
131
}
130
132
131
-
132
-
133
133
func getMemberName (node * ast.Node , ctx rule.RuleContext ) string {
134
134
var nameNode * ast.Node
135
135
switch kind := node .Kind ; kind {
@@ -204,9 +204,125 @@ func getNodeType(node *ast.Node, memberKind string) string {
204
204
// Removed getMemberHeadLoc and getParameterPropertyHeadLoc functions
205
205
// Now using ReportNode directly which handles positioning correctly
206
206
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
+ }
208
323
209
324
var ExplicitMemberAccessibilityRule = rule.Rule {
325
+
210
326
Name : "explicit-member-accessibility" ,
211
327
Run : func (ctx rule.RuleContext , options any ) rule.RuleListeners {
212
328
config := parseOptions (options )
@@ -314,8 +430,9 @@ var ExplicitMemberAccessibilityRule = rule.Rule{
314
430
}
315
431
}
316
432
} 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 {
319
436
Id : "missingAccessibility" ,
320
437
Description : fmt .Sprintf ("Missing accessibility modifier on %s %s." , nodeType , methodName ),
321
438
})
@@ -351,8 +468,9 @@ var ExplicitMemberAccessibilityRule = rule.Rule{
351
468
}
352
469
}
353
470
} 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 {
356
474
Id : "missingAccessibility" ,
357
475
Description : fmt .Sprintf ("Missing accessibility modifier on %s %s." , nodeType , propertyName ),
358
476
})
@@ -464,5 +582,3 @@ var ExplicitMemberAccessibilityRule = rule.Rule{
464
582
}
465
583
},
466
584
}
467
-
468
-
0 commit comments