@ttsc/lint 0.18.4 → 0.19.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +125 -43
- package/go.mod +5 -4
- package/internal/rulecode/rulecode.go +88 -0
- package/lib/index.d.ts +10 -1
- package/lib/index.js +45 -17
- package/lib/index.js.map +1 -1
- package/lib/structures/ITtscLintConfig.d.ts +3 -0
- package/lib/structures/TtscLintRuleSetting.d.ts +3 -2
- package/lib/structures/format/ITtscLintFormat.d.ts +3 -2
- package/lib/structures/format/ITtscLintFormatSortImports.d.ts +20 -4
- package/lib/structures/rules/ITtscLintBoundariesRuleOptions.d.ts +80 -8
- package/lib/structures/rules/ITtscLintBoundariesRules.d.ts +6 -8
- package/lib/structures/rules/ITtscLintContributorRules.d.ts +18 -2
- package/lib/structures/rules/ITtscLintCoreRuleOptions.d.ts +347 -0
- package/lib/structures/rules/ITtscLintCoreRuleOptions.js +3 -0
- package/lib/structures/rules/ITtscLintCoreRuleOptions.js.map +1 -0
- package/lib/structures/rules/ITtscLintCoreRules.d.ts +101 -67
- package/lib/structures/rules/ITtscLintRegexpRules.d.ts +7 -2
- package/lib/structures/rules/ITtscLintRuleOptionsMap.d.ts +53 -7
- package/lib/structures/rules/ITtscLintRules.d.ts +6 -2
- package/lib/structures/rules/ITtscLintSolidRules.d.ts +0 -8
- package/lib/structures/rules/ITtscLintTypeScriptRuleOptions.d.ts +206 -0
- package/lib/structures/rules/ITtscLintTypeScriptRuleOptions.js +8 -0
- package/lib/structures/rules/ITtscLintTypeScriptRuleOptions.js.map +1 -0
- package/lib/structures/rules/ITtscLintTypeScriptRules.d.ts +55 -42
- package/lib/structures/rules/ITtscLintUnicornRuleOptions.d.ts +276 -0
- package/lib/structures/rules/ITtscLintUnicornRuleOptions.js +3 -0
- package/lib/structures/rules/ITtscLintUnicornRuleOptions.js.map +1 -0
- package/lib/structures/rules/ITtscLintUnicornRules.d.ts +44 -36
- package/lib/structures/rules/index.d.ts +3 -0
- package/lib/structures/rules/index.js +3 -0
- package/lib/structures/rules/index.js.map +1 -1
- package/linthost/ast_helpers.go +521 -23
- package/linthost/ast_selector_matcher.go +1628 -0
- package/linthost/ast_selector_parser.go +628 -0
- package/linthost/comment_scan.go +154 -0
- package/linthost/compile.go +129 -79
- package/linthost/config.go +352 -88
- package/linthost/config_format.go +15 -1
- package/linthost/contrib_adapter.go +47 -8
- package/linthost/declaration_rules.go +12 -11
- package/linthost/directives.go +9 -67
- package/linthost/dispatch.go +37 -3
- package/linthost/engine.go +368 -53
- package/linthost/escape_string.go +77 -0
- package/linthost/fix.go +133 -23
- package/linthost/flags_gen.go +18 -17
- package/linthost/format.go +73 -167
- package/linthost/format_editor_settings.go +134 -26
- package/linthost/grapheme_tables_gen.go +1920 -0
- package/linthost/host.go +159 -61
- package/linthost/literal_escape_scan.go +117 -0
- package/linthost/lsp.go +545 -98
- package/linthost/numeric_literal_precision.go +228 -0
- package/linthost/polyfill_browserslist.go +1439 -0
- package/linthost/polyfill_browserslist_config.go +603 -0
- package/linthost/polyfill_corejs.go +750 -0
- package/linthost/polyfill_data.go +113 -0
- package/linthost/polyfill_data_gen.json +39868 -0
- package/linthost/print_nodes_array.go +7 -1
- package/linthost/print_nodes_object.go +7 -1
- package/linthost/project_engine.go +231 -0
- package/linthost/project_rules.go +78 -0
- package/linthost/regex_clean.go +191 -0
- package/linthost/regex_tree.go +1544 -0
- package/linthost/regex_tree_optimizer.go +1685 -0
- package/linthost/regexp_cache.go +44 -0
- package/linthost/rule_codes.go +105 -0
- package/linthost/rule_codes.json +745 -0
- package/linthost/rules_ban_ts_comment.go +312 -0
- package/linthost/rules_boundaries.go +128 -43
- package/linthost/rules_boundaries_dependencies.go +1013 -0
- package/linthost/rules_core_extra.go +62 -92
- package/linthost/rules_cypress.go +1 -1
- package/linthost/rules_default_case.go +87 -11
- package/linthost/rules_dupes.go +38 -13
- package/linthost/rules_empty.go +188 -20
- package/linthost/rules_escape.go +62 -48
- package/linthost/rules_format_arrow_parens.go +19 -5
- package/linthost/rules_format_bracket_spacing.go +1 -1
- package/linthost/rules_format_clause_join.go +1 -1
- package/linthost/rules_format_declaration_header.go +31 -19
- package/linthost/rules_format_indent.go +1 -1
- package/linthost/rules_format_jsdoc.go +1 -1
- package/linthost/rules_format_orphan_semi.go +1 -1
- package/linthost/rules_format_parameter_properties.go +17 -7
- package/linthost/rules_format_print_width.go +1 -1
- package/linthost/rules_format_quote_props.go +1 -1
- package/linthost/rules_format_quotes.go +1 -1
- package/linthost/rules_format_semi.go +106 -79
- package/linthost/rules_format_sort_imports.go +79 -45
- package/linthost/rules_format_statement_split.go +1 -1
- package/linthost/rules_format_ternary_nullish_parens.go +1 -1
- package/linthost/rules_format_trailing_comma.go +64 -1
- package/linthost/rules_format_whitespace.go +1 -1
- package/linthost/rules_functional.go +21 -22
- package/linthost/rules_gap.go +80 -3
- package/linthost/rules_grouped_accessor_pairs.go +84 -25
- package/linthost/rules_guard_for_in.go +64 -147
- package/linthost/rules_jsx_a11y.go +232 -22
- package/linthost/rules_logic.go +84 -38
- package/linthost/rules_nextjs.go +105 -16
- package/linthost/rules_no_class_assign.go +70 -0
- package/linthost/rules_no_duplicate_imports.go +266 -0
- package/linthost/rules_no_else_return.go +156 -38
- package/linthost/rules_no_extend_native.go +113 -17
- package/linthost/rules_no_fallthrough.go +1013 -0
- package/linthost/rules_no_func_assign.go +70 -0
- package/linthost/rules_no_import_assign.go +278 -86
- package/linthost/rules_no_inner_declarations.go +186 -0
- package/linthost/rules_no_loop_func.go +438 -53
- package/linthost/rules_no_mixed_operators.go +208 -52
- package/linthost/rules_no_param_reassign.go +376 -61
- package/linthost/rules_no_promise_executor_return.go +156 -0
- package/linthost/rules_no_restricted_imports.go +1013 -57
- package/linthost/rules_no_restricted_syntax.go +190 -26
- package/linthost/rules_no_useless_constructor.go +6 -36
- package/linthost/rules_problems.go +154 -311
- package/linthost/rules_promise.go +2578 -109
- package/linthost/rules_react_extras.go +71 -45
- package/linthost/rules_react_hooks.go +18 -2
- package/linthost/rules_react_perf.go +1 -0
- package/linthost/rules_react_refresh.go +1 -1
- package/linthost/rules_regexp.go +230 -14
- package/linthost/rules_security.go +45 -19
- package/linthost/rules_self.go +21 -1
- package/linthost/rules_solid.go +13 -7
- package/linthost/rules_storybook.go +1 -1
- package/linthost/rules_suggestions.go +490 -84
- package/linthost/rules_testing_library.go +12 -1
- package/linthost/rules_ts.go +227 -54
- package/linthost/rules_ts_async.go +0 -131
- package/linthost/rules_ts_no_base_to_string.go +3 -4
- package/linthost/rules_ts_no_misused_promises.go +817 -0
- package/linthost/rules_ts_no_restricted_types.go +320 -34
- package/linthost/rules_ts_no_unnecessary_qualifier.go +132 -55
- package/linthost/rules_ts_no_unsafe_assignment.go +608 -60
- package/linthost/rules_ts_require_array_sort_compare.go +5 -5
- package/linthost/rules_ts_switch_exhaustiveness_check.go +556 -86
- package/linthost/rules_unicorn_better_regex.go +224 -16
- package/linthost/rules_unicorn_consistent_destructuring.go +1002 -7
- package/linthost/rules_unicorn_consistent_existence_index_check.go +208 -29
- package/linthost/rules_unicorn_consistent_function_scoping.go +1041 -8
- package/linthost/rules_unicorn_consistent_template_literal_escape.go +178 -7
- package/linthost/rules_unicorn_escape_case.go +30 -19
- package/linthost/rules_unicorn_filename_case.go +763 -16
- package/linthost/rules_unicorn_import_style.go +783 -17
- package/linthost/rules_unicorn_isolated_functions.go +934 -14
- package/linthost/rules_unicorn_no_hex_escape.go +26 -11
- package/linthost/rules_unicorn_no_typeof_undefined.go +234 -25
- package/linthost/rules_unicorn_no_unnecessary_polyfills.go +376 -21
- package/linthost/rules_unicorn_no_unused_properties.go +756 -19
- package/linthost/rules_unicorn_number_literal_case.go +58 -39
- package/linthost/rules_unicorn_prefer_at.go +7 -3
- package/linthost/rules_unicorn_prefer_number_properties.go +381 -48
- package/linthost/rules_unicorn_prefer_optional_catch_binding.go +24 -24
- package/linthost/rules_unicorn_prefer_simple_condition_first.go +597 -19
- package/linthost/rules_unicorn_prefer_string_raw.go +147 -16
- package/linthost/rules_unicorn_prevent_abbreviations.go +1963 -95
- package/linthost/rules_unicorn_string_content.go +611 -15
- package/linthost/rules_unicorn_switch_case_break_position.go +172 -15
- package/linthost/rules_unicorn_template_indent.go +923 -18
- package/linthost/rules_unicorn_text_encoding_identifier_case.go +202 -42
- package/linthost/rules_unicorn_throw_new_error.go +218 -26
- package/linthost/rules_var.go +829 -142
- package/linthost/string_length.go +222 -0
- package/package.json +4 -3
- package/rule/project.go +206 -0
- package/rule/rule.go +58 -1
- package/src/index.ts +58 -17
- package/src/structures/ITtscLintConfig.ts +3 -0
- package/src/structures/TtscLintRuleSetting.ts +3 -2
- package/src/structures/format/ITtscLintFormat.ts +3 -2
- package/src/structures/format/ITtscLintFormatSortImports.ts +21 -4
- package/src/structures/rules/ITtscLintBoundariesRuleOptions.ts +122 -8
- package/src/structures/rules/ITtscLintBoundariesRules.ts +6 -8
- package/src/structures/rules/ITtscLintContributorRules.ts +18 -2
- package/src/structures/rules/ITtscLintCoreRuleOptions.ts +465 -0
- package/src/structures/rules/ITtscLintCoreRules.ts +122 -67
- package/src/structures/rules/ITtscLintRegexpRules.ts +7 -2
- package/src/structures/rules/ITtscLintRuleOptionsMap.ts +88 -7
- package/src/structures/rules/ITtscLintRules.ts +7 -2
- package/src/structures/rules/ITtscLintSolidRules.ts +0 -9
- package/src/structures/rules/ITtscLintTypeScriptRuleOptions.ts +248 -0
- package/src/structures/rules/ITtscLintTypeScriptRules.ts +64 -42
- package/src/structures/rules/ITtscLintUnicornRuleOptions.ts +350 -0
- package/src/structures/rules/ITtscLintUnicornRules.ts +60 -36
- package/src/structures/rules/index.ts +3 -0
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
package linthost
|
|
2
2
|
|
|
3
3
|
import (
|
|
4
|
+
"sync/atomic"
|
|
5
|
+
|
|
4
6
|
shimast "github.com/microsoft/typescript-go/shim/ast"
|
|
5
7
|
)
|
|
6
8
|
|
|
@@ -32,7 +34,7 @@ func (reactJSXNoUndef) Check(ctx *Context, node *shimast.Node) {
|
|
|
32
34
|
if first < 'A' || first > 'Z' {
|
|
33
35
|
return
|
|
34
36
|
}
|
|
35
|
-
if
|
|
37
|
+
if ctx.reactDeclaredNames()[name] {
|
|
36
38
|
return
|
|
37
39
|
}
|
|
38
40
|
ctx.Report(info.opening, "'"+name+"' is not defined.")
|
|
@@ -102,77 +104,101 @@ func reactExtrasOpeningTagNameNode(opening *shimast.Node) *shimast.Node {
|
|
|
102
104
|
return nil
|
|
103
105
|
}
|
|
104
106
|
|
|
105
|
-
//
|
|
106
|
-
//
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
//
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
107
|
+
// reactDeclaredNamesKey is the fileMemo sentinel under which a file's set
|
|
108
|
+
// of value-level declared names is cached.
|
|
109
|
+
type reactDeclaredNamesKey struct{}
|
|
110
|
+
|
|
111
|
+
// reactDeclaredNamesCollectCount records how many times
|
|
112
|
+
// reactExtrasFileDeclaredNames walks a file. The memoized accessor triggers
|
|
113
|
+
// it once per file, so a regression back to the per-tag walk makes this scale
|
|
114
|
+
// with the JSX-element count — the property the scaling test pins. Read only
|
|
115
|
+
// by tests; the per-file atomic add is negligible next to the walk it guards.
|
|
116
|
+
var reactDeclaredNamesCollectCount atomic.Int64
|
|
117
|
+
|
|
118
|
+
// reactDeclaredNames returns the set of value-level names declared anywhere
|
|
119
|
+
// in this file, computed once per file and cached on the shared fileMemo so
|
|
120
|
+
// react/jsx-no-undef does an O(1) membership check per capitalized tag rather
|
|
121
|
+
// than walking the whole file per JSX element. Without a memo (a Context built
|
|
122
|
+
// outside the engine) it recomputes, matching the pre-memoization behavior.
|
|
123
|
+
func (c *Context) reactDeclaredNames() map[string]bool {
|
|
124
|
+
if cached, ok := c.fileValue(reactDeclaredNamesKey{}); ok {
|
|
125
|
+
return cached.(map[string]bool)
|
|
126
|
+
}
|
|
127
|
+
names := reactExtrasFileDeclaredNames(c.File.AsNode())
|
|
128
|
+
c.setFileValue(reactDeclaredNamesKey{}, names)
|
|
129
|
+
return names
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// reactExtrasFileDeclaredNames collects every name bound in the source file
|
|
133
|
+
// by an import, function declaration, class declaration, variable
|
|
134
|
+
// declaration, parameter, or enum declaration. The walk is whole-file because
|
|
135
|
+
// JSX names resolve lexically in the surrounding closure, not against a single
|
|
136
|
+
// statement list; membership in the returned set answers exactly what the old
|
|
137
|
+
// per-name predicate did, so react/jsx-no-undef's findings are unchanged.
|
|
138
|
+
func reactExtrasFileDeclaredNames(root *shimast.Node) map[string]bool {
|
|
139
|
+
reactDeclaredNamesCollectCount.Add(1)
|
|
140
|
+
names := map[string]bool{}
|
|
141
|
+
if root == nil {
|
|
142
|
+
return names
|
|
143
|
+
}
|
|
144
|
+
add := func(name string) {
|
|
145
|
+
if name != "" {
|
|
146
|
+
names[name] = true
|
|
147
|
+
}
|
|
113
148
|
}
|
|
114
|
-
found := false
|
|
115
149
|
walkDescendants(root, func(child *shimast.Node) {
|
|
116
|
-
if
|
|
150
|
+
if child == nil {
|
|
117
151
|
return
|
|
118
152
|
}
|
|
119
153
|
switch child.Kind {
|
|
120
154
|
case shimast.KindFunctionDeclaration:
|
|
121
|
-
if fn := child.AsFunctionDeclaration(); fn != nil
|
|
122
|
-
|
|
155
|
+
if fn := child.AsFunctionDeclaration(); fn != nil {
|
|
156
|
+
add(identifierText(fn.Name()))
|
|
123
157
|
}
|
|
124
158
|
case shimast.KindClassDeclaration:
|
|
125
|
-
if cl := child.AsClassDeclaration(); cl != nil
|
|
126
|
-
|
|
159
|
+
if cl := child.AsClassDeclaration(); cl != nil {
|
|
160
|
+
add(identifierText(cl.Name()))
|
|
127
161
|
}
|
|
128
162
|
case shimast.KindVariableDeclaration:
|
|
129
|
-
if v := child.AsVariableDeclaration(); v != nil
|
|
130
|
-
|
|
163
|
+
if v := child.AsVariableDeclaration(); v != nil {
|
|
164
|
+
add(identifierText(v.Name()))
|
|
131
165
|
}
|
|
132
166
|
case shimast.KindParameter:
|
|
133
|
-
if p := child.AsParameterDeclaration(); p != nil
|
|
134
|
-
|
|
167
|
+
if p := child.AsParameterDeclaration(); p != nil {
|
|
168
|
+
add(identifierText(p.Name()))
|
|
135
169
|
}
|
|
136
170
|
case shimast.KindEnumDeclaration:
|
|
137
|
-
if e := child.AsEnumDeclaration(); e != nil
|
|
138
|
-
|
|
171
|
+
if e := child.AsEnumDeclaration(); e != nil {
|
|
172
|
+
add(identifierText(e.Name()))
|
|
139
173
|
}
|
|
140
174
|
case shimast.KindImportClause:
|
|
141
175
|
clause := child.AsImportClause()
|
|
142
176
|
if clause == nil {
|
|
143
177
|
return
|
|
144
178
|
}
|
|
145
|
-
|
|
146
|
-
|
|
179
|
+
add(identifierText(clause.Name()))
|
|
180
|
+
if clause.NamedBindings == nil {
|
|
147
181
|
return
|
|
148
182
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
ns
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
s := spec.AsImportSpecifier()
|
|
163
|
-
if s == nil {
|
|
164
|
-
continue
|
|
165
|
-
}
|
|
166
|
-
if identifierText(s.Name()) == name {
|
|
167
|
-
found = true
|
|
168
|
-
return
|
|
169
|
-
}
|
|
183
|
+
switch clause.NamedBindings.Kind {
|
|
184
|
+
case shimast.KindNamespaceImport:
|
|
185
|
+
if ns := clause.NamedBindings.AsNamespaceImport(); ns != nil {
|
|
186
|
+
add(identifierText(ns.Name()))
|
|
187
|
+
}
|
|
188
|
+
case shimast.KindNamedImports:
|
|
189
|
+
named := clause.NamedBindings.AsNamedImports()
|
|
190
|
+
if named == nil || named.Elements == nil {
|
|
191
|
+
return
|
|
192
|
+
}
|
|
193
|
+
for _, spec := range named.Elements.Nodes {
|
|
194
|
+
if s := spec.AsImportSpecifier(); s != nil {
|
|
195
|
+
add(identifierText(s.Name()))
|
|
170
196
|
}
|
|
171
197
|
}
|
|
172
198
|
}
|
|
173
199
|
}
|
|
174
200
|
})
|
|
175
|
-
return
|
|
201
|
+
return names
|
|
176
202
|
}
|
|
177
203
|
|
|
178
204
|
// reactExtrasIsDisplayNameWrapperCall reports whether the call
|
|
@@ -174,6 +174,9 @@ func (a *reactHooksAnalyzer) checkComponentHookFactories(root *shimast.Node) {
|
|
|
174
174
|
|
|
175
175
|
func (a *reactHooksAnalyzer) checkSetStateInRender(root *shimast.Node) {
|
|
176
176
|
walkDescendants(root, func(node *shimast.Node) {
|
|
177
|
+
if node == nil || node.Kind != shimast.KindCallExpression {
|
|
178
|
+
return
|
|
179
|
+
}
|
|
177
180
|
call := node.AsCallExpression()
|
|
178
181
|
if call == nil {
|
|
179
182
|
return
|
|
@@ -192,6 +195,9 @@ func (a *reactHooksAnalyzer) checkSetStateInRender(root *shimast.Node) {
|
|
|
192
195
|
|
|
193
196
|
func (a *reactHooksAnalyzer) checkSetStateInEffect(root *shimast.Node) {
|
|
194
197
|
walkDescendants(root, func(node *shimast.Node) {
|
|
198
|
+
if node == nil || node.Kind != shimast.KindCallExpression {
|
|
199
|
+
return
|
|
200
|
+
}
|
|
195
201
|
call := node.AsCallExpression()
|
|
196
202
|
if call == nil {
|
|
197
203
|
return
|
|
@@ -255,6 +261,9 @@ func (a *reactHooksAnalyzer) reportImmutableWrite(target *shimast.Node, reportNo
|
|
|
255
261
|
|
|
256
262
|
func (a *reactHooksAnalyzer) checkRefs(root *shimast.Node) {
|
|
257
263
|
walkDescendants(root, func(node *shimast.Node) {
|
|
264
|
+
if node == nil || node.Kind != shimast.KindPropertyAccessExpression {
|
|
265
|
+
return
|
|
266
|
+
}
|
|
258
267
|
access := node.AsPropertyAccessExpression()
|
|
259
268
|
if access == nil || identifierText(access.Name()) != "current" {
|
|
260
269
|
return
|
|
@@ -281,6 +290,9 @@ func (a *reactHooksAnalyzer) collectFunctionBindings(fn *reactHooksFunction) {
|
|
|
281
290
|
if a.nearestFunction(node) != fn {
|
|
282
291
|
return
|
|
283
292
|
}
|
|
293
|
+
if node == nil || node.Kind != shimast.KindVariableDeclaration {
|
|
294
|
+
return
|
|
295
|
+
}
|
|
284
296
|
decl := node.AsVariableDeclaration()
|
|
285
297
|
if decl == nil || decl.Initializer == nil {
|
|
286
298
|
return
|
|
@@ -487,7 +499,11 @@ func isNamedReactCallee(node *shimast.Node, name string) bool {
|
|
|
487
499
|
}
|
|
488
500
|
|
|
489
501
|
func isNamedReactCalleeFromCall(node *shimast.Node, name string) bool {
|
|
490
|
-
|
|
502
|
+
node = stripParens(node)
|
|
503
|
+
if node == nil || node.Kind != shimast.KindCallExpression {
|
|
504
|
+
return false
|
|
505
|
+
}
|
|
506
|
+
call := node.AsCallExpression()
|
|
491
507
|
return call != nil && isNamedReactCallee(call.Expression, name)
|
|
492
508
|
}
|
|
493
509
|
|
|
@@ -563,7 +579,7 @@ func statementContainsOwnReturn(stmt *shimast.Node, fnNode *shimast.Node) bool {
|
|
|
563
579
|
func functionContainsReactHookCall(node *shimast.Node) bool {
|
|
564
580
|
found := false
|
|
565
581
|
walkDescendants(reactFunctionBody(node), func(child *shimast.Node) {
|
|
566
|
-
if found || child == node {
|
|
582
|
+
if found || child == nil || child == node || child.Kind != shimast.KindCallExpression {
|
|
567
583
|
return
|
|
568
584
|
}
|
|
569
585
|
call := child.AsCallExpression()
|
|
@@ -9,7 +9,7 @@ import (
|
|
|
9
9
|
// onlyExportComponents enforces React Fast Refresh's module boundary rule:
|
|
10
10
|
// files that export React components should not also export non-components.
|
|
11
11
|
// Ported from eslint-plugin-react-refresh's only-export-components rule.
|
|
12
|
-
type onlyExportComponents struct{}
|
|
12
|
+
type onlyExportComponents struct{ optionsRule }
|
|
13
13
|
|
|
14
14
|
type onlyExportComponentsOptions struct {
|
|
15
15
|
ExtraHOCs []string `json:"extraHOCs"`
|
package/linthost/rules_regexp.go
CHANGED
|
@@ -4,8 +4,10 @@ import (
|
|
|
4
4
|
"sort"
|
|
5
5
|
"strconv"
|
|
6
6
|
"strings"
|
|
7
|
+
"unicode"
|
|
7
8
|
|
|
8
9
|
shimast "github.com/microsoft/typescript-go/shim/ast"
|
|
10
|
+
shimscanner "github.com/microsoft/typescript-go/shim/scanner"
|
|
9
11
|
)
|
|
10
12
|
|
|
11
13
|
// regexpSourceRule implements the high-confidence, source-text subset of
|
|
@@ -145,6 +147,43 @@ func regexpHasEmptyLookaround(parts regexpLiteralParts) bool {
|
|
|
145
147
|
})
|
|
146
148
|
}
|
|
147
149
|
|
|
150
|
+
// regexpHasEmptyCharacterClass reports non-negated character classes whose
|
|
151
|
+
// parsed element list is empty. The compiler's regexp parser is the syntax
|
|
152
|
+
// authority; the structural walk runs only after that parser accepts the full
|
|
153
|
+
// literal, so malformed escapes, flags, and class-set expressions cannot
|
|
154
|
+
// create lint findings.
|
|
155
|
+
func regexpHasEmptyCharacterClass(parts regexpLiteralParts) bool {
|
|
156
|
+
if !shimscanner.IsValidRegularExpressionLiteral(parts.raw) {
|
|
157
|
+
return false
|
|
158
|
+
}
|
|
159
|
+
unicodeSets := strings.Contains(parts.flags, "v")
|
|
160
|
+
depth := 0
|
|
161
|
+
for i := 0; i < len(parts.pattern); i++ {
|
|
162
|
+
switch parts.pattern[i] {
|
|
163
|
+
case '\\':
|
|
164
|
+
i++
|
|
165
|
+
case '[':
|
|
166
|
+
if depth != 0 && !unicodeSets {
|
|
167
|
+
continue
|
|
168
|
+
}
|
|
169
|
+
depth++
|
|
170
|
+
content := i + 1
|
|
171
|
+
negated := content < len(parts.pattern) && parts.pattern[content] == '^'
|
|
172
|
+
if negated {
|
|
173
|
+
content++
|
|
174
|
+
}
|
|
175
|
+
if !negated && content < len(parts.pattern) && parts.pattern[content] == ']' {
|
|
176
|
+
return true
|
|
177
|
+
}
|
|
178
|
+
case ']':
|
|
179
|
+
if depth > 0 {
|
|
180
|
+
depth--
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return false
|
|
185
|
+
}
|
|
186
|
+
|
|
148
187
|
func regexpHasZeroQuantifier(parts regexpLiteralParts) bool {
|
|
149
188
|
return scanRegexpQuantifiers(parts.pattern, func(min, max int, hasComma bool) bool {
|
|
150
189
|
return min == 0 && (!hasComma || max == 0)
|
|
@@ -205,11 +244,32 @@ func regexpFlagOrder(flag byte) int {
|
|
|
205
244
|
return len(order) + int(flag)
|
|
206
245
|
}
|
|
207
246
|
|
|
247
|
+
// regexpHasUselessFlag decides `regexp/no-useless-flag` for the two flags whose
|
|
248
|
+
// effect is visible in the pattern alone: `i` (nothing it could re-case) and `m`
|
|
249
|
+
// (no `^`/`$` for it to re-anchor).
|
|
250
|
+
//
|
|
251
|
+
// Both questions are answered on the regexp AST from regex_tree.go rather than
|
|
252
|
+
// on a byte scan. `scanRegexpPattern` never enters a character class -- correct
|
|
253
|
+
// for the rules that hunt `|`, `(`, `{` outside classes, and correct for `m`
|
|
254
|
+
// (`^`/`$` are literals inside a class), but fatal for `i`, because `[a-z]` is
|
|
255
|
+
// exactly where the flag earns its keep.
|
|
256
|
+
//
|
|
257
|
+
// A literal the parser rejects yields no finding at all: the rule tells people
|
|
258
|
+
// to delete a flag, so it stays silent whenever it cannot see the whole pattern.
|
|
208
259
|
func regexpHasUselessFlag(parts regexpLiteralParts) bool {
|
|
209
|
-
|
|
260
|
+
ignoreCase := strings.Contains(parts.flags, "i")
|
|
261
|
+
multiline := strings.Contains(parts.flags, "m")
|
|
262
|
+
if !ignoreCase && !multiline {
|
|
263
|
+
return false
|
|
264
|
+
}
|
|
265
|
+
parsed, err := regexParseLiteral(parts.raw)
|
|
266
|
+
if err != nil {
|
|
267
|
+
return false
|
|
268
|
+
}
|
|
269
|
+
if ignoreCase && !regexpNodeIsCaseVariant(parsed.Body, strings.ContainsAny(parts.flags, "uv")) {
|
|
210
270
|
return true
|
|
211
271
|
}
|
|
212
|
-
if
|
|
272
|
+
if multiline && !regexpNodeHasLineAnchor(parsed.Body) {
|
|
213
273
|
return true
|
|
214
274
|
}
|
|
215
275
|
return false
|
|
@@ -365,17 +425,175 @@ func classHasRange(content string) bool {
|
|
|
365
425
|
return false
|
|
366
426
|
}
|
|
367
427
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
428
|
+
// regexpCaseFoldScanLimit bounds the fold scan of a single character range.
|
|
429
|
+
// The scan exists to prove a range case-*invariant*, so refusing to walk an
|
|
430
|
+
// unbounded one costs at most a missed report, never a wrong one. Every script
|
|
431
|
+
// block that carries cased letters is orders of magnitude narrower than this.
|
|
432
|
+
const regexpCaseFoldScanLimit = 0x20000
|
|
433
|
+
|
|
434
|
+
// regexpNodeIsCaseVariant reports whether toggling the `i` flag can change what
|
|
435
|
+
// the node matches.
|
|
436
|
+
//
|
|
437
|
+
// Mirrors eslint-plugin-regexp's `isCaseVariant(pattern, flags, false)`, which
|
|
438
|
+
// judges a character class element by element rather than as a whole set: an
|
|
439
|
+
// element that the flag widens keeps the flag alive even when the class already
|
|
440
|
+
// spells both cases out, so `/[a-zA-Z]/i` is case-variant.
|
|
441
|
+
//
|
|
442
|
+
// The analysis is one-sided. Every construct it cannot settle from the AST --
|
|
443
|
+
// a Unicode property escape, a `v`-mode set-notation class, a range too wide to
|
|
444
|
+
// fold-scan, an unmodeled node -- counts as case-variant, so the rule stays
|
|
445
|
+
// quiet rather than order a load-bearing flag deleted. Backreferences are
|
|
446
|
+
// case-variant for a real reason and not merely out of caution: `i` makes the
|
|
447
|
+
// backreference comparison itself case-insensitive, so `/(.)\1/i` matches "aA"
|
|
448
|
+
// while `/(.)\1/` does not, without a single letter in the source.
|
|
449
|
+
func regexpNodeIsCaseVariant(node regexNode, unicodeMode bool) bool {
|
|
450
|
+
switch n := node.(type) {
|
|
451
|
+
case nil:
|
|
452
|
+
return false
|
|
453
|
+
case *regexCharNode:
|
|
454
|
+
return regexpCharIsCaseVariant(n, unicodeMode)
|
|
455
|
+
case *regexClassRangeNode:
|
|
456
|
+
return regexpClassRangeIsCaseVariant(n, unicodeMode)
|
|
457
|
+
case *regexClassNode:
|
|
458
|
+
for _, expression := range n.Expressions {
|
|
459
|
+
if regexpNodeIsCaseVariant(expression, unicodeMode) {
|
|
460
|
+
return true
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
return false
|
|
464
|
+
case *regexAlternativeNode:
|
|
465
|
+
for _, expression := range n.Expressions {
|
|
466
|
+
if regexpNodeIsCaseVariant(expression, unicodeMode) {
|
|
467
|
+
return true
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
return false
|
|
471
|
+
case *regexDisjunctionNode:
|
|
472
|
+
return regexpNodeIsCaseVariant(n.Left, unicodeMode) ||
|
|
473
|
+
regexpNodeIsCaseVariant(n.Right, unicodeMode)
|
|
474
|
+
case *regexGroupNode:
|
|
475
|
+
// A group name is never matched against the input, so `/(?<year>\d{4})/i`
|
|
476
|
+
// stays case-invariant despite the letters in `year`.
|
|
477
|
+
return regexpNodeIsCaseVariant(n.Expression, unicodeMode)
|
|
478
|
+
case *regexRepetitionNode:
|
|
479
|
+
return regexpNodeIsCaseVariant(n.Expression, unicodeMode)
|
|
480
|
+
case *regexAssertionNode:
|
|
481
|
+
switch n.Kind {
|
|
482
|
+
case "^", "$":
|
|
483
|
+
return false
|
|
484
|
+
case "\\b", "\\B":
|
|
485
|
+
// Word boundaries are defined in terms of `\w`, which grows by U+017F and
|
|
486
|
+
// U+212A under `iu`/`iv`.
|
|
487
|
+
return unicodeMode
|
|
488
|
+
}
|
|
489
|
+
return regexpNodeIsCaseVariant(n.Assertion, unicodeMode)
|
|
490
|
+
case *regexBackreferenceNode:
|
|
491
|
+
// `i` canonicalizes the backreference comparison itself.
|
|
492
|
+
return true
|
|
493
|
+
case *regexUnicodePropertyNode:
|
|
494
|
+
// Whether `\p{...}` is closed under case folding needs the Unicode property
|
|
495
|
+
// tables, not the source text: `\p{Lu}` moves under `i`, `\p{Nd}` does not.
|
|
496
|
+
return true
|
|
497
|
+
case *regexClassSetNode:
|
|
498
|
+
// A `v`-mode set-notation class is kept verbatim by the parser, so its
|
|
499
|
+
// members are not available to judge.
|
|
500
|
+
return true
|
|
501
|
+
}
|
|
502
|
+
// An unmodeled node keeps the flag: silence beats a wrong deletion.
|
|
503
|
+
return true
|
|
373
504
|
}
|
|
374
505
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
506
|
+
// regexpCharIsCaseVariant reports whether the `i` flag widens what a single
|
|
507
|
+
// character node matches.
|
|
508
|
+
func regexpCharIsCaseVariant(char *regexCharNode, unicodeMode bool) bool {
|
|
509
|
+
if !char.codePointIsNaN() {
|
|
510
|
+
return regexpRuneIsCaseVariant(rune(char.CodePoint))
|
|
511
|
+
}
|
|
512
|
+
switch char.Kind {
|
|
513
|
+
case "meta":
|
|
514
|
+
switch char.Value {
|
|
515
|
+
case "\\w", "\\W":
|
|
516
|
+
// `\w` is the one character set the flag moves: in Unicode mode
|
|
517
|
+
// Canonicalize folds U+017F and U+212A onto `s` and `k`, so they join the
|
|
518
|
+
// word characters (and leave `\W`).
|
|
519
|
+
return unicodeMode
|
|
520
|
+
case "\\d", "\\D", "\\s", "\\S", ".", "\\b":
|
|
521
|
+
return false
|
|
522
|
+
}
|
|
523
|
+
case "decimal":
|
|
524
|
+
// Annex B `\8` and `\9` match the bare digits.
|
|
525
|
+
return false
|
|
526
|
+
case "control":
|
|
527
|
+
// `\cX` is a control code point, but a dangling `\c` matches the two
|
|
528
|
+
// characters `\` and `c`, and that `c` re-cases.
|
|
529
|
+
return char.Value == "\\c"
|
|
530
|
+
}
|
|
531
|
+
return true
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// regexpClassRangeIsCaseVariant reports whether the `i` flag widens a character
|
|
535
|
+
// class range. A range is case-variant as soon as it contains one code point
|
|
536
|
+
// with a case-folded counterpart, because that counterpart may sit outside the
|
|
537
|
+
// range.
|
|
538
|
+
func regexpClassRangeIsCaseVariant(node *regexClassRangeNode, unicodeMode bool) bool {
|
|
539
|
+
if node.From == nil || node.To == nil {
|
|
540
|
+
return true
|
|
541
|
+
}
|
|
542
|
+
if node.From.codePointIsNaN() || node.To.codePointIsNaN() {
|
|
543
|
+
// Annex B reads `[\d-z]` as three independent members while the AST still
|
|
544
|
+
// models it as a range, so judge the two ends on their own.
|
|
545
|
+
return regexpCharIsCaseVariant(node.From, unicodeMode) ||
|
|
546
|
+
regexpCharIsCaseVariant(node.To, unicodeMode)
|
|
547
|
+
}
|
|
548
|
+
low, high := rune(node.From.CodePoint), rune(node.To.CodePoint)
|
|
549
|
+
if low > high || high-low >= regexpCaseFoldScanLimit {
|
|
550
|
+
return true
|
|
551
|
+
}
|
|
552
|
+
for r := low; r <= high; r++ {
|
|
553
|
+
if regexpRuneIsCaseVariant(r) {
|
|
554
|
+
return true
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
return false
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
// regexpRuneIsCaseVariant reports whether a code point has any case-folded
|
|
561
|
+
// counterpart.
|
|
562
|
+
//
|
|
563
|
+
// unicode.SimpleFold walks the simple case-folding orbit, which is exactly the
|
|
564
|
+
// equivalence class ECMAScript's Canonicalize builds in `u`/`v` mode. Legacy
|
|
565
|
+
// mode canonicalizes more narrowly -- it keeps U+017F, U+212A and U+00DF apart
|
|
566
|
+
// from their ASCII or uppercase partners -- so there the orbit is a superset,
|
|
567
|
+
// and the rule at worst keeps quiet about a flag it could have reported.
|
|
568
|
+
func regexpRuneIsCaseVariant(r rune) bool {
|
|
569
|
+
return unicode.SimpleFold(r) != r
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// regexpNodeHasLineAnchor reports whether the pattern contains a `^` or `$`
|
|
573
|
+
// assertion, the only thing the `m` flag re-defines. Character classes are not
|
|
574
|
+
// descended into: `^` and `$` are literal characters in there.
|
|
575
|
+
func regexpNodeHasLineAnchor(node regexNode) bool {
|
|
576
|
+
switch n := node.(type) {
|
|
577
|
+
case *regexAssertionNode:
|
|
578
|
+
if n.Kind == "^" || n.Kind == "$" {
|
|
579
|
+
return true
|
|
580
|
+
}
|
|
581
|
+
return regexpNodeHasLineAnchor(n.Assertion)
|
|
582
|
+
case *regexAlternativeNode:
|
|
583
|
+
for _, expression := range n.Expressions {
|
|
584
|
+
if regexpNodeHasLineAnchor(expression) {
|
|
585
|
+
return true
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
return false
|
|
589
|
+
case *regexDisjunctionNode:
|
|
590
|
+
return regexpNodeHasLineAnchor(n.Left) || regexpNodeHasLineAnchor(n.Right)
|
|
591
|
+
case *regexGroupNode:
|
|
592
|
+
return regexpNodeHasLineAnchor(n.Expression)
|
|
593
|
+
case *regexRepetitionNode:
|
|
594
|
+
return regexpNodeHasLineAnchor(n.Expression)
|
|
595
|
+
}
|
|
596
|
+
return false
|
|
379
597
|
}
|
|
380
598
|
|
|
381
599
|
func init() {
|
|
@@ -385,9 +603,7 @@ func init() {
|
|
|
385
603
|
Register(regexpSourceRule{name: "regexp/no-dupe-characters-character-class", check: regexpHasDuplicateClassCharacter})
|
|
386
604
|
Register(regexpSourceRule{name: "regexp/no-empty-alternative", check: regexpHasEmptyAlternative})
|
|
387
605
|
Register(regexpSourceRule{name: "regexp/no-empty-capturing-group", check: regexpHasEmptyCapturingGroup})
|
|
388
|
-
Register(regexpSourceRule{name: "regexp/no-empty-character-class", check:
|
|
389
|
-
return hasEmptyCharClass(parts.raw)
|
|
390
|
-
}})
|
|
606
|
+
Register(regexpSourceRule{name: "regexp/no-empty-character-class", check: regexpHasEmptyCharacterClass})
|
|
391
607
|
Register(regexpSourceRule{name: "regexp/no-empty-group", check: regexpHasEmptyGroup})
|
|
392
608
|
Register(regexpSourceRule{name: "regexp/no-empty-lookarounds-assertion", check: regexpHasEmptyLookaround})
|
|
393
609
|
Register(regexpSourceRule{name: "regexp/no-misleading-unicode-character", check: func(parts regexpLiteralParts) bool {
|
|
@@ -2,6 +2,7 @@ package linthost
|
|
|
2
2
|
|
|
3
3
|
import (
|
|
4
4
|
"strings"
|
|
5
|
+
"sync/atomic"
|
|
5
6
|
"unicode/utf8"
|
|
6
7
|
|
|
7
8
|
shimast "github.com/microsoft/typescript-go/shim/ast"
|
|
@@ -9,6 +10,33 @@ import (
|
|
|
9
10
|
|
|
10
11
|
const securityRulePrefix = "security/"
|
|
11
12
|
|
|
13
|
+
// securityBindingsKey is the fileMemo sentinel under which a file's
|
|
14
|
+
// security binding table is cached. Every `security/*` rule shares the
|
|
15
|
+
// identical file-invariant table, so keying it on the type — not on a
|
|
16
|
+
// per-rule value — lets one walk serve all of them for the whole file.
|
|
17
|
+
type securityBindingsKey struct{}
|
|
18
|
+
|
|
19
|
+
// securityBindingsCollectCount records how many times collectSecurityBindings
|
|
20
|
+
// actually walks a file. The memoized accessor triggers it once per file, so a
|
|
21
|
+
// regression that reintroduces the per-node rebuild makes this scale with the
|
|
22
|
+
// visited call-node count — the property the scaling test pins. Read only by
|
|
23
|
+
// tests; the per-file atomic add is negligible next to the walk it guards.
|
|
24
|
+
var securityBindingsCollectCount atomic.Int64
|
|
25
|
+
|
|
26
|
+
// securityBindings returns this file's binding table, computing it once
|
|
27
|
+
// per file and caching it on the shared fileMemo so every security rule
|
|
28
|
+
// and every visited call/new node reuses one table instead of rebuilding
|
|
29
|
+
// it. Without a memo (a Context built outside the engine) it recomputes,
|
|
30
|
+
// matching the pre-memoization behavior exactly.
|
|
31
|
+
func (c *Context) securityBindings() securityBindings {
|
|
32
|
+
if cached, ok := c.fileValue(securityBindingsKey{}); ok {
|
|
33
|
+
return cached.(securityBindings)
|
|
34
|
+
}
|
|
35
|
+
bindings := collectSecurityBindings(c.File)
|
|
36
|
+
c.setFileValue(securityBindingsKey{}, bindings)
|
|
37
|
+
return bindings
|
|
38
|
+
}
|
|
39
|
+
|
|
12
40
|
type securityDetectBidiCharacters struct{}
|
|
13
41
|
|
|
14
42
|
func (securityDetectBidiCharacters) Name() string {
|
|
@@ -63,7 +91,7 @@ func (securityDetectChildProcess) Check(ctx *Context, node *shimast.Node) {
|
|
|
63
91
|
if call == nil {
|
|
64
92
|
return
|
|
65
93
|
}
|
|
66
|
-
bindings :=
|
|
94
|
+
bindings := ctx.securityBindings()
|
|
67
95
|
if module, ok := requireCallModule(call); ok && isChildProcessModule(module) {
|
|
68
96
|
if isInlineChildProcessExecRequire(node, bindings) {
|
|
69
97
|
return
|
|
@@ -112,7 +140,7 @@ func (securityDetectEvalWithExpression) Check(ctx *Context, node *shimast.Node)
|
|
|
112
140
|
if call == nil || callCalleeName(call) != "eval" || call.Arguments == nil || len(call.Arguments.Nodes) == 0 {
|
|
113
141
|
return
|
|
114
142
|
}
|
|
115
|
-
bindings :=
|
|
143
|
+
bindings := ctx.securityBindings()
|
|
116
144
|
if !isSecurityStaticExpression(call.Arguments.Nodes[0], bindings, nil) {
|
|
117
145
|
ctx.Report(node, "eval called with a non-literal expression.")
|
|
118
146
|
}
|
|
@@ -129,7 +157,7 @@ func (securityDetectNewBuffer) Check(ctx *Context, node *shimast.Node) {
|
|
|
129
157
|
if expr == nil || identifierText(expr.Expression) != "Buffer" || expr.Arguments == nil || len(expr.Arguments.Nodes) == 0 {
|
|
130
158
|
return
|
|
131
159
|
}
|
|
132
|
-
bindings :=
|
|
160
|
+
bindings := ctx.securityBindings()
|
|
133
161
|
if !isSecurityStaticExpression(expr.Arguments.Nodes[0], bindings, nil) {
|
|
134
162
|
ctx.Report(node, "Found new Buffer with a non-literal argument.")
|
|
135
163
|
}
|
|
@@ -178,7 +206,7 @@ func (securityDetectNonLiteralFSFilename) Check(ctx *Context, node *shimast.Node
|
|
|
178
206
|
if call == nil || call.Arguments == nil || len(call.Arguments.Nodes) == 0 {
|
|
179
207
|
return
|
|
180
208
|
}
|
|
181
|
-
bindings :=
|
|
209
|
+
bindings := ctx.securityBindings()
|
|
182
210
|
method, module, ok := fsCallInfo(call, bindings)
|
|
183
211
|
if !ok || !isFSFilenameMethod(method) {
|
|
184
212
|
return
|
|
@@ -215,7 +243,7 @@ func (securityDetectNonLiteralRegexp) Check(ctx *Context, node *shimast.Node) {
|
|
|
215
243
|
if len(args) == 0 {
|
|
216
244
|
return
|
|
217
245
|
}
|
|
218
|
-
bindings :=
|
|
246
|
+
bindings := ctx.securityBindings()
|
|
219
247
|
if !isSecurityStaticExpression(args[0], bindings, nil) {
|
|
220
248
|
ctx.Report(node, "Found non-literal argument to RegExp constructor.")
|
|
221
249
|
}
|
|
@@ -234,7 +262,7 @@ func (securityDetectNonLiteralRequire) Check(ctx *Context, node *shimast.Node) {
|
|
|
234
262
|
if call == nil || callCalleeName(call) != "require" || call.Arguments == nil || len(call.Arguments.Nodes) == 0 {
|
|
235
263
|
return
|
|
236
264
|
}
|
|
237
|
-
bindings :=
|
|
265
|
+
bindings := ctx.securityBindings()
|
|
238
266
|
if !isSecurityStaticExpression(call.Arguments.Nodes[0], bindings, nil) {
|
|
239
267
|
ctx.Report(node, "Found non-literal argument in require.")
|
|
240
268
|
}
|
|
@@ -323,6 +351,7 @@ type securityBindings struct {
|
|
|
323
351
|
}
|
|
324
352
|
|
|
325
353
|
func collectSecurityBindings(file *shimast.SourceFile) securityBindings {
|
|
354
|
+
securityBindingsCollectCount.Add(1)
|
|
326
355
|
bindings := securityBindings{
|
|
327
356
|
Modules: map[string]string{},
|
|
328
357
|
Named: map[string]securityNamedBinding{},
|
|
@@ -367,7 +396,7 @@ func collectSecurityAssignedNames(node *shimast.Node) map[string]bool {
|
|
|
367
396
|
return
|
|
368
397
|
}
|
|
369
398
|
if expr.Operator == shimast.KindPlusPlusToken || expr.Operator == shimast.KindMinusMinusToken {
|
|
370
|
-
|
|
399
|
+
for _, name := range assignmentTargetNames(expr.Operand) {
|
|
371
400
|
assigned[name] = true
|
|
372
401
|
}
|
|
373
402
|
}
|
|
@@ -377,10 +406,18 @@ func collectSecurityAssignedNames(node *shimast.Node) map[string]bool {
|
|
|
377
406
|
return
|
|
378
407
|
}
|
|
379
408
|
if expr.Operator == shimast.KindPlusPlusToken || expr.Operator == shimast.KindMinusMinusToken {
|
|
380
|
-
|
|
409
|
+
for _, name := range assignmentTargetNames(expr.Operand) {
|
|
381
410
|
assigned[name] = true
|
|
382
411
|
}
|
|
383
412
|
}
|
|
413
|
+
case shimast.KindForOfStatement, shimast.KindForInStatement:
|
|
414
|
+
stmt := child.AsForInOrOfStatement()
|
|
415
|
+
if stmt == nil || stmt.Initializer == nil || stmt.Initializer.Kind == shimast.KindVariableDeclarationList {
|
|
416
|
+
return
|
|
417
|
+
}
|
|
418
|
+
for _, name := range assignmentTargetNames(stmt.Initializer) {
|
|
419
|
+
assigned[name] = true
|
|
420
|
+
}
|
|
384
421
|
}
|
|
385
422
|
})
|
|
386
423
|
return assigned
|
|
@@ -748,17 +785,6 @@ func isFalseLiteral(node *shimast.Node) bool {
|
|
|
748
785
|
return ok && !value
|
|
749
786
|
}
|
|
750
787
|
|
|
751
|
-
func isEqualityOperator(kind shimast.Kind) bool {
|
|
752
|
-
switch kind {
|
|
753
|
-
case shimast.KindEqualsEqualsToken,
|
|
754
|
-
shimast.KindEqualsEqualsEqualsToken,
|
|
755
|
-
shimast.KindExclamationEqualsToken,
|
|
756
|
-
shimast.KindExclamationEqualsEqualsToken:
|
|
757
|
-
return true
|
|
758
|
-
}
|
|
759
|
-
return false
|
|
760
|
-
}
|
|
761
|
-
|
|
762
788
|
func containsSecretIdentifier(node *shimast.Node) bool {
|
|
763
789
|
found := false
|
|
764
790
|
walkDescendants(node, func(child *shimast.Node) {
|