@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,33 @@
1
- // unicorn/consistent-existence-index-check: the same index-presence
2
- // test has two equivalent shapes `arr.indexOf(x) === -1` (and the
3
- // `!== -1` complement) versus `arr.indexOf(x) < 0` / `>= 0`. Either
4
- // works at runtime, but a project should pick one. The canonical form
5
- // pinned here is the explicit `=== -1` / `!== -1`; the magnitude
6
- // comparison reads as math rather than as a presence check.
1
+ // unicorn/consistent-existence-index-check: `indexOf`, `lastIndexOf`,
2
+ // `findIndex`, and `findLastIndex` answer "not found" with the sentinel `-1`,
3
+ // so an existence test reads clearest when it compares against that sentinel.
4
+ // Upstream targets one shape only — a `const` bound to such a call — and
5
+ // rewrites the magnitude comparisons on that binding: `index < 0` becomes
6
+ // `index === -1`, while `index >= 0` and `index > -1` become `index !== -1`.
7
+ // A bare `arr.indexOf(x) < 0` binds no index variable and is not reported,
8
+ // exactly as upstream leaves it alone.
7
9
  //
8
- // AST-only: visit each `BinaryExpression`. Fire when the operator is
9
- // `<` or `>=`, the LEFT operand is a `CallExpression(PropertyAccess(_,
10
- // methodName))` whose method name is one of the index-returning Array
11
- // prototype methods, AND the right operand is a `0` literal. Only the
12
- // canonical orientation (`indexOf(x) < 0` / `indexOf(x) >= 0`) is
13
- // flagged; the swapped form (`0 < indexOf(x)`) has different
14
- // semantics ("found at index >= 1") and is intentionally not
15
- // recognized. The `=== -1` / `!== -1` orientation is the desired form,
16
- // so it does not fire.
10
+ // Checker-backed, because the rule is scope analysis rather than syntax.
11
+ // Upstream walks the declared variable's eslint-scope references and inspects
12
+ // the comparisons among them; the port asks the same question from the other
13
+ // end, resolving the compared identifier to its checker binding and inspecting
14
+ // that binding's declaration. Both directions enumerate the same
15
+ // (const-index-declaration, comparison) pairs, and the binding identity is what
16
+ // keeps a shadowing `let index`, a same-named binding in a sibling scope, and a
17
+ // parameter named `index` out of the report set.
18
+ //
19
+ // The reversed orientations (`0 > index`, `-1 < index`) are references whose
20
+ // binary-expression parent holds them on the right, which upstream skips, so
21
+ // they are not recognized here either.
17
22
  // https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/consistent-existence-index-check.md
18
23
  package linthost
19
24
 
20
- import shimast "github.com/microsoft/typescript-go/shim/ast"
25
+ import (
26
+ "fmt"
27
+
28
+ shimast "github.com/microsoft/typescript-go/shim/ast"
29
+ shimscanner "github.com/microsoft/typescript-go/shim/scanner"
30
+ )
21
31
 
22
32
  var unicornConsistentExistenceIndexCheckMethods = map[string]struct{}{
23
33
  "indexOf": {},
@@ -26,6 +36,35 @@ var unicornConsistentExistenceIndexCheckMethods = map[string]struct{}{
26
36
  "findLastIndex": {},
27
37
  }
28
38
 
39
+ // unicornConsistentExistenceIndexCheckReplacement is one rewrite of upstream's
40
+ // getReplacement: the operator and right operand to write, paired with the ones
41
+ // being replaced. The originals are message data, and the original value also
42
+ // decides whether the right operand is edited at all — `index > -1` already
43
+ // spells `-1`, so only its operator moves.
44
+ type unicornConsistentExistenceIndexCheckReplacement struct {
45
+ operator string
46
+ value string
47
+ originalOperator string
48
+ originalValue string
49
+ }
50
+
51
+ // message renders upstream's message template. `===` is the non-existence
52
+ // check ("the element is absent"); `!==` is the existence check.
53
+ func (r unicornConsistentExistenceIndexCheckReplacement) message() string {
54
+ existence := "existence"
55
+ if r.operator == "===" {
56
+ existence = "non-existence"
57
+ }
58
+ return fmt.Sprintf(
59
+ "Prefer `%s %s` over `%s %s` to check %s.",
60
+ r.operator,
61
+ r.value,
62
+ r.originalOperator,
63
+ r.originalValue,
64
+ existence,
65
+ )
66
+ }
67
+
29
68
  type unicornConsistentExistenceIndexCheck struct{}
30
69
 
31
70
  func (unicornConsistentExistenceIndexCheck) Name() string {
@@ -34,35 +73,171 @@ func (unicornConsistentExistenceIndexCheck) Name() string {
34
73
  func (unicornConsistentExistenceIndexCheck) Visits() []shimast.Kind {
35
74
  return []shimast.Kind{shimast.KindBinaryExpression}
36
75
  }
76
+
77
+ // The checker supplies binding identity: resolving the compared identifier to
78
+ // the `const` it was declared by is exactly the lookup eslint-scope performs
79
+ // upstream, and a name-only scan would collude shadowed and unrelated indexes.
80
+ func (unicornConsistentExistenceIndexCheck) NeedsTypeChecker() bool { return true }
81
+
37
82
  func (unicornConsistentExistenceIndexCheck) Check(ctx *Context, node *shimast.Node) {
83
+ if ctx == nil || ctx.File == nil || ctx.Checker == nil || node == nil {
84
+ return
85
+ }
38
86
  bin := node.AsBinaryExpression()
39
87
  if bin == nil || bin.OperatorToken == nil || bin.Left == nil || bin.Right == nil {
40
88
  return
41
89
  }
42
- switch bin.OperatorToken.Kind {
43
- case shimast.KindLessThanToken, shimast.KindGreaterThanEqualsToken:
44
- default:
90
+ // Parentheses are the only wrapper ESTree elides, so `(index) < 0` is a
91
+ // comparison of the binding while `(index as number) < 0` is not.
92
+ left := stripParens(bin.Left)
93
+ if left == nil || left.Kind != shimast.KindIdentifier {
45
94
  return
46
95
  }
47
- left := stripParens(bin.Left)
48
96
  right := stripParens(bin.Right)
49
- if unicornConsistentExistenceIndexCheckMatch(left, right) {
50
- ctx.Report(node, "Use a consistent existence-check form for `indexOf` / `findIndex` — prefer `=== -1` / `!== -1` over magnitude comparisons.")
97
+ replacement, ok := unicornConsistentExistenceIndexCheckReplacementFor(
98
+ bin.OperatorToken.Kind,
99
+ right,
100
+ )
101
+ if !ok || !unicornConsistentExistenceIndexCheckIsIndexBinding(ctx, left) {
102
+ return
103
+ }
104
+
105
+ text := ctx.File.Text()
106
+ operatorStart := shimscanner.SkipTrivia(text, bin.OperatorToken.Pos())
107
+ edits := []TextEdit{{
108
+ Pos: operatorStart,
109
+ End: bin.OperatorToken.End(),
110
+ Text: replacement.operator,
111
+ }}
112
+ if replacement.value != replacement.originalValue {
113
+ edits = append(edits, TextEdit{
114
+ Pos: shimscanner.SkipTrivia(text, right.Pos()),
115
+ End: right.End(),
116
+ Text: replacement.value,
117
+ })
51
118
  }
119
+ // Upstream narrows the diagnostic to `<operator> <right>` rather than the
120
+ // whole comparison, so the caret lands on the part that has to change.
121
+ ctx.ReportRangeFix(operatorStart, right.End(), replacement.message(), edits...)
52
122
  }
53
123
 
54
- func unicornConsistentExistenceIndexCheckMatch(callSide, literalSide *shimast.Node) bool {
55
- if callSide == nil || literalSide == nil {
124
+ // unicornConsistentExistenceIndexCheckReplacementFor ports upstream's
125
+ // getReplacement. Only these three operator/operand pairs are magnitude
126
+ // spellings of the sentinel test; `index <= -1` and the equality forms are not
127
+ // recognized upstream and stay untouched here.
128
+ func unicornConsistentExistenceIndexCheckReplacementFor(
129
+ operator shimast.Kind,
130
+ right *shimast.Node,
131
+ ) (unicornConsistentExistenceIndexCheckReplacement, bool) {
132
+ switch operator {
133
+ case shimast.KindLessThanToken:
134
+ if unicornConsistentExistenceIndexCheckIsZero(right) {
135
+ return unicornConsistentExistenceIndexCheckReplacement{
136
+ operator: "===",
137
+ value: "-1",
138
+ originalOperator: "<",
139
+ originalValue: "0",
140
+ }, true
141
+ }
142
+ case shimast.KindGreaterThanToken:
143
+ if unicornConsistentExistenceIndexCheckIsNegativeOne(right) {
144
+ return unicornConsistentExistenceIndexCheckReplacement{
145
+ operator: "!==",
146
+ value: "-1",
147
+ originalOperator: ">",
148
+ originalValue: "-1",
149
+ }, true
150
+ }
151
+ case shimast.KindGreaterThanEqualsToken:
152
+ if unicornConsistentExistenceIndexCheckIsZero(right) {
153
+ return unicornConsistentExistenceIndexCheckReplacement{
154
+ operator: "!==",
155
+ value: "-1",
156
+ originalOperator: ">=",
157
+ originalValue: "0",
158
+ }, true
159
+ }
160
+ }
161
+ return unicornConsistentExistenceIndexCheckReplacement{}, false
162
+ }
163
+
164
+ // unicornConsistentExistenceIndexCheckIsZero mirrors upstream's numeric-value
165
+ // test. TypeScript normalizes a numeric literal's text to the JavaScript
166
+ // number-to-string form, so `0`, `0.0`, `0x0`, and `0e0` all spell "0" here,
167
+ // while `0n` is a BigInt literal and `-0` is a unary expression — neither is a
168
+ // numeric-literal zero.
169
+ func unicornConsistentExistenceIndexCheckIsZero(node *shimast.Node) bool {
170
+ return node != nil && node.Kind == shimast.KindNumericLiteral &&
171
+ numericLiteralText(node) == "0"
172
+ }
173
+
174
+ // unicornConsistentExistenceIndexCheckIsNegativeOne mirrors upstream's
175
+ // isNegativeOne: unary minus applied to the numeric literal one.
176
+ func unicornConsistentExistenceIndexCheckIsNegativeOne(node *shimast.Node) bool {
177
+ if node == nil || node.Kind != shimast.KindPrefixUnaryExpression {
178
+ return false
179
+ }
180
+ unary := node.AsPrefixUnaryExpression()
181
+ if unary == nil || unary.Operator != shimast.KindMinusToken {
182
+ return false
183
+ }
184
+ operand := stripParens(unary.Operand)
185
+ return operand != nil && operand.Kind == shimast.KindNumericLiteral &&
186
+ numericLiteralText(operand) == "1"
187
+ }
188
+
189
+ // unicornConsistentExistenceIndexCheckIsIndexBinding reports whether the
190
+ // identifier resolves to a `const` declared from an index-returning call — the
191
+ // checker equivalent of upstream reaching a comparison through the declared
192
+ // variable's references. `let`, `var`, `using`, destructured bindings,
193
+ // parameters, imports, and consts initialized from anything else all fail here,
194
+ // so their comparisons are left alone.
195
+ func unicornConsistentExistenceIndexCheckIsIndexBinding(
196
+ ctx *Context,
197
+ identifier *shimast.Node,
198
+ ) bool {
199
+ symbol := canonicalValueSymbol(ctx, identifier)
200
+ if symbol == nil || symbol.ValueDeclaration == nil {
56
201
  return false
57
202
  }
58
- if literalSide.Kind != shimast.KindNumericLiteral ||
59
- numericLiteralText(literalSide) != "0" {
203
+ declaration := symbol.ValueDeclaration
204
+ if declaration.Kind != shimast.KindVariableDeclaration ||
205
+ declaration.Parent == nil ||
206
+ declaration.Parent.Kind != shimast.KindVariableDeclarationList {
60
207
  return false
61
208
  }
62
- if callSide.Kind != shimast.KindCallExpression {
209
+ // `await using` carries the const flag too, so the block-scoped bits are
210
+ // compared as a whole against `const` alone.
211
+ if shimast.GetCombinedNodeFlags(declaration)&shimast.NodeFlagsBlockScoped !=
212
+ shimast.NodeFlagsConst {
63
213
  return false
64
214
  }
65
- call := callSide.AsCallExpression()
215
+ variable := declaration.AsVariableDeclaration()
216
+ if variable == nil || variable.Initializer == nil {
217
+ return false
218
+ }
219
+ if name := variable.Name(); name == nil || name.Kind != shimast.KindIdentifier {
220
+ return false
221
+ }
222
+ return unicornConsistentExistenceIndexCheckIsIndexCall(stripParens(variable.Initializer))
223
+ }
224
+
225
+ // unicornConsistentExistenceIndexCheckIsIndexCall ports upstream's
226
+ // `isMethodCall(init, {methods: [...]})`: a plain call of a non-computed member
227
+ // whose property names one of the index-returning methods. The receiver is
228
+ // unconstrained — `_.indexOf(…)` counts just as much as `array.indexOf(…)`.
229
+ func unicornConsistentExistenceIndexCheckIsIndexCall(node *shimast.Node) bool {
230
+ if node == nil || node.Kind != shimast.KindCallExpression {
231
+ return false
232
+ }
233
+ // An optional chain (`foo?.indexOf(bar)`, `foo.indexOf?.(bar)`, or any `?.`
234
+ // earlier in the same chain) is a ChainExpression upstream, which is not a
235
+ // CallExpression and therefore never matches. Parenthesizing breaks the
236
+ // chain on both sides, so `(foo?.bar).indexOf(baz)` still counts.
237
+ if node.Flags&shimast.NodeFlagsOptionalChain != 0 {
238
+ return false
239
+ }
240
+ call := node.AsCallExpression()
66
241
  if call == nil || call.Expression == nil ||
67
242
  call.Expression.Kind != shimast.KindPropertyAccessExpression {
68
243
  return false
@@ -71,7 +246,11 @@ func unicornConsistentExistenceIndexCheckMatch(callSide, literalSide *shimast.No
71
246
  if access == nil {
72
247
  return false
73
248
  }
74
- _, ok := unicornConsistentExistenceIndexCheckMethods[identifierText(access.Name())]
249
+ name := access.Name()
250
+ if name == nil || name.Kind != shimast.KindIdentifier {
251
+ return false
252
+ }
253
+ _, ok := unicornConsistentExistenceIndexCheckMethods[identifierText(name)]
75
254
  return ok
76
255
  }
77
256