@ttsc/lint 0.18.3 → 0.19.0

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 +95 -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 +129 -25
  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 +160 -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 +151 -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 +145 -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,32 +1,610 @@
1
- // unicorn/prefer-simple-condition-first: when a `&&`/`||` chain mixes
2
- // a "simple" operand (a bare identifier, a small literal comparison)
3
- // with a "complex" one (a function call, a property chain), short-
4
- // circuit evaluation reads more naturally if the simple operand sits
5
- // to the left the cheap test is the gate, the expensive test runs
6
- // only if the gate passes.
1
+ // unicorn/prefer-simple-condition-first keeps cheap, side-effect-free gates
2
+ // ahead of complex conditions in boolean && and || chains. The rule mirrors
3
+ // Unicorn's structural definition of "simple": identifiers, their negations,
4
+ // and strict comparisons built from identifiers/typeof identifiers and
5
+ // literals. It never ranks arbitrary expressions with a project-specific
6
+ // complexity score.
7
7
  //
8
- // Escape-hatch / no-op port: ranking arbitrary expressions by
9
- // "simpleness" requires a stable complexity heuristic the upstream rule
10
- // embeds across hundreds of lines (literal counts, identifier depth,
11
- // call-chain weight, parenthesization). Without a faithful port of that
12
- // heuristic the rule would either over-fire on equally-weighted operands
13
- // or under-fire on the cases it is meant to catch; either way users get
14
- // confusing diagnostics. Registered so the typed-key/Go-registry parity
15
- // check passes and so users can configure the rule, but it intentionally
16
- // reports nothing until the heuristic lands in a follow-up.
8
+ // A stable partition is offered only when every complex operand that crosses
9
+ // a simple operand is itself safe to move. Comments and TypeScript wrappers
10
+ // around logical subexpressions suppress the edit even when the diagnostic is
11
+ // safe, preserving evaluation order, source ownership, and syntax exactly.
17
12
  // https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-simple-condition-first.md
18
13
  package linthost
19
14
 
20
- import shimast "github.com/microsoft/typescript-go/shim/ast"
15
+ import (
16
+ "strings"
17
+
18
+ shimast "github.com/microsoft/typescript-go/shim/ast"
19
+ shimscanner "github.com/microsoft/typescript-go/shim/scanner"
20
+ )
21
+
22
+ const (
23
+ unicornPreferSimpleConditionFirstSafeMessagePrefix = "Prefer this simple condition first in the `"
24
+ unicornPreferSimpleConditionFirstUnsafeMessage = "Consider moving this simple condition first after verifying short-circuit behavior."
25
+ )
21
26
 
22
27
  type unicornPreferSimpleConditionFirst struct{}
23
28
 
29
+ type unicornPreferSimpleConditionFirstOperand struct {
30
+ node *shimast.Node
31
+ simple bool
32
+ }
33
+
34
+ type unicornPreferSimpleConditionFirstCommentSpan struct {
35
+ start int
36
+ end int
37
+ }
38
+
24
39
  func (unicornPreferSimpleConditionFirst) Name() string {
25
40
  return "unicorn/prefer-simple-condition-first"
26
41
  }
27
- func (unicornPreferSimpleConditionFirst) Visits() []shimast.Kind { return nil }
28
- func (unicornPreferSimpleConditionFirst) Check(_ *Context, _ *shimast.Node) {
29
- // Intentionally empty — see file header for the escape-hatch rationale.
42
+
43
+ func (unicornPreferSimpleConditionFirst) Visits() []shimast.Kind {
44
+ return []shimast.Kind{shimast.KindSourceFile}
45
+ }
46
+
47
+ // The checker is used only to distinguish the global Boolean conversion
48
+ // function from a same-named local binding. All other classification is AST
49
+ // structural.
50
+ func (unicornPreferSimpleConditionFirst) NeedsTypeChecker() bool { return true }
51
+
52
+ func (unicornPreferSimpleConditionFirst) Check(ctx *Context, root *shimast.Node) {
53
+ if ctx == nil || ctx.File == nil || root == nil {
54
+ return
55
+ }
56
+ comments := make([]unicornPreferSimpleConditionFirstCommentSpan, 0)
57
+ forEachCommentToken(ctx.File, func(_ shimast.Kind, start, end int) {
58
+ comments = append(comments, unicornPreferSimpleConditionFirstCommentSpan{start: start, end: end})
59
+ })
60
+ walkDescendants(root, func(node *shimast.Node) {
61
+ if node.Kind == shimast.KindBinaryExpression {
62
+ unicornPreferSimpleConditionFirstCheckLogical(ctx, node, comments)
63
+ }
64
+ })
65
+ }
66
+
67
+ func unicornPreferSimpleConditionFirstCheckLogical(
68
+ ctx *Context,
69
+ node *shimast.Node,
70
+ comments []unicornPreferSimpleConditionFirstCommentSpan,
71
+ ) {
72
+ binary := node.AsBinaryExpression()
73
+ if binary == nil || binary.OperatorToken == nil ||
74
+ !unicornPreferSimpleConditionFirstIsLogicalOperator(binary.OperatorToken.Kind) {
75
+ return
76
+ }
77
+ operator := binary.OperatorToken.Kind
78
+ if unicornPreferSimpleConditionFirstHasSameOperatorParent(node, operator) ||
79
+ !unicornPreferSimpleConditionFirstIsBooleanContext(ctx, node) {
80
+ return
81
+ }
82
+
83
+ nodes := unicornPreferSimpleConditionFirstLogicalOperands(node, operator, nil)
84
+ operands := make([]unicornPreferSimpleConditionFirstOperand, len(nodes))
85
+ firstComplex := -1
86
+ firstMisplacedSimple := -1
87
+ lastSimple := -1
88
+ for index, operand := range nodes {
89
+ simple := unicornPreferSimpleConditionFirstIsSimple(operand)
90
+ operands[index] = unicornPreferSimpleConditionFirstOperand{node: operand, simple: simple}
91
+ if simple {
92
+ lastSimple = index
93
+ if firstComplex >= 0 && firstMisplacedSimple < 0 {
94
+ firstMisplacedSimple = index
95
+ }
96
+ } else if firstComplex < 0 {
97
+ firstComplex = index
98
+ }
99
+ }
100
+ if firstComplex < 0 || firstMisplacedSimple < 0 {
101
+ return
102
+ }
103
+
104
+ canSafelyReorder := true
105
+ for index, operand := range operands {
106
+ if !operand.simple && index <= lastSimple &&
107
+ !unicornPreferSimpleConditionFirstIsSafeToMove(operand.node) {
108
+ canSafelyReorder = false
109
+ break
110
+ }
111
+ }
112
+
113
+ message := unicornPreferSimpleConditionFirstUnsafeMessage
114
+ if canSafelyReorder {
115
+ message = unicornPreferSimpleConditionFirstSafeMessagePrefix +
116
+ unicornPreferSimpleConditionFirstOperatorText(operator) + "` expression."
117
+ }
118
+
119
+ reportNode := unicornPreferSimpleConditionFirstReportNode(nodes[firstMisplacedSimple])
120
+ if !canSafelyReorder ||
121
+ unicornPreferSimpleConditionFirstHasTypeScriptWrappedLogical(node, operator) ||
122
+ unicornPreferSimpleConditionFirstCommentsPreventFix(ctx, node, nodes, comments) {
123
+ ctx.Report(reportNode, message)
124
+ return
125
+ }
126
+
127
+ start, end := tokenRange(ctx.File, node)
128
+ if start < 0 || end <= start {
129
+ ctx.Report(reportNode, message)
130
+ return
131
+ }
132
+ reordered := make([]*shimast.Node, 0, len(operands))
133
+ for _, operand := range operands {
134
+ if operand.simple {
135
+ reordered = append(reordered, operand.node)
136
+ }
137
+ }
138
+ for _, operand := range operands {
139
+ if !operand.simple {
140
+ reordered = append(reordered, operand.node)
141
+ }
142
+ }
143
+ parts := make([]string, len(reordered))
144
+ for index, operand := range reordered {
145
+ text, ok := unicornPreferSimpleConditionFirstOperandText(ctx, operand, operator, index == 0)
146
+ if !ok {
147
+ ctx.Report(reportNode, message)
148
+ return
149
+ }
150
+ parts[index] = text
151
+ }
152
+ ctx.ReportFix(
153
+ reportNode,
154
+ message,
155
+ TextEdit{
156
+ Pos: start,
157
+ End: end,
158
+ Text: strings.Join(parts, " "+unicornPreferSimpleConditionFirstOperatorText(operator)+" "),
159
+ },
160
+ )
161
+ }
162
+
163
+ func unicornPreferSimpleConditionFirstIsLogicalOperator(kind shimast.Kind) bool {
164
+ return kind == shimast.KindAmpersandAmpersandToken || kind == shimast.KindBarBarToken
165
+ }
166
+
167
+ func unicornPreferSimpleConditionFirstOperatorText(kind shimast.Kind) string {
168
+ if kind == shimast.KindAmpersandAmpersandToken {
169
+ return "&&"
170
+ }
171
+ return "||"
172
+ }
173
+
174
+ // Logical operands are flattened through parentheses and TypeScript's
175
+ // expression-only wrappers because ESTree exposes the same chain without
176
+ // those TypeScript AST nodes. The original operand is retained whenever the
177
+ // unwrapped value is not another matching logical expression so fixes preserve
178
+ // its exact wrapper text.
179
+ func unicornPreferSimpleConditionFirstLogicalOperands(
180
+ node *shimast.Node,
181
+ operator shimast.Kind,
182
+ operands []*shimast.Node,
183
+ ) []*shimast.Node {
184
+ unwrapped := unicornPreferSimpleConditionFirstUnwrap(node)
185
+ if unwrapped == nil || unwrapped.Kind != shimast.KindBinaryExpression {
186
+ return append(operands, node)
187
+ }
188
+ binary := unwrapped.AsBinaryExpression()
189
+ if binary == nil || binary.OperatorToken == nil || binary.OperatorToken.Kind != operator {
190
+ return append(operands, node)
191
+ }
192
+ operands = unicornPreferSimpleConditionFirstLogicalOperands(binary.Left, operator, operands)
193
+ return unicornPreferSimpleConditionFirstLogicalOperands(binary.Right, operator, operands)
194
+ }
195
+
196
+ func unicornPreferSimpleConditionFirstIsIdentifierOrTypeofIdentifier(node *shimast.Node) bool {
197
+ node = unicornPreferSimpleConditionFirstUnwrap(node)
198
+ if node == nil {
199
+ return false
200
+ }
201
+ if node.Kind == shimast.KindIdentifier {
202
+ return true
203
+ }
204
+ if node.Kind != shimast.KindTypeOfExpression {
205
+ return false
206
+ }
207
+ expression := unicornPreferSimpleConditionFirstUnwrap(node.Expression())
208
+ return expression != nil && expression.Kind == shimast.KindIdentifier
209
+ }
210
+
211
+ func unicornPreferSimpleConditionFirstIsLiteral(node *shimast.Node) bool {
212
+ node = unicornPreferSimpleConditionFirstUnwrap(node)
213
+ if node == nil {
214
+ return false
215
+ }
216
+ switch node.Kind {
217
+ case shimast.KindStringLiteral,
218
+ shimast.KindNumericLiteral,
219
+ shimast.KindBigIntLiteral,
220
+ shimast.KindRegularExpressionLiteral,
221
+ shimast.KindTrueKeyword,
222
+ shimast.KindFalseKeyword,
223
+ shimast.KindNullKeyword:
224
+ return true
225
+ }
226
+ return false
227
+ }
228
+
229
+ func unicornPreferSimpleConditionFirstIsSimpleOperand(node *shimast.Node) bool {
230
+ node = unicornPreferSimpleConditionFirstUnwrap(node)
231
+ if node == nil {
232
+ return false
233
+ }
234
+ if unicornPreferSimpleConditionFirstIsIdentifierOrTypeofIdentifier(node) ||
235
+ unicornPreferSimpleConditionFirstIsLiteral(node) {
236
+ return true
237
+ }
238
+ if node.Kind != shimast.KindPrefixUnaryExpression {
239
+ return false
240
+ }
241
+ prefix := node.AsPrefixUnaryExpression()
242
+ if prefix == nil || (prefix.Operator != shimast.KindMinusToken && prefix.Operator != shimast.KindPlusToken) {
243
+ return false
244
+ }
245
+ argument := unicornPreferSimpleConditionFirstUnwrap(prefix.Operand)
246
+ if argument == nil {
247
+ return false
248
+ }
249
+ return argument.Kind == shimast.KindNumericLiteral ||
250
+ (prefix.Operator == shimast.KindMinusToken && argument.Kind == shimast.KindBigIntLiteral)
251
+ }
252
+
253
+ func unicornPreferSimpleConditionFirstIsSimple(node *shimast.Node) bool {
254
+ node = unicornPreferSimpleConditionFirstUnwrap(node)
255
+ if node == nil {
256
+ return false
257
+ }
258
+ if node.Kind == shimast.KindIdentifier {
259
+ return true
260
+ }
261
+ if node.Kind == shimast.KindPrefixUnaryExpression {
262
+ prefix := node.AsPrefixUnaryExpression()
263
+ return prefix != nil && prefix.Operator == shimast.KindExclamationToken &&
264
+ unicornPreferSimpleConditionFirstIsSimple(prefix.Operand)
265
+ }
266
+ if node.Kind != shimast.KindBinaryExpression {
267
+ return false
268
+ }
269
+ binary := node.AsBinaryExpression()
270
+ if binary == nil || binary.OperatorToken == nil ||
271
+ (binary.OperatorToken.Kind != shimast.KindEqualsEqualsEqualsToken &&
272
+ binary.OperatorToken.Kind != shimast.KindExclamationEqualsEqualsToken) {
273
+ return false
274
+ }
275
+ left := unicornPreferSimpleConditionFirstUnwrap(binary.Left)
276
+ right := unicornPreferSimpleConditionFirstUnwrap(binary.Right)
277
+ return unicornPreferSimpleConditionFirstIsSimpleOperand(left) &&
278
+ unicornPreferSimpleConditionFirstIsSimpleOperand(right) &&
279
+ (unicornPreferSimpleConditionFirstIsIdentifierOrTypeofIdentifier(left) ||
280
+ unicornPreferSimpleConditionFirstIsIdentifierOrTypeofIdentifier(right))
281
+ }
282
+
283
+ func unicornPreferSimpleConditionFirstIsSafeConditional(node *shimast.Node) bool {
284
+ node = unicornPreferSimpleConditionFirstUnwrap(node)
285
+ if node == nil || node.Kind != shimast.KindConditionalExpression {
286
+ return false
287
+ }
288
+ conditional := node.AsConditionalExpression()
289
+ if conditional == nil {
290
+ return false
291
+ }
292
+ for _, child := range []*shimast.Node{conditional.Condition, conditional.WhenTrue, conditional.WhenFalse} {
293
+ if !unicornPreferSimpleConditionFirstIsSimple(child) &&
294
+ !unicornPreferSimpleConditionFirstIsSimpleOperand(child) &&
295
+ !unicornPreferSimpleConditionFirstIsSafeConditional(child) {
296
+ return false
297
+ }
298
+ }
299
+ return true
300
+ }
301
+
302
+ func unicornPreferSimpleConditionFirstIsSafeToMove(node *shimast.Node) bool {
303
+ return unicornPreferSimpleConditionFirstIsSimple(node) ||
304
+ unicornPreferSimpleConditionFirstIsSafeConditional(node)
305
+ }
306
+
307
+ func unicornPreferSimpleConditionFirstIsBooleanContext(ctx *Context, node *shimast.Node) bool {
308
+ current := node
309
+ for current != nil && current.Parent != nil {
310
+ parent := current.Parent
311
+ if child, ok := unicornPreferSimpleConditionFirstTransparentChild(parent); ok && child == current {
312
+ current = parent
313
+ continue
314
+ }
315
+ if parent.Kind == shimast.KindBinaryExpression {
316
+ binary := parent.AsBinaryExpression()
317
+ if binary != nil && binary.OperatorToken != nil &&
318
+ unicornPreferSimpleConditionFirstIsLogicalOperator(binary.OperatorToken.Kind) &&
319
+ (binary.Left == current || binary.Right == current) {
320
+ current = parent
321
+ continue
322
+ }
323
+ }
324
+ switch parent.Kind {
325
+ case shimast.KindIfStatement:
326
+ return parent.AsIfStatement().Expression == current
327
+ case shimast.KindWhileStatement:
328
+ return parent.AsWhileStatement().Expression == current
329
+ case shimast.KindDoStatement:
330
+ return parent.AsDoStatement().Expression == current
331
+ case shimast.KindForStatement:
332
+ return parent.AsForStatement().Condition == current
333
+ case shimast.KindConditionalExpression:
334
+ return parent.AsConditionalExpression().Condition == current
335
+ case shimast.KindPrefixUnaryExpression:
336
+ prefix := parent.AsPrefixUnaryExpression()
337
+ return prefix != nil && prefix.Operator == shimast.KindExclamationToken && prefix.Operand == current
338
+ case shimast.KindCallExpression:
339
+ call := parent.AsCallExpression()
340
+ return unicornPreferSimpleConditionFirstIsGlobalBooleanCall(ctx, call, current)
341
+ }
342
+ return false
343
+ }
344
+ return false
345
+ }
346
+
347
+ func unicornPreferSimpleConditionFirstIsGlobalBooleanCall(
348
+ ctx *Context,
349
+ call *shimast.CallExpression,
350
+ argument *shimast.Node,
351
+ ) bool {
352
+ if ctx == nil || ctx.Checker == nil || ctx.File == nil || call == nil ||
353
+ call.QuestionDotToken != nil || call.Arguments == nil || len(call.Arguments.Nodes) != 1 ||
354
+ call.Arguments.Nodes[0] != argument || argument.Kind == shimast.KindSpreadElement {
355
+ return false
356
+ }
357
+ // ESTree erases grouping parentheses around a callee, but retains
358
+ // TypeScript assertion wrappers. Only the former may be transparent here:
359
+ // `(Boolean)(value)` is the global conversion, while
360
+ // `(Boolean as Converter)(value)` is not the canonical call shape.
361
+ callee := stripParens(call.Expression)
362
+ if identifierText(callee) != "Boolean" {
363
+ return false
364
+ }
365
+ resolved := ctx.Checker.GetSymbolAtLocation(callee)
366
+ global := ctx.Checker.GetGlobalSymbol("Boolean", shimast.SymbolFlagsValue, nil)
367
+ if resolved == nil || global == nil {
368
+ return false
369
+ }
370
+ resolved = ctx.Checker.GetMergedSymbol(resolved)
371
+ global = ctx.Checker.GetMergedSymbol(global)
372
+ if resolved != global {
373
+ return false
374
+ }
375
+ for _, declaration := range resolved.Declarations {
376
+ if declaration != nil && shimast.GetSourceFileOfNode(declaration) == ctx.File &&
377
+ unicornPreferSimpleConditionFirstDeclarationIntroducesValue(declaration) {
378
+ return false
379
+ }
380
+ }
381
+ return true
382
+ }
383
+
384
+ func unicornPreferSimpleConditionFirstDeclarationIntroducesValue(declaration *shimast.Node) bool {
385
+ switch declaration.Kind {
386
+ case shimast.KindVariableDeclaration,
387
+ shimast.KindBindingElement,
388
+ shimast.KindFunctionDeclaration,
389
+ shimast.KindClassDeclaration,
390
+ shimast.KindEnumDeclaration,
391
+ shimast.KindModuleDeclaration,
392
+ shimast.KindImportEqualsDeclaration:
393
+ return true
394
+ }
395
+ return false
396
+ }
397
+
398
+ func unicornPreferSimpleConditionFirstHasSameOperatorParent(node *shimast.Node, operator shimast.Kind) bool {
399
+ current := node
400
+ parent := current.Parent
401
+ for parent != nil {
402
+ child, transparent := unicornPreferSimpleConditionFirstTransparentChild(parent)
403
+ if !transparent || child != current {
404
+ break
405
+ }
406
+ current = parent
407
+ parent = current.Parent
408
+ }
409
+ if parent == nil || parent.Kind != shimast.KindBinaryExpression {
410
+ return false
411
+ }
412
+ binary := parent.AsBinaryExpression()
413
+ return binary != nil && binary.OperatorToken != nil && binary.OperatorToken.Kind == operator
414
+ }
415
+
416
+ func unicornPreferSimpleConditionFirstHasTypeScriptWrappedLogical(node *shimast.Node, operator shimast.Kind) bool {
417
+ node = stripParens(node)
418
+ if node == nil {
419
+ return false
420
+ }
421
+ if unicornPreferSimpleConditionFirstIsTypeScriptWrapper(node) {
422
+ unwrapped := unicornPreferSimpleConditionFirstUnwrap(node)
423
+ if unwrapped != nil && unwrapped.Kind == shimast.KindBinaryExpression {
424
+ binary := unwrapped.AsBinaryExpression()
425
+ return binary != nil && binary.OperatorToken != nil && binary.OperatorToken.Kind == operator
426
+ }
427
+ return false
428
+ }
429
+ if node.Kind != shimast.KindBinaryExpression {
430
+ return false
431
+ }
432
+ binary := node.AsBinaryExpression()
433
+ return binary != nil && binary.OperatorToken != nil && binary.OperatorToken.Kind == operator &&
434
+ (unicornPreferSimpleConditionFirstHasTypeScriptWrappedLogical(binary.Left, operator) ||
435
+ unicornPreferSimpleConditionFirstHasTypeScriptWrappedLogical(binary.Right, operator))
436
+ }
437
+
438
+ func unicornPreferSimpleConditionFirstCommentsPreventFix(
439
+ ctx *Context,
440
+ node *shimast.Node,
441
+ operands []*shimast.Node,
442
+ comments []unicornPreferSimpleConditionFirstCommentSpan,
443
+ ) bool {
444
+ if ctx == nil || ctx.File == nil || len(operands) == 0 {
445
+ return true
446
+ }
447
+ source := ctx.File.Text()
448
+ rootStart, rootEnd := tokenRange(ctx.File, node)
449
+ firstStart, _ := tokenRange(ctx.File, operands[0])
450
+ _, lastEnd := tokenRange(ctx.File, operands[len(operands)-1])
451
+ if rootStart < 0 || rootEnd < rootStart || firstStart < 0 || lastEnd < 0 || lastEnd > len(source) {
452
+ return true
453
+ }
454
+ leadingStart := operands[0].Pos()
455
+ if leadingStart < 0 || leadingStart > firstStart {
456
+ leadingStart = firstStart
457
+ }
458
+ trailingEnd := shimscanner.SkipTrivia(source, lastEnd)
459
+ if trailingEnd < lastEnd || trailingEnd > len(source) {
460
+ trailingEnd = lastEnd
461
+ }
462
+ ranges := [][2]int{
463
+ {rootStart, rootEnd},
464
+ {leadingStart, firstStart},
465
+ {lastEnd, trailingEnd},
466
+ }
467
+ for _, span := range ranges {
468
+ if unicornPreferSimpleConditionFirstCommentsOverlap(comments, span[0], span[1]) {
469
+ return true
470
+ }
471
+ }
472
+ return false
473
+ }
474
+
475
+ // forEachCommentToken returns non-overlapping spans in source order. Find the
476
+ // first comment whose end lies after start, then only that span can be the
477
+ // earliest overlap. This keeps a comment-heavy file from multiplying every
478
+ // reportable chain by the file's complete comment count.
479
+ func unicornPreferSimpleConditionFirstCommentsOverlap(
480
+ comments []unicornPreferSimpleConditionFirstCommentSpan,
481
+ start int,
482
+ end int,
483
+ ) bool {
484
+ low := 0
485
+ high := len(comments)
486
+ for low < high {
487
+ middle := low + (high-low)/2
488
+ if comments[middle].end <= start {
489
+ low = middle + 1
490
+ } else {
491
+ high = middle
492
+ }
493
+ }
494
+ return low < len(comments) && comments[low].start < end
495
+ }
496
+
497
+ func unicornPreferSimpleConditionFirstOperandText(
498
+ ctx *Context,
499
+ node *shimast.Node,
500
+ operator shimast.Kind,
501
+ first bool,
502
+ ) (string, bool) {
503
+ start, end := tokenRange(ctx.File, node)
504
+ if start < 0 || end <= start {
505
+ return "", false
506
+ }
507
+ text := ctx.File.Text()[start:end]
508
+ if node.Kind != shimast.KindParenthesizedExpression &&
509
+ unicornPreferSimpleConditionFirstNeedsParentheses(node, operator, first) {
510
+ text = "(" + text + ")"
511
+ }
512
+ return text, true
513
+ }
514
+
515
+ func unicornPreferSimpleConditionFirstNeedsParentheses(node *shimast.Node, operator shimast.Kind, first bool) bool {
516
+ // first is intentionally part of the contract: upstream's utility accepts
517
+ // the child property even though both current branches share the same
518
+ // precedence decision.
519
+ _ = first
520
+ if node == nil {
521
+ return false
522
+ }
523
+ if node.Kind == shimast.KindBinaryExpression {
524
+ binary := node.AsBinaryExpression()
525
+ if binary != nil && binary.OperatorToken != nil && binary.OperatorToken.Kind == operator {
526
+ return false
527
+ }
528
+ return true
529
+ }
530
+ switch node.Kind {
531
+ case shimast.KindAwaitExpression,
532
+ shimast.KindConditionalExpression,
533
+ shimast.KindArrowFunction,
534
+ shimast.KindYieldExpression:
535
+ return true
536
+ }
537
+ return false
538
+ }
539
+
540
+ func unicornPreferSimpleConditionFirstReportNode(node *shimast.Node) *shimast.Node {
541
+ for node != nil && node.Kind == shimast.KindParenthesizedExpression {
542
+ parenthesized := node.AsParenthesizedExpression()
543
+ if parenthesized == nil || parenthesized.Expression == nil {
544
+ break
545
+ }
546
+ node = parenthesized.Expression
547
+ }
548
+ return node
549
+ }
550
+
551
+ func unicornPreferSimpleConditionFirstUnwrap(node *shimast.Node) *shimast.Node {
552
+ for node != nil {
553
+ child, ok := unicornPreferSimpleConditionFirstTransparentChild(node)
554
+ if !ok || child == nil {
555
+ return node
556
+ }
557
+ node = child
558
+ }
559
+ return nil
560
+ }
561
+
562
+ func unicornPreferSimpleConditionFirstIsTypeScriptWrapper(node *shimast.Node) bool {
563
+ if node == nil {
564
+ return false
565
+ }
566
+ switch node.Kind {
567
+ case shimast.KindAsExpression,
568
+ shimast.KindSatisfiesExpression,
569
+ shimast.KindNonNullExpression,
570
+ shimast.KindTypeAssertionExpression:
571
+ return true
572
+ }
573
+ return false
574
+ }
575
+
576
+ func unicornPreferSimpleConditionFirstTransparentChild(node *shimast.Node) (*shimast.Node, bool) {
577
+ if node == nil {
578
+ return nil, false
579
+ }
580
+ switch node.Kind {
581
+ case shimast.KindParenthesizedExpression:
582
+ expression := node.AsParenthesizedExpression()
583
+ if expression != nil {
584
+ return expression.Expression, true
585
+ }
586
+ case shimast.KindAsExpression:
587
+ expression := node.AsAsExpression()
588
+ if expression != nil {
589
+ return expression.Expression, true
590
+ }
591
+ case shimast.KindSatisfiesExpression:
592
+ expression := node.AsSatisfiesExpression()
593
+ if expression != nil {
594
+ return expression.Expression, true
595
+ }
596
+ case shimast.KindNonNullExpression:
597
+ expression := node.AsNonNullExpression()
598
+ if expression != nil {
599
+ return expression.Expression, true
600
+ }
601
+ case shimast.KindTypeAssertionExpression:
602
+ expression := node.AsTypeAssertion()
603
+ if expression != nil {
604
+ return expression.Expression, true
605
+ }
606
+ }
607
+ return nil, false
30
608
  }
31
609
 
32
610
  func init() {