@@ -3,7 +3,6 @@ package dot_notation
33import (
44 "fmt"
55 "regexp"
6- "sort"
76 "strings"
87
98 "github.com/microsoft/typescript-go/shim/ast"
@@ -76,49 +75,132 @@ var DotNotationRule = rule.Rule{
7675 patternRegex , _ = regexp .Compile (opts .AllowPattern )
7776 }
7877
79- // Queue dot-notation diagnostics to ensure deterministic, ascending source order
80- type queuedDiag struct {
81- start int
82- end int
83- msg rule.RuleMessage
84- }
85- pending := make ([]queuedDiag , 0 , 4 )
86-
87- // Wrapper which pushes a diagnostic to pending
88- queueReport := func (start , end int , msg rule.RuleMessage ) {
89- pending = append (pending , queuedDiag {start : start , end : end , msg : msg })
90- }
91-
9278 listeners := rule.RuleListeners {
9379 ast .KindElementAccessExpression : func (node * ast.Node ) {
94- // queue reports instead of immediate emit
95- start , end , msg , ok := computeDotNotationDiagnostic (ctx , node , opts , allowIndexSignaturePropertyAccess , patternRegex )
96- if ok {
97- queueReport (start , end , msg )
80+ // Simplified approach: check if this node should be converted to dot notation
81+ if shouldConvertToDotNotation (ctx , node , opts , allowIndexSignaturePropertyAccess , patternRegex ) {
82+ // Extract property name for fix
83+ elementAccess := node .AsElementAccessExpression ()
84+ if elementAccess != nil && elementAccess .ArgumentExpression != nil {
85+ argument := elementAccess .ArgumentExpression
86+ var propertyName string
87+
88+ switch argument .Kind {
89+ case ast .KindStringLiteral :
90+ stringLiteral := argument .AsStringLiteral ()
91+ if stringLiteral != nil {
92+ text := stringLiteral .Text
93+ if len (text ) >= 2 && ((text [0 ] == '"' && text [len (text )- 1 ] == '"' ) || (text [0 ] == '\'' && text [len (text )- 1 ] == '\'' )) {
94+ text = text [1 : len (text )- 1 ]
95+ }
96+ propertyName = text
97+ }
98+ case ast .KindNullKeyword :
99+ propertyName = "null"
100+ case ast .KindTrueKeyword :
101+ propertyName = "true"
102+ case ast .KindFalseKeyword :
103+ propertyName = "false"
104+ }
105+
106+ if propertyName != "" {
107+ msg := rule.RuleMessage {
108+ Id : "useDot" ,
109+ Description : fmt .Sprintf ("['%s'] is better written in dot notation." , propertyName ),
110+ }
111+ fix := createFix (ctx , node , propertyName )
112+ ctx .ReportNodeWithFixes (node , msg , fix )
113+ }
114+ }
98115 }
99116 },
100117 ast .KindPropertyAccessExpression : func (node * ast.Node ) {
101118 if ! opts .AllowKeywords {
102119 checkPropertyAccessKeywords (ctx , node )
103120 }
104121 },
105- // Flush pending on file exit sorted by start position so earlier lines come first
106- rule .ListenerOnExit (ast .KindSourceFile ): func (node * ast.Node ) {
107- if len (pending ) == 0 {
108- return
109- }
110- sort .SliceStable (pending , func (i , j int ) bool { return pending [i ].start < pending [j ].start })
111- for _ , d := range pending {
112- ctx .ReportRange (core .NewTextRange (d .start , d .end ), d .msg )
113- }
114- pending = pending [:0 ]
115- },
116122 }
117123
118124 return listeners
119125 },
120126}
121127
128+ // shouldConvertToDotNotation checks if a bracket access should be converted to dot notation
129+ func shouldConvertToDotNotation (ctx rule.RuleContext , node * ast.Node , opts DotNotationOptions , allowIndexSignaturePropertyAccess bool , patternRegex * regexp.Regexp ) bool {
130+ if ! ast .IsElementAccessExpression (node ) {
131+ return false
132+ }
133+
134+ elementAccess := node .AsElementAccessExpression ()
135+ if elementAccess == nil {
136+ return false
137+ }
138+
139+ argument := elementAccess .ArgumentExpression
140+ if argument == nil {
141+ return false
142+ }
143+
144+ // Only handle string literals, numeric literals, and identifiers that evaluate to strings
145+ var propertyName string
146+ isValidProperty := false
147+
148+ switch argument .Kind {
149+ case ast .KindStringLiteral :
150+ stringLiteral := argument .AsStringLiteral ()
151+ if stringLiteral == nil {
152+ return false
153+ }
154+ // Remove quotes from string literal text
155+ text := stringLiteral .Text
156+ if len (text ) >= 2 && ((text [0 ] == '"' && text [len (text )- 1 ] == '"' ) || (text [0 ] == '\'' && text [len (text )- 1 ] == '\'' )) {
157+ text = text [1 : len (text )- 1 ]
158+ }
159+ propertyName = text
160+ isValidProperty = true
161+ case ast .KindNoSubstitutionTemplateLiteral :
162+ // Handle `obj[`foo`]` (no expressions)
163+ propertyName = argument .AsNoSubstitutionTemplateLiteral ().Text
164+ isValidProperty = true
165+ case ast .KindNumericLiteral :
166+ // Numeric properties should use bracket notation
167+ return false
168+ case ast .KindNullKeyword , ast .KindTrueKeyword , ast .KindFalseKeyword :
169+ // These are allowed as dot notation
170+ propertyName = getKeywordText (argument )
171+ isValidProperty = true
172+ default :
173+ // Other cases (template literals, identifiers, etc.) should keep bracket notation
174+ return false
175+ }
176+
177+ if ! isValidProperty || propertyName == "" {
178+ return false
179+ }
180+
181+ // Check if it's a valid identifier
182+ if ! isValidIdentifierName (propertyName ) {
183+ return false
184+ }
185+
186+ // Check pattern allowlist
187+ if patternRegex != nil && patternRegex .MatchString (propertyName ) {
188+ return false
189+ }
190+
191+ // Check for keywords
192+ if ! opts .AllowKeywords && isReservedWord (propertyName ) {
193+ return false
194+ }
195+
196+ // Check for private/protected/index signature access
197+ if shouldAllowBracketNotation (ctx , node , propertyName , opts , allowIndexSignaturePropertyAccess ) {
198+ return false
199+ }
200+
201+ return true
202+ }
203+
122204// computeDotNotationDiagnostic computes a single diagnostic for a bracket access if it should be converted
123205// to dot notation. Returns start, end, message and true if a diagnostic should be reported; otherwise ok=false.
124206func computeDotNotationDiagnostic (ctx rule.RuleContext , node * ast.Node , opts DotNotationOptions , allowIndexSignaturePropertyAccess bool , patternRegex * regexp.Regexp ) (int , int , rule.RuleMessage , bool ) {
@@ -127,15 +209,31 @@ func computeDotNotationDiagnostic(ctx rule.RuleContext, node *ast.Node, opts Dot
127209 }
128210
129211 elementAccess := node .AsElementAccessExpression ()
212+ if elementAccess == nil {
213+ return 0 , 0 , rule.RuleMessage {}, false
214+ }
215+
130216 argument := elementAccess .ArgumentExpression
217+ if argument == nil {
218+ return 0 , 0 , rule.RuleMessage {}, false
219+ }
131220
132221 // Only handle string literals, numeric literals, and identifiers that evaluate to strings
133222 var propertyName string
134223 isValidProperty := false
135224
136225 switch argument .Kind {
137226 case ast .KindStringLiteral :
138- propertyName = argument .AsStringLiteral ().Text
227+ stringLiteral := argument .AsStringLiteral ()
228+ if stringLiteral == nil {
229+ return 0 , 0 , rule.RuleMessage {}, false
230+ }
231+ // Remove quotes from string literal text
232+ text := stringLiteral .Text
233+ if len (text ) >= 2 && ((text [0 ] == '"' && text [len (text )- 1 ] == '"' ) || (text [0 ] == '\'' && text [len (text )- 1 ] == '\'' )) {
234+ text = text [1 : len (text )- 1 ]
235+ }
236+ propertyName = text
139237 isValidProperty = true
140238 case ast .KindNoSubstitutionTemplateLiteral :
141239 // Handle `obj[`foo`]` (no expressions)
0 commit comments