@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
package/linthost/rules_var.go
CHANGED
|
@@ -4,42 +4,66 @@ import shimast "github.com/microsoft/typescript-go/shim/ast"
|
|
|
4
4
|
|
|
5
5
|
// noVar: ban `var` declarations. ESLint canonical:
|
|
6
6
|
// https://eslint.org/docs/latest/rules/no-var
|
|
7
|
+
//
|
|
8
|
+
// The rule registers KindVariableDeclarationList, not KindVariableStatement:
|
|
9
|
+
// the grammar puts a `var` declaration list either inside a VariableStatement
|
|
10
|
+
// or directly in a `for` / `for...in` / `for...of` header, and only the list
|
|
11
|
+
// node is common to all four shapes. Registering the statement kind alone
|
|
12
|
+
// left every loop-header `var` invisible (issue #409). Each list node occurs
|
|
13
|
+
// exactly once in the tree, so every shape reports exactly once and no shape
|
|
14
|
+
// can double-report.
|
|
7
15
|
type noVar struct{}
|
|
8
16
|
|
|
9
|
-
func (noVar) Name() string
|
|
10
|
-
func (noVar) Visits() []shimast.Kind {
|
|
17
|
+
func (noVar) Name() string { return "no-var" }
|
|
18
|
+
func (noVar) Visits() []shimast.Kind {
|
|
19
|
+
return []shimast.Kind{shimast.KindVariableDeclarationList}
|
|
20
|
+
}
|
|
11
21
|
func (noVar) Check(ctx *Context, node *shimast.Node) {
|
|
12
|
-
|
|
13
|
-
if stmt == nil || stmt.DeclarationList == nil {
|
|
22
|
+
if node.AsVariableDeclarationList() == nil || !shimast.IsVar(node) {
|
|
14
23
|
return
|
|
15
24
|
}
|
|
16
25
|
if ctx.File != nil && ctx.File.IsDeclarationFile {
|
|
17
26
|
return
|
|
18
27
|
}
|
|
19
|
-
|
|
28
|
+
owner := node.Parent
|
|
29
|
+
ownedByStatement := owner != nil && owner.Kind == shimast.KindVariableStatement
|
|
30
|
+
// `declare var x` describes an existing binding instead of creating one;
|
|
31
|
+
// like ESLint, the rule leaves ambient declarations alone. Only a
|
|
32
|
+
// VariableStatement can carry the modifier — loop headers cannot be
|
|
33
|
+
// ambient outside declaration files, which returned above.
|
|
34
|
+
if ownedByStatement && owner.ModifierFlags()&shimast.ModifierFlagsAmbient != 0 {
|
|
20
35
|
return
|
|
21
36
|
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
37
|
+
const message = "Unexpected var, use let or const instead."
|
|
38
|
+
start := keywordStart(ctx.File, node, "var")
|
|
39
|
+
if start >= 0 && isNoVarAutoFixSafe(ctx, node) {
|
|
40
|
+
ctx.ReportRangeFix(
|
|
41
|
+
start,
|
|
42
|
+
start+len("var"),
|
|
43
|
+
message,
|
|
44
|
+
TextEdit{Pos: start, End: start + len("var"), Text: "let"},
|
|
45
|
+
)
|
|
46
|
+
return
|
|
47
|
+
}
|
|
48
|
+
if ownedByStatement {
|
|
49
|
+
// Fixless statement reports keep the whole-statement range (through the
|
|
50
|
+
// terminating semicolon) the rule has always rendered.
|
|
51
|
+
ctx.Report(owner, message)
|
|
52
|
+
return
|
|
35
53
|
}
|
|
54
|
+
// A loop-header list has no wrapping statement of its own; the list node
|
|
55
|
+
// (`var i = 0`) is the natural diagnostic range.
|
|
56
|
+
ctx.Report(node, message)
|
|
36
57
|
}
|
|
37
58
|
|
|
38
|
-
// isNoVarAutoFixSafe reports whether rewriting a `var`
|
|
39
|
-
// `let` is safe using only AST-local information (no
|
|
40
|
-
//
|
|
41
|
-
//
|
|
42
|
-
//
|
|
59
|
+
// isNoVarAutoFixSafe reports whether rewriting a `var` declaration list's
|
|
60
|
+
// keyword to `let` is safe using only AST-local information (no
|
|
61
|
+
// scope/data-flow engine). `listNode` is the KindVariableDeclarationList the
|
|
62
|
+
// rule visited; its parent is a VariableStatement or a `for` / `for...in` /
|
|
63
|
+
// `for...of` header. It mirrors the conservative posture of
|
|
64
|
+
// isEqeqeqAutoFixSafe: over-declining is fine, corrupting source is not.
|
|
65
|
+
// Blindly turning every `var` into `let` breaks two real shapes that `var`
|
|
66
|
+
// hoisting tolerates but `let` does not:
|
|
43
67
|
// - redeclaration (`var x=1; var x=2;`) → two `let x` is a SyntaxError;
|
|
44
68
|
// - use-before-declaration (a binding referenced above its own line) →
|
|
45
69
|
// `let` makes that reference a TDZ ReferenceError.
|
|
@@ -47,8 +71,8 @@ func (noVar) Check(ctx *Context, node *shimast.Node) {
|
|
|
47
71
|
// Five corruption holes were patched piecemeal here before (var-vs-var,
|
|
48
72
|
// for-header var, function/class redeclaration, mixed destructuring sibling,
|
|
49
73
|
// object-literal shorthand, use-before-declaration). That whack-a-mole is
|
|
50
|
-
// replaced by one conservative rule with
|
|
51
|
-
// emitted only if
|
|
74
|
+
// replaced by one conservative rule with five preconditions; the fix is
|
|
75
|
+
// emitted only if ALL hold:
|
|
52
76
|
//
|
|
53
77
|
// 1. Single binding in the whole file. The declared name is introduced by
|
|
54
78
|
// EXACTLY ONE binding position anywhere in the source — counting every
|
|
@@ -60,50 +84,163 @@ func (noVar) Check(ctx *Context, node *shimast.Node) {
|
|
|
60
84
|
// destructure siblings, for-header var, …). It over-declines harmless
|
|
61
85
|
// cross-scope same-name bindings, which never corrupts.
|
|
62
86
|
// 2. No use-before-declaration / TDZ. The declared name is not referenced as
|
|
63
|
-
// a VALUE before the
|
|
87
|
+
// a VALUE before the list's Pos(). A non-reference occurrence of the
|
|
64
88
|
// same text — a member name (`o.x`), an object-literal key (`{x:1}`), a
|
|
65
89
|
// statement label (`x:`), or a type reference (`: x`) — binds no value and
|
|
66
90
|
// must not force a decline; isValueReferenceIdentifier classifies these.
|
|
91
|
+
// 3. No block-scope escape. `var` is function/global-scoped while `let` is
|
|
92
|
+
// block-scoped, so a `var` declared inside a block and read after the
|
|
93
|
+
// block (`if (c) { var x = 1; } log(x);`) would stop compiling under
|
|
94
|
+
// `let`. For a statement list the statement's parent must be a position
|
|
95
|
+
// where a lexical declaration is legal AND which forms the `let`'s block
|
|
96
|
+
// scope — a Block (plain, function-body, loop-body, …), a ModuleBlock, a
|
|
97
|
+
// switch Case/DefaultClause, or the SourceFile — and every value
|
|
98
|
+
// reference to the name must lie inside that parent's [Pos, End) span. A
|
|
99
|
+
// non-scope parent (`if (c) var x = 1;` — a single-statement body, where
|
|
100
|
+
// `let` is a SyntaxError outright) declines unconditionally. For a
|
|
101
|
+
// loop-header list the loop statement itself is the scope: a header
|
|
102
|
+
// `let` is always grammatically legal and scopes to the whole loop
|
|
103
|
+
// (header plus body), so references must stay inside the loop's span
|
|
104
|
+
// (`for (var i = 0; …) {}` followed by `log(i);` declines). Given
|
|
105
|
+
// precondition 1 (one binding file-wide), positional containment is
|
|
106
|
+
// exact: no other binding can shadow the name, so a reference outside
|
|
107
|
+
// the span really does resolve to this binding. Using the CaseClause
|
|
108
|
+
// (not the whole switch CaseBlock) as the scope over-declines cross-case
|
|
109
|
+
// references, which under `let` would flip from `undefined` reads to
|
|
110
|
+
// runtime TDZ throws — declining is the safe side. Mirrors ESLint
|
|
111
|
+
// no-var's isUsedFromOutsideOf.
|
|
112
|
+
// 4. No loop-closure capture. When the declaration sits inside a loop — a
|
|
113
|
+
// statement in a loop body OR the loop's own header — and the name is
|
|
114
|
+
// referenced from a function or arrow nested within that loop
|
|
115
|
+
// (`for (const k of keys) { var last = k; fns.push(() => last); }`,
|
|
116
|
+
// `for (var i = 0; i < n; i++) { fns.push(() => i); }`), every closure
|
|
117
|
+
// shares ONE `var` binding but would capture a FRESH per-iteration `let`
|
|
118
|
+
// binding — the rewrite silently changes runtime results. Mirrors ESLint
|
|
119
|
+
// no-var's isReferencedInClosure loop check.
|
|
120
|
+
// 5. Not declared under a `with` statement. `var` hoists PAST the with body
|
|
121
|
+
// to the function scope, so references inside the body resolve through
|
|
122
|
+
// the with object first (`o.x` shadows the var when present); `let`
|
|
123
|
+
// stays inside the body's block scope, where it shadows the with object
|
|
124
|
+
// instead. The rewrite can flip which binding every reference hits, so
|
|
125
|
+
// any var with a WithStatement ancestor below the nearest function
|
|
126
|
+
// boundary declines.
|
|
67
127
|
//
|
|
68
|
-
//
|
|
128
|
+
// Two loop-header-only grammar/TDZ hazards also decline:
|
|
129
|
+
// - a `for...in` / `for...of` declarator with an initializer (Annex B
|
|
130
|
+
// tolerates `for (var i = 0 in o)`; `for (let i = 0 in o)` is a
|
|
131
|
+
// SyntaxError), and
|
|
132
|
+
// - a value reference to the name inside the `for...in` / `for...of` head
|
|
133
|
+
// expression (`for (var x of x)` reads the hoisted undefined `var`;
|
|
134
|
+
// under `let` the head expression evaluates inside the binding's TDZ
|
|
135
|
+
// and throws a ReferenceError).
|
|
136
|
+
//
|
|
137
|
+
// The var list being fixed must itself be a single plain identifier
|
|
69
138
|
// declarator so the keyword rewrite has a simple `let x` rename target; a
|
|
70
|
-
// destructuring var (`var {a}=o`) or a multi-binding
|
|
71
|
-
// even though its names might each bind only once.
|
|
72
|
-
func isNoVarAutoFixSafe(ctx *Context,
|
|
139
|
+
// destructuring var (`var {a}=o`, `for (var [a] of …)`) or a multi-binding
|
|
140
|
+
// list is declined here even though its names might each bind only once.
|
|
141
|
+
func isNoVarAutoFixSafe(ctx *Context, listNode *shimast.Node) bool {
|
|
73
142
|
if ctx == nil || ctx.File == nil {
|
|
74
143
|
return false
|
|
75
144
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
//
|
|
81
|
-
|
|
82
|
-
|
|
145
|
+
owner := listNode.Parent
|
|
146
|
+
if owner == nil {
|
|
147
|
+
return false
|
|
148
|
+
}
|
|
149
|
+
// Precondition: the var being fixed is a single plain identifier
|
|
150
|
+
// declarator. identifierText returns "" for a destructuring pattern, so
|
|
151
|
+
// requiring exactly one VariableDeclaration node AND a non-empty plain
|
|
152
|
+
// name rejects both multi-binding lists and destructured declarators.
|
|
153
|
+
list := listNode.AsVariableDeclarationList()
|
|
154
|
+
if list == nil || list.Declarations == nil || len(list.Declarations.Nodes) != 1 {
|
|
155
|
+
return false
|
|
156
|
+
}
|
|
157
|
+
decl := list.Declarations.Nodes[0].AsVariableDeclaration()
|
|
158
|
+
if decl == nil {
|
|
159
|
+
return false
|
|
160
|
+
}
|
|
161
|
+
target := identifierText(decl.Name())
|
|
162
|
+
if target == "" {
|
|
83
163
|
return false
|
|
84
164
|
}
|
|
85
|
-
|
|
86
|
-
|
|
165
|
+
|
|
166
|
+
// A name `var` tolerates but `let` cannot redeclare: `let let = 1;` is a
|
|
167
|
+
// SyntaxError everywhere, and `let static = 1;` is one in strict mode
|
|
168
|
+
// (modules, classes) — while `var let` / `var static` parse in sloppy
|
|
169
|
+
// scripts. Mirrors upstream ESLint no-var's DISALLOWED_LET_NAMES.
|
|
170
|
+
if target == "let" || target == "static" {
|
|
87
171
|
return false
|
|
88
172
|
}
|
|
89
|
-
target := names[0]
|
|
90
173
|
|
|
91
|
-
|
|
174
|
+
// Precondition 3 setup: resolve the node that will delimit the rewritten
|
|
175
|
+
// `let` binding's block scope, per owner shape.
|
|
176
|
+
var scopeNode *shimast.Node
|
|
177
|
+
switch owner.Kind {
|
|
178
|
+
case shimast.KindVariableStatement:
|
|
179
|
+
// The statement's parent must both allow a lexical declaration and act
|
|
180
|
+
// as the `let`'s block scope. Any other parent kind is a
|
|
181
|
+
// single-statement body (`if (c) var x = 1;`, `while (c) var x = 1;`,
|
|
182
|
+
// `label: var x = 1;`, `with (o) var x = 1;`) where `let` is a plain
|
|
183
|
+
// SyntaxError, so the fix declines before any reference is examined.
|
|
184
|
+
scopeNode = owner.Parent
|
|
185
|
+
if scopeNode == nil || !isBlockScopeContainer(scopeNode.Kind) {
|
|
186
|
+
return false
|
|
187
|
+
}
|
|
188
|
+
case shimast.KindForStatement:
|
|
189
|
+
scopeNode = owner
|
|
190
|
+
case shimast.KindForInStatement, shimast.KindForOfStatement:
|
|
191
|
+
// Annex B parses `for (var i = 0 in o)` in sloppy scripts; the same
|
|
192
|
+
// header with `let` is a SyntaxError everywhere, so any initializer on
|
|
193
|
+
// a for-in/for-of declarator declines outright.
|
|
194
|
+
if decl.Initializer != nil {
|
|
195
|
+
return false
|
|
196
|
+
}
|
|
197
|
+
scopeNode = owner
|
|
198
|
+
default:
|
|
199
|
+
// A declaration list can only be owned by the four shapes above; an
|
|
200
|
+
// unrecognized owner keeps the diagnostic but never offers a rewrite.
|
|
201
|
+
return false
|
|
202
|
+
}
|
|
203
|
+
scopeStart, scopeEnd := scopeNode.Pos(), scopeNode.End()
|
|
204
|
+
|
|
205
|
+
// Precondition 5: a `var` declared under a `with` statement resolves its
|
|
206
|
+
// references through the with object; the block-scoped `let` would shadow
|
|
207
|
+
// that object instead, so the rewrite declines outright.
|
|
208
|
+
if isDeclaredInsideWithStatement(listNode) {
|
|
209
|
+
return false
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Precondition 4 setup: the outermost loop enclosing the declaration
|
|
213
|
+
// without an intervening function boundary. A loop-header list's first
|
|
214
|
+
// ancestor IS its loop, so header declarations always run the
|
|
215
|
+
// closure-capture check. nil when the declaration is not loop-local,
|
|
216
|
+
// which disables the check.
|
|
217
|
+
enclosingLoop := enclosingLoopWithinFunction(listNode)
|
|
218
|
+
|
|
219
|
+
declPos := listNode.Pos()
|
|
92
220
|
// The single declarator's initializer subtree. A value reference to `target`
|
|
93
221
|
// inside this range is a self-reference that runs while `target` is still in
|
|
94
222
|
// its temporal dead zone under `let` (`var x = typeof x;`, `var x = x;`,
|
|
95
223
|
// `var x = (() => x)();`), so it must also force a decline even though its
|
|
96
|
-
// Pos() is AFTER the
|
|
224
|
+
// Pos() is AFTER the list's start. initStart/initEnd are -1 when the
|
|
97
225
|
// declarator has no initializer, which disables the range check.
|
|
98
226
|
initStart, initEnd := -1, -1
|
|
99
|
-
if
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
227
|
+
if decl.Initializer != nil {
|
|
228
|
+
initStart, initEnd = decl.Initializer.Pos(), decl.Initializer.End()
|
|
229
|
+
}
|
|
230
|
+
// The for-in/for-of head expression evaluates while a `let` loop binding
|
|
231
|
+
// is still in its TDZ, so a self-reference there (`for (var x of x)`)
|
|
232
|
+
// must decline exactly like an initializer self-reference. -1 for the
|
|
233
|
+
// other owner shapes, which have no head expression.
|
|
234
|
+
exprStart, exprEnd := -1, -1
|
|
235
|
+
if owner.Kind == shimast.KindForInStatement || owner.Kind == shimast.KindForOfStatement {
|
|
236
|
+
if stmt := owner.AsForInOrOfStatement(); stmt != nil && stmt.Expression != nil {
|
|
237
|
+
exprStart, exprEnd = stmt.Expression.Pos(), stmt.Expression.End()
|
|
103
238
|
}
|
|
104
239
|
}
|
|
105
240
|
bindingCount := 0
|
|
106
241
|
referencedBefore := false
|
|
242
|
+
escapesScope := false
|
|
243
|
+
capturedInLoop := false
|
|
107
244
|
walkDescendants(ctx.File.AsNode(), func(child *shimast.Node) {
|
|
108
245
|
// Binding count: every position that introduces the name `target` as a
|
|
109
246
|
// binding (a declaration), not a value reference. Two or more positions
|
|
@@ -115,28 +252,167 @@ func isNoVarAutoFixSafe(ctx *Context, node *shimast.Node) bool {
|
|
|
115
252
|
}
|
|
116
253
|
// TDZ: a value reference to `target` turns into a ReferenceError under
|
|
117
254
|
// `let` when it executes while `target` is still in its temporal dead
|
|
118
|
-
// zone.
|
|
119
|
-
// - a reference BEFORE the
|
|
255
|
+
// zone. Three cases decline:
|
|
256
|
+
// - a reference BEFORE the list's own position (`log(x); var x = 1;`);
|
|
120
257
|
// - a self-reference WITHIN the declarator's own initializer range
|
|
121
258
|
// (`var x = typeof x;`). Conservatively, any value reference inside
|
|
122
259
|
// the initializer declines — including a deferred read in a nested
|
|
123
260
|
// closure (`var f = () => f;`) that is actually safe — because the
|
|
124
261
|
// AST-local gate does not track whether that closure runs during
|
|
125
|
-
// initialization. Over-declining never corrupts source
|
|
262
|
+
// initialization. Over-declining never corrupts source;
|
|
263
|
+
// - a self-reference within a for-in/for-of head expression
|
|
264
|
+
// (`for (var x of x)`), which evaluates inside the `let` TDZ.
|
|
126
265
|
if child.Kind == shimast.KindIdentifier && identifierText(child) == target &&
|
|
127
266
|
isValueReferenceIdentifier(child) {
|
|
128
267
|
pos := child.Pos()
|
|
129
|
-
if pos < declPos ||
|
|
268
|
+
if pos < declPos ||
|
|
269
|
+
(initStart >= 0 && pos >= initStart && pos < initEnd) ||
|
|
270
|
+
(exprStart >= 0 && pos >= exprStart && pos < exprEnd) {
|
|
130
271
|
referencedBefore = true
|
|
131
272
|
}
|
|
273
|
+
// Scope escape: a value reference outside the enclosing block-scope
|
|
274
|
+
// node's span stops resolving (or flips to a TDZ throw across switch
|
|
275
|
+
// cases) once the binding becomes block-scoped `let`. Nested blocks
|
|
276
|
+
// WITHIN the span stay fixable: containment, not clause equality.
|
|
277
|
+
if pos < scopeStart || pos >= scopeEnd {
|
|
278
|
+
escapesScope = true
|
|
279
|
+
}
|
|
280
|
+
// Loop-closure capture: a reference reached only by crossing a
|
|
281
|
+
// function/arrow boundary between itself and the enclosing loop would
|
|
282
|
+
// switch from one shared `var` binding to a fresh per-iteration `let`
|
|
283
|
+
// binding.
|
|
284
|
+
if enclosingLoop != nil && isCapturedInLoopClosure(child, enclosingLoop) {
|
|
285
|
+
capturedInLoop = true
|
|
286
|
+
}
|
|
132
287
|
}
|
|
133
288
|
})
|
|
134
|
-
if referencedBefore {
|
|
289
|
+
if referencedBefore || escapesScope || capturedInLoop {
|
|
135
290
|
return false
|
|
136
291
|
}
|
|
137
292
|
return bindingCount == 1
|
|
138
293
|
}
|
|
139
294
|
|
|
295
|
+
// isBlockScopeContainer reports whether a node kind is a legal parent for a
|
|
296
|
+
// lexical (`let`) declaration statement AND the node that delimits the
|
|
297
|
+
// resulting binding's block scope: a Block (plain, function body, loop body,
|
|
298
|
+
// try/catch/finally, labeled block, class static block body — all
|
|
299
|
+
// KindBlock), a namespace's ModuleBlock, a switch clause, or the SourceFile
|
|
300
|
+
// itself. Every other statement position (an unbraced if/else/loop/with body
|
|
301
|
+
// or a directly-labeled statement) rejects lexical declarations at the
|
|
302
|
+
// grammar level, so the keyword rewrite is never legal there.
|
|
303
|
+
//
|
|
304
|
+
// A CaseClause/DefaultClause is used as the scope rather than the enclosing
|
|
305
|
+
// CaseBlock even though `let` technically hoists to the whole switch block:
|
|
306
|
+
// a reference in a LATER clause compiles under `let` but changes an
|
|
307
|
+
// `undefined` read into a runtime TDZ ReferenceError when the declaring
|
|
308
|
+
// clause did not execute first. Bounding the scope at the clause declines
|
|
309
|
+
// that shape; over-declining never corrupts.
|
|
310
|
+
func isBlockScopeContainer(kind shimast.Kind) bool {
|
|
311
|
+
switch kind {
|
|
312
|
+
case shimast.KindBlock,
|
|
313
|
+
shimast.KindModuleBlock,
|
|
314
|
+
shimast.KindCaseClause,
|
|
315
|
+
shimast.KindDefaultClause,
|
|
316
|
+
shimast.KindSourceFile:
|
|
317
|
+
return true
|
|
318
|
+
}
|
|
319
|
+
return false
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// isFunctionCaptureBoundary reports whether a node creates a new function
|
|
323
|
+
// scope whose body captures outer bindings by closure: function
|
|
324
|
+
// declarations/expressions, arrows, methods, constructors, accessors, class
|
|
325
|
+
// static blocks, and class property declarations (an instance field
|
|
326
|
+
// initializer runs at construction time and closes over the class
|
|
327
|
+
// definition's environment exactly like a method body; escope models it as
|
|
328
|
+
// its own variable scope). A PropertyDeclaration's computed NAME evaluates
|
|
329
|
+
// immediately rather than deferred, so classifying the whole declaration
|
|
330
|
+
// over-declines that rare shape — which never corrupts. Used both to stop
|
|
331
|
+
// the enclosing-loop walk (a `var` inside a function nested in a loop is
|
|
332
|
+
// per-call regardless of the loop) and to detect references that reach the
|
|
333
|
+
// loop only through deferred code.
|
|
334
|
+
func isFunctionCaptureBoundary(node *shimast.Node) bool {
|
|
335
|
+
switch node.Kind {
|
|
336
|
+
case shimast.KindFunctionDeclaration,
|
|
337
|
+
shimast.KindFunctionExpression,
|
|
338
|
+
shimast.KindArrowFunction,
|
|
339
|
+
shimast.KindMethodDeclaration,
|
|
340
|
+
shimast.KindConstructor,
|
|
341
|
+
shimast.KindGetAccessor,
|
|
342
|
+
shimast.KindSetAccessor,
|
|
343
|
+
shimast.KindClassStaticBlockDeclaration,
|
|
344
|
+
shimast.KindPropertyDeclaration:
|
|
345
|
+
return true
|
|
346
|
+
}
|
|
347
|
+
return false
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// isDeclaredInsideWithStatement reports whether the declaration (a
|
|
351
|
+
// statement's or loop header's `var` list) has a WithStatement ancestor
|
|
352
|
+
// below the nearest function boundary. Under `var`
|
|
353
|
+
// the binding hoists past the with body to the function scope, so a
|
|
354
|
+
// same-name property on the with target intercepts every reference; under
|
|
355
|
+
// `let` the binding lives inside the body's block and shadows the with
|
|
356
|
+
// object instead. A function boundary resets the risk: a `var` inside a
|
|
357
|
+
// function nested in the with body binds tighter than the with object
|
|
358
|
+
// either way.
|
|
359
|
+
func isDeclaredInsideWithStatement(node *shimast.Node) bool {
|
|
360
|
+
for ancestor := node.Parent; ancestor != nil; ancestor = ancestor.Parent {
|
|
361
|
+
if isFunctionCaptureBoundary(ancestor) {
|
|
362
|
+
return false
|
|
363
|
+
}
|
|
364
|
+
if ancestor.Kind == shimast.KindWithStatement {
|
|
365
|
+
return true
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
return false
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// enclosingLoopWithinFunction returns the OUTERMOST loop statement enclosing
|
|
372
|
+
// `node` without an intervening function boundary, or nil when no such loop
|
|
373
|
+
// exists. The walk stops at the nearest function-like ancestor because a
|
|
374
|
+
// `var` declared inside a function nested in a loop already gets a fresh
|
|
375
|
+
// binding per call — the loop outside the function cannot make `var` and
|
|
376
|
+
// `let` capture semantics diverge for it.
|
|
377
|
+
func enclosingLoopWithinFunction(node *shimast.Node) *shimast.Node {
|
|
378
|
+
var loop *shimast.Node
|
|
379
|
+
for ancestor := node.Parent; ancestor != nil; ancestor = ancestor.Parent {
|
|
380
|
+
if isFunctionCaptureBoundary(ancestor) {
|
|
381
|
+
break
|
|
382
|
+
}
|
|
383
|
+
switch ancestor.Kind {
|
|
384
|
+
case shimast.KindForStatement,
|
|
385
|
+
shimast.KindForInStatement,
|
|
386
|
+
shimast.KindForOfStatement,
|
|
387
|
+
shimast.KindWhileStatement,
|
|
388
|
+
shimast.KindDoStatement:
|
|
389
|
+
loop = ancestor
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
return loop
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// isCapturedInLoopClosure reports whether a value-reference identifier
|
|
396
|
+
// reaches `loop` on its ancestor chain only after crossing a function
|
|
397
|
+
// boundary — i.e. the reference lives in a closure created inside the loop.
|
|
398
|
+
// Under `var` every iteration's closure shares one binding; under `let` each
|
|
399
|
+
// iteration captures a fresh binding, so such a reference makes the keyword
|
|
400
|
+
// rewrite change observable behavior. A reference whose chain never meets
|
|
401
|
+
// `loop` lies outside the loop entirely and is left to the scope-containment
|
|
402
|
+
// check.
|
|
403
|
+
func isCapturedInLoopClosure(ref, loop *shimast.Node) bool {
|
|
404
|
+
crossedFunction := false
|
|
405
|
+
for ancestor := ref.Parent; ancestor != nil; ancestor = ancestor.Parent {
|
|
406
|
+
if ancestor == loop {
|
|
407
|
+
return crossedFunction
|
|
408
|
+
}
|
|
409
|
+
if isFunctionCaptureBoundary(ancestor) {
|
|
410
|
+
crossedFunction = true
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
return false
|
|
414
|
+
}
|
|
415
|
+
|
|
140
416
|
// bindingNamesIntroducedBy returns every plain identifier name that `node`
|
|
141
417
|
// introduces as a binding in its OWN slot — never the names bound by its
|
|
142
418
|
// descendants, which the file-wide walk visits independently. The classifier
|
|
@@ -286,142 +562,553 @@ func isValueReferenceIdentifier(node *shimast.Node) bool {
|
|
|
286
562
|
return true
|
|
287
563
|
}
|
|
288
564
|
|
|
289
|
-
// preferConst
|
|
290
|
-
//
|
|
291
|
-
//
|
|
292
|
-
//
|
|
293
|
-
//
|
|
294
|
-
//
|
|
295
|
-
|
|
565
|
+
// preferConst follows ESLint's binding semantics rather than comparing
|
|
566
|
+
// identifier text. TypeScript's checker resolves every declaration and write
|
|
567
|
+
// target to its lexical symbol, so same-spelled bindings in sibling, nested,
|
|
568
|
+
// and shadowing scopes remain independent. The rule records the declaration's
|
|
569
|
+
// initialization plus subsequent writes, then reports bindings with exactly
|
|
570
|
+
// one effective initialization.
|
|
571
|
+
//
|
|
572
|
+
// Declaration-only bindings are diagnostic-only. Rewriting `let value;` plus
|
|
573
|
+
// a later `value = expression` requires moving comments and changing statement
|
|
574
|
+
// structure, so the existing keyword edit stays limited to an initialized,
|
|
575
|
+
// single-declarator list. ESLint canonical:
|
|
576
|
+
// https://eslint.org/docs/latest/rules/prefer-const
|
|
577
|
+
type preferConst struct{ optionsRule }
|
|
578
|
+
|
|
579
|
+
type preferConstOptions struct {
|
|
580
|
+
Destructuring string `json:"destructuring"`
|
|
581
|
+
IgnoreReadBeforeAssign bool `json:"ignoreReadBeforeAssign"`
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
type preferConstWriteKind uint8
|
|
585
|
+
|
|
586
|
+
const (
|
|
587
|
+
preferConstSimpleAssignment preferConstWriteKind = iota
|
|
588
|
+
preferConstReassignment
|
|
589
|
+
)
|
|
590
|
+
|
|
591
|
+
type preferConstWrite struct {
|
|
592
|
+
target *shimast.Node
|
|
593
|
+
assignment *shimast.Node
|
|
594
|
+
kind preferConstWriteKind
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
type preferConstCandidate struct {
|
|
598
|
+
symbol *shimast.Symbol
|
|
599
|
+
nameNode *shimast.Node
|
|
600
|
+
declaration *shimast.Node
|
|
601
|
+
listNode *shimast.Node
|
|
602
|
+
scope *shimast.Node
|
|
603
|
+
declarationGroup *shimast.Node
|
|
604
|
+
initialized bool
|
|
605
|
+
readBeforeAssign bool
|
|
606
|
+
invalid bool
|
|
607
|
+
writes []preferConstWrite
|
|
608
|
+
}
|
|
296
609
|
|
|
297
|
-
func (preferConst) Name() string
|
|
610
|
+
func (preferConst) Name() string { return "prefer-const" }
|
|
611
|
+
func (preferConst) NeedsTypeChecker() bool {
|
|
612
|
+
return true
|
|
613
|
+
}
|
|
298
614
|
func (preferConst) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindSourceFile} }
|
|
299
615
|
func (preferConst) Check(ctx *Context, node *shimast.Node) {
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
616
|
+
if ctx.Checker == nil {
|
|
617
|
+
return
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
options := preferConstOptions{Destructuring: "any"}
|
|
621
|
+
_ = ctx.DecodeOptions(&options)
|
|
622
|
+
if options.Destructuring != "all" {
|
|
623
|
+
options.Destructuring = "any"
|
|
304
624
|
}
|
|
305
|
-
|
|
306
|
-
|
|
625
|
+
|
|
626
|
+
candidates := make([]*preferConstCandidate, 0)
|
|
627
|
+
bySymbol := make(map[*shimast.Symbol]*preferConstCandidate)
|
|
628
|
+
declarationNames := make(map[*shimast.Node]struct{})
|
|
629
|
+
candidateNames := make(map[string]struct{})
|
|
630
|
+
groups := make(map[*shimast.Node][]*preferConstCandidate)
|
|
307
631
|
|
|
308
632
|
walkDescendants(node, func(child *shimast.Node) {
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
633
|
+
if child.Kind != shimast.KindVariableDeclaration {
|
|
634
|
+
return
|
|
635
|
+
}
|
|
636
|
+
decl := child.AsVariableDeclaration()
|
|
637
|
+
if decl == nil || child.Parent == nil || child.Parent.Kind != shimast.KindVariableDeclarationList {
|
|
638
|
+
return
|
|
639
|
+
}
|
|
640
|
+
listNode := child.Parent
|
|
641
|
+
if !shimast.IsLet(listNode) || preferConstIsForStatementInitializer(listNode) {
|
|
642
|
+
return
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
names := bindingIdentifierNodes(decl.Name())
|
|
646
|
+
if len(names) == 0 {
|
|
647
|
+
return
|
|
648
|
+
}
|
|
649
|
+
initialized := decl.Initializer != nil || preferConstIsForInOrOfInitializer(listNode)
|
|
650
|
+
destructuring := decl.Name() != nil && decl.Name().Kind != shimast.KindIdentifier
|
|
651
|
+
for _, nameNode := range names {
|
|
652
|
+
symbol := ctx.Checker.GetSymbolAtLocation(nameNode)
|
|
653
|
+
candidate := &preferConstCandidate{
|
|
654
|
+
symbol: symbol,
|
|
655
|
+
nameNode: nameNode,
|
|
656
|
+
declaration: child,
|
|
657
|
+
listNode: listNode,
|
|
658
|
+
scope: preferConstLexicalScope(nameNode),
|
|
659
|
+
initialized: initialized,
|
|
314
660
|
}
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
661
|
+
if destructuring {
|
|
662
|
+
candidate.declarationGroup = child
|
|
663
|
+
groups[child] = append(groups[child], candidate)
|
|
318
664
|
}
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
665
|
+
candidates = append(candidates, candidate)
|
|
666
|
+
declarationNames[nameNode] = struct{}{}
|
|
667
|
+
if name := identifierText(nameNode); name != "" {
|
|
668
|
+
candidateNames[name] = struct{}{}
|
|
322
669
|
}
|
|
323
|
-
if
|
|
324
|
-
|
|
670
|
+
if symbol == nil {
|
|
671
|
+
candidate.invalid = true
|
|
672
|
+
continue
|
|
325
673
|
}
|
|
326
|
-
|
|
674
|
+
if prior := bySymbol[symbol]; prior != nil {
|
|
675
|
+
// Duplicate lexical declarations are already a TypeScript error. Do
|
|
676
|
+
// not add a secondary const suggestion to either declaration.
|
|
677
|
+
prior.invalid = true
|
|
678
|
+
candidate.invalid = true
|
|
679
|
+
continue
|
|
680
|
+
}
|
|
681
|
+
bySymbol[symbol] = candidate
|
|
682
|
+
}
|
|
683
|
+
})
|
|
684
|
+
|
|
685
|
+
writeTargets := make(map[*shimast.Node]struct{})
|
|
686
|
+
walkDescendants(node, func(child *shimast.Node) {
|
|
687
|
+
switch child.Kind {
|
|
327
688
|
case shimast.KindBinaryExpression:
|
|
328
689
|
expr := child.AsBinaryExpression()
|
|
329
|
-
if expr == nil || expr.OperatorToken == nil || !isAssignmentOperator(expr.OperatorToken.Kind)
|
|
690
|
+
if expr == nil || expr.OperatorToken == nil || !isAssignmentOperator(expr.OperatorToken.Kind) ||
|
|
691
|
+
isDestructuringDefaultAssignment(child) {
|
|
330
692
|
return
|
|
331
693
|
}
|
|
332
|
-
|
|
333
|
-
|
|
694
|
+
kind := preferConstReassignment
|
|
695
|
+
if expr.OperatorToken.Kind == shimast.KindEqualsToken {
|
|
696
|
+
kind = preferConstSimpleAssignment
|
|
697
|
+
}
|
|
698
|
+
targets := assignmentTargetIdentifiers(expr.Left)
|
|
699
|
+
for _, target := range targets {
|
|
700
|
+
preferConstRecordWrite(ctx, bySymbol, writeTargets, target, child, kind)
|
|
701
|
+
}
|
|
702
|
+
if kind == preferConstSimpleAssignment && len(targets) > 1 {
|
|
703
|
+
for _, target := range targets {
|
|
704
|
+
symbol := valueSymbolAtIdentifier(ctx, target)
|
|
705
|
+
if candidate := bySymbol[symbol]; candidate != nil {
|
|
706
|
+
groups[child] = appendPreferConstCandidate(groups[child], candidate)
|
|
707
|
+
}
|
|
708
|
+
}
|
|
334
709
|
}
|
|
335
710
|
case shimast.KindPrefixUnaryExpression:
|
|
336
711
|
expr := child.AsPrefixUnaryExpression()
|
|
337
|
-
if expr
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
if expr.Operator == shimast.KindPlusPlusToken || expr.Operator == shimast.KindMinusMinusToken {
|
|
341
|
-
if name := identifierText(expr.Operand); name != "" {
|
|
342
|
-
assigned[name] = true
|
|
712
|
+
if expr != nil && (expr.Operator == shimast.KindPlusPlusToken || expr.Operator == shimast.KindMinusMinusToken) {
|
|
713
|
+
for _, target := range assignmentTargetIdentifiers(expr.Operand) {
|
|
714
|
+
preferConstRecordWrite(ctx, bySymbol, writeTargets, target, nil, preferConstReassignment)
|
|
343
715
|
}
|
|
344
716
|
}
|
|
345
717
|
case shimast.KindPostfixUnaryExpression:
|
|
346
718
|
expr := child.AsPostfixUnaryExpression()
|
|
347
|
-
if expr
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
if expr.Operator == shimast.KindPlusPlusToken || expr.Operator == shimast.KindMinusMinusToken {
|
|
351
|
-
if name := identifierText(expr.Operand); name != "" {
|
|
352
|
-
assigned[name] = true
|
|
719
|
+
if expr != nil && (expr.Operator == shimast.KindPlusPlusToken || expr.Operator == shimast.KindMinusMinusToken) {
|
|
720
|
+
for _, target := range assignmentTargetIdentifiers(expr.Operand) {
|
|
721
|
+
preferConstRecordWrite(ctx, bySymbol, writeTargets, target, nil, preferConstReassignment)
|
|
353
722
|
}
|
|
354
723
|
}
|
|
355
724
|
case shimast.KindForOfStatement, shimast.KindForInStatement:
|
|
356
|
-
// `for (x of …)` / `for (x in …)` reassigns the existing binding `x`
|
|
357
|
-
// on every iteration. When the initializer IS a
|
|
358
|
-
// VariableDeclarationList (`for (const y of …)`) it declares a fresh
|
|
359
|
-
// binding instead, so only a non-declaration initializer counts as a
|
|
360
|
-
// reassignment target. Missing this lets a pre-existing `let` be
|
|
361
|
-
// rewritten to `const` that the loop then assigns to — a TS error and
|
|
362
|
-
// runtime TypeError.
|
|
363
725
|
stmt := child.AsForInOrOfStatement()
|
|
364
|
-
if stmt == nil || stmt.Initializer == nil {
|
|
726
|
+
if stmt == nil || stmt.Initializer == nil || stmt.Initializer.Kind == shimast.KindVariableDeclarationList {
|
|
365
727
|
return
|
|
366
728
|
}
|
|
367
|
-
|
|
368
|
-
|
|
729
|
+
for _, target := range assignmentTargetIdentifiers(stmt.Initializer) {
|
|
730
|
+
preferConstRecordWrite(ctx, bySymbol, writeTargets, target, nil, preferConstReassignment)
|
|
369
731
|
}
|
|
370
|
-
|
|
371
|
-
|
|
732
|
+
}
|
|
733
|
+
})
|
|
734
|
+
|
|
735
|
+
// Only declaration-only candidates need read history. A read preceding the
|
|
736
|
+
// sole assignment changes the diagnostic location under the default option,
|
|
737
|
+
// and suppresses the diagnostic when ignoreReadBeforeAssign is enabled.
|
|
738
|
+
walkDescendants(node, func(child *shimast.Node) {
|
|
739
|
+
if child.Kind != shimast.KindIdentifier {
|
|
740
|
+
return
|
|
741
|
+
}
|
|
742
|
+
if _, ok := declarationNames[child]; ok {
|
|
743
|
+
return
|
|
744
|
+
}
|
|
745
|
+
if _, ok := writeTargets[child]; ok {
|
|
746
|
+
return
|
|
747
|
+
}
|
|
748
|
+
if _, ok := candidateNames[identifierText(child)]; !ok {
|
|
749
|
+
return
|
|
750
|
+
}
|
|
751
|
+
symbol := valueSymbolAtIdentifier(ctx, child)
|
|
752
|
+
candidate := bySymbol[symbol]
|
|
753
|
+
if candidate == nil || candidate.initialized || len(candidate.writes) != 1 {
|
|
754
|
+
return
|
|
755
|
+
}
|
|
756
|
+
if child.Pos() < candidate.writes[0].target.Pos() {
|
|
757
|
+
candidate.readBeforeAssign = true
|
|
758
|
+
}
|
|
759
|
+
})
|
|
760
|
+
|
|
761
|
+
eligible := make(map[*preferConstCandidate]bool, len(candidates))
|
|
762
|
+
for _, candidate := range candidates {
|
|
763
|
+
eligible[candidate] = preferConstCandidateIsEligible(ctx, candidate, bySymbol, options)
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
for _, candidate := range candidates {
|
|
767
|
+
if !eligible[candidate] {
|
|
768
|
+
continue
|
|
769
|
+
}
|
|
770
|
+
group := candidate.declarationGroup
|
|
771
|
+
if group == nil && !candidate.initialized && len(candidate.writes) == 1 {
|
|
772
|
+
assignment := candidate.writes[0].assignment
|
|
773
|
+
if len(groups[assignment]) > 1 {
|
|
774
|
+
group = assignment
|
|
372
775
|
}
|
|
373
776
|
}
|
|
777
|
+
if options.Destructuring == "all" && group != nil && !preferConstGroupIsEligible(groups[group], eligible) {
|
|
778
|
+
continue
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
reportNode := candidate.nameNode
|
|
782
|
+
if !candidate.initialized && !candidate.readBeforeAssign && len(candidate.writes) == 1 {
|
|
783
|
+
reportNode = candidate.writes[0].target
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
start := -1
|
|
787
|
+
if candidate.initialized && isSingleDeclarationList(candidate.listNode) {
|
|
788
|
+
declarationGroup := groups[candidate.declaration]
|
|
789
|
+
if len(declarationGroup) == 0 || preferConstGroupIsEligible(declarationGroup, eligible) {
|
|
790
|
+
start = keywordStart(ctx.File, candidate.listNode, "let")
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
if start >= 0 {
|
|
794
|
+
ctx.ReportFix(
|
|
795
|
+
reportNode,
|
|
796
|
+
"Use const instead of let.",
|
|
797
|
+
TextEdit{Pos: start, End: start + len("let"), Text: "const"},
|
|
798
|
+
)
|
|
799
|
+
} else {
|
|
800
|
+
ctx.Report(reportNode, "Use const instead of let.")
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
func preferConstRecordWrite(
|
|
806
|
+
ctx *Context,
|
|
807
|
+
bySymbol map[*shimast.Symbol]*preferConstCandidate,
|
|
808
|
+
writeTargets map[*shimast.Node]struct{},
|
|
809
|
+
target *shimast.Node,
|
|
810
|
+
assignment *shimast.Node,
|
|
811
|
+
kind preferConstWriteKind,
|
|
812
|
+
) {
|
|
813
|
+
if target == nil || target.Kind != shimast.KindIdentifier {
|
|
814
|
+
return
|
|
815
|
+
}
|
|
816
|
+
symbol := valueSymbolAtIdentifier(ctx, target)
|
|
817
|
+
candidate := bySymbol[symbol]
|
|
818
|
+
if candidate == nil {
|
|
819
|
+
return
|
|
820
|
+
}
|
|
821
|
+
candidate.writes = append(candidate.writes, preferConstWrite{
|
|
822
|
+
target: target,
|
|
823
|
+
assignment: assignment,
|
|
824
|
+
kind: kind,
|
|
374
825
|
})
|
|
826
|
+
writeTargets[target] = struct{}{}
|
|
827
|
+
}
|
|
375
828
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
829
|
+
func preferConstCandidateIsEligible(
|
|
830
|
+
ctx *Context,
|
|
831
|
+
candidate *preferConstCandidate,
|
|
832
|
+
bySymbol map[*shimast.Symbol]*preferConstCandidate,
|
|
833
|
+
options preferConstOptions,
|
|
834
|
+
) bool {
|
|
835
|
+
if candidate == nil || candidate.invalid || candidate.symbol == nil {
|
|
836
|
+
return false
|
|
837
|
+
}
|
|
838
|
+
if candidate.initialized {
|
|
839
|
+
return len(candidate.writes) == 0
|
|
840
|
+
}
|
|
841
|
+
if len(candidate.writes) != 1 || candidate.writes[0].kind != preferConstSimpleAssignment {
|
|
842
|
+
return false
|
|
843
|
+
}
|
|
844
|
+
if options.IgnoreReadBeforeAssign && candidate.readBeforeAssign {
|
|
845
|
+
return false
|
|
846
|
+
}
|
|
847
|
+
return preferConstAssignmentCanInitialize(ctx, candidate, candidate.writes[0], bySymbol)
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
func preferConstAssignmentCanInitialize(
|
|
851
|
+
ctx *Context,
|
|
852
|
+
candidate *preferConstCandidate,
|
|
853
|
+
write preferConstWrite,
|
|
854
|
+
bySymbol map[*shimast.Symbol]*preferConstCandidate,
|
|
855
|
+
) bool {
|
|
856
|
+
assignment := write.assignment
|
|
857
|
+
if assignment == nil || assignment.Kind != shimast.KindBinaryExpression ||
|
|
858
|
+
candidate.scope == nil || candidate.scope != preferConstLexicalScope(write.target) {
|
|
859
|
+
return false
|
|
860
|
+
}
|
|
861
|
+
expr := assignment.AsBinaryExpression()
|
|
862
|
+
if expr == nil || expr.OperatorToken == nil || expr.OperatorToken.Kind != shimast.KindEqualsToken {
|
|
863
|
+
return false
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
statementOwner := assignment
|
|
867
|
+
for statementOwner.Parent != nil && statementOwner.Parent.Kind == shimast.KindParenthesizedExpression {
|
|
868
|
+
statementOwner = statementOwner.Parent
|
|
869
|
+
}
|
|
870
|
+
if statementOwner.Parent == nil || statementOwner.Parent.Kind != shimast.KindExpressionStatement {
|
|
871
|
+
return false
|
|
872
|
+
}
|
|
873
|
+
container := statementOwner.Parent.Parent
|
|
874
|
+
if container == nil {
|
|
875
|
+
return false
|
|
876
|
+
}
|
|
877
|
+
switch container.Kind {
|
|
878
|
+
case shimast.KindSourceFile,
|
|
879
|
+
shimast.KindBlock,
|
|
880
|
+
shimast.KindModuleBlock,
|
|
881
|
+
shimast.KindCaseClause,
|
|
882
|
+
shimast.KindDefaultClause:
|
|
883
|
+
default:
|
|
884
|
+
return false
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
if preferConstAssignmentTargetHasMember(expr.Left) {
|
|
888
|
+
return false
|
|
889
|
+
}
|
|
890
|
+
targets := assignmentTargetIdentifiers(expr.Left)
|
|
891
|
+
if len(targets) == 0 {
|
|
892
|
+
return false
|
|
893
|
+
}
|
|
894
|
+
for _, target := range targets {
|
|
895
|
+
symbol := valueSymbolAtIdentifier(ctx, target)
|
|
896
|
+
grouped := bySymbol[symbol]
|
|
897
|
+
if symbol == nil || !preferConstSymbolIsLocalToScope(symbol, candidate.scope) ||
|
|
898
|
+
(grouped != nil && grouped.invalid) {
|
|
899
|
+
return false
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
return true
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
func preferConstSymbolIsLocalToScope(symbol *shimast.Symbol, scope *shimast.Node) bool {
|
|
906
|
+
if symbol == nil || scope == nil {
|
|
907
|
+
return false
|
|
908
|
+
}
|
|
909
|
+
for _, declaration := range symbol.Declarations {
|
|
910
|
+
if preferConstDeclarationScope(declaration) == scope {
|
|
911
|
+
return true
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
return false
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
// preferConstDeclarationScope returns the scope that owns a sibling target in
|
|
918
|
+
// a destructuring assignment. Parameters cannot be folded into a declaration,
|
|
919
|
+
// and `var` declarations hoist past ordinary blocks to their function-like
|
|
920
|
+
// owner; every other declaration uses lexical scope.
|
|
921
|
+
func preferConstDeclarationScope(declaration *shimast.Node) *shimast.Node {
|
|
922
|
+
if declaration == nil {
|
|
923
|
+
return nil
|
|
924
|
+
}
|
|
925
|
+
for ancestor := declaration; ancestor != nil; ancestor = ancestor.Parent {
|
|
926
|
+
if ancestor.Kind == shimast.KindParameter {
|
|
927
|
+
return nil
|
|
928
|
+
}
|
|
929
|
+
if isFunctionLikeKind(ancestor) {
|
|
930
|
+
break
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
for ancestor := declaration.Parent; ancestor != nil; ancestor = ancestor.Parent {
|
|
934
|
+
if ancestor.Kind != shimast.KindVariableDeclarationList {
|
|
935
|
+
continue
|
|
936
|
+
}
|
|
937
|
+
if !shimast.IsVar(ancestor) {
|
|
938
|
+
break
|
|
939
|
+
}
|
|
940
|
+
for scope := ancestor.Parent; scope != nil; scope = scope.Parent {
|
|
941
|
+
if scope.Kind == shimast.KindSourceFile || scope.Kind == shimast.KindModuleBlock ||
|
|
942
|
+
scope.Kind == shimast.KindClassStaticBlockDeclaration || isFunctionLikeKind(scope) {
|
|
943
|
+
return scope
|
|
381
944
|
}
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
945
|
+
}
|
|
946
|
+
return nil
|
|
947
|
+
}
|
|
948
|
+
return preferConstLexicalScope(declaration)
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
func preferConstAssignmentTargetHasMember(node *shimast.Node) bool {
|
|
952
|
+
if node == nil {
|
|
953
|
+
return false
|
|
954
|
+
}
|
|
955
|
+
switch node.Kind {
|
|
956
|
+
case shimast.KindPropertyAccessExpression, shimast.KindElementAccessExpression:
|
|
957
|
+
return true
|
|
958
|
+
case shimast.KindParenthesizedExpression:
|
|
959
|
+
return preferConstAssignmentTargetHasMember(stripParens(node))
|
|
960
|
+
case shimast.KindNonNullExpression:
|
|
961
|
+
if expression := node.AsNonNullExpression(); expression != nil {
|
|
962
|
+
return preferConstAssignmentTargetHasMember(expression.Expression)
|
|
963
|
+
}
|
|
964
|
+
case shimast.KindAsExpression:
|
|
965
|
+
if expression := node.AsAsExpression(); expression != nil {
|
|
966
|
+
return preferConstAssignmentTargetHasMember(expression.Expression)
|
|
967
|
+
}
|
|
968
|
+
case shimast.KindTypeAssertionExpression:
|
|
969
|
+
if expression := node.AsTypeAssertion(); expression != nil {
|
|
970
|
+
return preferConstAssignmentTargetHasMember(expression.Expression)
|
|
971
|
+
}
|
|
972
|
+
case shimast.KindSatisfiesExpression:
|
|
973
|
+
if expression := node.AsSatisfiesExpression(); expression != nil {
|
|
974
|
+
return preferConstAssignmentTargetHasMember(expression.Expression)
|
|
975
|
+
}
|
|
976
|
+
case shimast.KindArrayLiteralExpression:
|
|
977
|
+
if array := node.AsArrayLiteralExpression(); array != nil && array.Elements != nil {
|
|
978
|
+
for _, element := range array.Elements.Nodes {
|
|
979
|
+
if preferConstAssignmentTargetHasMember(element) {
|
|
980
|
+
return true
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
case shimast.KindObjectLiteralExpression:
|
|
985
|
+
if object := node.AsObjectLiteralExpression(); object != nil && object.Properties != nil {
|
|
986
|
+
for _, property := range object.Properties.Nodes {
|
|
987
|
+
if preferConstAssignmentTargetHasMember(property) {
|
|
988
|
+
return true
|
|
989
|
+
}
|
|
390
990
|
}
|
|
391
991
|
}
|
|
992
|
+
case shimast.KindSpreadElement:
|
|
993
|
+
if spread := node.AsSpreadElement(); spread != nil {
|
|
994
|
+
return preferConstAssignmentTargetHasMember(spread.Expression)
|
|
995
|
+
}
|
|
996
|
+
case shimast.KindSpreadAssignment:
|
|
997
|
+
if spread := node.AsSpreadAssignment(); spread != nil {
|
|
998
|
+
return preferConstAssignmentTargetHasMember(spread.Expression)
|
|
999
|
+
}
|
|
1000
|
+
case shimast.KindPropertyAssignment:
|
|
1001
|
+
if property := node.AsPropertyAssignment(); property != nil {
|
|
1002
|
+
return preferConstAssignmentTargetHasMember(property.Initializer)
|
|
1003
|
+
}
|
|
1004
|
+
case shimast.KindBinaryExpression:
|
|
1005
|
+
if expression := node.AsBinaryExpression(); expression != nil && expression.OperatorToken != nil &&
|
|
1006
|
+
expression.OperatorToken.Kind == shimast.KindEqualsToken {
|
|
1007
|
+
return preferConstAssignmentTargetHasMember(expression.Left)
|
|
1008
|
+
}
|
|
1009
|
+
// A shorthand property assignment writes its name. Its optional object
|
|
1010
|
+
// assignment initializer is a default-value read, so member access there
|
|
1011
|
+
// does not make the destructuring target unsafe.
|
|
1012
|
+
case shimast.KindShorthandPropertyAssignment, shimast.KindIdentifier:
|
|
1013
|
+
return false
|
|
392
1014
|
}
|
|
1015
|
+
return false
|
|
393
1016
|
}
|
|
394
1017
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
1018
|
+
func preferConstGroupIsEligible(group []*preferConstCandidate, eligible map[*preferConstCandidate]bool) bool {
|
|
1019
|
+
if len(group) == 0 {
|
|
1020
|
+
return false
|
|
1021
|
+
}
|
|
1022
|
+
for _, candidate := range group {
|
|
1023
|
+
if !eligible[candidate] {
|
|
1024
|
+
return false
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
return true
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
func appendPreferConstCandidate(group []*preferConstCandidate, candidate *preferConstCandidate) []*preferConstCandidate {
|
|
1031
|
+
for _, existing := range group {
|
|
1032
|
+
if existing == candidate {
|
|
1033
|
+
return group
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
return append(group, candidate)
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
func bindingIdentifierNodes(node *shimast.Node) []*shimast.Node {
|
|
400
1040
|
if node == nil {
|
|
1041
|
+
return nil
|
|
1042
|
+
}
|
|
1043
|
+
if node.Kind == shimast.KindIdentifier {
|
|
1044
|
+
return []*shimast.Node{node}
|
|
1045
|
+
}
|
|
1046
|
+
pattern := node.AsBindingPattern()
|
|
1047
|
+
if pattern == nil || pattern.Elements == nil {
|
|
1048
|
+
return nil
|
|
1049
|
+
}
|
|
1050
|
+
var identifiers []*shimast.Node
|
|
1051
|
+
for _, elementNode := range pattern.Elements.Nodes {
|
|
1052
|
+
element := elementNode.AsBindingElement()
|
|
1053
|
+
if element != nil {
|
|
1054
|
+
identifiers = append(identifiers, bindingIdentifierNodes(element.Name())...)
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
return identifiers
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
func preferConstIsForStatementInitializer(listNode *shimast.Node) bool {
|
|
1061
|
+
return listNode != nil && listNode.Parent != nil && listNode.Parent.Kind == shimast.KindForStatement
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
func preferConstIsForInOrOfInitializer(listNode *shimast.Node) bool {
|
|
1065
|
+
if listNode == nil || listNode.Parent == nil {
|
|
401
1066
|
return false
|
|
402
1067
|
}
|
|
403
|
-
|
|
404
|
-
return list != nil && list.Declarations != nil && len(list.Declarations.Nodes) == 1
|
|
1068
|
+
return listNode.Parent.Kind == shimast.KindForInStatement || listNode.Parent.Kind == shimast.KindForOfStatement
|
|
405
1069
|
}
|
|
406
1070
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
1071
|
+
func preferConstLexicalScope(node *shimast.Node) *shimast.Node {
|
|
1072
|
+
if node == nil {
|
|
1073
|
+
return nil
|
|
1074
|
+
}
|
|
1075
|
+
for ancestor := node.Parent; ancestor != nil; ancestor = ancestor.Parent {
|
|
1076
|
+
if isFunctionLikeKind(ancestor) {
|
|
1077
|
+
return ancestor
|
|
1078
|
+
}
|
|
1079
|
+
switch ancestor.Kind {
|
|
1080
|
+
case shimast.KindSourceFile,
|
|
1081
|
+
shimast.KindModuleBlock,
|
|
1082
|
+
shimast.KindCaseBlock,
|
|
1083
|
+
shimast.KindForStatement,
|
|
1084
|
+
shimast.KindForInStatement,
|
|
1085
|
+
shimast.KindForOfStatement,
|
|
1086
|
+
shimast.KindCatchClause,
|
|
1087
|
+
shimast.KindClassStaticBlockDeclaration,
|
|
1088
|
+
shimast.KindPropertyDeclaration:
|
|
1089
|
+
return ancestor
|
|
1090
|
+
case shimast.KindBlock:
|
|
1091
|
+
if ancestor.Parent != nil && (isFunctionLikeKind(ancestor.Parent) ||
|
|
1092
|
+
ancestor.Parent.Kind == shimast.KindClassStaticBlockDeclaration) {
|
|
1093
|
+
return ancestor.Parent
|
|
1094
|
+
}
|
|
1095
|
+
return ancestor
|
|
420
1096
|
}
|
|
421
|
-
return true
|
|
422
1097
|
}
|
|
423
|
-
return
|
|
424
|
-
|
|
1098
|
+
return nil
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
// isSingleDeclarationList reports whether the VariableDeclarationList contains
|
|
1102
|
+
// exactly one declarator. Multiple comma-separated declarators share one
|
|
1103
|
+
// keyword, so replacing it for only one declarator is not valid. One
|
|
1104
|
+
// destructuring declarator may still contain several bindings; preferConst
|
|
1105
|
+
// separately requires every affected binding to be eligible before fixing it.
|
|
1106
|
+
func isSingleDeclarationList(node *shimast.Node) bool {
|
|
1107
|
+
if node == nil {
|
|
1108
|
+
return false
|
|
1109
|
+
}
|
|
1110
|
+
list := node.AsVariableDeclarationList()
|
|
1111
|
+
return list != nil && list.Declarations != nil && len(list.Declarations.Nodes) == 1
|
|
425
1112
|
}
|
|
426
1113
|
|
|
427
1114
|
// noUndefInit: forbid `let x = undefined` and `var x = undefined`.
|