@@ -3,7 +3,6 @@ package dot_notation
3
3
import (
4
4
"fmt"
5
5
"regexp"
6
- "sort"
7
6
"strings"
8
7
9
8
"github.com/microsoft/typescript-go/shim/ast"
@@ -76,49 +75,132 @@ var DotNotationRule = rule.Rule{
76
75
patternRegex , _ = regexp .Compile (opts .AllowPattern )
77
76
}
78
77
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
-
92
78
listeners := rule.RuleListeners {
93
79
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
+ }
98
115
}
99
116
},
100
117
ast .KindPropertyAccessExpression : func (node * ast.Node ) {
101
118
if ! opts .AllowKeywords {
102
119
checkPropertyAccessKeywords (ctx , node )
103
120
}
104
121
},
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
- },
116
122
}
117
123
118
124
return listeners
119
125
},
120
126
}
121
127
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
+
122
204
// computeDotNotationDiagnostic computes a single diagnostic for a bracket access if it should be converted
123
205
// to dot notation. Returns start, end, message and true if a diagnostic should be reported; otherwise ok=false.
124
206
func 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
127
209
}
128
210
129
211
elementAccess := node .AsElementAccessExpression ()
212
+ if elementAccess == nil {
213
+ return 0 , 0 , rule.RuleMessage {}, false
214
+ }
215
+
130
216
argument := elementAccess .ArgumentExpression
217
+ if argument == nil {
218
+ return 0 , 0 , rule.RuleMessage {}, false
219
+ }
131
220
132
221
// Only handle string literals, numeric literals, and identifiers that evaluate to strings
133
222
var propertyName string
134
223
isValidProperty := false
135
224
136
225
switch argument .Kind {
137
226
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
139
237
isValidProperty = true
140
238
case ast .KindNoSubstitutionTemplateLiteral :
141
239
// Handle `obj[`foo`]` (no expressions)
0 commit comments