@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.
- package/README.md +125 -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 +47 -8
- 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 +159 -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 +231 -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 +206 -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,13 +1,38 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
// which is out of scope for this AST-only port.
|
|
1
|
+
// unicorn/consistent-destructuring: once a property has been destructured
|
|
2
|
+
// from an object with `const {a} = foo`, later reads of `foo.a` in the same
|
|
3
|
+
// or a nested scope must use the destructured binding instead.
|
|
5
4
|
//
|
|
6
|
-
//
|
|
5
|
+
// The port mirrors the current upstream implementation: only `const`
|
|
6
|
+
// declarations whose initializer is a bare identifier or `this` are tracked;
|
|
7
|
+
// member reads are matched to the most recent preceding declaration through
|
|
8
|
+
// checker binding identity so shadowed roots, shadowed bindings, and
|
|
9
|
+
// same-name-different-variable pairs never collude. Reads are exempt when
|
|
10
|
+
// the root variable or the same member was written between the declaration
|
|
11
|
+
// and the read (conservatively including writes from other function scopes,
|
|
12
|
+
// which may execute in between through calls or closures), when the read
|
|
13
|
+
// sits in a positive `'prop' in obj` guarded branch (TypeScript narrowing),
|
|
14
|
+
// or when the member expression is itself written, called, tagged, or
|
|
15
|
+
// computed. Replacements ship as editor suggestions — never autofixes —
|
|
16
|
+
// exactly like upstream's `hasSuggestions` contract; nested member chains
|
|
17
|
+
// report without a suggestion.
|
|
18
|
+
//
|
|
19
|
+
// eslint-scope concepts translate as follows: variable identity is the
|
|
20
|
+
// checker symbol at the identifier (export/merge-normalized on both sides of
|
|
21
|
+
// every comparison), scope containment is AST ancestry of the declaration's
|
|
22
|
+
// nearest scope-creating node, and `variableScope` is the nearest
|
|
23
|
+
// function-like boundary (source file, function, accessor, static block,
|
|
24
|
+
// class-field initializer, namespace, or enum body).
|
|
7
25
|
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/consistent-destructuring.md
|
|
8
26
|
package linthost
|
|
9
27
|
|
|
10
|
-
import
|
|
28
|
+
import (
|
|
29
|
+
"fmt"
|
|
30
|
+
|
|
31
|
+
shimast "github.com/microsoft/typescript-go/shim/ast"
|
|
32
|
+
shimscanner "github.com/microsoft/typescript-go/shim/scanner"
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
const unicornConsistentDestructuringMessage = "Use destructured variables over properties."
|
|
11
36
|
|
|
12
37
|
type unicornConsistentDestructuring struct{}
|
|
13
38
|
|
|
@@ -17,7 +42,977 @@ func (unicornConsistentDestructuring) Name() string {
|
|
|
17
42
|
func (unicornConsistentDestructuring) Visits() []shimast.Kind {
|
|
18
43
|
return []shimast.Kind{shimast.KindSourceFile}
|
|
19
44
|
}
|
|
20
|
-
|
|
45
|
+
|
|
46
|
+
// The checker supplies binding identity: distinguishing a shadowing inner
|
|
47
|
+
// `foo` (or a shadowing inner destructured binding) from the declaration's
|
|
48
|
+
// root is exactly the resolution eslint-scope performs upstream.
|
|
49
|
+
func (unicornConsistentDestructuring) NeedsTypeChecker() bool { return true }
|
|
50
|
+
|
|
51
|
+
// unicornConsistentDestructuringDeclaration is one tracked
|
|
52
|
+
// `const {…} = foo` / `const {…} = this` declarator.
|
|
53
|
+
type unicornConsistentDestructuringDeclaration struct {
|
|
54
|
+
// object is the unwrapped initializer node (Identifier or `this`).
|
|
55
|
+
object *shimast.Node
|
|
56
|
+
// objectEnd is the initializer's end offset; upstream orders every
|
|
57
|
+
// declaration/read/write comparison against it.
|
|
58
|
+
objectEnd int
|
|
59
|
+
// key is the raw source text of the initializer ("foo" / "this").
|
|
60
|
+
key string
|
|
61
|
+
// rootName is the cooked identifier name; empty for `this`.
|
|
62
|
+
rootName string
|
|
63
|
+
// rootSymbol is the checker binding of the initializer identifier; nil
|
|
64
|
+
// for `this` or an unresolvable global.
|
|
65
|
+
rootSymbol *shimast.Symbol
|
|
66
|
+
// thisBoundary is the enclosing `this` boundary for `this` initializers.
|
|
67
|
+
thisBoundary *shimast.Node
|
|
68
|
+
// pattern is the ObjectBindingPattern being declared.
|
|
69
|
+
pattern *shimast.Node
|
|
70
|
+
// scope is the nearest scope-creating ancestor of the declarator; a read
|
|
71
|
+
// matches only when it sits inside this scope.
|
|
72
|
+
scope *shimast.Node
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// unicornConsistentDestructuringMemberWrite is one recorded write through a
|
|
76
|
+
// member expression (`foo.a = 1`, `foo.a++`, `delete foo.a`, destructuring
|
|
77
|
+
// assignment targets, `for (foo.a of …)`).
|
|
78
|
+
type unicornConsistentDestructuringMemberWrite struct {
|
|
79
|
+
property string
|
|
80
|
+
rootName string
|
|
81
|
+
rootSymbol *shimast.Symbol
|
|
82
|
+
thisBoundary *shimast.Node
|
|
83
|
+
start int
|
|
84
|
+
variableScope *shimast.Node
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// unicornConsistentDestructuringRootWrite is one recorded write to an
|
|
88
|
+
// identifier binding (assignment, update, for-in/of target, declarator init).
|
|
89
|
+
type unicornConsistentDestructuringRootWrite struct {
|
|
90
|
+
symbol *shimast.Symbol
|
|
91
|
+
start int
|
|
92
|
+
variableScope *shimast.Node
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// unicornConsistentDestructuringState is the per-file collection pass state.
|
|
96
|
+
type unicornConsistentDestructuringState struct {
|
|
97
|
+
ctx *Context
|
|
98
|
+
declarations map[string][]*unicornConsistentDestructuringDeclaration
|
|
99
|
+
members []*shimast.Node
|
|
100
|
+
memberWrites []unicornConsistentDestructuringMemberWrite
|
|
101
|
+
rootWrites []unicornConsistentDestructuringRootWrite
|
|
102
|
+
memberWriteSeen map[*shimast.Node]struct{}
|
|
103
|
+
rootWriteSeen map[*shimast.Node]struct{}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
func (unicornConsistentDestructuring) Check(ctx *Context, root *shimast.Node) {
|
|
107
|
+
if ctx == nil || ctx.File == nil || root == nil {
|
|
108
|
+
return
|
|
109
|
+
}
|
|
110
|
+
state := &unicornConsistentDestructuringState{
|
|
111
|
+
ctx: ctx,
|
|
112
|
+
declarations: make(map[string][]*unicornConsistentDestructuringDeclaration),
|
|
113
|
+
memberWriteSeen: make(map[*shimast.Node]struct{}),
|
|
114
|
+
rootWriteSeen: make(map[*shimast.Node]struct{}),
|
|
115
|
+
}
|
|
116
|
+
walkDescendants(root, state.collect)
|
|
117
|
+
for _, member := range state.members {
|
|
118
|
+
state.checkMember(member)
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// collect records declarations, member reads, member writes, and root-binding
|
|
123
|
+
// writes in one source-order pass, mirroring upstream's listener set.
|
|
124
|
+
func (s *unicornConsistentDestructuringState) collect(node *shimast.Node) {
|
|
125
|
+
switch node.Kind {
|
|
126
|
+
case shimast.KindVariableDeclaration:
|
|
127
|
+
s.collectDeclaration(node)
|
|
128
|
+
s.collectDeclaratorRootWrites(node)
|
|
129
|
+
case shimast.KindBinaryExpression:
|
|
130
|
+
expression := node.AsBinaryExpression()
|
|
131
|
+
if expression != nil && expression.OperatorToken != nil &&
|
|
132
|
+
isAssignmentOperator(expression.OperatorToken.Kind) {
|
|
133
|
+
s.collectMemberWritesFromTarget(expression.Left)
|
|
134
|
+
s.collectRootWrites(node)
|
|
135
|
+
}
|
|
136
|
+
case shimast.KindPrefixUnaryExpression:
|
|
137
|
+
expression := node.AsPrefixUnaryExpression()
|
|
138
|
+
if expression != nil &&
|
|
139
|
+
(expression.Operator == shimast.KindPlusPlusToken || expression.Operator == shimast.KindMinusMinusToken) {
|
|
140
|
+
s.addDirectMemberWrite(stripParens(expression.Operand))
|
|
141
|
+
s.collectRootWrites(node)
|
|
142
|
+
}
|
|
143
|
+
case shimast.KindPostfixUnaryExpression:
|
|
144
|
+
expression := node.AsPostfixUnaryExpression()
|
|
145
|
+
if expression != nil &&
|
|
146
|
+
(expression.Operator == shimast.KindPlusPlusToken || expression.Operator == shimast.KindMinusMinusToken) {
|
|
147
|
+
s.addDirectMemberWrite(stripParens(expression.Operand))
|
|
148
|
+
s.collectRootWrites(node)
|
|
149
|
+
}
|
|
150
|
+
case shimast.KindDeleteExpression:
|
|
151
|
+
expression := node.AsDeleteExpression()
|
|
152
|
+
if expression != nil {
|
|
153
|
+
s.addDirectMemberWrite(stripParens(expression.Expression))
|
|
154
|
+
}
|
|
155
|
+
case shimast.KindForInStatement, shimast.KindForOfStatement:
|
|
156
|
+
statement := node.AsForInOrOfStatement()
|
|
157
|
+
if statement == nil || statement.Initializer == nil {
|
|
158
|
+
return
|
|
159
|
+
}
|
|
160
|
+
if statement.Initializer.Kind == shimast.KindVariableDeclarationList {
|
|
161
|
+
s.collectForHeadDeclarationRootWrites(statement.Initializer)
|
|
162
|
+
return
|
|
163
|
+
}
|
|
164
|
+
s.collectMemberWritesFromTarget(statement.Initializer)
|
|
165
|
+
s.collectRootWrites(node)
|
|
166
|
+
case shimast.KindPropertyAccessExpression:
|
|
167
|
+
if !unicornConsistentDestructuringIsJsxTagName(node) {
|
|
168
|
+
s.members = append(s.members, node)
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// collectDeclaration tracks `const {…} = <identifier|this>` declarators.
|
|
174
|
+
// Upstream requires the `const` kind, an object pattern, and a "simple"
|
|
175
|
+
// initializer (identifier or `this`); parentheses are unwrapped because the
|
|
176
|
+
// ESTree oracle never materializes them, while TypeScript assertion wrappers
|
|
177
|
+
// intentionally disqualify the initializer exactly as upstream does.
|
|
178
|
+
func (s *unicornConsistentDestructuringState) collectDeclaration(node *shimast.Node) {
|
|
179
|
+
declaration := node.AsVariableDeclaration()
|
|
180
|
+
if declaration == nil || declaration.Initializer == nil {
|
|
181
|
+
return
|
|
182
|
+
}
|
|
183
|
+
name := declaration.Name()
|
|
184
|
+
if name == nil || name.Kind != shimast.KindObjectBindingPattern {
|
|
185
|
+
return
|
|
186
|
+
}
|
|
187
|
+
if node.Parent == nil || node.Parent.Kind != shimast.KindVariableDeclarationList ||
|
|
188
|
+
!shimast.IsConst(node) {
|
|
189
|
+
return
|
|
190
|
+
}
|
|
191
|
+
object := stripParens(declaration.Initializer)
|
|
192
|
+
if object == nil ||
|
|
193
|
+
(object.Kind != shimast.KindIdentifier && object.Kind != shimast.KindThisKeyword) {
|
|
194
|
+
return
|
|
195
|
+
}
|
|
196
|
+
entry := &unicornConsistentDestructuringDeclaration{
|
|
197
|
+
object: object,
|
|
198
|
+
objectEnd: object.End(),
|
|
199
|
+
key: nodeText(s.ctx.File, object),
|
|
200
|
+
pattern: name,
|
|
201
|
+
scope: unicornConsistentDestructuringNearestScope(node),
|
|
202
|
+
}
|
|
203
|
+
if object.Kind == shimast.KindIdentifier {
|
|
204
|
+
entry.rootName = identifierText(object)
|
|
205
|
+
entry.rootSymbol = canonicalValueSymbol(s.ctx, object)
|
|
206
|
+
} else {
|
|
207
|
+
entry.thisBoundary = unicornConsistentDestructuringThisBoundary(object)
|
|
208
|
+
}
|
|
209
|
+
if entry.key == "" {
|
|
210
|
+
return
|
|
211
|
+
}
|
|
212
|
+
s.declarations[entry.key] = append(s.declarations[entry.key], entry)
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// collectMemberWritesFromTarget descends one assignment target the way
|
|
216
|
+
// upstream's addMemberExpressionWrites walks ESTree patterns. TypeScript-Go
|
|
217
|
+
// parses assignment destructuring targets as array/object literal
|
|
218
|
+
// expressions; property keys and default-value right sides are reads and are
|
|
219
|
+
// therefore not descended into.
|
|
220
|
+
func (s *unicornConsistentDestructuringState) collectMemberWritesFromTarget(node *shimast.Node) {
|
|
221
|
+
if node == nil {
|
|
222
|
+
return
|
|
223
|
+
}
|
|
224
|
+
switch node.Kind {
|
|
225
|
+
case shimast.KindPropertyAccessExpression:
|
|
226
|
+
s.addDirectMemberWrite(node)
|
|
227
|
+
case shimast.KindParenthesizedExpression:
|
|
228
|
+
s.collectMemberWritesFromTarget(stripParens(node))
|
|
229
|
+
case shimast.KindBinaryExpression:
|
|
230
|
+
// A default inside a pattern (`[foo.a = 1] = …`) parses as an `=`
|
|
231
|
+
// binary expression; only its left side is written.
|
|
232
|
+
expression := node.AsBinaryExpression()
|
|
233
|
+
if expression != nil && expression.OperatorToken != nil &&
|
|
234
|
+
expression.OperatorToken.Kind == shimast.KindEqualsToken {
|
|
235
|
+
s.collectMemberWritesFromTarget(expression.Left)
|
|
236
|
+
}
|
|
237
|
+
case shimast.KindArrayLiteralExpression:
|
|
238
|
+
array := node.AsArrayLiteralExpression()
|
|
239
|
+
if array != nil && array.Elements != nil {
|
|
240
|
+
for _, element := range array.Elements.Nodes {
|
|
241
|
+
s.collectMemberWritesFromTarget(element)
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
case shimast.KindObjectLiteralExpression:
|
|
245
|
+
object := node.AsObjectLiteralExpression()
|
|
246
|
+
if object == nil || object.Properties == nil {
|
|
247
|
+
return
|
|
248
|
+
}
|
|
249
|
+
for _, property := range object.Properties.Nodes {
|
|
250
|
+
switch property.Kind {
|
|
251
|
+
case shimast.KindPropertyAssignment:
|
|
252
|
+
if assignment := property.AsPropertyAssignment(); assignment != nil {
|
|
253
|
+
s.collectMemberWritesFromTarget(assignment.Initializer)
|
|
254
|
+
}
|
|
255
|
+
case shimast.KindSpreadAssignment:
|
|
256
|
+
if spread := property.AsSpreadAssignment(); spread != nil {
|
|
257
|
+
s.collectMemberWritesFromTarget(spread.Expression)
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
case shimast.KindSpreadElement:
|
|
262
|
+
if spread := node.AsSpreadElement(); spread != nil {
|
|
263
|
+
s.collectMemberWritesFromTarget(spread.Expression)
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// addDirectMemberWrite records one `<identifier|this>.<name>` write target.
|
|
269
|
+
// Computed properties and private names are skipped, mirroring upstream's
|
|
270
|
+
// identifier-property requirement; the object is unwrapped through parens
|
|
271
|
+
// and TypeScript assertion wrappers so `foo!.a = 1` still shields `foo.a`.
|
|
272
|
+
func (s *unicornConsistentDestructuringState) addDirectMemberWrite(node *shimast.Node) {
|
|
273
|
+
if node == nil || node.Kind != shimast.KindPropertyAccessExpression {
|
|
274
|
+
return
|
|
275
|
+
}
|
|
276
|
+
access := node.AsPropertyAccessExpression()
|
|
277
|
+
if access == nil {
|
|
278
|
+
return
|
|
279
|
+
}
|
|
280
|
+
name := access.Name()
|
|
281
|
+
if name == nil || name.Kind != shimast.KindIdentifier {
|
|
282
|
+
return
|
|
283
|
+
}
|
|
284
|
+
object := unicornConsistentDestructuringUnwrapObject(access.Expression)
|
|
285
|
+
if object == nil ||
|
|
286
|
+
(object.Kind != shimast.KindIdentifier && object.Kind != shimast.KindThisKeyword) {
|
|
287
|
+
return
|
|
288
|
+
}
|
|
289
|
+
if _, duplicate := s.memberWriteSeen[node]; duplicate {
|
|
290
|
+
return
|
|
291
|
+
}
|
|
292
|
+
s.memberWriteSeen[node] = struct{}{}
|
|
293
|
+
write := unicornConsistentDestructuringMemberWrite{
|
|
294
|
+
property: identifierText(name),
|
|
295
|
+
start: shimscanner.SkipTrivia(s.ctx.File.Text(), node.Pos()),
|
|
296
|
+
variableScope: unicornConsistentDestructuringVariableScope(node),
|
|
297
|
+
}
|
|
298
|
+
if object.Kind == shimast.KindIdentifier {
|
|
299
|
+
write.rootName = identifierText(object)
|
|
300
|
+
write.rootSymbol = canonicalValueSymbol(s.ctx, object)
|
|
301
|
+
} else {
|
|
302
|
+
write.thisBoundary = unicornConsistentDestructuringThisBoundary(object)
|
|
303
|
+
}
|
|
304
|
+
s.memberWrites = append(s.memberWrites, write)
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// collectRootWrites records identifier write targets of one assignment,
|
|
308
|
+
// update, or for-in/of head — the same references eslint-scope marks
|
|
309
|
+
// isWrite() on the root variable.
|
|
310
|
+
func (s *unicornConsistentDestructuringState) collectRootWrites(node *shimast.Node) {
|
|
311
|
+
for _, identifier := range writeTargetIdentifiers(node) {
|
|
312
|
+
s.addRootWrite(identifier)
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// collectDeclaratorRootWrites records the initialized bindings of one
|
|
317
|
+
// declarator. eslint-scope counts a declarator's init as a write reference,
|
|
318
|
+
// which matters when a `var` redeclaration re-binds the tracked root.
|
|
319
|
+
func (s *unicornConsistentDestructuringState) collectDeclaratorRootWrites(node *shimast.Node) {
|
|
320
|
+
declaration := node.AsVariableDeclaration()
|
|
321
|
+
if declaration == nil || declaration.Initializer == nil {
|
|
322
|
+
return
|
|
323
|
+
}
|
|
324
|
+
s.addBindingNameWrites(declaration.Name())
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// collectForHeadDeclarationRootWrites records `for (const x of …)` style
|
|
328
|
+
// heads: the declared names are written on every iteration even though the
|
|
329
|
+
// declarator itself has no initializer.
|
|
330
|
+
func (s *unicornConsistentDestructuringState) collectForHeadDeclarationRootWrites(list *shimast.Node) {
|
|
331
|
+
declarationList := list.AsVariableDeclarationList()
|
|
332
|
+
if declarationList == nil || declarationList.Declarations == nil {
|
|
333
|
+
return
|
|
334
|
+
}
|
|
335
|
+
for _, declaration := range declarationList.Declarations.Nodes {
|
|
336
|
+
if variable := declaration.AsVariableDeclaration(); variable != nil {
|
|
337
|
+
s.addBindingNameWrites(variable.Name())
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// addBindingNameWrites walks a binding name (identifier or nested pattern)
|
|
343
|
+
// and records each bound identifier as a write.
|
|
344
|
+
func (s *unicornConsistentDestructuringState) addBindingNameWrites(name *shimast.Node) {
|
|
345
|
+
if name == nil {
|
|
346
|
+
return
|
|
347
|
+
}
|
|
348
|
+
switch name.Kind {
|
|
349
|
+
case shimast.KindIdentifier:
|
|
350
|
+
s.addRootWrite(name)
|
|
351
|
+
case shimast.KindObjectBindingPattern, shimast.KindArrayBindingPattern:
|
|
352
|
+
pattern := name.AsBindingPattern()
|
|
353
|
+
if pattern == nil || pattern.Elements == nil {
|
|
354
|
+
return
|
|
355
|
+
}
|
|
356
|
+
for _, element := range pattern.Elements.Nodes {
|
|
357
|
+
if binding := element.AsBindingElement(); binding != nil {
|
|
358
|
+
s.addBindingNameWrites(binding.Name())
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
func (s *unicornConsistentDestructuringState) addRootWrite(identifier *shimast.Node) {
|
|
365
|
+
if identifier == nil || identifier.Kind != shimast.KindIdentifier {
|
|
366
|
+
return
|
|
367
|
+
}
|
|
368
|
+
if _, duplicate := s.rootWriteSeen[identifier]; duplicate {
|
|
369
|
+
return
|
|
370
|
+
}
|
|
371
|
+
s.rootWriteSeen[identifier] = struct{}{}
|
|
372
|
+
symbol := canonicalValueSymbol(s.ctx, identifier)
|
|
373
|
+
if symbol == nil {
|
|
374
|
+
return
|
|
375
|
+
}
|
|
376
|
+
s.rootWrites = append(s.rootWrites, unicornConsistentDestructuringRootWrite{
|
|
377
|
+
symbol: symbol,
|
|
378
|
+
start: shimscanner.SkipTrivia(s.ctx.File.Text(), identifier.Pos()),
|
|
379
|
+
variableScope: unicornConsistentDestructuringVariableScope(identifier),
|
|
380
|
+
})
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// checkMember mirrors upstream's getProblem for one collected member read.
|
|
384
|
+
func (s *unicornConsistentDestructuringState) checkMember(node *shimast.Node) {
|
|
385
|
+
access := node.AsPropertyAccessExpression()
|
|
386
|
+
if access == nil || access.Name() == nil {
|
|
387
|
+
return
|
|
388
|
+
}
|
|
389
|
+
if s.shouldIgnoreMember(node) {
|
|
390
|
+
return
|
|
391
|
+
}
|
|
392
|
+
object := unicornConsistentDestructuringUnwrapObject(access.Expression)
|
|
393
|
+
if object == nil ||
|
|
394
|
+
(object.Kind != shimast.KindIdentifier && object.Kind != shimast.KindThisKeyword) {
|
|
395
|
+
return
|
|
396
|
+
}
|
|
397
|
+
matching := s.declarations[nodeText(s.ctx.File, object)]
|
|
398
|
+
if len(matching) == 0 {
|
|
399
|
+
return
|
|
400
|
+
}
|
|
401
|
+
memberStart := shimscanner.SkipTrivia(s.ctx.File.Text(), node.Pos())
|
|
402
|
+
memberVariableScope := unicornConsistentDestructuringVariableScope(node)
|
|
403
|
+
var memberRootName string
|
|
404
|
+
var memberRootSymbol *shimast.Symbol
|
|
405
|
+
var memberThisBoundary *shimast.Node
|
|
406
|
+
if object.Kind == shimast.KindIdentifier {
|
|
407
|
+
memberRootName = identifierText(object)
|
|
408
|
+
memberRootSymbol = canonicalValueSymbol(s.ctx, object)
|
|
409
|
+
} else {
|
|
410
|
+
memberThisBoundary = unicornConsistentDestructuringThisBoundary(object)
|
|
411
|
+
}
|
|
412
|
+
// Upstream matches the destructured key against the raw property text
|
|
413
|
+
// (`sourceCode.getText(node.property)`), while write shielding compares
|
|
414
|
+
// cooked names (`node.property.name`). Keep both projections.
|
|
415
|
+
memberRaw := nodeText(s.ctx.File, access.Name())
|
|
416
|
+
propertyCooked := identifierText(access.Name())
|
|
417
|
+
|
|
418
|
+
var destructured *shimast.Node
|
|
419
|
+
for index := len(matching) - 1; index >= 0; index-- {
|
|
420
|
+
declaration := matching[index]
|
|
421
|
+
if !s.declarationMatches(
|
|
422
|
+
declaration,
|
|
423
|
+
node,
|
|
424
|
+
memberStart,
|
|
425
|
+
memberVariableScope,
|
|
426
|
+
memberRootName,
|
|
427
|
+
memberRootSymbol,
|
|
428
|
+
memberThisBoundary,
|
|
429
|
+
propertyCooked,
|
|
430
|
+
) {
|
|
431
|
+
continue
|
|
432
|
+
}
|
|
433
|
+
destructured = s.availableDestructuredMember(declaration, memberRaw, node)
|
|
434
|
+
if destructured != nil {
|
|
435
|
+
break
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
if destructured == nil {
|
|
439
|
+
return
|
|
440
|
+
}
|
|
441
|
+
if s.inTypeGuardedBranch(node) {
|
|
442
|
+
return
|
|
443
|
+
}
|
|
444
|
+
if unicornConsistentDestructuringHasMemberParent(node) {
|
|
445
|
+
// Upstream never offers to rewrite a nested member chain.
|
|
446
|
+
s.ctx.Report(node, unicornConsistentDestructuringMessage)
|
|
447
|
+
return
|
|
448
|
+
}
|
|
449
|
+
replacement := identifierText(destructured)
|
|
450
|
+
s.ctx.ReportSuggestion(
|
|
451
|
+
node,
|
|
452
|
+
unicornConsistentDestructuringMessage,
|
|
453
|
+
fmt.Sprintf(
|
|
454
|
+
"Replace `%s` with destructured property `%s`.",
|
|
455
|
+
nodeText(s.ctx.File, node),
|
|
456
|
+
replacement,
|
|
457
|
+
),
|
|
458
|
+
TextEdit{Pos: memberStart, End: node.End(), Text: replacement},
|
|
459
|
+
)
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// declarationMatches ports upstream's isMatchingDeclaration: order, shadowed
|
|
463
|
+
// roots, `this` boundaries, root reassignment, member reassignment, and
|
|
464
|
+
// scope containment.
|
|
465
|
+
func (s *unicornConsistentDestructuringState) declarationMatches(
|
|
466
|
+
declaration *unicornConsistentDestructuringDeclaration,
|
|
467
|
+
member *shimast.Node,
|
|
468
|
+
memberStart int,
|
|
469
|
+
memberVariableScope *shimast.Node,
|
|
470
|
+
memberRootName string,
|
|
471
|
+
memberRootSymbol *shimast.Symbol,
|
|
472
|
+
memberThisBoundary *shimast.Node,
|
|
473
|
+
propertyCooked string,
|
|
474
|
+
) bool {
|
|
475
|
+
if declaration.objectEnd >= memberStart {
|
|
476
|
+
return false
|
|
477
|
+
}
|
|
478
|
+
if declaration.rootName != "" && memberRootName == declaration.rootName &&
|
|
479
|
+
memberRootSymbol != declaration.rootSymbol {
|
|
480
|
+
return false
|
|
481
|
+
}
|
|
482
|
+
if declaration.thisBoundary != nil && memberThisBoundary != declaration.thisBoundary {
|
|
483
|
+
return false
|
|
484
|
+
}
|
|
485
|
+
if s.rootVariableReassigned(declaration, memberStart, memberVariableScope) {
|
|
486
|
+
return false
|
|
487
|
+
}
|
|
488
|
+
if s.memberExpressionReassigned(
|
|
489
|
+
declaration,
|
|
490
|
+
memberStart,
|
|
491
|
+
memberVariableScope,
|
|
492
|
+
propertyCooked,
|
|
493
|
+
memberRootName,
|
|
494
|
+
memberRootSymbol,
|
|
495
|
+
memberThisBoundary,
|
|
496
|
+
) {
|
|
497
|
+
return false
|
|
498
|
+
}
|
|
499
|
+
return unicornConsistentDestructuringIsAncestor(declaration.scope, member)
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// rootVariableReassigned reports whether the declaration's root binding was
|
|
503
|
+
// written after the declaration and before the read — conservatively
|
|
504
|
+
// treating writes from other function scopes as always intervening, since a
|
|
505
|
+
// call or closure may run them between the two points.
|
|
506
|
+
func (s *unicornConsistentDestructuringState) rootVariableReassigned(
|
|
507
|
+
declaration *unicornConsistentDestructuringDeclaration,
|
|
508
|
+
memberStart int,
|
|
509
|
+
memberVariableScope *shimast.Node,
|
|
510
|
+
) bool {
|
|
511
|
+
if declaration.rootSymbol == nil {
|
|
512
|
+
return false
|
|
513
|
+
}
|
|
514
|
+
for _, write := range s.rootWrites {
|
|
515
|
+
if write.symbol != declaration.rootSymbol {
|
|
516
|
+
continue
|
|
517
|
+
}
|
|
518
|
+
if write.start < declaration.objectEnd {
|
|
519
|
+
continue
|
|
520
|
+
}
|
|
521
|
+
if write.variableScope != memberVariableScope {
|
|
522
|
+
return true
|
|
523
|
+
}
|
|
524
|
+
if write.start <= memberStart {
|
|
525
|
+
return true
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
return false
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// memberExpressionReassigned reports whether the same `<root>.<property>`
|
|
532
|
+
// member was written between the declaration and the read, with the same
|
|
533
|
+
// cross-function-scope conservatism as root reassignment.
|
|
534
|
+
func (s *unicornConsistentDestructuringState) memberExpressionReassigned(
|
|
535
|
+
declaration *unicornConsistentDestructuringDeclaration,
|
|
536
|
+
memberStart int,
|
|
537
|
+
memberVariableScope *shimast.Node,
|
|
538
|
+
propertyCooked string,
|
|
539
|
+
memberRootName string,
|
|
540
|
+
memberRootSymbol *shimast.Symbol,
|
|
541
|
+
memberThisBoundary *shimast.Node,
|
|
542
|
+
) bool {
|
|
543
|
+
for _, write := range s.memberWrites {
|
|
544
|
+
if write.property != propertyCooked ||
|
|
545
|
+
write.rootName != memberRootName ||
|
|
546
|
+
write.rootSymbol != memberRootSymbol ||
|
|
547
|
+
write.thisBoundary != memberThisBoundary {
|
|
548
|
+
continue
|
|
549
|
+
}
|
|
550
|
+
if write.variableScope != memberVariableScope {
|
|
551
|
+
return true
|
|
552
|
+
}
|
|
553
|
+
if write.start >= declaration.objectEnd && write.start <= memberStart {
|
|
554
|
+
return true
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
return false
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
// availableDestructuredMember finds the declaration's binding for the read
|
|
561
|
+
// property: a non-rest element with a plain identifier key, a plain
|
|
562
|
+
// identifier value, and no default, whose binding is still what the name
|
|
563
|
+
// resolves to at the read site (not shadowed in between).
|
|
564
|
+
func (s *unicornConsistentDestructuringState) availableDestructuredMember(
|
|
565
|
+
declaration *unicornConsistentDestructuringDeclaration,
|
|
566
|
+
memberRaw string,
|
|
567
|
+
member *shimast.Node,
|
|
568
|
+
) *shimast.Node {
|
|
569
|
+
pattern := declaration.pattern.AsBindingPattern()
|
|
570
|
+
if pattern == nil || pattern.Elements == nil {
|
|
571
|
+
return nil
|
|
572
|
+
}
|
|
573
|
+
for _, element := range pattern.Elements.Nodes {
|
|
574
|
+
binding := element.AsBindingElement()
|
|
575
|
+
if binding == nil || binding.DotDotDotToken != nil || binding.Initializer != nil {
|
|
576
|
+
continue
|
|
577
|
+
}
|
|
578
|
+
value := binding.Name()
|
|
579
|
+
if value == nil || value.Kind != shimast.KindIdentifier {
|
|
580
|
+
continue
|
|
581
|
+
}
|
|
582
|
+
key := binding.PropertyName
|
|
583
|
+
if key == nil {
|
|
584
|
+
key = value
|
|
585
|
+
}
|
|
586
|
+
if key.Kind != shimast.KindIdentifier || identifierText(key) != memberRaw {
|
|
587
|
+
continue
|
|
588
|
+
}
|
|
589
|
+
bindingSymbol := canonicalValueSymbol(s.ctx, value)
|
|
590
|
+
if bindingSymbol == nil {
|
|
591
|
+
continue
|
|
592
|
+
}
|
|
593
|
+
if s.resolveValueSymbol(identifierText(value), member) != bindingSymbol {
|
|
594
|
+
continue
|
|
595
|
+
}
|
|
596
|
+
return value
|
|
597
|
+
}
|
|
598
|
+
return nil
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
// resolveValueSymbol resolves a name from the member read's lexical position
|
|
602
|
+
// — the checker equivalent of upstream's findVariable(memberScope, name) —
|
|
603
|
+
// normalized the same way canonicalValueSymbol normalizes declarations.
|
|
604
|
+
func (s *unicornConsistentDestructuringState) resolveValueSymbol(
|
|
605
|
+
name string,
|
|
606
|
+
location *shimast.Node,
|
|
607
|
+
) *shimast.Symbol {
|
|
608
|
+
if s.ctx == nil || s.ctx.Checker == nil || name == "" || location == nil {
|
|
609
|
+
return nil
|
|
610
|
+
}
|
|
611
|
+
symbol := s.ctx.Checker.ResolveName(name, location, shimast.SymbolFlagsValue, false /*excludeGlobals*/)
|
|
612
|
+
if symbol == nil {
|
|
613
|
+
return nil
|
|
614
|
+
}
|
|
615
|
+
if symbol.Flags&shimast.SymbolFlagsExportValue != 0 && symbol.ExportSymbol != nil {
|
|
616
|
+
symbol = symbol.ExportSymbol
|
|
617
|
+
}
|
|
618
|
+
return s.ctx.Checker.GetMergedSymbol(symbol)
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
// shouldIgnoreMember ports upstream's shouldIgnoreMemberExpression plus its
|
|
622
|
+
// isLeftHandSide util. Element access never reaches here (only property
|
|
623
|
+
// accesses are collected), which subsumes the `computed` arm. Parentheses
|
|
624
|
+
// are transparent in the ESTree oracle, so relations are checked against the
|
|
625
|
+
// outermost paren wrapper.
|
|
626
|
+
func (s *unicornConsistentDestructuringState) shouldIgnoreMember(node *shimast.Node) bool {
|
|
627
|
+
wrapped := skipParents(node)
|
|
628
|
+
parent := wrapped.Parent
|
|
629
|
+
if parent != nil {
|
|
630
|
+
switch parent.Kind {
|
|
631
|
+
case shimast.KindCallExpression:
|
|
632
|
+
if call := parent.AsCallExpression(); call != nil && call.Expression == wrapped {
|
|
633
|
+
return true
|
|
634
|
+
}
|
|
635
|
+
case shimast.KindNewExpression:
|
|
636
|
+
if newExpression := parent.AsNewExpression(); newExpression != nil && newExpression.Expression == wrapped {
|
|
637
|
+
return true
|
|
638
|
+
}
|
|
639
|
+
case shimast.KindTaggedTemplateExpression:
|
|
640
|
+
// Replacing the tag would change the `this` binding, like a method
|
|
641
|
+
// call.
|
|
642
|
+
if tagged := parent.AsTaggedTemplateExpression(); tagged != nil && tagged.Tag == wrapped {
|
|
643
|
+
return true
|
|
644
|
+
}
|
|
645
|
+
case shimast.KindBinaryExpression:
|
|
646
|
+
binary := parent.AsBinaryExpression()
|
|
647
|
+
if binary != nil && binary.OperatorToken != nil &&
|
|
648
|
+
isAssignmentOperator(binary.OperatorToken.Kind) && binary.Left == wrapped {
|
|
649
|
+
return true
|
|
650
|
+
}
|
|
651
|
+
case shimast.KindPrefixUnaryExpression:
|
|
652
|
+
prefix := parent.AsPrefixUnaryExpression()
|
|
653
|
+
if prefix != nil &&
|
|
654
|
+
(prefix.Operator == shimast.KindPlusPlusToken || prefix.Operator == shimast.KindMinusMinusToken) &&
|
|
655
|
+
prefix.Operand == wrapped {
|
|
656
|
+
return true
|
|
657
|
+
}
|
|
658
|
+
case shimast.KindPostfixUnaryExpression:
|
|
659
|
+
postfix := parent.AsPostfixUnaryExpression()
|
|
660
|
+
if postfix != nil &&
|
|
661
|
+
(postfix.Operator == shimast.KindPlusPlusToken || postfix.Operator == shimast.KindMinusMinusToken) &&
|
|
662
|
+
postfix.Operand == wrapped {
|
|
663
|
+
return true
|
|
664
|
+
}
|
|
665
|
+
case shimast.KindDeleteExpression:
|
|
666
|
+
if deleted := parent.AsDeleteExpression(); deleted != nil && deleted.Expression == wrapped {
|
|
667
|
+
return true
|
|
668
|
+
}
|
|
669
|
+
case shimast.KindArrayLiteralExpression:
|
|
670
|
+
// ESTree's ArrayPattern-element arm: the read is a write slot only
|
|
671
|
+
// when the containing literal is itself a destructuring target.
|
|
672
|
+
return unicornConsistentDestructuringIsAssignmentPattern(parent)
|
|
673
|
+
case shimast.KindSpreadElement:
|
|
674
|
+
// ESTree's RestElement-argument arm; SpreadElement in expression
|
|
675
|
+
// position (call arguments, plain array literals) is a read.
|
|
676
|
+
if spread := parent.AsSpreadElement(); spread != nil && spread.Expression == wrapped {
|
|
677
|
+
return unicornConsistentDestructuringIsAssignmentPattern(parent)
|
|
678
|
+
}
|
|
679
|
+
case shimast.KindSpreadAssignment:
|
|
680
|
+
// Object-rest form of the RestElement-argument arm.
|
|
681
|
+
if spread := parent.AsSpreadAssignment(); spread != nil && spread.Expression == wrapped {
|
|
682
|
+
return unicornConsistentDestructuringIsAssignmentPattern(parent)
|
|
683
|
+
}
|
|
684
|
+
case shimast.KindPropertyAssignment:
|
|
685
|
+
// ESTree's Property-value-in-ObjectPattern arm; the property name is
|
|
686
|
+
// a read, and a value in a plain object literal is a read.
|
|
687
|
+
if property := parent.AsPropertyAssignment(); property != nil && property.Initializer == wrapped {
|
|
688
|
+
return unicornConsistentDestructuringIsAssignmentPattern(parent)
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
return false
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
// unicornConsistentDestructuringIsAssignmentPattern reports whether node (a
|
|
696
|
+
// pattern slot: array/object literal, spread, or property assignment) sits in
|
|
697
|
+
// a destructuring-assignment target position. This is the TypeScript-AST
|
|
698
|
+
// equivalent of ESTree spelling the containing literal as ArrayPattern /
|
|
699
|
+
// ObjectPattern: the walk ascends only through pattern edges (literal
|
|
700
|
+
// elements, property values, spreads, `=`-default lefts) and stops at
|
|
701
|
+
// anything else, so a literal in a computed index, call argument, or
|
|
702
|
+
// default-value position never counts.
|
|
703
|
+
func unicornConsistentDestructuringIsAssignmentPattern(node *shimast.Node) bool {
|
|
704
|
+
for node != nil {
|
|
705
|
+
parent := node.Parent
|
|
706
|
+
if parent == nil {
|
|
707
|
+
return false
|
|
708
|
+
}
|
|
709
|
+
switch parent.Kind {
|
|
710
|
+
case shimast.KindBinaryExpression:
|
|
711
|
+
// `[…] = value` anywhere is a destructuring assignment; `x = […]`
|
|
712
|
+
// and pattern defaults keep the literal on the right, a read.
|
|
713
|
+
binary := parent.AsBinaryExpression()
|
|
714
|
+
return binary != nil && binary.OperatorToken != nil &&
|
|
715
|
+
binary.OperatorToken.Kind == shimast.KindEqualsToken && binary.Left == node
|
|
716
|
+
case shimast.KindForInStatement, shimast.KindForOfStatement:
|
|
717
|
+
statement := parent.AsForInOrOfStatement()
|
|
718
|
+
return statement != nil && statement.Initializer == node
|
|
719
|
+
case shimast.KindArrayLiteralExpression, shimast.KindSpreadElement,
|
|
720
|
+
shimast.KindSpreadAssignment, shimast.KindObjectLiteralExpression:
|
|
721
|
+
node = parent
|
|
722
|
+
case shimast.KindPropertyAssignment:
|
|
723
|
+
property := parent.AsPropertyAssignment()
|
|
724
|
+
if property == nil || property.Initializer != node {
|
|
725
|
+
return false
|
|
726
|
+
}
|
|
727
|
+
node = parent
|
|
728
|
+
default:
|
|
729
|
+
return false
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
return false
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
// inTypeGuardedBranch ports upstream's isInTypeGuardedBranch: a read inside
|
|
736
|
+
// the positive branch of a `'prop' in obj` test narrows the type, so the
|
|
737
|
+
// destructured binding is not equivalent. Class member boundaries stop the
|
|
738
|
+
// upward walk exactly as upstream's boundary list does.
|
|
739
|
+
func (s *unicornConsistentDestructuringState) inTypeGuardedBranch(node *shimast.Node) bool {
|
|
740
|
+
child := node
|
|
741
|
+
for parent := node.Parent; parent != nil; parent = parent.Parent {
|
|
742
|
+
if unicornConsistentDestructuringIsGuardBoundary(parent) {
|
|
743
|
+
return false
|
|
744
|
+
}
|
|
745
|
+
if s.inPositiveGuardBranch(parent, child, node) {
|
|
746
|
+
return true
|
|
747
|
+
}
|
|
748
|
+
child = parent
|
|
749
|
+
}
|
|
750
|
+
return false
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
func (s *unicornConsistentDestructuringState) inPositiveGuardBranch(
|
|
754
|
+
parent *shimast.Node,
|
|
755
|
+
child *shimast.Node,
|
|
756
|
+
member *shimast.Node,
|
|
757
|
+
) bool {
|
|
758
|
+
switch parent.Kind {
|
|
759
|
+
case shimast.KindConditionalExpression:
|
|
760
|
+
conditional := parent.AsConditionalExpression()
|
|
761
|
+
return conditional != nil && conditional.WhenTrue == child &&
|
|
762
|
+
s.hasMatchingInExpression(conditional.Condition, member)
|
|
763
|
+
case shimast.KindBinaryExpression:
|
|
764
|
+
binary := parent.AsBinaryExpression()
|
|
765
|
+
return binary != nil && binary.OperatorToken != nil &&
|
|
766
|
+
binary.OperatorToken.Kind == shimast.KindAmpersandAmpersandToken &&
|
|
767
|
+
binary.Right == child &&
|
|
768
|
+
s.hasMatchingInExpression(binary.Left, member)
|
|
769
|
+
case shimast.KindIfStatement:
|
|
770
|
+
ifStatement := parent.AsIfStatement()
|
|
771
|
+
return ifStatement != nil && ifStatement.ThenStatement == child &&
|
|
772
|
+
s.hasMatchingInExpression(ifStatement.Expression, member)
|
|
773
|
+
}
|
|
774
|
+
return false
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
func (s *unicornConsistentDestructuringState) hasMatchingInExpression(
|
|
778
|
+
node *shimast.Node,
|
|
779
|
+
member *shimast.Node,
|
|
780
|
+
) bool {
|
|
781
|
+
node = stripParens(node)
|
|
782
|
+
if node == nil {
|
|
783
|
+
return false
|
|
784
|
+
}
|
|
785
|
+
if s.matchesInExpression(node, member) {
|
|
786
|
+
return true
|
|
787
|
+
}
|
|
788
|
+
if node.Kind != shimast.KindBinaryExpression {
|
|
789
|
+
return false
|
|
790
|
+
}
|
|
791
|
+
binary := node.AsBinaryExpression()
|
|
792
|
+
if binary == nil || binary.OperatorToken == nil ||
|
|
793
|
+
binary.OperatorToken.Kind != shimast.KindAmpersandAmpersandToken {
|
|
794
|
+
return false
|
|
795
|
+
}
|
|
796
|
+
return s.hasMatchingInExpression(binary.Left, member) ||
|
|
797
|
+
s.hasMatchingInExpression(binary.Right, member)
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
// matchesInExpression recognizes `'name' in <object>` where name is the read
|
|
801
|
+
// property and the right side spells the same source text as the read's
|
|
802
|
+
// object (paren-stripped on both sides, since the ESTree oracle has no paren
|
|
803
|
+
// nodes).
|
|
804
|
+
func (s *unicornConsistentDestructuringState) matchesInExpression(
|
|
805
|
+
node *shimast.Node,
|
|
806
|
+
member *shimast.Node,
|
|
807
|
+
) bool {
|
|
808
|
+
if node.Kind != shimast.KindBinaryExpression {
|
|
809
|
+
return false
|
|
810
|
+
}
|
|
811
|
+
binary := node.AsBinaryExpression()
|
|
812
|
+
if binary == nil || binary.OperatorToken == nil ||
|
|
813
|
+
binary.OperatorToken.Kind != shimast.KindInKeyword {
|
|
814
|
+
return false
|
|
815
|
+
}
|
|
816
|
+
left := stripParens(binary.Left)
|
|
817
|
+
if left == nil || left.Kind != shimast.KindStringLiteral {
|
|
818
|
+
return false
|
|
819
|
+
}
|
|
820
|
+
access := member.AsPropertyAccessExpression()
|
|
821
|
+
if access == nil {
|
|
822
|
+
return false
|
|
823
|
+
}
|
|
824
|
+
property := access.Name()
|
|
825
|
+
if property == nil || property.Kind != shimast.KindIdentifier {
|
|
826
|
+
return false
|
|
827
|
+
}
|
|
828
|
+
if stringLiteralText(left) != identifierText(property) {
|
|
829
|
+
return false
|
|
830
|
+
}
|
|
831
|
+
return nodeText(s.ctx.File, stripParens(binary.Right)) ==
|
|
832
|
+
nodeText(s.ctx.File, stripParens(access.Expression))
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
// unicornConsistentDestructuringIsGuardBoundary mirrors upstream's
|
|
836
|
+
// isInTypeGuardBoundary set: function declarations, class fields (including
|
|
837
|
+
// accessor fields), and class methods. Object-literal methods intentionally
|
|
838
|
+
// pass through — ESTree walks their FunctionExpression, which is not a
|
|
839
|
+
// boundary.
|
|
840
|
+
func unicornConsistentDestructuringIsGuardBoundary(node *shimast.Node) bool {
|
|
841
|
+
switch node.Kind {
|
|
842
|
+
case shimast.KindFunctionDeclaration, shimast.KindPropertyDeclaration:
|
|
843
|
+
return true
|
|
844
|
+
case shimast.KindMethodDeclaration, shimast.KindGetAccessor,
|
|
845
|
+
shimast.KindSetAccessor, shimast.KindConstructor:
|
|
846
|
+
return node.Parent != nil &&
|
|
847
|
+
(node.Parent.Kind == shimast.KindClassDeclaration ||
|
|
848
|
+
node.Parent.Kind == shimast.KindClassExpression)
|
|
849
|
+
}
|
|
850
|
+
return false
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
// unicornConsistentDestructuringHasMemberParent decides whether the ESTree
|
|
854
|
+
// parent of the read would itself be a MemberExpression, in which case
|
|
855
|
+
// upstream reports without a suggestion. Parentheses do not exist in ESTree,
|
|
856
|
+
// so they are skipped — except that a parenthesized (or computed-argument)
|
|
857
|
+
// optional chain is capped by a ChainExpression wrapper upstream and
|
|
858
|
+
// therefore does get a suggestion.
|
|
859
|
+
func unicornConsistentDestructuringHasMemberParent(node *shimast.Node) bool {
|
|
860
|
+
wrapped := node
|
|
861
|
+
passedParens := false
|
|
862
|
+
for wrapped.Parent != nil && wrapped.Parent.Kind == shimast.KindParenthesizedExpression {
|
|
863
|
+
wrapped = wrapped.Parent
|
|
864
|
+
passedParens = true
|
|
865
|
+
}
|
|
866
|
+
parent := wrapped.Parent
|
|
867
|
+
if parent == nil {
|
|
868
|
+
return false
|
|
869
|
+
}
|
|
870
|
+
objectPosition := false
|
|
871
|
+
switch parent.Kind {
|
|
872
|
+
case shimast.KindPropertyAccessExpression:
|
|
873
|
+
access := parent.AsPropertyAccessExpression()
|
|
874
|
+
if access == nil || access.Expression != wrapped {
|
|
875
|
+
return false
|
|
876
|
+
}
|
|
877
|
+
objectPosition = true
|
|
878
|
+
case shimast.KindElementAccessExpression:
|
|
879
|
+
access := parent.AsElementAccessExpression()
|
|
880
|
+
if access == nil {
|
|
881
|
+
return false
|
|
882
|
+
}
|
|
883
|
+
objectPosition = access.Expression == wrapped
|
|
884
|
+
if !objectPosition && access.ArgumentExpression != wrapped {
|
|
885
|
+
return false
|
|
886
|
+
}
|
|
887
|
+
default:
|
|
888
|
+
return false
|
|
889
|
+
}
|
|
890
|
+
if node.Flags&shimast.NodeFlagsOptionalChain != 0 {
|
|
891
|
+
// A ChainExpression caps the optional chain unless the outer access
|
|
892
|
+
// continues the same chain in object position.
|
|
893
|
+
if passedParens || !objectPosition {
|
|
894
|
+
return false
|
|
895
|
+
}
|
|
896
|
+
return parent.Flags&shimast.NodeFlagsOptionalChain != 0
|
|
897
|
+
}
|
|
898
|
+
return true
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
// unicornConsistentDestructuringUnwrapObject unwraps parentheses and
|
|
902
|
+
// TypeScript assertion wrappers, the union of ESTree's paren transparency
|
|
903
|
+
// and upstream's unwrapTypeScriptExpression.
|
|
904
|
+
func unicornConsistentDestructuringUnwrapObject(node *shimast.Node) *shimast.Node {
|
|
905
|
+
for node != nil {
|
|
906
|
+
switch node.Kind {
|
|
907
|
+
case shimast.KindParenthesizedExpression:
|
|
908
|
+
node = node.AsParenthesizedExpression().Expression
|
|
909
|
+
case shimast.KindAsExpression:
|
|
910
|
+
node = node.AsAsExpression().Expression
|
|
911
|
+
case shimast.KindSatisfiesExpression:
|
|
912
|
+
node = node.AsSatisfiesExpression().Expression
|
|
913
|
+
case shimast.KindNonNullExpression:
|
|
914
|
+
node = node.AsNonNullExpression().Expression
|
|
915
|
+
case shimast.KindTypeAssertionExpression:
|
|
916
|
+
node = node.AsTypeAssertion().Expression
|
|
917
|
+
default:
|
|
918
|
+
return node
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
return nil
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
// unicornConsistentDestructuringIsJsxTagName reports whether the property
|
|
925
|
+
// access spells (part of) a JSX element tag name. ESTree types those as
|
|
926
|
+
// JSXMemberExpression, which upstream's MemberExpression listener never
|
|
927
|
+
// sees.
|
|
928
|
+
func unicornConsistentDestructuringIsJsxTagName(node *shimast.Node) bool {
|
|
929
|
+
current := node
|
|
930
|
+
for current.Parent != nil && current.Parent.Kind == shimast.KindPropertyAccessExpression {
|
|
931
|
+
access := current.Parent.AsPropertyAccessExpression()
|
|
932
|
+
if access == nil || access.Expression != current {
|
|
933
|
+
return false
|
|
934
|
+
}
|
|
935
|
+
current = current.Parent
|
|
936
|
+
}
|
|
937
|
+
parent := current.Parent
|
|
938
|
+
if parent == nil {
|
|
939
|
+
return false
|
|
940
|
+
}
|
|
941
|
+
switch parent.Kind {
|
|
942
|
+
case shimast.KindJsxOpeningElement, shimast.KindJsxClosingElement,
|
|
943
|
+
shimast.KindJsxSelfClosingElement:
|
|
944
|
+
return true
|
|
945
|
+
}
|
|
946
|
+
return false
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
// unicornConsistentDestructuringNearestScope returns the nearest
|
|
950
|
+
// scope-creating ancestor. A read matches a declaration only when the read
|
|
951
|
+
// sits inside this node, which is the AST equivalent of eslint-scope's
|
|
952
|
+
// child-scope chain containment.
|
|
953
|
+
func unicornConsistentDestructuringNearestScope(node *shimast.Node) *shimast.Node {
|
|
954
|
+
for current := node.Parent; current != nil; current = current.Parent {
|
|
955
|
+
switch current.Kind {
|
|
956
|
+
case shimast.KindSourceFile, shimast.KindBlock, shimast.KindModuleBlock,
|
|
957
|
+
shimast.KindCaseBlock, shimast.KindCatchClause,
|
|
958
|
+
shimast.KindForStatement, shimast.KindForInStatement, shimast.KindForOfStatement,
|
|
959
|
+
shimast.KindFunctionDeclaration, shimast.KindFunctionExpression, shimast.KindArrowFunction,
|
|
960
|
+
shimast.KindMethodDeclaration, shimast.KindGetAccessor, shimast.KindSetAccessor,
|
|
961
|
+
shimast.KindConstructor, shimast.KindClassStaticBlockDeclaration,
|
|
962
|
+
shimast.KindClassDeclaration, shimast.KindClassExpression,
|
|
963
|
+
shimast.KindPropertyDeclaration, shimast.KindEnumDeclaration, shimast.KindModuleDeclaration:
|
|
964
|
+
return current
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
return nil
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
// unicornConsistentDestructuringVariableScope returns the nearest
|
|
971
|
+
// function-like scope — eslint-scope's variableScope — used by the
|
|
972
|
+
// conservative cross-scope write checks.
|
|
973
|
+
func unicornConsistentDestructuringVariableScope(node *shimast.Node) *shimast.Node {
|
|
974
|
+
for current := node.Parent; current != nil; current = current.Parent {
|
|
975
|
+
switch current.Kind {
|
|
976
|
+
case shimast.KindSourceFile,
|
|
977
|
+
shimast.KindFunctionDeclaration, shimast.KindFunctionExpression, shimast.KindArrowFunction,
|
|
978
|
+
shimast.KindMethodDeclaration, shimast.KindGetAccessor, shimast.KindSetAccessor,
|
|
979
|
+
shimast.KindConstructor, shimast.KindClassStaticBlockDeclaration,
|
|
980
|
+
shimast.KindPropertyDeclaration, shimast.KindEnumDeclaration, shimast.KindModuleDeclaration:
|
|
981
|
+
return current
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
return nil
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
// unicornConsistentDestructuringThisBoundary returns the node that owns the
|
|
988
|
+
// `this` binding at `node`. Arrow functions inherit `this` and are
|
|
989
|
+
// deliberately absent, matching upstream's boundary list.
|
|
990
|
+
func unicornConsistentDestructuringThisBoundary(node *shimast.Node) *shimast.Node {
|
|
991
|
+
for current := node.Parent; current != nil; current = current.Parent {
|
|
992
|
+
switch current.Kind {
|
|
993
|
+
case shimast.KindSourceFile,
|
|
994
|
+
shimast.KindFunctionDeclaration, shimast.KindFunctionExpression,
|
|
995
|
+
shimast.KindMethodDeclaration, shimast.KindGetAccessor, shimast.KindSetAccessor,
|
|
996
|
+
shimast.KindConstructor, shimast.KindPropertyDeclaration,
|
|
997
|
+
shimast.KindClassStaticBlockDeclaration:
|
|
998
|
+
return current
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
return nil
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
// unicornConsistentDestructuringIsAncestor reports whether scope is an
|
|
1005
|
+
// ancestor of node.
|
|
1006
|
+
func unicornConsistentDestructuringIsAncestor(scope *shimast.Node, node *shimast.Node) bool {
|
|
1007
|
+
if scope == nil || node == nil {
|
|
1008
|
+
return false
|
|
1009
|
+
}
|
|
1010
|
+
for current := node.Parent; current != nil; current = current.Parent {
|
|
1011
|
+
if current == scope {
|
|
1012
|
+
return true
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
return false
|
|
21
1016
|
}
|
|
22
1017
|
|
|
23
1018
|
func init() {
|