@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.
- package/README.md +95 -43
- package/go.mod +5 -4
- package/internal/rulecode/rulecode.go +88 -0
- package/lib/index.d.ts +10 -1
- package/lib/index.js +45 -17
- package/lib/index.js.map +1 -1
- package/lib/structures/ITtscLintConfig.d.ts +3 -0
- package/lib/structures/TtscLintRuleSetting.d.ts +3 -2
- package/lib/structures/format/ITtscLintFormat.d.ts +3 -2
- package/lib/structures/format/ITtscLintFormatSortImports.d.ts +20 -4
- package/lib/structures/rules/ITtscLintBoundariesRuleOptions.d.ts +80 -8
- package/lib/structures/rules/ITtscLintBoundariesRules.d.ts +6 -8
- package/lib/structures/rules/ITtscLintContributorRules.d.ts +18 -2
- package/lib/structures/rules/ITtscLintCoreRuleOptions.d.ts +347 -0
- package/lib/structures/rules/ITtscLintCoreRuleOptions.js +3 -0
- package/lib/structures/rules/ITtscLintCoreRuleOptions.js.map +1 -0
- package/lib/structures/rules/ITtscLintCoreRules.d.ts +101 -67
- package/lib/structures/rules/ITtscLintRegexpRules.d.ts +7 -2
- package/lib/structures/rules/ITtscLintRuleOptionsMap.d.ts +53 -7
- package/lib/structures/rules/ITtscLintRules.d.ts +6 -2
- package/lib/structures/rules/ITtscLintSolidRules.d.ts +0 -8
- package/lib/structures/rules/ITtscLintTypeScriptRuleOptions.d.ts +206 -0
- package/lib/structures/rules/ITtscLintTypeScriptRuleOptions.js +8 -0
- package/lib/structures/rules/ITtscLintTypeScriptRuleOptions.js.map +1 -0
- package/lib/structures/rules/ITtscLintTypeScriptRules.d.ts +55 -42
- package/lib/structures/rules/ITtscLintUnicornRuleOptions.d.ts +276 -0
- package/lib/structures/rules/ITtscLintUnicornRuleOptions.js +3 -0
- package/lib/structures/rules/ITtscLintUnicornRuleOptions.js.map +1 -0
- package/lib/structures/rules/ITtscLintUnicornRules.d.ts +44 -36
- package/lib/structures/rules/index.d.ts +3 -0
- package/lib/structures/rules/index.js +3 -0
- package/lib/structures/rules/index.js.map +1 -1
- package/linthost/ast_helpers.go +521 -23
- package/linthost/ast_selector_matcher.go +1628 -0
- package/linthost/ast_selector_parser.go +628 -0
- package/linthost/comment_scan.go +154 -0
- package/linthost/compile.go +129 -79
- package/linthost/config.go +352 -88
- package/linthost/config_format.go +15 -1
- package/linthost/contrib_adapter.go +129 -25
- package/linthost/declaration_rules.go +12 -11
- package/linthost/directives.go +9 -67
- package/linthost/dispatch.go +37 -3
- package/linthost/engine.go +368 -53
- package/linthost/escape_string.go +77 -0
- package/linthost/fix.go +133 -23
- package/linthost/flags_gen.go +18 -17
- package/linthost/format.go +73 -167
- package/linthost/format_editor_settings.go +134 -26
- package/linthost/grapheme_tables_gen.go +1920 -0
- package/linthost/host.go +160 -61
- package/linthost/literal_escape_scan.go +117 -0
- package/linthost/lsp.go +545 -98
- package/linthost/numeric_literal_precision.go +228 -0
- package/linthost/polyfill_browserslist.go +1439 -0
- package/linthost/polyfill_browserslist_config.go +603 -0
- package/linthost/polyfill_corejs.go +750 -0
- package/linthost/polyfill_data.go +113 -0
- package/linthost/polyfill_data_gen.json +39868 -0
- package/linthost/print_nodes_array.go +7 -1
- package/linthost/print_nodes_object.go +7 -1
- package/linthost/project_engine.go +151 -0
- package/linthost/project_rules.go +78 -0
- package/linthost/regex_clean.go +191 -0
- package/linthost/regex_tree.go +1544 -0
- package/linthost/regex_tree_optimizer.go +1685 -0
- package/linthost/regexp_cache.go +44 -0
- package/linthost/rule_codes.go +105 -0
- package/linthost/rule_codes.json +745 -0
- package/linthost/rules_ban_ts_comment.go +312 -0
- package/linthost/rules_boundaries.go +128 -43
- package/linthost/rules_boundaries_dependencies.go +1013 -0
- package/linthost/rules_core_extra.go +62 -92
- package/linthost/rules_cypress.go +1 -1
- package/linthost/rules_default_case.go +87 -11
- package/linthost/rules_dupes.go +38 -13
- package/linthost/rules_empty.go +188 -20
- package/linthost/rules_escape.go +62 -48
- package/linthost/rules_format_arrow_parens.go +19 -5
- package/linthost/rules_format_bracket_spacing.go +1 -1
- package/linthost/rules_format_clause_join.go +1 -1
- package/linthost/rules_format_declaration_header.go +31 -19
- package/linthost/rules_format_indent.go +1 -1
- package/linthost/rules_format_jsdoc.go +1 -1
- package/linthost/rules_format_orphan_semi.go +1 -1
- package/linthost/rules_format_parameter_properties.go +17 -7
- package/linthost/rules_format_print_width.go +1 -1
- package/linthost/rules_format_quote_props.go +1 -1
- package/linthost/rules_format_quotes.go +1 -1
- package/linthost/rules_format_semi.go +106 -79
- package/linthost/rules_format_sort_imports.go +79 -45
- package/linthost/rules_format_statement_split.go +1 -1
- package/linthost/rules_format_ternary_nullish_parens.go +1 -1
- package/linthost/rules_format_trailing_comma.go +64 -1
- package/linthost/rules_format_whitespace.go +1 -1
- package/linthost/rules_functional.go +21 -22
- package/linthost/rules_gap.go +80 -3
- package/linthost/rules_grouped_accessor_pairs.go +84 -25
- package/linthost/rules_guard_for_in.go +64 -147
- package/linthost/rules_jsx_a11y.go +232 -22
- package/linthost/rules_logic.go +84 -38
- package/linthost/rules_nextjs.go +105 -16
- package/linthost/rules_no_class_assign.go +70 -0
- package/linthost/rules_no_duplicate_imports.go +266 -0
- package/linthost/rules_no_else_return.go +156 -38
- package/linthost/rules_no_extend_native.go +113 -17
- package/linthost/rules_no_fallthrough.go +1013 -0
- package/linthost/rules_no_func_assign.go +70 -0
- package/linthost/rules_no_import_assign.go +278 -86
- package/linthost/rules_no_inner_declarations.go +186 -0
- package/linthost/rules_no_loop_func.go +438 -53
- package/linthost/rules_no_mixed_operators.go +208 -52
- package/linthost/rules_no_param_reassign.go +376 -61
- package/linthost/rules_no_promise_executor_return.go +156 -0
- package/linthost/rules_no_restricted_imports.go +1013 -57
- package/linthost/rules_no_restricted_syntax.go +190 -26
- package/linthost/rules_no_useless_constructor.go +6 -36
- package/linthost/rules_problems.go +154 -311
- package/linthost/rules_promise.go +2578 -109
- package/linthost/rules_react_extras.go +71 -45
- package/linthost/rules_react_hooks.go +18 -2
- package/linthost/rules_react_perf.go +1 -0
- package/linthost/rules_react_refresh.go +1 -1
- package/linthost/rules_regexp.go +230 -14
- package/linthost/rules_security.go +45 -19
- package/linthost/rules_self.go +21 -1
- package/linthost/rules_solid.go +13 -7
- package/linthost/rules_storybook.go +1 -1
- package/linthost/rules_suggestions.go +490 -84
- package/linthost/rules_testing_library.go +12 -1
- package/linthost/rules_ts.go +227 -54
- package/linthost/rules_ts_async.go +0 -131
- package/linthost/rules_ts_no_base_to_string.go +3 -4
- package/linthost/rules_ts_no_misused_promises.go +817 -0
- package/linthost/rules_ts_no_restricted_types.go +320 -34
- package/linthost/rules_ts_no_unnecessary_qualifier.go +132 -55
- package/linthost/rules_ts_no_unsafe_assignment.go +608 -60
- package/linthost/rules_ts_require_array_sort_compare.go +5 -5
- package/linthost/rules_ts_switch_exhaustiveness_check.go +556 -86
- package/linthost/rules_unicorn_better_regex.go +224 -16
- package/linthost/rules_unicorn_consistent_destructuring.go +1002 -7
- package/linthost/rules_unicorn_consistent_existence_index_check.go +208 -29
- package/linthost/rules_unicorn_consistent_function_scoping.go +1041 -8
- package/linthost/rules_unicorn_consistent_template_literal_escape.go +178 -7
- package/linthost/rules_unicorn_escape_case.go +30 -19
- package/linthost/rules_unicorn_filename_case.go +763 -16
- package/linthost/rules_unicorn_import_style.go +783 -17
- package/linthost/rules_unicorn_isolated_functions.go +934 -14
- package/linthost/rules_unicorn_no_hex_escape.go +26 -11
- package/linthost/rules_unicorn_no_typeof_undefined.go +234 -25
- package/linthost/rules_unicorn_no_unnecessary_polyfills.go +376 -21
- package/linthost/rules_unicorn_no_unused_properties.go +756 -19
- package/linthost/rules_unicorn_number_literal_case.go +58 -39
- package/linthost/rules_unicorn_prefer_at.go +7 -3
- package/linthost/rules_unicorn_prefer_number_properties.go +381 -48
- package/linthost/rules_unicorn_prefer_optional_catch_binding.go +24 -24
- package/linthost/rules_unicorn_prefer_simple_condition_first.go +597 -19
- package/linthost/rules_unicorn_prefer_string_raw.go +147 -16
- package/linthost/rules_unicorn_prevent_abbreviations.go +1963 -95
- package/linthost/rules_unicorn_string_content.go +611 -15
- package/linthost/rules_unicorn_switch_case_break_position.go +172 -15
- package/linthost/rules_unicorn_template_indent.go +923 -18
- package/linthost/rules_unicorn_text_encoding_identifier_case.go +202 -42
- package/linthost/rules_unicorn_throw_new_error.go +218 -26
- package/linthost/rules_var.go +829 -142
- package/linthost/string_length.go +222 -0
- package/package.json +4 -3
- package/rule/project.go +145 -0
- package/rule/rule.go +58 -1
- package/src/index.ts +58 -17
- package/src/structures/ITtscLintConfig.ts +3 -0
- package/src/structures/TtscLintRuleSetting.ts +3 -2
- package/src/structures/format/ITtscLintFormat.ts +3 -2
- package/src/structures/format/ITtscLintFormatSortImports.ts +21 -4
- package/src/structures/rules/ITtscLintBoundariesRuleOptions.ts +122 -8
- package/src/structures/rules/ITtscLintBoundariesRules.ts +6 -8
- package/src/structures/rules/ITtscLintContributorRules.ts +18 -2
- package/src/structures/rules/ITtscLintCoreRuleOptions.ts +465 -0
- package/src/structures/rules/ITtscLintCoreRules.ts +122 -67
- package/src/structures/rules/ITtscLintRegexpRules.ts +7 -2
- package/src/structures/rules/ITtscLintRuleOptionsMap.ts +88 -7
- package/src/structures/rules/ITtscLintRules.ts +7 -2
- package/src/structures/rules/ITtscLintSolidRules.ts +0 -9
- package/src/structures/rules/ITtscLintTypeScriptRuleOptions.ts +248 -0
- package/src/structures/rules/ITtscLintTypeScriptRules.ts +64 -42
- package/src/structures/rules/ITtscLintUnicornRuleOptions.ts +350 -0
- package/src/structures/rules/ITtscLintUnicornRules.ts +60 -36
- package/src/structures/rules/index.ts +3 -0
|
@@ -1,32 +1,610 @@
|
|
|
1
|
-
// unicorn/prefer-simple-condition-first
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
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
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
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
|
|
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
|
-
|
|
28
|
-
func (unicornPreferSimpleConditionFirst)
|
|
29
|
-
|
|
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() {
|