@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,23 +1,1056 @@
1
- // MVP: registers the rule so the typed-key parity test passes; the
2
- // upstream behavior needs scope analysis (determine which free
3
- // variables a nested function references in the enclosing scope), which
4
- // is out of scope for this AST-only port.
1
+ // unicorn/consistent-function-scoping reports nested function definitions that
2
+ // can move to an outer scope without losing a captured binding. TypeScript
3
+ // checker symbols preserve binding identity through shadowing, destructuring,
4
+ // imports, JSX, and declaration merging; source spelling is never used as a
5
+ // substitute for scope analysis.
5
6
  //
6
- // unicorn/consistent-function-scoping:
7
- // https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/consistent-function-scoping.md
7
+ // The surrounding-scope calculation mirrors the upstream rule's meaningful
8
+ // exceptions. Loop header and body scopes are considered together, returned
9
+ // arrow chains are checked from the returner's scope, and immediate children
10
+ // of React hooks and IIFEs are left in place. Jest mock factories are excluded
11
+ // at every depth because Jest forbids their out-of-scope references.
12
+ //
13
+ // Two deliberate deviations extend upstream, both demanded by the issue
14
+ // contract and both conservative (they only suppress reports):
15
+ // - Arrow functions that capture lexical super or `arguments` are immovable
16
+ // alongside upstream's lexical-this check, and private-name uses pin any
17
+ // candidate; the constructs cannot survive relocation.
18
+ // - The block a function is defined in always joins the pin set, not only
19
+ // when it chains up to a loop body: a binding declared beside the
20
+ // function would go out of scope if the function moved.
21
+ //
22
+ // https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/rules/consistent-function-scoping.js
8
23
  package linthost
9
24
 
10
- import shimast "github.com/microsoft/typescript-go/shim/ast"
25
+ import (
26
+ "bytes"
27
+ "encoding/json"
28
+ "fmt"
29
+
30
+ shimast "github.com/microsoft/typescript-go/shim/ast"
31
+ shimscanner "github.com/microsoft/typescript-go/shim/scanner"
32
+ )
33
+
34
+ type unicornConsistentFunctionScoping struct{ optionsRule }
35
+
36
+ type unicornConsistentFunctionScopingOptions struct {
37
+ checkArrowFunctions bool
38
+ }
39
+
40
+ type unicornConsistentFunctionScopingAnalysis struct {
41
+ ctx *Context
42
+ referencesBySymbol map[*shimast.Symbol][]*shimast.Node
43
+ unresolvedPrivate []*shimast.Node
44
+ }
11
45
 
12
- type unicornConsistentFunctionScoping struct{}
46
+ type unicornConsistentFunctionScopingScopeSet struct {
47
+ owners map[*shimast.Node]struct{}
48
+ main *shimast.Node
49
+ }
13
50
 
14
51
  func (unicornConsistentFunctionScoping) Name() string {
15
52
  return "unicorn/consistent-function-scoping"
16
53
  }
54
+
55
+ func (unicornConsistentFunctionScoping) NeedsTypeChecker() bool { return true }
56
+
17
57
  func (unicornConsistentFunctionScoping) Visits() []shimast.Kind {
18
58
  return []shimast.Kind{shimast.KindSourceFile}
19
59
  }
60
+
61
+ func (unicornConsistentFunctionScoping) ValidateOptions(raw json.RawMessage) error {
62
+ _, err := decodeUnicornConsistentFunctionScopingOptions(raw)
63
+ return err
64
+ }
65
+
20
66
  func (unicornConsistentFunctionScoping) Check(ctx *Context, node *shimast.Node) {
67
+ if ctx == nil || ctx.File == nil || ctx.Checker == nil || node == nil {
68
+ return
69
+ }
70
+ options, err := decodeUnicornConsistentFunctionScopingOptions(ctx.Options)
71
+ if err != nil {
72
+ return
73
+ }
74
+
75
+ analysis := newUnicornConsistentFunctionScopingAnalysis(ctx, node)
76
+ walkDescendants(node, func(function *shimast.Node) {
77
+ if !unicornConsistentFunctionScopingIsCandidate(function) ||
78
+ (function.Kind == shimast.KindArrowFunction && !options.checkArrowFunctions) {
79
+ return
80
+ }
81
+ scopes, ok := unicornConsistentFunctionScopingParentScopes(function)
82
+ if !ok || scopes.containsKind(shimast.KindSourceFile) ||
83
+ unicornConsistentFunctionScopingInsideJestMockFactory(function) ||
84
+ unicornConsistentFunctionScopingIsReactHookScope(scopes.main) ||
85
+ unicornConsistentFunctionScopingIsIIFE(scopes.main) ||
86
+ analysis.capturesParentScope(function, scopes) {
87
+ return
88
+ }
89
+
90
+ start, end := unicornConsistentFunctionScopingReportRange(ctx.File, function)
91
+ if start < 0 || end < start {
92
+ ctx.Report(function, unicornConsistentFunctionScopingMessage(function))
93
+ return
94
+ }
95
+ ctx.ReportRange(start, end, unicornConsistentFunctionScopingMessage(function))
96
+ })
97
+ }
98
+
99
+ func decodeUnicornConsistentFunctionScopingOptions(
100
+ raw json.RawMessage,
101
+ ) (unicornConsistentFunctionScopingOptions, error) {
102
+ options := unicornConsistentFunctionScopingOptions{checkArrowFunctions: true}
103
+ trimmed := bytes.TrimSpace(raw)
104
+ if len(trimmed) == 0 {
105
+ return options, nil
106
+ }
107
+ if trimmed[0] != '{' {
108
+ return options, fmt.Errorf("options must be an object")
109
+ }
110
+
111
+ var fields map[string]json.RawMessage
112
+ if err := json.Unmarshal(trimmed, &fields); err != nil || fields == nil {
113
+ if err == nil {
114
+ err = fmt.Errorf("null object")
115
+ }
116
+ return options, fmt.Errorf("options must be an object: %w", err)
117
+ }
118
+ for name := range fields {
119
+ if name != "checkArrowFunctions" {
120
+ return options, fmt.Errorf("unknown option %q", name)
121
+ }
122
+ }
123
+ if value, exists := fields["checkArrowFunctions"]; exists {
124
+ if bytes.Equal(bytes.TrimSpace(value), []byte("null")) ||
125
+ json.Unmarshal(value, &options.checkArrowFunctions) != nil {
126
+ return options, fmt.Errorf("option %q must be a boolean", "checkArrowFunctions")
127
+ }
128
+ }
129
+ return options, nil
130
+ }
131
+
132
+ func newUnicornConsistentFunctionScopingAnalysis(
133
+ ctx *Context,
134
+ source *shimast.Node,
135
+ ) *unicornConsistentFunctionScopingAnalysis {
136
+ analysis := &unicornConsistentFunctionScopingAnalysis{
137
+ ctx: ctx,
138
+ referencesBySymbol: make(map[*shimast.Symbol][]*shimast.Node),
139
+ }
140
+ walkDescendants(source, func(node *shimast.Node) {
141
+ if !analysis.isReference(node) {
142
+ return
143
+ }
144
+ symbol := unicornConsistentFunctionScopingSymbol(ctx, node)
145
+ if symbol == nil {
146
+ if node.Kind == shimast.KindPrivateIdentifier {
147
+ analysis.unresolvedPrivate = append(analysis.unresolvedPrivate, node)
148
+ }
149
+ return
150
+ }
151
+ if unicornConsistentFunctionScopingIsImplicitArguments(node, symbol) ||
152
+ unicornConsistentFunctionScopingIsDeclarationName(node, symbol) {
153
+ return
154
+ }
155
+ analysis.referencesBySymbol[symbol] = append(analysis.referencesBySymbol[symbol], node)
156
+ })
157
+ return analysis
158
+ }
159
+
160
+ // The checker resolves every plain `arguments` reference to one shared
161
+ // implicit symbol without declarations, so the grouped reference map would
162
+ // fuse unrelated functions' `arguments` objects into a single binding and
163
+ // let a parent's own `arguments` pin a nested function. Each non-arrow
164
+ // function rebinds `arguments`; only the arrow lexical-environment check may
165
+ // treat the implicit symbol as a capture.
166
+ func unicornConsistentFunctionScopingIsImplicitArguments(
167
+ node *shimast.Node,
168
+ symbol *shimast.Symbol,
169
+ ) bool {
170
+ return node.Kind == shimast.KindIdentifier && identifierText(node) == "arguments" &&
171
+ symbol.ValueDeclaration == nil && len(symbol.Declarations) == 0
172
+ }
173
+
174
+ func (analysis *unicornConsistentFunctionScopingAnalysis) isReference(node *shimast.Node) bool {
175
+ if node == nil {
176
+ return false
177
+ }
178
+ switch node.Kind {
179
+ case shimast.KindPrivateIdentifier:
180
+ return !unicornConsistentFunctionScopingIsPrivateDeclaration(node)
181
+ case shimast.KindIdentifier:
182
+ default:
183
+ return false
184
+ }
185
+ if !unicornConsistentFunctionScopingIsSemanticReferenceIdentifier(node) {
186
+ return false
187
+ }
188
+ if parent := node.Parent; parent != nil && parent.Kind == shimast.KindBindingElement {
189
+ binding := parent.AsBindingElement()
190
+ if binding != nil && binding.PropertyName == node {
191
+ return false
192
+ }
193
+ }
194
+ return true
195
+ }
196
+
197
+ // TypeScript's scope manager treats type-space dependencies as references too:
198
+ // moving a function past a local type alias or namespace would still break the
199
+ // program even though the reference erases at runtime. The shared value helper
200
+ // already handles qualified-name property slots; only a simple TypeReference's
201
+ // TypeName needs to be admitted explicitly.
202
+ func unicornConsistentFunctionScopingIsSemanticReferenceIdentifier(node *shimast.Node) bool {
203
+ if node == nil || node.Parent == nil {
204
+ return node != nil
205
+ }
206
+ parent := node.Parent
207
+ switch parent.Kind {
208
+ case shimast.KindJsxOpeningElement,
209
+ shimast.KindJsxSelfClosingElement,
210
+ shimast.KindJsxClosingElement:
211
+ if parent.TagName() == node && shimscanner.IsIntrinsicJsxName(identifierText(node)) {
212
+ return false
213
+ }
214
+ case shimast.KindJsxAttribute,
215
+ shimast.KindNamedTupleMember,
216
+ shimast.KindImportAttribute,
217
+ shimast.KindNamespaceExport,
218
+ shimast.KindNamespaceExportDeclaration,
219
+ shimast.KindMetaProperty:
220
+ if parent.Name() == node {
221
+ return false
222
+ }
223
+ case shimast.KindJsxNamespacedName:
224
+ return false
225
+ }
226
+ if isValueReferenceIdentifier(node) {
227
+ return true
228
+ }
229
+ if parent.Kind != shimast.KindTypeReference {
230
+ return false
231
+ }
232
+ reference := parent.AsTypeReferenceNode()
233
+ return reference != nil && reference.TypeName == node
234
+ }
235
+
236
+ // Checker symbols are the complete declaration oracle. Comparing against
237
+ // their declaration-name nodes avoids maintaining a brittle syntax-kind list
238
+ // while keeping JSX component tag uses distinct from the declarations they
239
+ // resolve to.
240
+ func unicornConsistentFunctionScopingIsDeclarationName(
241
+ node *shimast.Node,
242
+ symbol *shimast.Symbol,
243
+ ) bool {
244
+ if node == nil || symbol == nil {
245
+ return false
246
+ }
247
+ if declaration := symbol.ValueDeclaration; declaration != nil && declaration.Name() == node {
248
+ return true
249
+ }
250
+ for _, declaration := range symbol.Declarations {
251
+ if declaration != nil && declaration.Name() == node {
252
+ return true
253
+ }
254
+ }
255
+ return false
256
+ }
257
+
258
+ func unicornConsistentFunctionScopingIsPrivateDeclaration(node *shimast.Node) bool {
259
+ if node == nil || node.Kind != shimast.KindPrivateIdentifier || node.Parent == nil {
260
+ return false
261
+ }
262
+ switch node.Parent.Kind {
263
+ case shimast.KindPropertyDeclaration,
264
+ shimast.KindMethodDeclaration,
265
+ shimast.KindGetAccessor,
266
+ shimast.KindSetAccessor:
267
+ return node.Parent.Name() == node
268
+ }
269
+ return false
270
+ }
271
+
272
+ func unicornConsistentFunctionScopingSymbol(ctx *Context, node *shimast.Node) *shimast.Symbol {
273
+ if ctx == nil || ctx.Checker == nil || node == nil {
274
+ return nil
275
+ }
276
+ if node.Kind == shimast.KindIdentifier {
277
+ return canonicalValueSymbol(ctx, node)
278
+ }
279
+ symbol := ctx.Checker.GetSymbolAtLocation(node)
280
+ if symbol == nil {
281
+ return nil
282
+ }
283
+ return ctx.Checker.GetMergedSymbol(symbol)
284
+ }
285
+
286
+ func (analysis *unicornConsistentFunctionScopingAnalysis) capturesParentScope(
287
+ function *shimast.Node,
288
+ scopes unicornConsistentFunctionScopingScopeSet,
289
+ ) bool {
290
+ if function.Kind == shimast.KindArrowFunction &&
291
+ analysis.arrowCapturesLexicalEnvironment(function) {
292
+ return true
293
+ }
294
+ for _, private := range analysis.unresolvedPrivate {
295
+ if noLoopFuncNodeContains(function, private) {
296
+ return true
297
+ }
298
+ }
299
+
300
+ for symbol, references := range analysis.referencesBySymbol {
301
+ usedInside := false
302
+ privateReference := false
303
+ for _, reference := range references {
304
+ if noLoopFuncNodeContains(function, reference) {
305
+ usedInside = true
306
+ privateReference = privateReference || reference.Kind == shimast.KindPrivateIdentifier
307
+ }
308
+ }
309
+ if !usedInside {
310
+ continue
311
+ }
312
+
313
+ // Upstream keeps a nested function beside direct uses of the same binding
314
+ // in the surrounding scope, and it applies this check before the
315
+ // recursive-name exemption, so an outer call of a self-recursive function
316
+ // pins it in place. Require an exact scope owner so a sibling closure
317
+ // does not masquerade as its parent.
318
+ for _, reference := range references {
319
+ if noLoopFuncNodeContains(function, reference) {
320
+ continue
321
+ }
322
+ if scopes.contains(unicornConsistentFunctionScopingScopeOwner(reference)) {
323
+ return true
324
+ }
325
+ }
326
+ if unicornConsistentFunctionScopingSymbolDeclaredInside(symbol, function) {
327
+ continue
328
+ }
329
+ if privateReference || unicornConsistentFunctionScopingSymbolDeclaredInScopes(symbol, scopes) {
330
+ return true
331
+ }
332
+ }
333
+ return false
334
+ }
335
+
336
+ func unicornConsistentFunctionScopingSymbolDeclaredInside(
337
+ symbol *shimast.Symbol,
338
+ function *shimast.Node,
339
+ ) bool {
340
+ if symbol == nil || function == nil {
341
+ return false
342
+ }
343
+ if symbol.ValueDeclaration != nil && noLoopFuncNodeContains(function, symbol.ValueDeclaration) {
344
+ return true
345
+ }
346
+ for _, declaration := range symbol.Declarations {
347
+ if declaration != nil && noLoopFuncNodeContains(function, declaration) {
348
+ return true
349
+ }
350
+ }
351
+ return false
352
+ }
353
+
354
+ func unicornConsistentFunctionScopingSymbolDeclaredInScopes(
355
+ symbol *shimast.Symbol,
356
+ scopes unicornConsistentFunctionScopingScopeSet,
357
+ ) bool {
358
+ if symbol == nil {
359
+ return false
360
+ }
361
+ if unicornConsistentFunctionScopingDeclarationPins(symbol.ValueDeclaration, scopes) {
362
+ return true
363
+ }
364
+ for _, declaration := range symbol.Declarations {
365
+ if unicornConsistentFunctionScopingDeclarationPins(declaration, scopes) {
366
+ return true
367
+ }
368
+ }
369
+ return false
370
+ }
371
+
372
+ // declarationPins mirrors two upstream checks at once: `resolved.scope` in
373
+ // the parent scopes (a binding declared beside the function) and
374
+ // `scopeManager.acquire(definition.node)` in the parent scopes (a function
375
+ // whose own scope IS a parent scope, i.e. the name of the function the
376
+ // candidate is nested in).
377
+ func unicornConsistentFunctionScopingDeclarationPins(
378
+ declaration *shimast.Node,
379
+ scopes unicornConsistentFunctionScopingScopeSet,
380
+ ) bool {
381
+ if declaration == nil {
382
+ return false
383
+ }
384
+ return scopes.contains(declaration) ||
385
+ scopes.contains(unicornConsistentFunctionScopingDeclarationScope(declaration))
386
+ }
387
+
388
+ func unicornConsistentFunctionScopingDeclarationScope(declaration *shimast.Node) *shimast.Node {
389
+ if declaration == nil {
390
+ return nil
391
+ }
392
+ switch declaration.Kind {
393
+ case shimast.KindFunctionDeclaration,
394
+ shimast.KindClassDeclaration,
395
+ shimast.KindTypeAliasDeclaration,
396
+ shimast.KindInterfaceDeclaration,
397
+ shimast.KindEnumDeclaration,
398
+ shimast.KindModuleDeclaration:
399
+ // These declarations open their own scope; the binding they introduce
400
+ // lives in the surrounding one.
401
+ return unicornConsistentFunctionScopingScopeOwner(declaration.Parent)
402
+ }
403
+ if root := unicornConsistentFunctionScopingRootVariableDeclaration(declaration); root != nil &&
404
+ shimast.GetCombinedNodeFlags(root)&shimast.NodeFlagsBlockScoped == 0 {
405
+ return unicornConsistentFunctionScopingVarScope(root)
406
+ }
407
+ return unicornConsistentFunctionScopingScopeOwner(declaration)
408
+ }
409
+
410
+ // rootVariableDeclaration resolves destructuring binding elements to the
411
+ // variable declaration that owns them; parameter destructuring yields nil so
412
+ // the caller falls back to the syntactic scope walk.
413
+ func unicornConsistentFunctionScopingRootVariableDeclaration(node *shimast.Node) *shimast.Node {
414
+ for node != nil && node.Kind == shimast.KindBindingElement {
415
+ if node.Parent == nil {
416
+ return nil
417
+ }
418
+ node = node.Parent.Parent
419
+ }
420
+ if node == nil || node.Kind != shimast.KindVariableDeclaration {
421
+ return nil
422
+ }
423
+ return node
424
+ }
425
+
426
+ // varScope returns the binding scope of a `var` declaration: the nearest
427
+ // enclosing function, class static block, namespace body, or source file.
428
+ // eslint-scope resolves such bindings to the hoisted home even when the
429
+ // declaration is written inside a nested block, so the pin comparison must
430
+ // not use the syntactic position.
431
+ func unicornConsistentFunctionScopingVarScope(declaration *shimast.Node) *shimast.Node {
432
+ for ancestor := declaration.Parent; ancestor != nil; ancestor = ancestor.Parent {
433
+ if isFunctionLikeKind(ancestor) {
434
+ return ancestor
435
+ }
436
+ switch ancestor.Kind {
437
+ case shimast.KindClassStaticBlockDeclaration,
438
+ shimast.KindModuleBlock,
439
+ shimast.KindSourceFile:
440
+ return ancestor
441
+ }
442
+ }
443
+ return nil
444
+ }
445
+
446
+ // arrowCapturesLexicalEnvironment ports upstream's isNodeContainsLexicalThis
447
+ // descent, extended to super and the implicit arguments object per the issue
448
+ // contract. The walk covers the arrow's parameters and body, descends through
449
+ // nested arrows (which share the lexical environment), stops at non-arrow
450
+ // functions and class bodies (which rebind this/arguments and scope super to
451
+ // their own methods), and still inspects the head positions that evaluate in
452
+ // the enclosing environment: computed member keys, heritage expressions, and
453
+ // (deviation, conservative) decorators.
454
+ func (analysis *unicornConsistentFunctionScopingAnalysis) arrowCapturesLexicalEnvironment(
455
+ arrow *shimast.Node,
456
+ ) bool {
457
+ walker := unicornConsistentFunctionScopingLexicalWalker{analysis: analysis}
458
+ walker.childCB = walker.visit
459
+ arrow.ForEachChild(walker.childCB)
460
+ return walker.captured
461
+ }
462
+
463
+ type unicornConsistentFunctionScopingLexicalWalker struct {
464
+ analysis *unicornConsistentFunctionScopingAnalysis
465
+ captured bool
466
+ childCB func(*shimast.Node) bool
467
+ }
468
+
469
+ // visit returns true (aborting ForEachChild) once a capture is found.
470
+ func (walker *unicornConsistentFunctionScopingLexicalWalker) visit(node *shimast.Node) bool {
471
+ if walker.captured {
472
+ return true
473
+ }
474
+ if node == nil {
475
+ return false
476
+ }
477
+ switch node.Kind {
478
+ case shimast.KindThisKeyword, shimast.KindSuperKeyword:
479
+ walker.captured = true
480
+ case shimast.KindIdentifier:
481
+ walker.captured = walker.analysis.isImplicitArgumentsReference(node)
482
+ case shimast.KindClassDeclaration, shimast.KindClassExpression:
483
+ walker.visitClassOuterPositions(node)
484
+ default:
485
+ if isFunctionLikeKind(node) && node.Kind != shimast.KindArrowFunction {
486
+ walker.visitFunctionHeadPositions(node)
487
+ break
488
+ }
489
+ node.ForEachChild(walker.childCB)
490
+ }
491
+ return walker.captured
492
+ }
493
+
494
+ // visitFunctionHeadPositions inspects the pieces of a non-arrow function that
495
+ // still evaluate in the enclosing lexical environment: a computed member key
496
+ // and any decorators (own or parameter), which run at class-definition time.
497
+ // Everything else inside the function rebinds this, super, and arguments.
498
+ func (walker *unicornConsistentFunctionScopingLexicalWalker) visitFunctionHeadPositions(
499
+ function *shimast.Node,
500
+ ) {
501
+ if name := function.Name(); name != nil && name.Kind == shimast.KindComputedPropertyName {
502
+ walker.visit(name)
503
+ }
504
+ walker.visitDecorators(function)
505
+ for _, parameter := range function.Parameters() {
506
+ walker.visitDecorators(parameter)
507
+ }
508
+ }
509
+
510
+ // visitClassOuterPositions mirrors the upstream class case: heritage
511
+ // expressions and computed member keys evaluate in the enclosing lexical
512
+ // environment, while member bodies, field initializers, and static blocks
513
+ // rebind it. Decorators join as a conservative deviation because legacy
514
+ // TypeScript decorators also evaluate beside the class definition.
515
+ func (walker *unicornConsistentFunctionScopingLexicalWalker) visitClassOuterPositions(
516
+ class *shimast.Node,
517
+ ) {
518
+ walker.visitDecorators(class)
519
+ if data := class.ClassLikeData(); data != nil && data.HeritageClauses != nil {
520
+ for _, clause := range data.HeritageClauses.Nodes {
521
+ if walker.visit(clause) {
522
+ return
523
+ }
524
+ }
525
+ }
526
+ for _, member := range class.Members() {
527
+ if walker.captured {
528
+ return
529
+ }
530
+ if name := member.Name(); name != nil && name.Kind == shimast.KindComputedPropertyName {
531
+ walker.visit(name)
532
+ }
533
+ walker.visitDecorators(member)
534
+ if isFunctionLikeKind(member) {
535
+ for _, parameter := range member.Parameters() {
536
+ walker.visitDecorators(parameter)
537
+ }
538
+ }
539
+ }
540
+ }
541
+
542
+ func (walker *unicornConsistentFunctionScopingLexicalWalker) visitDecorators(
543
+ node *shimast.Node,
544
+ ) {
545
+ modifiers := node.Modifiers()
546
+ if modifiers == nil {
547
+ return
548
+ }
549
+ for _, modifier := range modifiers.Nodes {
550
+ if walker.captured {
551
+ return
552
+ }
553
+ if modifier != nil && modifier.Kind == shimast.KindDecorator {
554
+ walker.visit(modifier)
555
+ }
556
+ }
557
+ }
558
+
559
+ // The checker resolves plain arguments references to one shared implicit
560
+ // symbol; inside the descent no non-arrow function boundary has been
561
+ // crossed, so such a reference reads the enclosing function's arguments
562
+ // object. User-declared bindings named arguments stay with the ordinary
563
+ // reference analysis instead.
564
+ func (analysis *unicornConsistentFunctionScopingAnalysis) isImplicitArgumentsReference(
565
+ node *shimast.Node,
566
+ ) bool {
567
+ if identifierText(node) != "arguments" || !analysis.isReference(node) {
568
+ return false
569
+ }
570
+ symbol := unicornConsistentFunctionScopingSymbol(analysis.ctx, node)
571
+ return symbol == nil || unicornConsistentFunctionScopingIsImplicitArguments(node, symbol)
572
+ }
573
+
574
+ func unicornConsistentFunctionScopingIsCandidate(node *shimast.Node) bool {
575
+ if node == nil {
576
+ return false
577
+ }
578
+ switch node.Kind {
579
+ case shimast.KindFunctionDeclaration, shimast.KindFunctionExpression, shimast.KindArrowFunction:
580
+ return true
581
+ }
582
+ return false
583
+ }
584
+
585
+ func unicornConsistentFunctionScopingParentScopes(
586
+ function *shimast.Node,
587
+ ) (unicornConsistentFunctionScopingScopeSet, bool) {
588
+ parent, block := unicornConsistentFunctionScopingDefinitionParent(function)
589
+ main := unicornConsistentFunctionScopingAcquiredScope(parent)
590
+ chain := unicornConsistentFunctionScopingLoopScopeChain(block)
591
+ // Upstream proceeds when either the acquired parent scope or the loop-body
592
+ // chain contributes a scope (a while/do body has no acquirable statement
593
+ // scope but its block still counts); with neither, the function stays
594
+ // unchecked.
595
+ if main == nil && len(chain) == 0 {
596
+ return unicornConsistentFunctionScopingScopeSet{}, false
597
+ }
598
+
599
+ scopes := unicornConsistentFunctionScopingScopeSet{
600
+ owners: make(map[*shimast.Node]struct{}),
601
+ main: main,
602
+ }
603
+ scopes.add(main)
604
+ for _, owner := range chain {
605
+ scopes.add(owner)
606
+ }
607
+ if block != nil {
608
+ // Deviation from upstream: the definition block always joins the pin
609
+ // set, not only through the loop-body chain (see the package comment).
610
+ scopes.add(unicornConsistentFunctionScopingScopeIdentity(block))
611
+ }
612
+ return scopes, true
613
+ }
614
+
615
+ func unicornConsistentFunctionScopingDefinitionParent(
616
+ function *shimast.Node,
617
+ ) (*shimast.Node, *shimast.Node) {
618
+ expression := unicornConsistentFunctionScopingOuterExpression(function)
619
+ parent := expression.Parent
620
+ if parent == nil {
621
+ return nil, nil
622
+ }
623
+
624
+ if function.Kind == shimast.KindArrowFunction && parent.Kind == shimast.KindArrowFunction {
625
+ directParent := parent
626
+ chain := parent
627
+ for {
628
+ outer := unicornConsistentFunctionScopingOuterExpression(chain)
629
+ next := outer.Parent
630
+ if next == nil || next.Kind != shimast.KindArrowFunction {
631
+ parent = directParent
632
+ if next != nil && next.Kind == shimast.KindReturnStatement {
633
+ parent = next.Parent
634
+ }
635
+ break
636
+ }
637
+ chain = next
638
+ }
639
+ } else if parent.Kind == shimast.KindVariableDeclaration {
640
+ declaration := parent.AsVariableDeclaration()
641
+ if declaration == nil || declaration.Initializer != expression {
642
+ return nil, nil
643
+ }
644
+ parent = parent.Parent
645
+ if parent != nil && parent.Kind == shimast.KindVariableDeclarationList {
646
+ parent = parent.Parent
647
+ }
648
+ if parent != nil && parent.Kind == shimast.KindVariableStatement {
649
+ parent = parent.Parent
650
+ }
651
+ } else if function.Kind == shimast.KindArrowFunction && parent.Kind == shimast.KindReturnStatement {
652
+ statement := parent.AsReturnStatement()
653
+ if statement == nil || statement.Expression != expression {
654
+ return nil, nil
655
+ }
656
+ parent = parent.Parent
657
+ }
658
+
659
+ var block *shimast.Node
660
+ if parent != nil && parent.Kind == shimast.KindBlock {
661
+ block = parent
662
+ parent = parent.Parent
663
+ }
664
+ return parent, block
665
+ }
666
+
667
+ func unicornConsistentFunctionScopingOuterExpression(node *shimast.Node) *shimast.Node {
668
+ current := node
669
+ for current != nil && current.Parent != nil {
670
+ parent := current.Parent
671
+ switch parent.Kind {
672
+ case shimast.KindParenthesizedExpression,
673
+ shimast.KindAsExpression,
674
+ shimast.KindSatisfiesExpression,
675
+ shimast.KindNonNullExpression,
676
+ shimast.KindTypeAssertionExpression:
677
+ if parent.Expression() != current {
678
+ return current
679
+ }
680
+ current = parent
681
+ default:
682
+ return current
683
+ }
684
+ }
685
+ return current
686
+ }
687
+
688
+ // unicornConsistentFunctionScopingAcquiredScope mirrors eslint-scope's
689
+ // `scopeManager.acquire`: it yields a scope only for nodes that create one.
690
+ // A function body block never carries its own block scope, so a definition
691
+ // sitting one standalone block below it has no acquirable parent scope and
692
+ // stays unchecked, and namespace bodies attach their scope to the module
693
+ // declaration, which the TypeScript scope manager likewise never surfaces as
694
+ // a checkable parent.
695
+ func unicornConsistentFunctionScopingAcquiredScope(node *shimast.Node) *shimast.Node {
696
+ if node == nil {
697
+ return nil
698
+ }
699
+ switch node.Kind {
700
+ case shimast.KindSourceFile,
701
+ shimast.KindCatchClause,
702
+ shimast.KindClassStaticBlockDeclaration,
703
+ shimast.KindSwitchStatement,
704
+ shimast.KindClassDeclaration,
705
+ shimast.KindClassExpression:
706
+ return node
707
+ case shimast.KindBlock:
708
+ if parent := node.Parent; parent != nil && isFunctionLikeKind(parent) && parent.Body() == node {
709
+ return nil
710
+ }
711
+ return node
712
+ case shimast.KindForStatement, shimast.KindForInStatement, shimast.KindForOfStatement:
713
+ if unicornConsistentFunctionScopingIsLexicalLoop(node) {
714
+ return node
715
+ }
716
+ return nil
717
+ }
718
+ if isFunctionLikeKind(node) {
719
+ return node
720
+ }
721
+ return nil
722
+ }
723
+
724
+ // isLexicalLoop reports whether a for/for-in/for-of statement declares
725
+ // let/const bindings in its head. eslint-scope creates a for scope only in
726
+ // that case; `var` heads and plain assignment targets resolve into the
727
+ // surrounding scope instead.
728
+ func unicornConsistentFunctionScopingIsLexicalLoop(loop *shimast.Node) bool {
729
+ if loop == nil {
730
+ return false
731
+ }
732
+ var initializer *shimast.Node
733
+ switch loop.Kind {
734
+ case shimast.KindForStatement:
735
+ if statement := loop.AsForStatement(); statement != nil {
736
+ initializer = statement.Initializer
737
+ }
738
+ case shimast.KindForInStatement, shimast.KindForOfStatement:
739
+ if statement := loop.AsForInOrOfStatement(); statement != nil {
740
+ initializer = statement.Initializer
741
+ }
742
+ }
743
+ return initializer != nil && initializer.Kind == shimast.KindVariableDeclarationList &&
744
+ initializer.Flags&shimast.NodeFlagsBlockScoped != 0
745
+ }
746
+
747
+ // unicornConsistentFunctionScopingScopeOwner returns the innermost node that
748
+ // owns the scope a reference reads from, the analogue of `reference.from` in
749
+ // eslint-scope. Beyond runtime scopes it stops at type-level scope holders
750
+ // (function/constructor types, signatures, conditional and mapped types, and
751
+ // type/interface/enum declarations): the TypeScript scope manager gives those
752
+ // their own scopes, so an annotation nested in them does not read from the
753
+ // surrounding function scope.
754
+ func unicornConsistentFunctionScopingScopeOwner(node *shimast.Node) *shimast.Node {
755
+ for ancestor := node; ancestor != nil; ancestor = ancestor.Parent {
756
+ if isFunctionLikeKind(ancestor) {
757
+ return ancestor
758
+ }
759
+ switch ancestor.Kind {
760
+ case shimast.KindBlock:
761
+ return unicornConsistentFunctionScopingScopeIdentity(ancestor)
762
+ case shimast.KindForStatement, shimast.KindForInStatement, shimast.KindForOfStatement:
763
+ if unicornConsistentFunctionScopingIsLexicalLoop(ancestor) {
764
+ return ancestor
765
+ }
766
+ case shimast.KindModuleBlock,
767
+ shimast.KindCatchClause,
768
+ shimast.KindClassStaticBlockDeclaration,
769
+ shimast.KindSwitchStatement,
770
+ shimast.KindClassDeclaration,
771
+ shimast.KindClassExpression,
772
+ shimast.KindPropertyDeclaration,
773
+ shimast.KindSourceFile,
774
+ shimast.KindFunctionType,
775
+ shimast.KindConstructorType,
776
+ shimast.KindCallSignature,
777
+ shimast.KindConstructSignature,
778
+ shimast.KindMethodSignature,
779
+ shimast.KindConditionalType,
780
+ shimast.KindMappedType,
781
+ shimast.KindTypeAliasDeclaration,
782
+ shimast.KindInterfaceDeclaration,
783
+ shimast.KindEnumDeclaration:
784
+ return ancestor
785
+ }
786
+ }
787
+ return nil
788
+ }
789
+
790
+ func unicornConsistentFunctionScopingScopeIdentity(scope *shimast.Node) *shimast.Node {
791
+ if scope == nil || scope.Kind != shimast.KindBlock || scope.Parent == nil ||
792
+ !isFunctionLikeKind(scope.Parent) || scope.Parent.Body() != scope {
793
+ return scope
794
+ }
795
+ return scope.Parent
796
+ }
797
+
798
+ func unicornConsistentFunctionScopingLoopScopeChain(block *shimast.Node) []*shimast.Node {
799
+ var scopes []*shimast.Node
800
+ for current := block; current != nil && current.Kind == shimast.KindBlock; current = current.Parent {
801
+ scopes = append(scopes, unicornConsistentFunctionScopingScopeIdentity(current))
802
+ parent := current.Parent
803
+ if parent == nil {
804
+ break
805
+ }
806
+ switch parent.Kind {
807
+ case shimast.KindDoStatement,
808
+ shimast.KindWhileStatement,
809
+ shimast.KindForStatement,
810
+ shimast.KindForInStatement,
811
+ shimast.KindForOfStatement:
812
+ if unicornConsistentFunctionScopingLoopBody(parent) == current {
813
+ // The loop statement carries a scope of its own only when its head
814
+ // declares let/const bindings; while/do statements and `var` heads
815
+ // contribute nothing beyond the body blocks.
816
+ if unicornConsistentFunctionScopingIsLexicalLoop(parent) {
817
+ scopes = append(scopes, parent)
818
+ }
819
+ return scopes
820
+ }
821
+ }
822
+ }
823
+ return nil
824
+ }
825
+
826
+ // unicornConsistentFunctionScopingLoopBody returns a loop statement's body.
827
+ // Loop statements store it in IterationStatementBase.Statement; Node.Body()
828
+ // covers only the function-like BodyData carriers and silently yields nil
829
+ // for loops, which would sever every loop-body scope chain.
830
+ func unicornConsistentFunctionScopingLoopBody(loop *shimast.Node) *shimast.Node {
831
+ switch loop.Kind {
832
+ case shimast.KindDoStatement:
833
+ if statement := loop.AsDoStatement(); statement != nil {
834
+ return statement.Statement
835
+ }
836
+ case shimast.KindWhileStatement:
837
+ if statement := loop.AsWhileStatement(); statement != nil {
838
+ return statement.Statement
839
+ }
840
+ case shimast.KindForStatement:
841
+ if statement := loop.AsForStatement(); statement != nil {
842
+ return statement.Statement
843
+ }
844
+ case shimast.KindForInStatement, shimast.KindForOfStatement:
845
+ if statement := loop.AsForInOrOfStatement(); statement != nil {
846
+ return statement.Statement
847
+ }
848
+ }
849
+ return nil
850
+ }
851
+
852
+ func (scopes *unicornConsistentFunctionScopingScopeSet) add(scope *shimast.Node) {
853
+ if scopes == nil || scope == nil {
854
+ return
855
+ }
856
+ scopes.owners[scope] = struct{}{}
857
+ }
858
+
859
+ func (scopes unicornConsistentFunctionScopingScopeSet) contains(scope *shimast.Node) bool {
860
+ if scope == nil {
861
+ return false
862
+ }
863
+ _, exists := scopes.owners[scope]
864
+ return exists
865
+ }
866
+
867
+ func (scopes unicornConsistentFunctionScopingScopeSet) containsKind(kind shimast.Kind) bool {
868
+ for scope := range scopes.owners {
869
+ if scope.Kind == kind {
870
+ return true
871
+ }
872
+ }
873
+ return false
874
+ }
875
+
876
+ var unicornConsistentFunctionScopingReactHooks = map[string]struct{}{
877
+ "useState": {}, "useEffect": {}, "useContext": {}, "useReducer": {},
878
+ "useCallback": {}, "useMemo": {}, "useRef": {}, "useImperativeHandle": {},
879
+ "useLayoutEffect": {}, "useDebugValue": {},
880
+ }
881
+
882
+ func unicornConsistentFunctionScopingIsReactHookScope(scope *shimast.Node) bool {
883
+ call := unicornConsistentFunctionScopingContainingCall(scope)
884
+ if call == nil {
885
+ return false
886
+ }
887
+ expression := call.AsCallExpression().Expression
888
+ if expression == nil {
889
+ return false
890
+ }
891
+ expression = stripParens(expression)
892
+ if expression.Kind == shimast.KindIdentifier {
893
+ _, exists := unicornConsistentFunctionScopingReactHooks[identifierText(expression)]
894
+ return exists
895
+ }
896
+ if expression.Kind != shimast.KindPropertyAccessExpression {
897
+ return false
898
+ }
899
+ access := expression.AsPropertyAccessExpression()
900
+ if access == nil || access.QuestionDotToken != nil ||
901
+ identifierText(stripParens(access.Expression)) != "React" {
902
+ return false
903
+ }
904
+ _, exists := unicornConsistentFunctionScopingReactHooks[identifierText(access.Name())]
905
+ return exists
906
+ }
907
+
908
+ func unicornConsistentFunctionScopingContainingCall(function *shimast.Node) *shimast.Node {
909
+ if !unicornConsistentFunctionScopingIsCandidate(function) {
910
+ return nil
911
+ }
912
+ expression := unicornConsistentFunctionScopingOuterExpression(function)
913
+ if expression == nil || expression.Parent == nil || expression.Parent.Kind != shimast.KindCallExpression {
914
+ return nil
915
+ }
916
+ return expression.Parent
917
+ }
918
+
919
+ func unicornConsistentFunctionScopingIsIIFE(function *shimast.Node) bool {
920
+ call := unicornConsistentFunctionScopingContainingCall(function)
921
+ if call == nil {
922
+ return false
923
+ }
924
+ expression := call.AsCallExpression()
925
+ return expression != nil && stripParens(expression.Expression) == function
926
+ }
927
+
928
+ func unicornConsistentFunctionScopingInsideJestMockFactory(function *shimast.Node) bool {
929
+ for ancestor := function; ancestor != nil; ancestor = ancestor.Parent {
930
+ if !unicornConsistentFunctionScopingIsCandidate(ancestor) {
931
+ continue
932
+ }
933
+ call := unicornConsistentFunctionScopingContainingCall(ancestor)
934
+ if call == nil {
935
+ continue
936
+ }
937
+ expression := call.AsCallExpression()
938
+ if expression == nil || expression.Arguments == nil || len(expression.Arguments.Nodes) < 2 ||
939
+ stripParens(expression.Arguments.Nodes[1]) != ancestor ||
940
+ !unicornConsistentFunctionScopingMatchesPath(expression.Expression, "jest", "mock") {
941
+ continue
942
+ }
943
+ return true
944
+ }
945
+ return false
946
+ }
947
+
948
+ func unicornConsistentFunctionScopingMatchesPath(
949
+ expression *shimast.Node,
950
+ object string,
951
+ property string,
952
+ ) bool {
953
+ expression = stripParens(expression)
954
+ if expression == nil || expression.Kind != shimast.KindPropertyAccessExpression {
955
+ return false
956
+ }
957
+ access := expression.AsPropertyAccessExpression()
958
+ return access != nil && access.QuestionDotToken == nil &&
959
+ identifierText(stripParens(access.Expression)) == object &&
960
+ identifierText(access.Name()) == property
961
+ }
962
+
963
+ func unicornConsistentFunctionScopingMessage(function *shimast.Node) string {
964
+ kind := "function"
965
+ async := function.ModifierFlags()&shimast.ModifierFlagsAsync != 0
966
+ generator := false
967
+ switch function.Kind {
968
+ case shimast.KindArrowFunction:
969
+ kind = "arrow function"
970
+ case shimast.KindFunctionDeclaration:
971
+ declaration := function.AsFunctionDeclaration()
972
+ generator = declaration != nil && declaration.AsteriskToken != nil
973
+ case shimast.KindFunctionExpression:
974
+ expression := function.AsFunctionExpression()
975
+ generator = expression != nil && expression.AsteriskToken != nil
976
+ }
977
+ switch {
978
+ case async && generator:
979
+ kind = "async generator function"
980
+ case generator:
981
+ kind = "generator function"
982
+ case async:
983
+ kind = "async " + kind
984
+ }
985
+ if name := unicornConsistentFunctionScopingFunctionName(function); name != "" {
986
+ kind += " '" + name + "'"
987
+ }
988
+ return "Move " + kind + " to the outer scope."
989
+ }
990
+
991
+ func unicornConsistentFunctionScopingFunctionName(function *shimast.Node) string {
992
+ if name := identifierText(function.Name()); name != "" {
993
+ return name
994
+ }
995
+ expression := unicornConsistentFunctionScopingOuterExpression(function)
996
+ if expression == nil || expression.Parent == nil || expression.Parent.Kind != shimast.KindVariableDeclaration {
997
+ return ""
998
+ }
999
+ declaration := expression.Parent.AsVariableDeclaration()
1000
+ if declaration == nil || declaration.Initializer != expression {
1001
+ return ""
1002
+ }
1003
+ return identifierText(declaration.Name())
1004
+ }
1005
+
1006
+ func unicornConsistentFunctionScopingReportRange(
1007
+ file *shimast.SourceFile,
1008
+ function *shimast.Node,
1009
+ ) (int, int) {
1010
+ if file == nil || function == nil {
1011
+ return -1, -1
1012
+ }
1013
+ source := file.Text()
1014
+ if function.Kind == shimast.KindArrowFunction {
1015
+ arrow := function.AsArrowFunction()
1016
+ if arrow == nil || arrow.EqualsGreaterThanToken == nil {
1017
+ return -1, -1
1018
+ }
1019
+ // Token positions include leading trivia; the finding must cover the
1020
+ // bare `=>` the way upstream's getFunctionHeadLocation does.
1021
+ start := shimscanner.SkipTrivia(source, arrow.EqualsGreaterThanToken.Pos())
1022
+ end := arrow.EqualsGreaterThanToken.End()
1023
+ if start < 0 || end < start || end > len(source) {
1024
+ return -1, -1
1025
+ }
1026
+ return start, end
1027
+ }
1028
+
1029
+ start := shimscanner.SkipTrivia(source, function.Pos())
1030
+ if start < 0 || start >= len(source) {
1031
+ return -1, -1
1032
+ }
1033
+ // Upstream's getFunctionHeadLocation ends the head at the opening paren of
1034
+ // the parameters: the token after the name for named functions, the first
1035
+ // paren in the node otherwise.
1036
+ scanFrom := start
1037
+ if name := function.Name(); name != nil && name.End() > start && name.End() <= len(source) {
1038
+ scanFrom = name.End()
1039
+ }
1040
+ if scanFrom >= function.End() || function.End() > len(source) {
1041
+ return -1, -1
1042
+ }
1043
+ scanner := shimscanner.NewScanner()
1044
+ scanner.SetText(source[scanFrom:function.End()])
1045
+ scanner.SetSkipTrivia(true)
1046
+ for {
1047
+ switch scanner.Scan() {
1048
+ case shimast.KindOpenParenToken:
1049
+ return start, scanFrom + scanner.TokenStart()
1050
+ case shimast.KindEndOfFile:
1051
+ return -1, -1
1052
+ }
1053
+ }
21
1054
  }
22
1055
 
23
1056
  func init() {