@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
@@ -0,0 +1,1013 @@
1
+ // noFallthrough: `switch` cases whose end is reachable and that spill into
2
+ // the next `case` / `default` label without an intentional-fallthrough
3
+ // comment.
4
+ //
5
+ // The rule mirrors ESLint's `no-fallthrough` semantics:
6
+ //
7
+ // - Reachability is decided by a structured statement completion
8
+ // analysis ("can this statement list complete normally?") that
9
+ // composes blocks, `if/else`, loops, `switch`, labeled statements,
10
+ // and `try/catch/finally`. Nested function bodies, class field
11
+ // initializers, and static blocks keep separate code paths, so their
12
+ // completions do not terminate the enclosing case; immediately evaluated
13
+ // class heritage and computed names remain visible. The analysis is
14
+ // deliberately implemented here
15
+ // instead of reusing the checker's flow nodes: binder flow graphs
16
+ // require a bound Program plus checker-side reachability walks,
17
+ // while this rule must stay AST-only so the engine can keep running
18
+ // it in the parallel no-checker lane.
19
+ // - An intentional fallthrough is marked by the last comment before
20
+ // the next case label (or, when the case body is exactly one block
21
+ // statement, the last comment before that block's closing brace)
22
+ // matching the fallthrough comment pattern. The default pattern is
23
+ // ESLint's `/falls?\s?through/i`. Directive comments
24
+ // (`eslint-disable-next-line …`, `lint-disable …`, `globals …`) are
25
+ // never markers even when their text happens to match.
26
+ // - `commentPattern`, `allowEmptyCase`, and
27
+ // `reportUnusedFallthroughComment` options match the upstream
28
+ // schema. A custom `commentPattern` replaces the default marker
29
+ // pattern entirely, exactly like ESLint.
30
+ //
31
+ // https://eslint.org/docs/latest/rules/no-fallthrough
32
+ package linthost
33
+
34
+ import (
35
+ "regexp"
36
+ "strconv"
37
+ "strings"
38
+
39
+ shimast "github.com/microsoft/typescript-go/shim/ast"
40
+ shimscanner "github.com/microsoft/typescript-go/shim/scanner"
41
+ )
42
+
43
+ // noFallthroughDefaultCommentPattern is ESLint's DEFAULT_FALLTHROUGH_COMMENT
44
+ // (`/falls?\s?through/iu`) translated to RE2. It accepts `fall through`,
45
+ // `falls through`, `fallthrough`, `fallsthrough`, any letter case.
46
+ var noFallthroughDefaultCommentPattern = regexp.MustCompile(`(?i)falls?\s?through`)
47
+
48
+ // noFallthroughDirectivePattern ports ESLint's shared `directivesPattern`
49
+ // and extends it with the `lint-*` directive family this host also
50
+ // recognizes (see directives.go). A directive comment is configuration,
51
+ // not documentation, so it never counts as an intentional-fallthrough
52
+ // marker even when its text matches the comment pattern (e.g.
53
+ // `// eslint-enable no-fallthrough`).
54
+ var noFallthroughDirectivePattern = regexp.MustCompile(
55
+ `^(?:eslint(?:-env|-enable|-disable(?:(?:-next)?-line)?)?|lint-(?:enable|disable(?:(?:-next)?-line)?)|exported|globals?)(?:\s|$)`,
56
+ )
57
+
58
+ // noFallthroughOptions mirrors the upstream rule's options schema.
59
+ type noFallthroughOptions struct {
60
+ // CommentPattern replaces the default fallthrough marker pattern when
61
+ // non-empty. Compiled as an RE2 expression against the comment's inner
62
+ // text; an invalid pattern falls back to the default marker pattern so
63
+ // a config typo cannot silently disable marker recognition.
64
+ CommentPattern string `json:"commentPattern"`
65
+ // AllowEmptyCase suppresses the blank-line heuristic for empty cases:
66
+ // by default an empty case separated from the next label by at least
67
+ // one blank line is treated as an accidental fallthrough.
68
+ AllowEmptyCase bool `json:"allowEmptyCase"`
69
+ // ReportUnusedFallthroughComment reports fallthrough marker comments on
70
+ // cases that cannot actually fall through (e.g. after a `break`).
71
+ ReportUnusedFallthroughComment bool `json:"reportUnusedFallthroughComment"`
72
+ }
73
+
74
+ type noFallthrough struct{ optionsRule }
75
+
76
+ func (noFallthrough) Name() string { return "no-fallthrough" }
77
+ func (noFallthrough) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindSwitchStatement} }
78
+ func (noFallthrough) Check(ctx *Context, node *shimast.Node) {
79
+ sw := node.AsSwitchStatement()
80
+ if sw == nil || sw.CaseBlock == nil || ctx.File == nil {
81
+ return
82
+ }
83
+ block := sw.CaseBlock.AsCaseBlock()
84
+ if block == nil || block.Clauses == nil {
85
+ return
86
+ }
87
+ var opts noFallthroughOptions
88
+ ctx.DecodeOptions(&opts)
89
+ pattern := noFallthroughDefaultCommentPattern
90
+ if opts.CommentPattern != "" {
91
+ if custom, err := compileUserPattern(opts.CommentPattern); err == nil {
92
+ pattern = custom
93
+ }
94
+ }
95
+ clauses := block.Clauses.Nodes
96
+ for i := 0; i+1 < len(clauses); i++ {
97
+ clause := clauses[i].AsCaseOrDefaultClause()
98
+ next := clauses[i+1]
99
+ if clause == nil || next == nil {
100
+ continue
101
+ }
102
+ stmts := clauseStatements(clause)
103
+ // ESLint calls this `isSwitchExitReachable`: the code path segment at
104
+ // the end of the case's consequent is reachable, i.e. the statement
105
+ // list can complete normally.
106
+ endReachable := statementListCompletion(stmts).normal
107
+ // The last case never falls through (there is no next label), which
108
+ // the `i+1 < len(clauses)` loop bound already guarantees.
109
+ fallsThrough := endReachable &&
110
+ (len(stmts) > 0 ||
111
+ (!opts.AllowEmptyCase && noFallthroughHasBlankLinesBetween(ctx.File, clauses[i], next)))
112
+ commentPos, commentEnd, hasMarker := noFallthroughMarkerComment(ctx.File, clause, next, pattern)
113
+ if fallsThrough && !hasMarker {
114
+ label := "case"
115
+ if next.Kind == shimast.KindDefaultClause {
116
+ label = "default"
117
+ }
118
+ ctx.Report(next, "Expected a 'break' statement before '"+label+"'.")
119
+ } else if opts.ReportUnusedFallthroughComment && !endReachable && hasMarker {
120
+ ctx.ReportRange(commentPos, commentEnd, "Found a comment that would permit fallthrough, but case cannot fall through.")
121
+ }
122
+ }
123
+ }
124
+
125
+ // clauseStatements returns a clause's statement list, tolerating nil
126
+ // intermediates on malformed parses.
127
+ func clauseStatements(clause *shimast.CaseOrDefaultClause) []*shimast.Node {
128
+ if clause == nil || clause.Statements == nil {
129
+ return nil
130
+ }
131
+ return clause.Statements.Nodes
132
+ }
133
+
134
+ // noFallthroughHasBlankLinesBetween reports whether at least one blank
135
+ // line separates the end of `clause` from the first token of `next`
136
+ // (comments between them are skipped, exactly like ESLint's
137
+ // `getTokenAfter`). ESLint treats a blank-line gap after an empty case as
138
+ // an accidental fallthrough unless `allowEmptyCase` is set.
139
+ func noFallthroughHasBlankLinesBetween(file *shimast.SourceFile, clause, next *shimast.Node) bool {
140
+ text := file.Text()
141
+ nextToken := shimscanner.SkipTrivia(text, next.Pos())
142
+ endLine := shimscanner.GetECMALineOfPosition(file, clause.End())
143
+ nextLine := shimscanner.GetECMALineOfPosition(file, nextToken)
144
+ return nextLine > endLine+1
145
+ }
146
+
147
+ // noFallthroughMarkerComment locates ESLint's intentional-fallthrough
148
+ // marker for the transition `clause` → `next` and returns its source
149
+ // range. Two positions are eligible, checked in upstream order:
150
+ //
151
+ // 1. When the case body is exactly one block statement, the last
152
+ // comment before the block's closing brace.
153
+ // 2. The last comment between the end of `clause` and the `case` /
154
+ // `default` keyword of `next`.
155
+ //
156
+ // Only the LAST comment of each region counts (`getCommentsBefore(...).pop()`
157
+ // upstream), so an unrelated comment after the marker invalidates it.
158
+ func noFallthroughMarkerComment(
159
+ file *shimast.SourceFile,
160
+ clause *shimast.CaseOrDefaultClause,
161
+ next *shimast.Node,
162
+ pattern *regexp.Regexp,
163
+ ) (pos, end int, ok bool) {
164
+ text := file.Text()
165
+ stmts := clauseStatements(clause)
166
+ if len(stmts) == 1 && stmts[0] != nil && stmts[0].Kind == shimast.KindBlock {
167
+ if block := stmts[0].AsBlock(); block != nil {
168
+ from := blockInteriorStart(text, block)
169
+ closeBrace := block.End() - 1 // exclusive End covers the `}` token
170
+ if from >= 0 && closeBrace > from {
171
+ if p, e, found := lastCommentInTrivia(text, from, closeBrace); found &&
172
+ isNoFallthroughMarker(text[p:e], pattern) {
173
+ return p, e, true
174
+ }
175
+ }
176
+ }
177
+ }
178
+ nextToken := shimscanner.SkipTrivia(text, next.Pos())
179
+ if p, e, found := lastCommentInTrivia(text, clause.End(), nextToken); found &&
180
+ isNoFallthroughMarker(text[p:e], pattern) {
181
+ return p, e, true
182
+ }
183
+ return 0, 0, false
184
+ }
185
+
186
+ // blockInteriorStart returns the offset just past a block's last interior
187
+ // token: after the final statement when the block has one, otherwise
188
+ // after the opening `{`. The region from there to the closing brace is
189
+ // trivia-only. Returns -1 when the block shape is malformed.
190
+ func blockInteriorStart(text string, block *shimast.Block) int {
191
+ if block.Statements != nil {
192
+ if nodes := block.Statements.Nodes; len(nodes) > 0 {
193
+ last := nodes[len(nodes)-1]
194
+ if last == nil {
195
+ return -1
196
+ }
197
+ return last.End()
198
+ }
199
+ }
200
+ openBrace := shimscanner.SkipTrivia(text, block.Pos())
201
+ if openBrace < 0 || openBrace >= len(text) || text[openBrace] != '{' {
202
+ return -1
203
+ }
204
+ return openBrace + 1
205
+ }
206
+
207
+ // isNoFallthroughMarker reports whether one raw comment token (delimiters
208
+ // included) is an intentional-fallthrough marker: its inner text matches
209
+ // the configured pattern and it is not a directive comment. Mirrors
210
+ // upstream `isFallThroughComment`, which tests the un-trimmed comment
211
+ // value against the pattern but the trimmed value against the directive
212
+ // pattern.
213
+ func isNoFallthroughMarker(raw string, pattern *regexp.Regexp) bool {
214
+ value := commentInnerText(raw)
215
+ return pattern.MatchString(value) &&
216
+ !noFallthroughDirectivePattern.MatchString(strings.TrimSpace(value))
217
+ }
218
+
219
+ // commentInnerText strips the comment delimiters from a raw comment token
220
+ // and returns the interior verbatim — no trimming, no JSDoc `*` handling —
221
+ // matching ESLint's `comment.value`. (stripCommentDelimiters is not reused
222
+ // here because it trims and strips JSDoc stars, which would let anchored
223
+ // custom patterns match text ESLint would reject.)
224
+ func commentInnerText(raw string) string {
225
+ switch {
226
+ case strings.HasPrefix(raw, "//"):
227
+ return raw[2:]
228
+ case strings.HasPrefix(raw, "/*"):
229
+ inner := raw[2:]
230
+ inner = strings.TrimSuffix(inner, "*/")
231
+ return inner
232
+ default:
233
+ return raw
234
+ }
235
+ }
236
+
237
+ // lastCommentInTrivia scans the trivia-only region text[from:to) and
238
+ // returns the source range of the last comment token in it. Both regions
239
+ // this rule inspects — between a clause end and the next case keyword,
240
+ // and between a block's last token and its closing brace — contain only
241
+ // whitespace and comments by construction, so a local scan is exact. The
242
+ // scan bails out on any unexpected non-trivia byte instead of guessing.
243
+ func lastCommentInTrivia(text string, from, to int) (pos, end int, ok bool) {
244
+ if from < 0 || to > len(text) || from >= to {
245
+ return 0, 0, false
246
+ }
247
+ i := from
248
+ for i < to {
249
+ c := text[i]
250
+ switch {
251
+ case c == ' ' || c == '\t' || c == '\r' || c == '\n' || c == '\v' || c == '\f':
252
+ i++
253
+ case c == '/' && i+1 < to && text[i+1] == '/':
254
+ start := i
255
+ i += 2
256
+ for i < to && text[i] != '\n' && text[i] != '\r' {
257
+ i++
258
+ }
259
+ pos, end, ok = start, i, true
260
+ case c == '/' && i+1 < to && text[i+1] == '*':
261
+ start := i
262
+ i += 2
263
+ for i < to {
264
+ if text[i] == '*' && i+1 < to && text[i+1] == '/' {
265
+ i += 2
266
+ break
267
+ }
268
+ i++
269
+ }
270
+ pos, end, ok = start, i, true
271
+ case c >= 0x80:
272
+ // Exotic Unicode whitespace (NBSP, U+2028, …) is legal trivia;
273
+ // skip the whole rune. Any other non-ASCII byte here would mean
274
+ // the region was not trivia-only, and the rune skip is still the
275
+ // safest way to keep scanning without splitting a code point.
276
+ i++
277
+ for i < to && text[i]&0xC0 == 0x80 {
278
+ i++
279
+ }
280
+ default:
281
+ // Not a trivia byte: the region assumption was violated
282
+ // (malformed parse). Stop rather than misattribute comments.
283
+ return pos, end, ok
284
+ }
285
+ }
286
+ return pos, end, ok
287
+ }
288
+
289
+ // caseCompletion records every way a statement can finish: normally, by
290
+ // return or throw, or through a labeled/unlabeled break or continue. The
291
+ // empty-string key stands for the unlabeled form. Enclosing constructs
292
+ // consume the completions they own and propagate the rest.
293
+ type caseCompletion struct {
294
+ normal bool
295
+ returns bool
296
+ throws bool
297
+ breaks map[string]struct{}
298
+ continues map[string]struct{}
299
+ }
300
+
301
+ // expressionCompletion records abrupt paths created while evaluating an
302
+ // expression. In addition to ordinary exception edges, `yield` can resume via
303
+ // either generator.throw() or generator.return(), so both paths must survive
304
+ // into enclosing try/finally composition.
305
+ type expressionCompletion struct {
306
+ returns bool
307
+ throws bool
308
+ }
309
+
310
+ func (c *expressionCompletion) merge(other expressionCompletion) {
311
+ c.returns = c.returns || other.returns
312
+ c.throws = c.throws || other.throws
313
+ }
314
+
315
+ func (c *caseCompletion) addBreak(label string) {
316
+ if c.breaks == nil {
317
+ c.breaks = map[string]struct{}{}
318
+ }
319
+ c.breaks[label] = struct{}{}
320
+ }
321
+
322
+ func (c *caseCompletion) addContinue(label string) {
323
+ if c.continues == nil {
324
+ c.continues = map[string]struct{}{}
325
+ }
326
+ c.continues[label] = struct{}{}
327
+ }
328
+
329
+ func (c *caseCompletion) hasBreak(label string) bool {
330
+ _, ok := c.breaks[label]
331
+ return ok
332
+ }
333
+
334
+ func (c *caseCompletion) hasContinue(label string) bool {
335
+ _, ok := c.continues[label]
336
+ return ok
337
+ }
338
+
339
+ func (c *caseCompletion) removeBreak(label string) { delete(c.breaks, label) }
340
+ func (c *caseCompletion) removeContinue(label string) { delete(c.continues, label) }
341
+
342
+ // mergeAbrupt unions the other completion's abrupt paths into this one.
343
+ // normal is deliberately untouched; each composition rule computes it.
344
+ func (c *caseCompletion) mergeAbrupt(other caseCompletion) {
345
+ c.returns = c.returns || other.returns
346
+ c.throws = c.throws || other.throws
347
+ for label := range other.breaks {
348
+ c.addBreak(label)
349
+ }
350
+ for label := range other.continues {
351
+ c.addContinue(label)
352
+ }
353
+ }
354
+
355
+ // hasCompletion reports whether at least one control-flow path leaves the
356
+ // statement. An infinite loop with no reachable escape has no completion,
357
+ // which also means a following finally block is unreachable.
358
+ func (c *caseCompletion) hasCompletion() bool {
359
+ return c.normal || c.returns || c.throws || len(c.breaks) > 0 || len(c.continues) > 0
360
+ }
361
+
362
+ // statementListCompletion runs the completion analysis over a statement
363
+ // list executed in order. Once a statement cannot complete normally the
364
+ // remainder of the list is unreachable: it neither restores normal
365
+ // completion nor contributes escapes (an unreachable `break` can never
366
+ // execute, matching ESLint's unreachable-segment tracking).
367
+ func statementListCompletion(stmts []*shimast.Node) caseCompletion {
368
+ out := caseCompletion{normal: true}
369
+ for _, stmt := range stmts {
370
+ if stmt == nil {
371
+ continue
372
+ }
373
+ if !out.normal {
374
+ break
375
+ }
376
+ r := statementCompletion(stmt, nil)
377
+ out.mergeAbrupt(r)
378
+ out.normal = r.normal
379
+ }
380
+ return out
381
+ }
382
+
383
+ // statementCompletion computes how a single statement can complete.
384
+ // `labels` carries the label names bound to this statement by directly
385
+ // wrapping labeled statements, so loops can absorb `continue L` / `break L`
386
+ // aimed at themselves. Evaluated expressions are inspected for throwable
387
+ // nodes and abrupt generator resumptions; nested function, class-field, and
388
+ // static-block code paths remain isolated from the enclosing case.
389
+ func statementCompletion(stmt *shimast.Node, labels []string) caseCompletion {
390
+ if stmt == nil {
391
+ return caseCompletion{normal: true}
392
+ }
393
+ switch stmt.Kind {
394
+ case shimast.KindReturnStatement:
395
+ ret := stmt.AsReturnStatement()
396
+ expression := expressionCompletion{}
397
+ if ret != nil {
398
+ expression = executableNodeCompletion(ret.Expression)
399
+ }
400
+ return caseCompletion{
401
+ returns: true,
402
+ throws: expression.throws,
403
+ }
404
+ case shimast.KindThrowStatement:
405
+ // Evaluating the operand may throw first, and the statement itself
406
+ // always produces a throw completion. Both reach the same catch edge.
407
+ throw := stmt.AsThrowStatement()
408
+ expression := expressionCompletion{}
409
+ if throw != nil {
410
+ expression = executableNodeCompletion(throw.Expression)
411
+ }
412
+ return caseCompletion{returns: expression.returns, throws: true}
413
+ case shimast.KindBreakStatement:
414
+ out := caseCompletion{}
415
+ out.addBreak(identifierText(stmt.AsBreakStatement().Label))
416
+ return out
417
+ case shimast.KindContinueStatement:
418
+ out := caseCompletion{}
419
+ out.addContinue(identifierText(stmt.AsContinueStatement().Label))
420
+ return out
421
+ case shimast.KindBlock:
422
+ block := stmt.AsBlock()
423
+ if block == nil || block.Statements == nil {
424
+ return caseCompletion{normal: true}
425
+ }
426
+ return statementListCompletion(block.Statements.Nodes)
427
+ case shimast.KindIfStatement:
428
+ return ifCompletion(stmt.AsIfStatement())
429
+ case shimast.KindLabeledStatement:
430
+ return labeledCompletion(stmt.AsLabeledStatement(), labels)
431
+ case shimast.KindWhileStatement:
432
+ s := stmt.AsWhileStatement()
433
+ constTrue, constFalse := literalTruthiness(s.Expression)
434
+ return loopCompletion(
435
+ s.Statement,
436
+ labels,
437
+ constTrue,
438
+ constFalse,
439
+ false,
440
+ executableNodeCompletion(s.Expression),
441
+ expressionCompletion{},
442
+ )
443
+ case shimast.KindDoStatement:
444
+ s := stmt.AsDoStatement()
445
+ constTrue, constFalse := literalTruthiness(s.Expression)
446
+ return loopCompletion(
447
+ s.Statement,
448
+ labels,
449
+ constTrue,
450
+ constFalse,
451
+ true,
452
+ executableNodeCompletion(s.Expression),
453
+ expressionCompletion{},
454
+ )
455
+ case shimast.KindForStatement:
456
+ s := stmt.AsForStatement()
457
+ constTrue, constFalse := literalTruthiness(s.Condition)
458
+ if s.Condition == nil {
459
+ // `for (;;)` loops forever unless something breaks out.
460
+ constTrue, constFalse = true, false
461
+ }
462
+ out := loopCompletion(
463
+ s.Statement,
464
+ labels,
465
+ constTrue,
466
+ constFalse,
467
+ false,
468
+ executableNodeCompletion(s.Condition),
469
+ executableNodeCompletion(s.Incrementor),
470
+ )
471
+ mergeExpressionCompletion(&out, executableNodeCompletion(s.Initializer))
472
+ return out
473
+ case shimast.KindForInStatement, shimast.KindForOfStatement:
474
+ // The iterated collection may be empty, so the loop always offers
475
+ // normal completion — identical to a non-constant loop test.
476
+ s := stmt.AsForInOrOfStatement()
477
+ out := loopCompletion(
478
+ s.Statement,
479
+ labels,
480
+ false,
481
+ false,
482
+ false,
483
+ expressionCompletion{},
484
+ expressionCompletion{},
485
+ )
486
+ mergeExpressionCompletion(&out, executableNodeCompletion(s.Initializer))
487
+ mergeExpressionCompletion(&out, executableNodeCompletion(s.Expression))
488
+ return out
489
+ case shimast.KindSwitchStatement:
490
+ return switchCompletion(stmt.AsSwitchStatement(), labels)
491
+ case shimast.KindTryStatement:
492
+ return tryCompletion(stmt.AsTryStatement())
493
+ case shimast.KindWithStatement:
494
+ s := stmt.AsWithStatement()
495
+ out := statementCompletion(s.Statement, nil)
496
+ mergeExpressionCompletion(&out, executableNodeCompletion(s.Expression))
497
+ return out
498
+ default:
499
+ // Leaf statements complete normally, but their evaluated expressions
500
+ // can still enter an enclosing catch. The walker excludes type syntax
501
+ // and nested function/class execution contexts.
502
+ expression := executableNodeCompletion(stmt)
503
+ return caseCompletion{normal: true, returns: expression.returns, throws: expression.throws}
504
+ }
505
+ }
506
+
507
+ // ifCompletion: an `if` without `else` can always complete normally (the
508
+ // condition may be false); with an `else`, normal completion requires at
509
+ // least one branch to complete normally. Conditions are not constant-
510
+ // folded, matching ESLint's code path analysis which folds only loop
511
+ // tests.
512
+ func ifCompletion(s *shimast.IfStatement) caseCompletion {
513
+ if s == nil {
514
+ return caseCompletion{normal: true}
515
+ }
516
+ then := statementCompletion(s.ThenStatement, nil)
517
+ expression := executableNodeCompletion(s.Expression)
518
+ if s.ElseStatement == nil {
519
+ then.normal = true
520
+ mergeExpressionCompletion(&then, expression)
521
+ return then
522
+ }
523
+ els := statementCompletion(s.ElseStatement, nil)
524
+ out := caseCompletion{
525
+ normal: then.normal || els.normal,
526
+ returns: expression.returns,
527
+ throws: expression.throws,
528
+ }
529
+ out.mergeAbrupt(then)
530
+ out.mergeAbrupt(els)
531
+ return out
532
+ }
533
+
534
+ // labeledCompletion: `L: stmt` completes normally when the body does, or
535
+ // when a `break L` escapes the body (control resumes right after the
536
+ // labeled statement). The label is added to the body's label set so a
537
+ // directly-labeled loop can absorb `continue L` itself.
538
+ func labeledCompletion(s *shimast.LabeledStatement, labels []string) caseCompletion {
539
+ if s == nil {
540
+ return caseCompletion{normal: true}
541
+ }
542
+ name := identifierText(s.Label)
543
+ if name == "" {
544
+ return statementCompletion(s.Statement, labels)
545
+ }
546
+ inner := statementCompletion(s.Statement, append(labels, name))
547
+ if inner.hasBreak(name) {
548
+ inner.normal = true
549
+ }
550
+ inner.removeBreak(name)
551
+ inner.removeContinue(name)
552
+ return inner
553
+ }
554
+
555
+ // loopCompletion is the shared engine for `while`, `do/while`, `for`,
556
+ // `for-in`, and `for-of` bodies. Loop tests are constant-folded only for
557
+ // simple literals (`true`, `1`, `"x"`, `null`, …), matching ESLint's
558
+ // `getBooleanValueIfSimpleConstant`. The loop absorbs unlabeled breaks
559
+ // and continues plus the labeled forms naming the loop itself (via
560
+ // `labels`); everything else escapes to the enclosing construct.
561
+ func loopCompletion(
562
+ body *shimast.Node,
563
+ labels []string,
564
+ constTrue, constFalse, isDoWhile bool,
565
+ test, increment expressionCompletion,
566
+ ) caseCompletion {
567
+ if constFalse && !isDoWhile {
568
+ // The body never runs, so nothing inside it (including breaks) can
569
+ // execute. The test is still evaluated once before normal completion.
570
+ return caseCompletion{normal: true, returns: test.returns, throws: test.throws}
571
+ }
572
+ r := statementCompletion(body, nil)
573
+ exitByBreak := r.hasBreak("")
574
+ iterationEnds := r.normal || r.hasContinue("")
575
+ for _, l := range labels {
576
+ exitByBreak = exitByBreak || r.hasBreak(l)
577
+ iterationEnds = iterationEnds || r.hasContinue(l)
578
+ }
579
+ if !isDoWhile || iterationEnds {
580
+ // while/for tests run before the first iteration. A do/while test is
581
+ // reachable only when the body reaches the iteration boundary.
582
+ mergeExpressionCompletion(&r, test)
583
+ }
584
+ if iterationEnds {
585
+ mergeExpressionCompletion(&r, increment)
586
+ }
587
+ var normal bool
588
+ switch {
589
+ case constTrue:
590
+ // Infinite loop: only a break reaches the code after it.
591
+ normal = exitByBreak
592
+ case isDoWhile:
593
+ // The body runs at least once; afterwards the test can fail.
594
+ normal = exitByBreak || iterationEnds
595
+ default:
596
+ // The test may fail before the first iteration.
597
+ normal = true
598
+ }
599
+ r.removeBreak("")
600
+ r.removeContinue("")
601
+ for _, l := range labels {
602
+ r.removeBreak(l)
603
+ r.removeContinue(l)
604
+ }
605
+ r.normal = normal
606
+ return r
607
+ }
608
+
609
+ // switchCompletion: a nested `switch` completes normally when it has no
610
+ // `default` (the discriminant may match nothing), when its final clause
611
+ // completes normally (falling off the end), or when any reachable `break`
612
+ // targets it. Labeled breaks naming the switch (via `labels`) are
613
+ // absorbed the same way; `continue` never targets a switch and passes
614
+ // through.
615
+ func switchCompletion(s *shimast.SwitchStatement, labels []string) caseCompletion {
616
+ if s == nil {
617
+ return caseCompletion{normal: true}
618
+ }
619
+ discriminant := executableNodeCompletion(s.Expression)
620
+ out := caseCompletion{normal: true, returns: discriminant.returns, throws: discriminant.throws}
621
+ if s.CaseBlock == nil {
622
+ return out
623
+ }
624
+ block := s.CaseBlock.AsCaseBlock()
625
+ if block == nil || block.Clauses == nil || len(block.Clauses.Nodes) == 0 {
626
+ return out
627
+ }
628
+ clauses := block.Clauses.Nodes
629
+ hasDefault := false
630
+ exitByBreak := false
631
+ lastNormal := false
632
+ out.normal = false
633
+ for i, clauseNode := range clauses {
634
+ if clauseNode == nil {
635
+ continue
636
+ }
637
+ if clauseNode.Kind == shimast.KindDefaultClause {
638
+ hasDefault = true
639
+ }
640
+ clause := clauseNode.AsCaseOrDefaultClause()
641
+ if clause != nil {
642
+ mergeExpressionCompletion(&out, executableNodeCompletion(clause.Expression))
643
+ }
644
+ r := statementListCompletion(clauseStatements(clause))
645
+ if r.hasBreak("") {
646
+ exitByBreak = true
647
+ }
648
+ for _, l := range labels {
649
+ if r.hasBreak(l) {
650
+ exitByBreak = true
651
+ }
652
+ }
653
+ if i == len(clauses)-1 {
654
+ lastNormal = r.normal
655
+ }
656
+ out.mergeAbrupt(r)
657
+ }
658
+ out.removeBreak("")
659
+ for _, l := range labels {
660
+ out.removeBreak(l)
661
+ }
662
+ out.normal = !hasDefault || lastNormal || exitByBreak
663
+ return out
664
+ }
665
+
666
+ // tryCompletion composes normal, return, throw, break, and continue paths.
667
+ // A catch contributes only when the protected block has a reachable throw
668
+ // edge. A reachable finally runs for every completion from try/catch; its
669
+ // abrupt completions override the incoming completion, while an ordinary
670
+ // finally path preserves it.
671
+ func tryCompletion(s *shimast.TryStatement) caseCompletion {
672
+ if s == nil {
673
+ return caseCompletion{normal: true}
674
+ }
675
+ tryC := blockNodeCompletion(s.TryBlock)
676
+ var catchC caseCompletion
677
+ hasCatch := false
678
+ if s.CatchClause != nil {
679
+ if clause := s.CatchClause.AsCatchClause(); clause != nil {
680
+ hasCatch = true
681
+ catchC = blockNodeCompletion(clause.Block)
682
+ mergeExpressionCompletion(&catchC, executableNodeCompletion(clause.VariableDeclaration))
683
+ }
684
+ }
685
+ main := tryC
686
+ if hasCatch && tryC.throws {
687
+ // The catch consumes every throw from the protected block and replaces
688
+ // those paths with the catch block's possible completions.
689
+ main.throws = false
690
+ main.normal = tryC.normal || catchC.normal
691
+ main.mergeAbrupt(catchC)
692
+ }
693
+ if s.FinallyBlock == nil {
694
+ return main
695
+ }
696
+ if !main.hasCompletion() {
697
+ // No path leaves the protected region (for example, a closed infinite
698
+ // loop), so execution never enters the finally block.
699
+ return caseCompletion{}
700
+ }
701
+ finallyC := blockNodeCompletion(s.FinallyBlock)
702
+ out := caseCompletion{}
703
+ if finallyC.normal {
704
+ out.mergeAbrupt(main)
705
+ }
706
+ out.mergeAbrupt(finallyC)
707
+ out.normal = main.normal && finallyC.normal
708
+ return out
709
+ }
710
+
711
+ // blockNodeCompletion analyzes a *BlockNode (try/catch/finally bodies).
712
+ func blockNodeCompletion(node *shimast.Node) caseCompletion {
713
+ if node == nil {
714
+ return caseCompletion{normal: true}
715
+ }
716
+ block := node.AsBlock()
717
+ if block == nil || block.Statements == nil {
718
+ return caseCompletion{normal: true}
719
+ }
720
+ return statementListCompletion(block.Statements.Nodes)
721
+ }
722
+
723
+ // executableNodeCompletion mirrors ESLint's expression-level code-path
724
+ // effects. A reachable value-reference identifier, member access, call,
725
+ // import call, construction, or abrupt yield resumption can enter the nearest
726
+ // catch. Type syntax and separately analyzed function/class execution contexts
727
+ // never leak abrupt paths into the enclosing statement.
728
+ func executableNodeCompletion(node *shimast.Node) expressionCompletion {
729
+ if node == nil {
730
+ return expressionCompletion{}
731
+ }
732
+ if noFallthroughNodeIsJSXTagName(node) {
733
+ return expressionCompletion{}
734
+ }
735
+ if node.Kind == shimast.KindExpressionWithTypeArguments {
736
+ if node.Parent != nil &&
737
+ ((node.Parent.Kind >= shimast.KindFirstJSDocNode && node.Parent.Kind <= shimast.KindLastJSDocNode) ||
738
+ (node.Parent.Kind >= shimast.KindFirstJSDocTagNode && node.Parent.Kind <= shimast.KindLastJSDocTagNode)) {
739
+ return expressionCompletion{}
740
+ }
741
+ if node.Parent == nil || node.Parent.Kind != shimast.KindHeritageClause {
742
+ expression := node.AsExpressionWithTypeArguments()
743
+ if expression == nil {
744
+ return expressionCompletion{}
745
+ }
746
+ return executableNodeCompletion(expression.Expression)
747
+ }
748
+ return runtimeHeritageExpressionCompletion(node)
749
+ }
750
+ if (node.Kind >= shimast.KindFirstJSDocNode && node.Kind <= shimast.KindLastJSDocNode) ||
751
+ (node.Kind >= shimast.KindFirstJSDocTagNode && node.Kind <= shimast.KindLastJSDocTagNode) {
752
+ return expressionCompletion{}
753
+ }
754
+ if node.Kind >= shimast.KindFirstTypeNode && node.Kind <= shimast.KindLastTypeNode {
755
+ return expressionCompletion{}
756
+ }
757
+ switch node.Kind {
758
+ case shimast.KindFunctionDeclaration,
759
+ shimast.KindFunctionExpression,
760
+ shimast.KindArrowFunction,
761
+ shimast.KindClassStaticBlockDeclaration,
762
+ shimast.KindInterfaceDeclaration,
763
+ shimast.KindTypeAliasDeclaration,
764
+ shimast.KindJsxNamespacedName:
765
+ return expressionCompletion{}
766
+ case shimast.KindMethodDeclaration,
767
+ shimast.KindConstructor,
768
+ shimast.KindGetAccessor,
769
+ shimast.KindSetAccessor,
770
+ shimast.KindPropertyDeclaration:
771
+ return classElementHeaderCompletion(node)
772
+ case shimast.KindIdentifier:
773
+ return expressionCompletion{throws: noFallthroughIdentifierIsReference(node)}
774
+ case shimast.KindYieldExpression:
775
+ expression := expressionCompletion{returns: true, throws: true}
776
+ node.ForEachChild(func(child *shimast.Node) bool {
777
+ expression.merge(executableNodeCompletion(child))
778
+ return expression.returns && expression.throws
779
+ })
780
+ return expression
781
+ case shimast.KindPropertyAccessExpression,
782
+ shimast.KindElementAccessExpression,
783
+ shimast.KindCallExpression,
784
+ shimast.KindNewExpression:
785
+ expression := expressionCompletion{throws: true}
786
+ node.ForEachChild(func(child *shimast.Node) bool {
787
+ expression.merge(executableNodeCompletion(child))
788
+ return expression.returns && expression.throws
789
+ })
790
+ return expression
791
+ }
792
+ expression := expressionCompletion{}
793
+ node.ForEachChild(func(child *shimast.Node) bool {
794
+ expression.merge(executableNodeCompletion(child))
795
+ return expression.returns && expression.throws
796
+ })
797
+ return expression
798
+ }
799
+
800
+ func mergeExpressionCompletion(completion *caseCompletion, expression expressionCompletion) {
801
+ completion.returns = completion.returns || expression.returns
802
+ completion.throws = completion.throws || expression.throws
803
+ }
804
+
805
+ // runtimeHeritageExpressionCompletion retains the value expression in
806
+ // a class extends clause while excluding interface heritage and implements
807
+ // clauses, which are type-only even though they share the same AST node kind.
808
+ func runtimeHeritageExpressionCompletion(node *shimast.Node) expressionCompletion {
809
+ if node == nil || node.Parent == nil || node.Parent.Kind != shimast.KindHeritageClause {
810
+ return expressionCompletion{}
811
+ }
812
+ clauseNode := node.Parent
813
+ clause := clauseNode.AsHeritageClause()
814
+ if clause == nil || clause.Token != shimast.KindExtendsKeyword || clauseNode.Parent == nil {
815
+ return expressionCompletion{}
816
+ }
817
+ if clauseNode.Parent.Kind != shimast.KindClassDeclaration &&
818
+ clauseNode.Parent.Kind != shimast.KindClassExpression {
819
+ return expressionCompletion{}
820
+ }
821
+ expression := node.AsExpressionWithTypeArguments()
822
+ if expression == nil {
823
+ return expressionCompletion{}
824
+ }
825
+ return executableNodeCompletion(expression.Expression)
826
+ }
827
+
828
+ // classElementHeaderCompletion keeps class member bodies and field
829
+ // initializers in their own code paths while retaining immediately evaluated
830
+ // decorators and computed property names in the enclosing class evaluation.
831
+ func classElementHeaderCompletion(node *shimast.Node) expressionCompletion {
832
+ if node == nil {
833
+ return expressionCompletion{}
834
+ }
835
+ completion := expressionCompletion{}
836
+ if modifiers := node.Modifiers(); modifiers != nil {
837
+ for _, modifier := range modifiers.Nodes {
838
+ completion.merge(executableNodeCompletion(modifier))
839
+ }
840
+ }
841
+ completion.merge(executableNodeCompletion(node.Name()))
842
+ return completion
843
+ }
844
+
845
+ // noFallthroughIdentifierIsReference excludes declaration names, property
846
+ // names, and statement labels. Every remaining identifier is a value read,
847
+ // matching the first-throwable-node rule used by ESLint inside try blocks.
848
+ func noFallthroughIdentifierIsReference(node *shimast.Node) bool {
849
+ if node == nil || node.Parent == nil {
850
+ return true
851
+ }
852
+ parent := node.Parent
853
+ switch parent.Kind {
854
+ case shimast.KindJsxAttribute,
855
+ shimast.KindImportClause,
856
+ shimast.KindImportSpecifier,
857
+ shimast.KindNamespaceImport,
858
+ shimast.KindCatchClause:
859
+ return false
860
+ case shimast.KindArrayLiteralExpression:
861
+ return !isDestructuringAssignmentTarget(parent)
862
+ case shimast.KindSpreadElement, shimast.KindSpreadAssignment:
863
+ return !isDestructuringAssignmentTarget(parent)
864
+ case shimast.KindPropertyAccessExpression:
865
+ access := parent.AsPropertyAccessExpression()
866
+ return access == nil || access.Name() != node
867
+ case shimast.KindPropertyAssignment:
868
+ assignment := parent.AsPropertyAssignment()
869
+ return assignment == nil || assignment.Name() != node
870
+ case shimast.KindBindingElement:
871
+ element := parent.AsBindingElement()
872
+ if element == nil || (element.Name() != node && element.PropertyName != node) {
873
+ return true
874
+ }
875
+ if element.PropertyName == node || element.DotDotDotToken != nil {
876
+ return false
877
+ }
878
+ if element.Initializer != nil {
879
+ return true
880
+ }
881
+ return parent.Parent != nil && parent.Parent.Kind == shimast.KindObjectBindingPattern
882
+ case shimast.KindVariableDeclaration:
883
+ declaration := parent.AsVariableDeclaration()
884
+ return declaration == nil || declaration.Name() != node
885
+ case shimast.KindParameter:
886
+ parameter := parent.AsParameterDeclaration()
887
+ return parameter == nil || parameter.Name() != node
888
+ case shimast.KindFunctionDeclaration,
889
+ shimast.KindFunctionExpression,
890
+ shimast.KindClassDeclaration,
891
+ shimast.KindClassExpression,
892
+ shimast.KindMethodDeclaration,
893
+ shimast.KindPropertyDeclaration,
894
+ shimast.KindGetAccessor,
895
+ shimast.KindSetAccessor,
896
+ shimast.KindEnumDeclaration,
897
+ shimast.KindEnumMember,
898
+ shimast.KindModuleDeclaration:
899
+ return parent.Name() != node
900
+ case shimast.KindLabeledStatement:
901
+ statement := parent.AsLabeledStatement()
902
+ return statement == nil || statement.Label != node
903
+ case shimast.KindBreakStatement:
904
+ statement := parent.AsBreakStatement()
905
+ return statement == nil || statement.Label != node
906
+ case shimast.KindContinueStatement:
907
+ statement := parent.AsContinueStatement()
908
+ return statement == nil || statement.Label != node
909
+ }
910
+ return true
911
+ }
912
+
913
+ // noFallthroughNodeIsJSXTagName excludes JSX tag names from Identifier and
914
+ // MemberExpression throw heuristics. ESTree represents them as JSXIdentifier /
915
+ // JSXMemberExpression rather than runtime Identifier / MemberExpression nodes;
916
+ // evaluated attribute and child expressions remain traversable.
917
+ func noFallthroughNodeIsJSXTagName(node *shimast.Node) bool {
918
+ current := node
919
+ for current != nil && current.Parent != nil && current.Parent.Kind == shimast.KindPropertyAccessExpression {
920
+ current = current.Parent
921
+ }
922
+ if current == nil || current.Parent == nil {
923
+ return false
924
+ }
925
+ switch current.Parent.Kind {
926
+ case shimast.KindJsxOpeningElement,
927
+ shimast.KindJsxSelfClosingElement,
928
+ shimast.KindJsxClosingElement:
929
+ return current.Parent.TagName() == current
930
+ default:
931
+ return false
932
+ }
933
+ }
934
+
935
+ // literalTruthiness folds a loop test into a constant when it is a simple
936
+ // literal, mirroring ESLint's `getBooleanValueIfSimpleConstant` (ESTree
937
+ // `Literal` nodes only — template literals and negations are NOT folded).
938
+ // Returns (false, false) for non-literal or unparseable expressions.
939
+ func literalTruthiness(expr *shimast.Node) (constTrue, constFalse bool) {
940
+ expr = stripParens(expr)
941
+ if expr == nil {
942
+ return false, false
943
+ }
944
+ switch expr.Kind {
945
+ case shimast.KindTrueKeyword:
946
+ return true, false
947
+ case shimast.KindFalseKeyword, shimast.KindNullKeyword:
948
+ return false, true
949
+ case shimast.KindRegularExpressionLiteral:
950
+ return true, false
951
+ case shimast.KindStringLiteral:
952
+ if lit := expr.AsStringLiteral(); lit != nil {
953
+ return lit.Text != "", lit.Text == ""
954
+ }
955
+ case shimast.KindNumericLiteral:
956
+ if lit := expr.AsNumericLiteral(); lit != nil {
957
+ if value, err := strconv.ParseFloat(lit.Text, 64); err == nil {
958
+ return value != 0, value == 0
959
+ }
960
+ }
961
+ case shimast.KindBigIntLiteral:
962
+ if lit := expr.AsBigIntLiteral(); lit != nil {
963
+ return bigIntLiteralTruthiness(lit.Text)
964
+ }
965
+ }
966
+ return false, false
967
+ }
968
+
969
+ // bigIntLiteralTruthiness determines zero/non-zero without converting the
970
+ // value to a fixed-width integer. The scanner can preserve any accepted
971
+ // radix prefix, digit separators, and arbitrarily wide values.
972
+ func bigIntLiteralTruthiness(text string) (constTrue, constFalse bool) {
973
+ if !strings.HasSuffix(text, "n") {
974
+ return false, false
975
+ }
976
+ digits := strings.ReplaceAll(strings.TrimSuffix(text, "n"), "_", "")
977
+ base := byte(10)
978
+ if len(digits) >= 2 && digits[0] == '0' {
979
+ switch digits[1] {
980
+ case 'b', 'B':
981
+ base, digits = 2, digits[2:]
982
+ case 'o', 'O':
983
+ base, digits = 8, digits[2:]
984
+ case 'x', 'X':
985
+ base, digits = 16, digits[2:]
986
+ }
987
+ }
988
+ if digits == "" {
989
+ return false, false
990
+ }
991
+ nonZero := false
992
+ for i := 0; i < len(digits); i++ {
993
+ digit := digits[i]
994
+ value := byte(255)
995
+ switch {
996
+ case digit >= '0' && digit <= '9':
997
+ value = digit - '0'
998
+ case digit >= 'a' && digit <= 'f':
999
+ value = digit - 'a' + 10
1000
+ case digit >= 'A' && digit <= 'F':
1001
+ value = digit - 'A' + 10
1002
+ }
1003
+ if value >= base {
1004
+ return false, false
1005
+ }
1006
+ nonZero = nonZero || value != 0
1007
+ }
1008
+ return nonZero, !nonZero
1009
+ }
1010
+
1011
+ func init() {
1012
+ Register(noFallthrough{})
1013
+ }