@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.
Files changed (188) hide show
  1. package/README.md +125 -43
  2. package/go.mod +5 -4
  3. package/internal/rulecode/rulecode.go +88 -0
  4. package/lib/index.d.ts +10 -1
  5. package/lib/index.js +45 -17
  6. package/lib/index.js.map +1 -1
  7. package/lib/structures/ITtscLintConfig.d.ts +3 -0
  8. package/lib/structures/TtscLintRuleSetting.d.ts +3 -2
  9. package/lib/structures/format/ITtscLintFormat.d.ts +3 -2
  10. package/lib/structures/format/ITtscLintFormatSortImports.d.ts +20 -4
  11. package/lib/structures/rules/ITtscLintBoundariesRuleOptions.d.ts +80 -8
  12. package/lib/structures/rules/ITtscLintBoundariesRules.d.ts +6 -8
  13. package/lib/structures/rules/ITtscLintContributorRules.d.ts +18 -2
  14. package/lib/structures/rules/ITtscLintCoreRuleOptions.d.ts +347 -0
  15. package/lib/structures/rules/ITtscLintCoreRuleOptions.js +3 -0
  16. package/lib/structures/rules/ITtscLintCoreRuleOptions.js.map +1 -0
  17. package/lib/structures/rules/ITtscLintCoreRules.d.ts +101 -67
  18. package/lib/structures/rules/ITtscLintRegexpRules.d.ts +7 -2
  19. package/lib/structures/rules/ITtscLintRuleOptionsMap.d.ts +53 -7
  20. package/lib/structures/rules/ITtscLintRules.d.ts +6 -2
  21. package/lib/structures/rules/ITtscLintSolidRules.d.ts +0 -8
  22. package/lib/structures/rules/ITtscLintTypeScriptRuleOptions.d.ts +206 -0
  23. package/lib/structures/rules/ITtscLintTypeScriptRuleOptions.js +8 -0
  24. package/lib/structures/rules/ITtscLintTypeScriptRuleOptions.js.map +1 -0
  25. package/lib/structures/rules/ITtscLintTypeScriptRules.d.ts +55 -42
  26. package/lib/structures/rules/ITtscLintUnicornRuleOptions.d.ts +276 -0
  27. package/lib/structures/rules/ITtscLintUnicornRuleOptions.js +3 -0
  28. package/lib/structures/rules/ITtscLintUnicornRuleOptions.js.map +1 -0
  29. package/lib/structures/rules/ITtscLintUnicornRules.d.ts +44 -36
  30. package/lib/structures/rules/index.d.ts +3 -0
  31. package/lib/structures/rules/index.js +3 -0
  32. package/lib/structures/rules/index.js.map +1 -1
  33. package/linthost/ast_helpers.go +521 -23
  34. package/linthost/ast_selector_matcher.go +1628 -0
  35. package/linthost/ast_selector_parser.go +628 -0
  36. package/linthost/comment_scan.go +154 -0
  37. package/linthost/compile.go +129 -79
  38. package/linthost/config.go +352 -88
  39. package/linthost/config_format.go +15 -1
  40. package/linthost/contrib_adapter.go +47 -8
  41. package/linthost/declaration_rules.go +12 -11
  42. package/linthost/directives.go +9 -67
  43. package/linthost/dispatch.go +37 -3
  44. package/linthost/engine.go +368 -53
  45. package/linthost/escape_string.go +77 -0
  46. package/linthost/fix.go +133 -23
  47. package/linthost/flags_gen.go +18 -17
  48. package/linthost/format.go +73 -167
  49. package/linthost/format_editor_settings.go +134 -26
  50. package/linthost/grapheme_tables_gen.go +1920 -0
  51. package/linthost/host.go +159 -61
  52. package/linthost/literal_escape_scan.go +117 -0
  53. package/linthost/lsp.go +545 -98
  54. package/linthost/numeric_literal_precision.go +228 -0
  55. package/linthost/polyfill_browserslist.go +1439 -0
  56. package/linthost/polyfill_browserslist_config.go +603 -0
  57. package/linthost/polyfill_corejs.go +750 -0
  58. package/linthost/polyfill_data.go +113 -0
  59. package/linthost/polyfill_data_gen.json +39868 -0
  60. package/linthost/print_nodes_array.go +7 -1
  61. package/linthost/print_nodes_object.go +7 -1
  62. package/linthost/project_engine.go +231 -0
  63. package/linthost/project_rules.go +78 -0
  64. package/linthost/regex_clean.go +191 -0
  65. package/linthost/regex_tree.go +1544 -0
  66. package/linthost/regex_tree_optimizer.go +1685 -0
  67. package/linthost/regexp_cache.go +44 -0
  68. package/linthost/rule_codes.go +105 -0
  69. package/linthost/rule_codes.json +745 -0
  70. package/linthost/rules_ban_ts_comment.go +312 -0
  71. package/linthost/rules_boundaries.go +128 -43
  72. package/linthost/rules_boundaries_dependencies.go +1013 -0
  73. package/linthost/rules_core_extra.go +62 -92
  74. package/linthost/rules_cypress.go +1 -1
  75. package/linthost/rules_default_case.go +87 -11
  76. package/linthost/rules_dupes.go +38 -13
  77. package/linthost/rules_empty.go +188 -20
  78. package/linthost/rules_escape.go +62 -48
  79. package/linthost/rules_format_arrow_parens.go +19 -5
  80. package/linthost/rules_format_bracket_spacing.go +1 -1
  81. package/linthost/rules_format_clause_join.go +1 -1
  82. package/linthost/rules_format_declaration_header.go +31 -19
  83. package/linthost/rules_format_indent.go +1 -1
  84. package/linthost/rules_format_jsdoc.go +1 -1
  85. package/linthost/rules_format_orphan_semi.go +1 -1
  86. package/linthost/rules_format_parameter_properties.go +17 -7
  87. package/linthost/rules_format_print_width.go +1 -1
  88. package/linthost/rules_format_quote_props.go +1 -1
  89. package/linthost/rules_format_quotes.go +1 -1
  90. package/linthost/rules_format_semi.go +106 -79
  91. package/linthost/rules_format_sort_imports.go +79 -45
  92. package/linthost/rules_format_statement_split.go +1 -1
  93. package/linthost/rules_format_ternary_nullish_parens.go +1 -1
  94. package/linthost/rules_format_trailing_comma.go +64 -1
  95. package/linthost/rules_format_whitespace.go +1 -1
  96. package/linthost/rules_functional.go +21 -22
  97. package/linthost/rules_gap.go +80 -3
  98. package/linthost/rules_grouped_accessor_pairs.go +84 -25
  99. package/linthost/rules_guard_for_in.go +64 -147
  100. package/linthost/rules_jsx_a11y.go +232 -22
  101. package/linthost/rules_logic.go +84 -38
  102. package/linthost/rules_nextjs.go +105 -16
  103. package/linthost/rules_no_class_assign.go +70 -0
  104. package/linthost/rules_no_duplicate_imports.go +266 -0
  105. package/linthost/rules_no_else_return.go +156 -38
  106. package/linthost/rules_no_extend_native.go +113 -17
  107. package/linthost/rules_no_fallthrough.go +1013 -0
  108. package/linthost/rules_no_func_assign.go +70 -0
  109. package/linthost/rules_no_import_assign.go +278 -86
  110. package/linthost/rules_no_inner_declarations.go +186 -0
  111. package/linthost/rules_no_loop_func.go +438 -53
  112. package/linthost/rules_no_mixed_operators.go +208 -52
  113. package/linthost/rules_no_param_reassign.go +376 -61
  114. package/linthost/rules_no_promise_executor_return.go +156 -0
  115. package/linthost/rules_no_restricted_imports.go +1013 -57
  116. package/linthost/rules_no_restricted_syntax.go +190 -26
  117. package/linthost/rules_no_useless_constructor.go +6 -36
  118. package/linthost/rules_problems.go +154 -311
  119. package/linthost/rules_promise.go +2578 -109
  120. package/linthost/rules_react_extras.go +71 -45
  121. package/linthost/rules_react_hooks.go +18 -2
  122. package/linthost/rules_react_perf.go +1 -0
  123. package/linthost/rules_react_refresh.go +1 -1
  124. package/linthost/rules_regexp.go +230 -14
  125. package/linthost/rules_security.go +45 -19
  126. package/linthost/rules_self.go +21 -1
  127. package/linthost/rules_solid.go +13 -7
  128. package/linthost/rules_storybook.go +1 -1
  129. package/linthost/rules_suggestions.go +490 -84
  130. package/linthost/rules_testing_library.go +12 -1
  131. package/linthost/rules_ts.go +227 -54
  132. package/linthost/rules_ts_async.go +0 -131
  133. package/linthost/rules_ts_no_base_to_string.go +3 -4
  134. package/linthost/rules_ts_no_misused_promises.go +817 -0
  135. package/linthost/rules_ts_no_restricted_types.go +320 -34
  136. package/linthost/rules_ts_no_unnecessary_qualifier.go +132 -55
  137. package/linthost/rules_ts_no_unsafe_assignment.go +608 -60
  138. package/linthost/rules_ts_require_array_sort_compare.go +5 -5
  139. package/linthost/rules_ts_switch_exhaustiveness_check.go +556 -86
  140. package/linthost/rules_unicorn_better_regex.go +224 -16
  141. package/linthost/rules_unicorn_consistent_destructuring.go +1002 -7
  142. package/linthost/rules_unicorn_consistent_existence_index_check.go +208 -29
  143. package/linthost/rules_unicorn_consistent_function_scoping.go +1041 -8
  144. package/linthost/rules_unicorn_consistent_template_literal_escape.go +178 -7
  145. package/linthost/rules_unicorn_escape_case.go +30 -19
  146. package/linthost/rules_unicorn_filename_case.go +763 -16
  147. package/linthost/rules_unicorn_import_style.go +783 -17
  148. package/linthost/rules_unicorn_isolated_functions.go +934 -14
  149. package/linthost/rules_unicorn_no_hex_escape.go +26 -11
  150. package/linthost/rules_unicorn_no_typeof_undefined.go +234 -25
  151. package/linthost/rules_unicorn_no_unnecessary_polyfills.go +376 -21
  152. package/linthost/rules_unicorn_no_unused_properties.go +756 -19
  153. package/linthost/rules_unicorn_number_literal_case.go +58 -39
  154. package/linthost/rules_unicorn_prefer_at.go +7 -3
  155. package/linthost/rules_unicorn_prefer_number_properties.go +381 -48
  156. package/linthost/rules_unicorn_prefer_optional_catch_binding.go +24 -24
  157. package/linthost/rules_unicorn_prefer_simple_condition_first.go +597 -19
  158. package/linthost/rules_unicorn_prefer_string_raw.go +147 -16
  159. package/linthost/rules_unicorn_prevent_abbreviations.go +1963 -95
  160. package/linthost/rules_unicorn_string_content.go +611 -15
  161. package/linthost/rules_unicorn_switch_case_break_position.go +172 -15
  162. package/linthost/rules_unicorn_template_indent.go +923 -18
  163. package/linthost/rules_unicorn_text_encoding_identifier_case.go +202 -42
  164. package/linthost/rules_unicorn_throw_new_error.go +218 -26
  165. package/linthost/rules_var.go +829 -142
  166. package/linthost/string_length.go +222 -0
  167. package/package.json +4 -3
  168. package/rule/project.go +206 -0
  169. package/rule/rule.go +58 -1
  170. package/src/index.ts +58 -17
  171. package/src/structures/ITtscLintConfig.ts +3 -0
  172. package/src/structures/TtscLintRuleSetting.ts +3 -2
  173. package/src/structures/format/ITtscLintFormat.ts +3 -2
  174. package/src/structures/format/ITtscLintFormatSortImports.ts +21 -4
  175. package/src/structures/rules/ITtscLintBoundariesRuleOptions.ts +122 -8
  176. package/src/structures/rules/ITtscLintBoundariesRules.ts +6 -8
  177. package/src/structures/rules/ITtscLintContributorRules.ts +18 -2
  178. package/src/structures/rules/ITtscLintCoreRuleOptions.ts +465 -0
  179. package/src/structures/rules/ITtscLintCoreRules.ts +122 -67
  180. package/src/structures/rules/ITtscLintRegexpRules.ts +7 -2
  181. package/src/structures/rules/ITtscLintRuleOptionsMap.ts +88 -7
  182. package/src/structures/rules/ITtscLintRules.ts +7 -2
  183. package/src/structures/rules/ITtscLintSolidRules.ts +0 -9
  184. package/src/structures/rules/ITtscLintTypeScriptRuleOptions.ts +248 -0
  185. package/src/structures/rules/ITtscLintTypeScriptRules.ts +64 -42
  186. package/src/structures/rules/ITtscLintUnicornRuleOptions.ts +350 -0
  187. package/src/structures/rules/ITtscLintUnicornRules.ts +60 -36
  188. package/src/structures/rules/index.ts +3 -0
@@ -1,85 +1,470 @@
1
- // noLoopFunc reports a `function` declaration, function expression, or
2
- // arrow function defined inside the body of a loop. The function value
3
- // allocated each iteration almost always closes over the loop counter or
4
- // some other binding that changes between iterations, so a deferred call
5
- // (timer, microtask, event handler) observes whatever the binding holds
6
- // at call time — usually the loop's terminal value, never the value the
7
- // developer reasoned about at the textual position.
1
+ // noLoopFunc reports closures created in loops only when they capture a
2
+ // binding whose value can change between iterations. The analysis mirrors
3
+ // ESLint's reference semantics while using TypeScript-Go symbols to preserve
4
+ // lexical identity through shadowing, destructuring, and TypeScript syntax.
8
5
  // https://eslint.org/docs/latest/rules/no-loop-func
9
- //
10
- // Conservative baseline: every function-like syntactically inside a loop
11
- // body is flagged. Capture analysis (the rule's full upstream
12
- // implementation only fires when the function actually closes over a
13
- // `let` / `var` / `const` declared outside) is deferred; the false-
14
- // positive rate is acceptable because the fix — hoist the function out
15
- // of the loop — is almost always the cleaner code in either case.
16
- //
17
- // The walk stops at nested loop boundaries — each enclosing loop visits
18
- // itself once, so an inner loop reports its own nested function-likes
19
- // and the outer loop only owns the function-likes directly inside it.
20
- // Inner function-likes are also boundaries: a closure inside an outer
21
- // function-likes is that inner scope's problem.
22
6
  package linthost
23
7
 
24
8
  import (
9
+ "strings"
10
+
25
11
  shimast "github.com/microsoft/typescript-go/shim/ast"
26
12
  )
27
13
 
28
14
  type noLoopFunc struct{}
29
15
 
30
- func (noLoopFunc) Name() string { return "no-loop-func" }
31
- func (noLoopFunc) Visits() []shimast.Kind {
32
- return []shimast.Kind{
33
- shimast.KindForStatement,
34
- shimast.KindForInStatement,
35
- shimast.KindForOfStatement,
36
- shimast.KindWhileStatement,
37
- shimast.KindDoStatement,
38
- }
16
+ type noLoopFuncReference struct {
17
+ node *shimast.Node
18
+ symbol *shimast.Symbol
19
+ scope *shimast.Node
20
+ }
21
+
22
+ type noLoopFuncBindingKind uint8
23
+
24
+ const (
25
+ noLoopFuncOtherBinding noLoopFuncBindingKind = iota
26
+ noLoopFuncLetBinding
27
+ noLoopFuncConstantBinding
28
+ )
29
+
30
+ type noLoopFuncBinding struct {
31
+ rangeNode *shimast.Node
32
+ scope *shimast.Node
33
+ kind noLoopFuncBindingKind
39
34
  }
35
+
36
+ type noLoopFuncAnalysis struct {
37
+ ctx *Context
38
+ declarationIdentifiers map[*shimast.Node]struct{}
39
+ writeIdentifiers map[*shimast.Node]struct{}
40
+ functionReferences map[*shimast.Node][]noLoopFuncReference
41
+ writesBySymbol map[*shimast.Symbol][]noLoopFuncReference
42
+ bindings map[*shimast.Symbol]noLoopFuncBinding
43
+ skippedIIFEs map[*shimast.Node]struct{}
44
+ }
45
+
46
+ func (noLoopFunc) Name() string { return "no-loop-func" }
47
+ func (noLoopFunc) NeedsTypeChecker() bool { return true }
48
+ func (noLoopFunc) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindSourceFile} }
40
49
  func (noLoopFunc) Check(ctx *Context, node *shimast.Node) {
41
- var walk func(n *shimast.Node)
42
- walk = func(n *shimast.Node) {
43
- if n == nil {
50
+ if ctx.Checker == nil || node == nil {
51
+ return
52
+ }
53
+
54
+ analysis := &noLoopFuncAnalysis{
55
+ ctx: ctx,
56
+ declarationIdentifiers: make(map[*shimast.Node]struct{}),
57
+ writeIdentifiers: make(map[*shimast.Node]struct{}),
58
+ functionReferences: make(map[*shimast.Node][]noLoopFuncReference),
59
+ writesBySymbol: make(map[*shimast.Symbol][]noLoopFuncReference),
60
+ bindings: make(map[*shimast.Symbol]noLoopFuncBinding),
61
+ skippedIIFEs: make(map[*shimast.Node]struct{}),
62
+ }
63
+ analysis.collectSyntax(node)
64
+ analysis.collectReferences(node)
65
+ analysis.checkFunctions(node)
66
+ }
67
+
68
+ func (a *noLoopFuncAnalysis) collectSyntax(source *shimast.Node) {
69
+ walkDescendants(source, func(node *shimast.Node) {
70
+ if node.Kind != shimast.KindShorthandPropertyAssignment {
71
+ for _, identifier := range noLoopFuncNamedIdentifiers(node.Name()) {
72
+ a.declarationIdentifiers[identifier] = struct{}{}
73
+ }
74
+ }
75
+
76
+ for _, identifier := range writeTargetIdentifiers(node) {
77
+ a.writeIdentifiers[identifier] = struct{}{}
78
+ }
79
+
80
+ if node.Kind != shimast.KindVariableDeclaration {
44
81
  return
45
82
  }
46
- if isFunctionLikeKind(n) {
47
- ctx.Report(n, "Function declared in a loop closes over the loop's bindings — hoist it out.")
83
+ declaration := node.AsVariableDeclaration()
84
+ if declaration == nil {
48
85
  return
49
86
  }
50
- if n != node && isLoopKind(n) {
51
- // Nested loops report their own contents when visited.
87
+ initialized := declaration.Initializer != nil
88
+ if !initialized && node.Parent != nil && node.Parent.Kind == shimast.KindVariableDeclarationList &&
89
+ node.Parent.Parent != nil &&
90
+ (node.Parent.Parent.Kind == shimast.KindForInStatement || node.Parent.Parent.Kind == shimast.KindForOfStatement) {
91
+ statement := node.Parent.Parent.AsForInOrOfStatement()
92
+ initialized = statement != nil && statement.Initializer == node.Parent
93
+ }
94
+ if initialized {
95
+ for _, identifier := range bindingIdentifierNodes(declaration.Name()) {
96
+ a.writeIdentifiers[identifier] = struct{}{}
97
+ }
98
+ }
99
+ })
100
+ }
101
+
102
+ // noLoopFuncNamedIdentifiers returns identifier-shaped names without asking
103
+ // the AST to reinterpret arbitrary declaration names as binding patterns.
104
+ // Computed names contain evaluated expressions and must remain available to
105
+ // reference analysis, while ordinary named declarations and members are not
106
+ // runtime references to their own spelling.
107
+ func noLoopFuncNamedIdentifiers(name *shimast.Node) []*shimast.Node {
108
+ if name == nil {
109
+ return nil
110
+ }
111
+ switch name.Kind {
112
+ case shimast.KindIdentifier:
113
+ return []*shimast.Node{name}
114
+ case shimast.KindObjectBindingPattern, shimast.KindArrayBindingPattern:
115
+ return bindingIdentifierNodes(name)
116
+ }
117
+ return nil
118
+ }
119
+
120
+ func (a *noLoopFuncAnalysis) collectReferences(source *shimast.Node) {
121
+ walkDescendants(source, func(node *shimast.Node) {
122
+ if node.Kind != shimast.KindIdentifier {
52
123
  return
53
124
  }
54
- n.ForEachChild(func(child *shimast.Node) bool {
55
- walk(child)
56
- return false
57
- })
125
+
126
+ if _, written := a.writeIdentifiers[node]; written {
127
+ if symbol := canonicalValueSymbol(a.ctx, node); symbol != nil {
128
+ a.writesBySymbol[symbol] = append(a.writesBySymbol[symbol], noLoopFuncReference{
129
+ node: node,
130
+ symbol: symbol,
131
+ scope: noLoopFuncVariableScope(node),
132
+ })
133
+ }
134
+ }
135
+
136
+ if !a.isRuntimeReference(node) {
137
+ return
138
+ }
139
+ symbol := canonicalValueSymbol(a.ctx, node)
140
+ if symbol == nil {
141
+ return
142
+ }
143
+ reference := noLoopFuncReference{
144
+ node: node,
145
+ symbol: symbol,
146
+ scope: noLoopFuncVariableScope(node),
147
+ }
148
+ for ancestor := node.Parent; ancestor != nil; ancestor = ancestor.Parent {
149
+ if noLoopFuncIsCheckedFunction(ancestor) && noLoopFuncReferenceBelongsToFunction(node, ancestor) {
150
+ a.functionReferences[ancestor] = append(a.functionReferences[ancestor], reference)
151
+ }
152
+ }
153
+ })
154
+ }
155
+
156
+ func (a *noLoopFuncAnalysis) isRuntimeReference(node *shimast.Node) bool {
157
+ if _, declaration := a.declarationIdentifiers[node]; declaration {
158
+ return false
159
+ }
160
+ if shimast.IsPartOfTypeNode(node) || noLoopFuncInsideTypeQuery(node) {
161
+ return false
58
162
  }
59
- node.ForEachChild(func(child *shimast.Node) bool {
60
- walk(child)
163
+ if !isValueReferenceIdentifier(node) {
61
164
  return false
165
+ }
166
+ parent := node.Parent
167
+ if parent != nil && parent.Kind == shimast.KindBindingElement {
168
+ binding := parent.AsBindingElement()
169
+ if binding != nil && binding.PropertyName == node {
170
+ return false
171
+ }
172
+ }
173
+ return true
174
+ }
175
+
176
+ func (a *noLoopFuncAnalysis) checkFunctions(source *shimast.Node) {
177
+ walkDescendants(source, func(function *shimast.Node) {
178
+ if !noLoopFuncIsCheckedFunction(function) {
179
+ return
180
+ }
181
+ loop := noLoopFuncContainingLoop(function, a.skippedIIFEs)
182
+ if loop == nil {
183
+ return
184
+ }
185
+ if noLoopFuncIsEligibleIIFE(function) && !a.isReferencedNamedIIFE(function) {
186
+ a.skippedIIFEs[function] = struct{}{}
187
+ return
188
+ }
189
+
190
+ unsafeNames := make([]string, 0)
191
+ seenNames := make(map[string]struct{})
192
+ safety := make(map[*shimast.Symbol]bool)
193
+ for _, reference := range a.functionReferences[function] {
194
+ if a.symbolDeclaredInside(reference.symbol, function) {
195
+ continue
196
+ }
197
+ safe, exists := safety[reference.symbol]
198
+ if !exists {
199
+ safe = a.isSafe(loop, reference.symbol)
200
+ safety[reference.symbol] = safe
201
+ }
202
+ if safe {
203
+ continue
204
+ }
205
+ name := identifierText(reference.node)
206
+ if name == "" {
207
+ continue
208
+ }
209
+ if _, exists := seenNames[name]; exists {
210
+ continue
211
+ }
212
+ seenNames[name] = struct{}{}
213
+ unsafeNames = append(unsafeNames, name)
214
+ }
215
+ if len(unsafeNames) == 0 {
216
+ return
217
+ }
218
+ quoted := make([]string, len(unsafeNames))
219
+ for index, name := range unsafeNames {
220
+ quoted[index] = "'" + name + "'"
221
+ }
222
+ a.ctx.Report(function, "Function declared in a loop contains unsafe references to variable(s) "+strings.Join(quoted, ", ")+".")
62
223
  })
63
224
  }
64
225
 
65
- // isLoopKind reports whether n is one of the loop statement kinds this
66
- // rule visits. Used to stop the walk at a nested loop boundary so each
67
- // loop reports only the function-likes directly inside its own body.
68
- func isLoopKind(n *shimast.Node) bool {
69
- if n == nil {
226
+ func (a *noLoopFuncAnalysis) isReferencedNamedIIFE(function *shimast.Node) bool {
227
+ if function.Kind != shimast.KindFunctionExpression {
70
228
  return false
71
229
  }
72
- switch n.Kind {
73
- case shimast.KindForStatement,
74
- shimast.KindForInStatement,
75
- shimast.KindForOfStatement,
76
- shimast.KindWhileStatement,
77
- shimast.KindDoStatement:
230
+ name := function.Name()
231
+ if name == nil || name.Kind != shimast.KindIdentifier {
232
+ return false
233
+ }
234
+ symbol := canonicalValueSymbol(a.ctx, name)
235
+ if symbol == nil {
236
+ return false
237
+ }
238
+ for _, reference := range a.functionReferences[function] {
239
+ if reference.symbol == symbol {
240
+ return true
241
+ }
242
+ }
243
+ return false
244
+ }
245
+
246
+ func (a *noLoopFuncAnalysis) symbolDeclaredInside(symbol *shimast.Symbol, function *shimast.Node) bool {
247
+ for _, declaration := range symbol.Declarations {
248
+ if declaration != nil && declaration.Pos() >= function.Pos() && declaration.End() <= function.End() {
249
+ return true
250
+ }
251
+ }
252
+ return false
253
+ }
254
+
255
+ func (a *noLoopFuncAnalysis) isSafe(loop *shimast.Node, symbol *shimast.Symbol) bool {
256
+ binding := a.binding(symbol)
257
+ if binding.kind == noLoopFuncConstantBinding {
78
258
  return true
79
259
  }
260
+ if binding.kind == noLoopFuncLetBinding && binding.rangeNode != nil &&
261
+ binding.rangeNode.Pos() > loop.Pos() && binding.rangeNode.End() < loop.End() {
262
+ return true
263
+ }
264
+
265
+ var excluded *shimast.Node
266
+ if binding.kind == noLoopFuncLetBinding {
267
+ excluded = binding.rangeNode
268
+ }
269
+ border := noLoopFuncTopLoop(loop, excluded, a.skippedIIFEs)
270
+ for _, write := range a.writesBySymbol[symbol] {
271
+ if write.scope != binding.scope || write.node.Pos() >= border.Pos() {
272
+ return false
273
+ }
274
+ }
275
+ return true
276
+ }
277
+
278
+ func (a *noLoopFuncAnalysis) binding(symbol *shimast.Symbol) noLoopFuncBinding {
279
+ if binding, exists := a.bindings[symbol]; exists {
280
+ return binding
281
+ }
282
+ binding := noLoopFuncBinding{kind: noLoopFuncOtherBinding}
283
+ declarations := symbol.Declarations
284
+ if symbol.ValueDeclaration != nil {
285
+ declarations = append([]*shimast.Node{symbol.ValueDeclaration}, declarations...)
286
+ }
287
+ for _, declaration := range declarations {
288
+ root := noLoopFuncRootVariableDeclaration(declaration)
289
+ if root == nil || root.Kind != shimast.KindVariableDeclaration {
290
+ if binding.rangeNode == nil && declaration != nil {
291
+ binding.rangeNode = declaration
292
+ binding.scope = noLoopFuncVariableScope(declaration)
293
+ }
294
+ continue
295
+ }
296
+ binding.rangeNode = root
297
+ if root.Parent != nil && root.Parent.Kind == shimast.KindVariableDeclarationList {
298
+ binding.rangeNode = root.Parent
299
+ }
300
+ binding.scope = noLoopFuncVariableScope(root)
301
+ flags := shimast.GetCombinedNodeFlags(root)
302
+ switch {
303
+ case flags&shimast.NodeFlagsConstant != 0:
304
+ binding.kind = noLoopFuncConstantBinding
305
+ case flags&shimast.NodeFlagsLet != 0:
306
+ binding.kind = noLoopFuncLetBinding
307
+ }
308
+ break
309
+ }
310
+ a.bindings[symbol] = binding
311
+ return binding
312
+ }
313
+
314
+ func noLoopFuncRootVariableDeclaration(node *shimast.Node) *shimast.Node {
315
+ for node != nil && node.Kind == shimast.KindBindingElement {
316
+ if node.Parent == nil {
317
+ return nil
318
+ }
319
+ node = node.Parent.Parent
320
+ }
321
+ return node
322
+ }
323
+
324
+ func noLoopFuncVariableScope(node *shimast.Node) *shimast.Node {
325
+ for ancestor := node; ancestor != nil; ancestor = ancestor.Parent {
326
+ if noLoopFuncIsAnyFunction(ancestor) || ancestor.Kind == shimast.KindSourceFile ||
327
+ ancestor.Kind == shimast.KindClassStaticBlockDeclaration {
328
+ return ancestor
329
+ }
330
+ if ancestor.Kind == shimast.KindPropertyDeclaration {
331
+ property := ancestor.AsPropertyDeclaration()
332
+ if property != nil && noLoopFuncNodeContains(property.Initializer, node) {
333
+ return ancestor
334
+ }
335
+ }
336
+ }
337
+ return nil
338
+ }
339
+
340
+ func noLoopFuncContainingLoop(node *shimast.Node, skippedIIFEs map[*shimast.Node]struct{}) *shimast.Node {
341
+ child := node
342
+ for parent := node.Parent; parent != nil; parent = parent.Parent {
343
+ switch parent.Kind {
344
+ case shimast.KindWhileStatement, shimast.KindDoStatement:
345
+ return parent
346
+ case shimast.KindForStatement:
347
+ statement := parent.AsForStatement()
348
+ if statement == nil || statement.Initializer != child {
349
+ return parent
350
+ }
351
+ case shimast.KindForInStatement, shimast.KindForOfStatement:
352
+ statement := parent.AsForInOrOfStatement()
353
+ if statement == nil || statement.Expression != child {
354
+ return parent
355
+ }
356
+ default:
357
+ if noLoopFuncIsAnyFunction(parent) {
358
+ if _, skipped := skippedIIFEs[parent]; !skipped {
359
+ return nil
360
+ }
361
+ }
362
+ }
363
+ child = parent
364
+ }
365
+ return nil
366
+ }
367
+
368
+ func noLoopFuncTopLoop(loop, excluded *shimast.Node, skippedIIFEs map[*shimast.Node]struct{}) *shimast.Node {
369
+ top := loop
370
+ for {
371
+ outer := noLoopFuncContainingLoop(top, skippedIIFEs)
372
+ if outer == nil || (excluded != nil && outer.Pos() < excluded.End()) {
373
+ return top
374
+ }
375
+ top = outer
376
+ }
377
+ }
378
+
379
+ func noLoopFuncIsEligibleIIFE(function *shimast.Node) bool {
380
+ if function.Kind != shimast.KindFunctionExpression && function.Kind != shimast.KindArrowFunction {
381
+ return false
382
+ }
383
+ if function.ModifierFlags()&shimast.ModifierFlagsAsync != 0 || noLoopFuncIsGenerator(function) {
384
+ return false
385
+ }
386
+ expression := function
387
+ parent := expression.Parent
388
+ for parent != nil && parent.Kind == shimast.KindParenthesizedExpression && parent.Expression() == expression {
389
+ expression = parent
390
+ parent = parent.Parent
391
+ }
392
+ if parent == nil || parent.Kind != shimast.KindCallExpression {
393
+ return false
394
+ }
395
+ call := parent.AsCallExpression()
396
+ return call != nil && stripParens(call.Expression) == function
397
+ }
398
+
399
+ func noLoopFuncIsGenerator(function *shimast.Node) bool {
400
+ if function.Kind == shimast.KindFunctionExpression {
401
+ expression := function.AsFunctionExpression()
402
+ return expression != nil && expression.AsteriskToken != nil
403
+ }
80
404
  return false
81
405
  }
82
406
 
407
+ func noLoopFuncInsideTypeQuery(node *shimast.Node) bool {
408
+ for ancestor := node.Parent; ancestor != nil; ancestor = ancestor.Parent {
409
+ if ancestor.Kind == shimast.KindTypeQuery {
410
+ return true
411
+ }
412
+ if noLoopFuncIsAnyFunction(ancestor) || ancestor.Kind == shimast.KindSourceFile {
413
+ return false
414
+ }
415
+ }
416
+ return false
417
+ }
418
+
419
+ func noLoopFuncReferenceBelongsToFunction(reference, function *shimast.Node) bool {
420
+ child := reference
421
+ for child != nil && child.Parent != function {
422
+ child = child.Parent
423
+ }
424
+ if child == nil {
425
+ return false
426
+ }
427
+ if child == function.Body() {
428
+ return true
429
+ }
430
+ if child.Kind != shimast.KindParameter {
431
+ return false
432
+ }
433
+ parameter := child.AsParameterDeclaration()
434
+ return parameter != nil &&
435
+ (noLoopFuncNodeContains(parameter.Name(), reference) || noLoopFuncNodeContains(parameter.Initializer, reference))
436
+ }
437
+
438
+ func noLoopFuncNodeContains(container, node *shimast.Node) bool {
439
+ for current := node; current != nil; current = current.Parent {
440
+ if current == container {
441
+ return true
442
+ }
443
+ }
444
+ return false
445
+ }
446
+
447
+ func noLoopFuncIsCheckedFunction(node *shimast.Node) bool {
448
+ if node == nil {
449
+ return false
450
+ }
451
+ switch node.Kind {
452
+ case shimast.KindArrowFunction,
453
+ shimast.KindFunctionExpression,
454
+ shimast.KindFunctionDeclaration,
455
+ shimast.KindMethodDeclaration,
456
+ shimast.KindGetAccessor,
457
+ shimast.KindSetAccessor,
458
+ shimast.KindConstructor:
459
+ return true
460
+ }
461
+ return false
462
+ }
463
+
464
+ func noLoopFuncIsAnyFunction(node *shimast.Node) bool {
465
+ return node != nil && isFunctionLikeKind(node)
466
+ }
467
+
83
468
  func init() {
84
469
  Register(noLoopFunc{})
85
470
  }