@ttsc/lint 0.18.3 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +95 -43
- package/go.mod +5 -4
- package/internal/rulecode/rulecode.go +88 -0
- package/lib/index.d.ts +10 -1
- package/lib/index.js +45 -17
- package/lib/index.js.map +1 -1
- package/lib/structures/ITtscLintConfig.d.ts +3 -0
- package/lib/structures/TtscLintRuleSetting.d.ts +3 -2
- package/lib/structures/format/ITtscLintFormat.d.ts +3 -2
- package/lib/structures/format/ITtscLintFormatSortImports.d.ts +20 -4
- package/lib/structures/rules/ITtscLintBoundariesRuleOptions.d.ts +80 -8
- package/lib/structures/rules/ITtscLintBoundariesRules.d.ts +6 -8
- package/lib/structures/rules/ITtscLintContributorRules.d.ts +18 -2
- package/lib/structures/rules/ITtscLintCoreRuleOptions.d.ts +347 -0
- package/lib/structures/rules/ITtscLintCoreRuleOptions.js +3 -0
- package/lib/structures/rules/ITtscLintCoreRuleOptions.js.map +1 -0
- package/lib/structures/rules/ITtscLintCoreRules.d.ts +101 -67
- package/lib/structures/rules/ITtscLintRegexpRules.d.ts +7 -2
- package/lib/structures/rules/ITtscLintRuleOptionsMap.d.ts +53 -7
- package/lib/structures/rules/ITtscLintRules.d.ts +6 -2
- package/lib/structures/rules/ITtscLintSolidRules.d.ts +0 -8
- package/lib/structures/rules/ITtscLintTypeScriptRuleOptions.d.ts +206 -0
- package/lib/structures/rules/ITtscLintTypeScriptRuleOptions.js +8 -0
- package/lib/structures/rules/ITtscLintTypeScriptRuleOptions.js.map +1 -0
- package/lib/structures/rules/ITtscLintTypeScriptRules.d.ts +55 -42
- package/lib/structures/rules/ITtscLintUnicornRuleOptions.d.ts +276 -0
- package/lib/structures/rules/ITtscLintUnicornRuleOptions.js +3 -0
- package/lib/structures/rules/ITtscLintUnicornRuleOptions.js.map +1 -0
- package/lib/structures/rules/ITtscLintUnicornRules.d.ts +44 -36
- package/lib/structures/rules/index.d.ts +3 -0
- package/lib/structures/rules/index.js +3 -0
- package/lib/structures/rules/index.js.map +1 -1
- package/linthost/ast_helpers.go +521 -23
- package/linthost/ast_selector_matcher.go +1628 -0
- package/linthost/ast_selector_parser.go +628 -0
- package/linthost/comment_scan.go +154 -0
- package/linthost/compile.go +129 -79
- package/linthost/config.go +352 -88
- package/linthost/config_format.go +15 -1
- package/linthost/contrib_adapter.go +129 -25
- package/linthost/declaration_rules.go +12 -11
- package/linthost/directives.go +9 -67
- package/linthost/dispatch.go +37 -3
- package/linthost/engine.go +368 -53
- package/linthost/escape_string.go +77 -0
- package/linthost/fix.go +133 -23
- package/linthost/flags_gen.go +18 -17
- package/linthost/format.go +73 -167
- package/linthost/format_editor_settings.go +134 -26
- package/linthost/grapheme_tables_gen.go +1920 -0
- package/linthost/host.go +160 -61
- package/linthost/literal_escape_scan.go +117 -0
- package/linthost/lsp.go +545 -98
- package/linthost/numeric_literal_precision.go +228 -0
- package/linthost/polyfill_browserslist.go +1439 -0
- package/linthost/polyfill_browserslist_config.go +603 -0
- package/linthost/polyfill_corejs.go +750 -0
- package/linthost/polyfill_data.go +113 -0
- package/linthost/polyfill_data_gen.json +39868 -0
- package/linthost/print_nodes_array.go +7 -1
- package/linthost/print_nodes_object.go +7 -1
- package/linthost/project_engine.go +151 -0
- package/linthost/project_rules.go +78 -0
- package/linthost/regex_clean.go +191 -0
- package/linthost/regex_tree.go +1544 -0
- package/linthost/regex_tree_optimizer.go +1685 -0
- package/linthost/regexp_cache.go +44 -0
- package/linthost/rule_codes.go +105 -0
- package/linthost/rule_codes.json +745 -0
- package/linthost/rules_ban_ts_comment.go +312 -0
- package/linthost/rules_boundaries.go +128 -43
- package/linthost/rules_boundaries_dependencies.go +1013 -0
- package/linthost/rules_core_extra.go +62 -92
- package/linthost/rules_cypress.go +1 -1
- package/linthost/rules_default_case.go +87 -11
- package/linthost/rules_dupes.go +38 -13
- package/linthost/rules_empty.go +188 -20
- package/linthost/rules_escape.go +62 -48
- package/linthost/rules_format_arrow_parens.go +19 -5
- package/linthost/rules_format_bracket_spacing.go +1 -1
- package/linthost/rules_format_clause_join.go +1 -1
- package/linthost/rules_format_declaration_header.go +31 -19
- package/linthost/rules_format_indent.go +1 -1
- package/linthost/rules_format_jsdoc.go +1 -1
- package/linthost/rules_format_orphan_semi.go +1 -1
- package/linthost/rules_format_parameter_properties.go +17 -7
- package/linthost/rules_format_print_width.go +1 -1
- package/linthost/rules_format_quote_props.go +1 -1
- package/linthost/rules_format_quotes.go +1 -1
- package/linthost/rules_format_semi.go +106 -79
- package/linthost/rules_format_sort_imports.go +79 -45
- package/linthost/rules_format_statement_split.go +1 -1
- package/linthost/rules_format_ternary_nullish_parens.go +1 -1
- package/linthost/rules_format_trailing_comma.go +64 -1
- package/linthost/rules_format_whitespace.go +1 -1
- package/linthost/rules_functional.go +21 -22
- package/linthost/rules_gap.go +80 -3
- package/linthost/rules_grouped_accessor_pairs.go +84 -25
- package/linthost/rules_guard_for_in.go +64 -147
- package/linthost/rules_jsx_a11y.go +232 -22
- package/linthost/rules_logic.go +84 -38
- package/linthost/rules_nextjs.go +105 -16
- package/linthost/rules_no_class_assign.go +70 -0
- package/linthost/rules_no_duplicate_imports.go +266 -0
- package/linthost/rules_no_else_return.go +156 -38
- package/linthost/rules_no_extend_native.go +113 -17
- package/linthost/rules_no_fallthrough.go +1013 -0
- package/linthost/rules_no_func_assign.go +70 -0
- package/linthost/rules_no_import_assign.go +278 -86
- package/linthost/rules_no_inner_declarations.go +186 -0
- package/linthost/rules_no_loop_func.go +438 -53
- package/linthost/rules_no_mixed_operators.go +208 -52
- package/linthost/rules_no_param_reassign.go +376 -61
- package/linthost/rules_no_promise_executor_return.go +156 -0
- package/linthost/rules_no_restricted_imports.go +1013 -57
- package/linthost/rules_no_restricted_syntax.go +190 -26
- package/linthost/rules_no_useless_constructor.go +6 -36
- package/linthost/rules_problems.go +154 -311
- package/linthost/rules_promise.go +2578 -109
- package/linthost/rules_react_extras.go +71 -45
- package/linthost/rules_react_hooks.go +18 -2
- package/linthost/rules_react_perf.go +1 -0
- package/linthost/rules_react_refresh.go +1 -1
- package/linthost/rules_regexp.go +230 -14
- package/linthost/rules_security.go +45 -19
- package/linthost/rules_self.go +21 -1
- package/linthost/rules_solid.go +13 -7
- package/linthost/rules_storybook.go +1 -1
- package/linthost/rules_suggestions.go +490 -84
- package/linthost/rules_testing_library.go +12 -1
- package/linthost/rules_ts.go +227 -54
- package/linthost/rules_ts_async.go +0 -131
- package/linthost/rules_ts_no_base_to_string.go +3 -4
- package/linthost/rules_ts_no_misused_promises.go +817 -0
- package/linthost/rules_ts_no_restricted_types.go +320 -34
- package/linthost/rules_ts_no_unnecessary_qualifier.go +132 -55
- package/linthost/rules_ts_no_unsafe_assignment.go +608 -60
- package/linthost/rules_ts_require_array_sort_compare.go +5 -5
- package/linthost/rules_ts_switch_exhaustiveness_check.go +556 -86
- package/linthost/rules_unicorn_better_regex.go +224 -16
- package/linthost/rules_unicorn_consistent_destructuring.go +1002 -7
- package/linthost/rules_unicorn_consistent_existence_index_check.go +208 -29
- package/linthost/rules_unicorn_consistent_function_scoping.go +1041 -8
- package/linthost/rules_unicorn_consistent_template_literal_escape.go +178 -7
- package/linthost/rules_unicorn_escape_case.go +30 -19
- package/linthost/rules_unicorn_filename_case.go +763 -16
- package/linthost/rules_unicorn_import_style.go +783 -17
- package/linthost/rules_unicorn_isolated_functions.go +934 -14
- package/linthost/rules_unicorn_no_hex_escape.go +26 -11
- package/linthost/rules_unicorn_no_typeof_undefined.go +234 -25
- package/linthost/rules_unicorn_no_unnecessary_polyfills.go +376 -21
- package/linthost/rules_unicorn_no_unused_properties.go +756 -19
- package/linthost/rules_unicorn_number_literal_case.go +58 -39
- package/linthost/rules_unicorn_prefer_at.go +7 -3
- package/linthost/rules_unicorn_prefer_number_properties.go +381 -48
- package/linthost/rules_unicorn_prefer_optional_catch_binding.go +24 -24
- package/linthost/rules_unicorn_prefer_simple_condition_first.go +597 -19
- package/linthost/rules_unicorn_prefer_string_raw.go +147 -16
- package/linthost/rules_unicorn_prevent_abbreviations.go +1963 -95
- package/linthost/rules_unicorn_string_content.go +611 -15
- package/linthost/rules_unicorn_switch_case_break_position.go +172 -15
- package/linthost/rules_unicorn_template_indent.go +923 -18
- package/linthost/rules_unicorn_text_encoding_identifier_case.go +202 -42
- package/linthost/rules_unicorn_throw_new_error.go +218 -26
- package/linthost/rules_var.go +829 -142
- package/linthost/string_length.go +222 -0
- package/package.json +4 -3
- package/rule/project.go +145 -0
- package/rule/rule.go +58 -1
- package/src/index.ts +58 -17
- package/src/structures/ITtscLintConfig.ts +3 -0
- package/src/structures/TtscLintRuleSetting.ts +3 -2
- package/src/structures/format/ITtscLintFormat.ts +3 -2
- package/src/structures/format/ITtscLintFormatSortImports.ts +21 -4
- package/src/structures/rules/ITtscLintBoundariesRuleOptions.ts +122 -8
- package/src/structures/rules/ITtscLintBoundariesRules.ts +6 -8
- package/src/structures/rules/ITtscLintContributorRules.ts +18 -2
- package/src/structures/rules/ITtscLintCoreRuleOptions.ts +465 -0
- package/src/structures/rules/ITtscLintCoreRules.ts +122 -67
- package/src/structures/rules/ITtscLintRegexpRules.ts +7 -2
- package/src/structures/rules/ITtscLintRuleOptionsMap.ts +88 -7
- package/src/structures/rules/ITtscLintRules.ts +7 -2
- package/src/structures/rules/ITtscLintSolidRules.ts +0 -9
- package/src/structures/rules/ITtscLintTypeScriptRuleOptions.ts +248 -0
- package/src/structures/rules/ITtscLintTypeScriptRules.ts +64 -42
- package/src/structures/rules/ITtscLintUnicornRuleOptions.ts +350 -0
- package/src/structures/rules/ITtscLintUnicornRules.ts +60 -36
- package/src/structures/rules/index.ts +3 -0
|
@@ -1,96 +1,411 @@
|
|
|
1
|
-
// noParamReassign
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
1
|
+
// noParamReassign rejects writes to the binding introduced by a function
|
|
2
|
+
// parameter. Binding identity comes from the TypeScript checker, so a local
|
|
3
|
+
// shadow is independent while a nested function that closes over the parameter
|
|
4
|
+
// still counts. Destructured, defaulted, and rest parameters are resolved by
|
|
5
|
+
// their individual binding leaves.
|
|
6
6
|
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
// parameter name will produce a false positive in v1 — proper scope
|
|
12
|
-
// tracking needs the resolver, which the AST-only baseline avoids.
|
|
13
|
-
// Destructured parameter bindings are skipped for the same reason.
|
|
7
|
+
// With `props: true`, the rule also follows the parameter reference through
|
|
8
|
+
// member, call, conditional-result, and destructuring-target syntax to find
|
|
9
|
+
// property writes. The two official ignore lists apply only to those property
|
|
10
|
+
// writes; assigning the parameter binding itself is always reported.
|
|
14
11
|
// https://eslint.org/docs/latest/rules/no-param-reassign
|
|
15
12
|
package linthost
|
|
16
13
|
|
|
17
|
-
import
|
|
14
|
+
import (
|
|
15
|
+
"encoding/json"
|
|
16
|
+
"fmt"
|
|
17
|
+
"regexp"
|
|
18
|
+
"sort"
|
|
18
19
|
|
|
19
|
-
|
|
20
|
+
shimast "github.com/microsoft/typescript-go/shim/ast"
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
type noParamReassign struct{ optionsRule }
|
|
24
|
+
|
|
25
|
+
type noParamReassignOptions struct {
|
|
26
|
+
Props bool `json:"props"`
|
|
27
|
+
IgnorePropertyModificationsFor []string `json:"ignorePropertyModificationsFor"`
|
|
28
|
+
IgnorePropertyModificationsForRegex []string `json:"ignorePropertyModificationsForRegex"`
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
type noParamReassignParameter struct {
|
|
32
|
+
name string
|
|
33
|
+
}
|
|
20
34
|
|
|
21
35
|
func (noParamReassign) Name() string { return "no-param-reassign" }
|
|
22
|
-
func (noParamReassign)
|
|
23
|
-
return
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
36
|
+
func (noParamReassign) NeedsTypeChecker() bool {
|
|
37
|
+
return true
|
|
38
|
+
}
|
|
39
|
+
func (noParamReassign) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindSourceFile} }
|
|
40
|
+
|
|
41
|
+
func (noParamReassign) ValidateOptions(raw json.RawMessage) error {
|
|
42
|
+
if len(raw) == 0 {
|
|
43
|
+
return nil
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
var decoded any
|
|
47
|
+
if err := json.Unmarshal(raw, &decoded); err != nil {
|
|
48
|
+
return fmt.Errorf("options must be valid JSON: %w", err)
|
|
49
|
+
}
|
|
50
|
+
options, ok := decoded.(map[string]any)
|
|
51
|
+
if !ok {
|
|
52
|
+
return fmt.Errorf("options must be an object")
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
unknown := make([]string, 0)
|
|
56
|
+
for name := range options {
|
|
57
|
+
switch name {
|
|
58
|
+
case "props", "ignorePropertyModificationsFor", "ignorePropertyModificationsForRegex":
|
|
59
|
+
default:
|
|
60
|
+
unknown = append(unknown, name)
|
|
61
|
+
}
|
|
31
62
|
}
|
|
63
|
+
if len(unknown) > 0 {
|
|
64
|
+
sort.Strings(unknown)
|
|
65
|
+
return fmt.Errorf("unknown option %q", unknown[0])
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
props := false
|
|
69
|
+
propsPresent := false
|
|
70
|
+
if value, present := options["props"]; present {
|
|
71
|
+
propsPresent = true
|
|
72
|
+
configured, valid := value.(bool)
|
|
73
|
+
if !valid {
|
|
74
|
+
return fmt.Errorf("option %q must be a boolean", "props")
|
|
75
|
+
}
|
|
76
|
+
props = configured
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
_, exactPresent, err := noParamReassignStringListOption(
|
|
80
|
+
options,
|
|
81
|
+
"ignorePropertyModificationsFor",
|
|
82
|
+
)
|
|
83
|
+
if err != nil {
|
|
84
|
+
return err
|
|
85
|
+
}
|
|
86
|
+
patterns, regexPresent, err := noParamReassignStringListOption(
|
|
87
|
+
options,
|
|
88
|
+
"ignorePropertyModificationsForRegex",
|
|
89
|
+
)
|
|
90
|
+
if err != nil {
|
|
91
|
+
return err
|
|
92
|
+
}
|
|
93
|
+
for index, pattern := range patterns {
|
|
94
|
+
if _, err := regexp.Compile(pattern); err != nil {
|
|
95
|
+
return fmt.Errorf(
|
|
96
|
+
"option %q[%d] must be a valid regular expression: %w",
|
|
97
|
+
"ignorePropertyModificationsForRegex",
|
|
98
|
+
index,
|
|
99
|
+
err,
|
|
100
|
+
)
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
if propsPresent && !props && (exactPresent || regexPresent) {
|
|
104
|
+
return fmt.Errorf("ignore options cannot be combined with %q set to false", "props")
|
|
105
|
+
}
|
|
106
|
+
return nil
|
|
32
107
|
}
|
|
108
|
+
|
|
109
|
+
func noParamReassignStringListOption(
|
|
110
|
+
options map[string]any,
|
|
111
|
+
name string,
|
|
112
|
+
) ([]string, bool, error) {
|
|
113
|
+
value, present := options[name]
|
|
114
|
+
if !present {
|
|
115
|
+
return nil, false, nil
|
|
116
|
+
}
|
|
117
|
+
values, valid := value.([]any)
|
|
118
|
+
if !valid {
|
|
119
|
+
return nil, true, fmt.Errorf("option %q must be an array of strings", name)
|
|
120
|
+
}
|
|
121
|
+
decoded := make([]string, 0, len(values))
|
|
122
|
+
seen := make(map[string]struct{}, len(values))
|
|
123
|
+
for index, item := range values {
|
|
124
|
+
text, valid := item.(string)
|
|
125
|
+
if !valid {
|
|
126
|
+
return nil, true, fmt.Errorf("option %q[%d] must be a string", name, index)
|
|
127
|
+
}
|
|
128
|
+
if _, duplicate := seen[text]; duplicate {
|
|
129
|
+
return nil, true, fmt.Errorf("option %q contains duplicate value %q", name, text)
|
|
130
|
+
}
|
|
131
|
+
seen[text] = struct{}{}
|
|
132
|
+
decoded = append(decoded, text)
|
|
133
|
+
}
|
|
134
|
+
return decoded, true, nil
|
|
135
|
+
}
|
|
136
|
+
|
|
33
137
|
func (noParamReassign) Check(ctx *Context, node *shimast.Node) {
|
|
34
|
-
|
|
35
|
-
if body == nil {
|
|
138
|
+
if ctx == nil || ctx.Checker == nil || node == nil {
|
|
36
139
|
return
|
|
37
140
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
141
|
+
|
|
142
|
+
var options noParamReassignOptions
|
|
143
|
+
if err := ctx.DecodeOptions(&options); err != nil {
|
|
144
|
+
return
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
parameters := make(map[*shimast.Symbol]noParamReassignParameter)
|
|
148
|
+
parameterNames := make(map[string]struct{})
|
|
149
|
+
walkDescendants(node, func(child *shimast.Node) {
|
|
150
|
+
if !isFunctionLikeKind(child) {
|
|
151
|
+
return
|
|
152
|
+
}
|
|
153
|
+
for _, parameterNode := range child.Parameters() {
|
|
154
|
+
parameter := parameterNode.AsParameterDeclaration()
|
|
155
|
+
if parameter == nil {
|
|
156
|
+
continue
|
|
157
|
+
}
|
|
158
|
+
for _, nameNode := range bindingIdentifierNodes(parameter.Name()) {
|
|
159
|
+
name := identifierText(nameNode)
|
|
160
|
+
symbol := noParamReassignParameterSymbol(ctx, parameterNode, nameNode, name)
|
|
161
|
+
if symbol == nil || name == "" {
|
|
162
|
+
continue
|
|
163
|
+
}
|
|
164
|
+
parameters[symbol] = noParamReassignParameter{name: name}
|
|
165
|
+
parameterNames[name] = struct{}{}
|
|
43
166
|
}
|
|
44
167
|
}
|
|
45
|
-
}
|
|
46
|
-
if len(
|
|
168
|
+
})
|
|
169
|
+
if len(parameters) == 0 {
|
|
47
170
|
return
|
|
48
171
|
}
|
|
49
|
-
|
|
50
|
-
|
|
172
|
+
|
|
173
|
+
directTargets := make(map[*shimast.Node]struct{})
|
|
174
|
+
reportDirectTarget := func(target *shimast.Node) {
|
|
175
|
+
if target == nil || target.Kind != shimast.KindIdentifier {
|
|
176
|
+
return
|
|
177
|
+
}
|
|
178
|
+
if _, reported := directTargets[target]; reported {
|
|
179
|
+
return
|
|
180
|
+
}
|
|
181
|
+
parameter, ok := parameters[valueSymbolAtIdentifier(ctx, target)]
|
|
182
|
+
if !ok {
|
|
183
|
+
return
|
|
184
|
+
}
|
|
185
|
+
directTargets[target] = struct{}{}
|
|
186
|
+
ctx.Report(target, "Assignment to function parameter '"+parameter.name+"'.")
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
walkDescendants(node, func(child *shimast.Node) {
|
|
190
|
+
switch child.Kind {
|
|
191
|
+
case shimast.KindBinaryExpression:
|
|
192
|
+
expression := child.AsBinaryExpression()
|
|
193
|
+
if expression == nil || expression.OperatorToken == nil ||
|
|
194
|
+
!isAssignmentOperator(expression.OperatorToken.Kind) ||
|
|
195
|
+
isDestructuringDefaultAssignment(child) {
|
|
196
|
+
return
|
|
197
|
+
}
|
|
198
|
+
for _, target := range assignmentTargetIdentifiers(expression.Left) {
|
|
199
|
+
reportDirectTarget(target)
|
|
200
|
+
}
|
|
201
|
+
case shimast.KindPrefixUnaryExpression:
|
|
202
|
+
expression := child.AsPrefixUnaryExpression()
|
|
203
|
+
if expression == nil ||
|
|
204
|
+
(expression.Operator != shimast.KindPlusPlusToken && expression.Operator != shimast.KindMinusMinusToken) {
|
|
205
|
+
return
|
|
206
|
+
}
|
|
207
|
+
for _, target := range assignmentTargetIdentifiers(expression.Operand) {
|
|
208
|
+
reportDirectTarget(target)
|
|
209
|
+
}
|
|
210
|
+
case shimast.KindPostfixUnaryExpression:
|
|
211
|
+
expression := child.AsPostfixUnaryExpression()
|
|
212
|
+
if expression == nil ||
|
|
213
|
+
(expression.Operator != shimast.KindPlusPlusToken && expression.Operator != shimast.KindMinusMinusToken) {
|
|
214
|
+
return
|
|
215
|
+
}
|
|
216
|
+
for _, target := range assignmentTargetIdentifiers(expression.Operand) {
|
|
217
|
+
reportDirectTarget(target)
|
|
218
|
+
}
|
|
219
|
+
case shimast.KindForInStatement, shimast.KindForOfStatement:
|
|
220
|
+
statement := child.AsForInOrOfStatement()
|
|
221
|
+
if statement == nil || statement.Initializer == nil ||
|
|
222
|
+
statement.Initializer.Kind == shimast.KindVariableDeclarationList {
|
|
223
|
+
return
|
|
224
|
+
}
|
|
225
|
+
for _, target := range assignmentTargetIdentifiers(statement.Initializer) {
|
|
226
|
+
reportDirectTarget(target)
|
|
227
|
+
}
|
|
228
|
+
}
|
|
51
229
|
})
|
|
52
|
-
}
|
|
53
230
|
|
|
54
|
-
|
|
55
|
-
// without crossing nested function-like scopes. The target identifier
|
|
56
|
-
// must be a bare name in `names` for `report` to fire.
|
|
57
|
-
func walkParamReassignBody(root *shimast.Node, names map[string]bool, report func(*shimast.Node)) {
|
|
58
|
-
if root == nil {
|
|
231
|
+
if !options.Props {
|
|
59
232
|
return
|
|
60
233
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
name
|
|
64
|
-
return name != "" && names[name]
|
|
234
|
+
ignoredNames := make(map[string]struct{}, len(options.IgnorePropertyModificationsFor))
|
|
235
|
+
for _, name := range options.IgnorePropertyModificationsFor {
|
|
236
|
+
ignoredNames[name] = struct{}{}
|
|
65
237
|
}
|
|
66
|
-
|
|
67
|
-
|
|
238
|
+
ignoredPatterns := make([]*regexp.Regexp, 0, len(options.IgnorePropertyModificationsForRegex))
|
|
239
|
+
for _, pattern := range options.IgnorePropertyModificationsForRegex {
|
|
240
|
+
compiled, err := compileUserPattern(pattern)
|
|
241
|
+
if err != nil {
|
|
68
242
|
return
|
|
69
243
|
}
|
|
70
|
-
|
|
244
|
+
ignoredPatterns = append(ignoredPatterns, compiled)
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
walkDescendants(node, func(child *shimast.Node) {
|
|
248
|
+
if child.Kind != shimast.KindIdentifier {
|
|
249
|
+
return
|
|
250
|
+
}
|
|
251
|
+
if _, direct := directTargets[child]; direct {
|
|
252
|
+
return
|
|
253
|
+
}
|
|
254
|
+
if _, possibleParameter := parameterNames[identifierText(child)]; !possibleParameter {
|
|
255
|
+
return
|
|
256
|
+
}
|
|
257
|
+
parameter, ok := parameters[valueSymbolAtIdentifier(ctx, child)]
|
|
258
|
+
if !ok || noParamReassignIgnoresProperty(parameter.name, ignoredNames, ignoredPatterns) {
|
|
259
|
+
return
|
|
260
|
+
}
|
|
261
|
+
if noParamReassignModifiesProperty(child) {
|
|
262
|
+
ctx.Report(child, "Assignment to property of function parameter '"+parameter.name+"'.")
|
|
263
|
+
}
|
|
264
|
+
})
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// A TypeScript parameter property has two symbols at one declaration: the
|
|
268
|
+
// constructor-local parameter and the class member. GetSymbolAtLocation is
|
|
269
|
+
// deliberately fuzzy for declaration names and may select the member, while
|
|
270
|
+
// references in the constructor body resolve to the parameter. The constructor
|
|
271
|
+
// locals are the canonical binding table for body references; looking up that
|
|
272
|
+
// table also avoids the checker's pair API, which panics on malformed parameter
|
|
273
|
+
// properties instead of returning a partial result.
|
|
274
|
+
func noParamReassignParameterSymbol(
|
|
275
|
+
ctx *Context,
|
|
276
|
+
parameterNode *shimast.Node,
|
|
277
|
+
nameNode *shimast.Node,
|
|
278
|
+
name string,
|
|
279
|
+
) *shimast.Symbol {
|
|
280
|
+
if ctx == nil || ctx.Checker == nil || parameterNode == nil || nameNode == nil || name == "" {
|
|
281
|
+
return nil
|
|
282
|
+
}
|
|
283
|
+
if parameterNode.Parent != nil && parameterNode.Parent.Kind == shimast.KindConstructor &&
|
|
284
|
+
isParameterProperty(parameterNode) {
|
|
285
|
+
return parameterNode.Parent.Locals()[name]
|
|
286
|
+
}
|
|
287
|
+
return ctx.Checker.GetSymbolAtLocation(nameNode)
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
func noParamReassignIgnoresProperty(
|
|
291
|
+
name string,
|
|
292
|
+
names map[string]struct{},
|
|
293
|
+
patterns []*regexp.Regexp,
|
|
294
|
+
) bool {
|
|
295
|
+
if _, ignored := names[name]; ignored {
|
|
296
|
+
return true
|
|
297
|
+
}
|
|
298
|
+
for _, pattern := range patterns {
|
|
299
|
+
if pattern.MatchString(name) {
|
|
300
|
+
return true
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
return false
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// noParamReassignModifiesProperty mirrors ESLint's reference-parent walk. A
|
|
307
|
+
// parameter reference contributes to the value being mutated only through the
|
|
308
|
+
// receiver/callee side of member and call expressions. Computed keys, call
|
|
309
|
+
// arguments, property names, and conditional tests are read-only branches.
|
|
310
|
+
func noParamReassignModifiesProperty(identifier *shimast.Node) bool {
|
|
311
|
+
current := identifier
|
|
312
|
+
for current != nil && current.Parent != nil {
|
|
313
|
+
parent := current.Parent
|
|
314
|
+
switch parent.Kind {
|
|
71
315
|
case shimast.KindBinaryExpression:
|
|
72
|
-
|
|
73
|
-
if
|
|
74
|
-
|
|
75
|
-
report(node)
|
|
316
|
+
expression := parent.AsBinaryExpression()
|
|
317
|
+
if expression != nil && expression.OperatorToken != nil && isAssignmentOperator(expression.OperatorToken.Kind) {
|
|
318
|
+
return expression.Left == current
|
|
76
319
|
}
|
|
77
320
|
case shimast.KindPrefixUnaryExpression:
|
|
78
|
-
|
|
79
|
-
if
|
|
80
|
-
|
|
321
|
+
expression := parent.AsPrefixUnaryExpression()
|
|
322
|
+
if expression != nil &&
|
|
323
|
+
(expression.Operator == shimast.KindPlusPlusToken || expression.Operator == shimast.KindMinusMinusToken) {
|
|
324
|
+
return expression.Operand == current
|
|
81
325
|
}
|
|
82
326
|
case shimast.KindPostfixUnaryExpression:
|
|
83
|
-
|
|
84
|
-
if
|
|
85
|
-
|
|
327
|
+
expression := parent.AsPostfixUnaryExpression()
|
|
328
|
+
if expression != nil &&
|
|
329
|
+
(expression.Operator == shimast.KindPlusPlusToken || expression.Operator == shimast.KindMinusMinusToken) {
|
|
330
|
+
return expression.Operand == current
|
|
331
|
+
}
|
|
332
|
+
case shimast.KindDeleteExpression:
|
|
333
|
+
expression := parent.AsDeleteExpression()
|
|
334
|
+
return expression != nil && expression.Expression == current
|
|
335
|
+
case shimast.KindForInStatement, shimast.KindForOfStatement:
|
|
336
|
+
statement := parent.AsForInOrOfStatement()
|
|
337
|
+
return statement != nil && statement.Initializer == current
|
|
338
|
+
case shimast.KindCallExpression:
|
|
339
|
+
expression := parent.AsCallExpression()
|
|
340
|
+
if expression == nil || expression.Expression != current {
|
|
341
|
+
return false
|
|
342
|
+
}
|
|
343
|
+
case shimast.KindNewExpression:
|
|
344
|
+
expression := parent.AsNewExpression()
|
|
345
|
+
if expression == nil || expression.Expression != current {
|
|
346
|
+
return false
|
|
347
|
+
}
|
|
348
|
+
case shimast.KindPropertyAccessExpression:
|
|
349
|
+
expression := parent.AsPropertyAccessExpression()
|
|
350
|
+
if expression == nil || expression.Expression != current {
|
|
351
|
+
return false
|
|
352
|
+
}
|
|
353
|
+
case shimast.KindElementAccessExpression:
|
|
354
|
+
expression := parent.AsElementAccessExpression()
|
|
355
|
+
if expression == nil || expression.Expression != current {
|
|
356
|
+
return false
|
|
86
357
|
}
|
|
358
|
+
case shimast.KindConditionalExpression:
|
|
359
|
+
expression := parent.AsConditionalExpression()
|
|
360
|
+
if expression == nil || expression.Condition == current {
|
|
361
|
+
return false
|
|
362
|
+
}
|
|
363
|
+
case shimast.KindPropertyAssignment:
|
|
364
|
+
property := parent.AsPropertyAssignment()
|
|
365
|
+
if property == nil || property.Name() == current {
|
|
366
|
+
return false
|
|
367
|
+
}
|
|
368
|
+
case shimast.KindShorthandPropertyAssignment:
|
|
369
|
+
property := parent.AsShorthandPropertyAssignment()
|
|
370
|
+
if property == nil || property.Name() == current {
|
|
371
|
+
return false
|
|
372
|
+
}
|
|
373
|
+
case shimast.KindBindingElement:
|
|
374
|
+
element := parent.AsBindingElement()
|
|
375
|
+
if element == nil || element.PropertyName == current || element.Initializer == current {
|
|
376
|
+
return false
|
|
377
|
+
}
|
|
378
|
+
case shimast.KindComputedPropertyName:
|
|
379
|
+
return false
|
|
87
380
|
}
|
|
88
|
-
|
|
89
|
-
|
|
381
|
+
|
|
382
|
+
if noParamReassignPropertyWalkStopsAt(parent) {
|
|
90
383
|
return false
|
|
91
|
-
}
|
|
384
|
+
}
|
|
385
|
+
current = parent
|
|
386
|
+
}
|
|
387
|
+
return false
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
func noParamReassignPropertyWalkStopsAt(node *shimast.Node) bool {
|
|
391
|
+
if node == nil || node.Kind == shimast.KindSourceFile || isFunctionLikeKind(node) {
|
|
392
|
+
return true
|
|
393
|
+
}
|
|
394
|
+
if node.Kind >= shimast.KindFirstStatement && node.Kind <= shimast.KindLastStatement {
|
|
395
|
+
return true
|
|
396
|
+
}
|
|
397
|
+
switch node.Kind {
|
|
398
|
+
case shimast.KindParameter,
|
|
399
|
+
shimast.KindVariableDeclaration,
|
|
400
|
+
shimast.KindPropertyDeclaration,
|
|
401
|
+
shimast.KindClassDeclaration,
|
|
402
|
+
shimast.KindInterfaceDeclaration,
|
|
403
|
+
shimast.KindTypeAliasDeclaration,
|
|
404
|
+
shimast.KindEnumDeclaration,
|
|
405
|
+
shimast.KindModuleDeclaration:
|
|
406
|
+
return true
|
|
92
407
|
}
|
|
93
|
-
|
|
408
|
+
return false
|
|
94
409
|
}
|
|
95
410
|
|
|
96
411
|
func init() {
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
// noPromiseExecutorReturn rejects values returned by the callback passed as
|
|
2
|
+
// the first argument to the global Promise constructor. Promise ignores its
|
|
3
|
+
// executor's return value, so a value return almost always reflects a missing
|
|
4
|
+
// resolve/reject call or an accidentally concise arrow body.
|
|
5
|
+
//
|
|
6
|
+
// The rule uses checker binding identity rather than identifier text: a local
|
|
7
|
+
// class, variable, import, or parameter named Promise is a different
|
|
8
|
+
// constructor and remains untouched. It walks every statement nested inside
|
|
9
|
+
// the executor, but stops at nested function-like boundaries so those
|
|
10
|
+
// functions retain their own return scopes.
|
|
11
|
+
// https://eslint.org/docs/latest/rules/no-promise-executor-return
|
|
12
|
+
package linthost
|
|
13
|
+
|
|
14
|
+
import shimast "github.com/microsoft/typescript-go/shim/ast"
|
|
15
|
+
|
|
16
|
+
const noPromiseExecutorReturnMessage = "Return values from promise executor functions cannot be read."
|
|
17
|
+
|
|
18
|
+
type noPromiseExecutorReturn struct{ optionsRule }
|
|
19
|
+
|
|
20
|
+
type noPromiseExecutorReturnOptions struct {
|
|
21
|
+
AllowVoid bool `json:"allowVoid"`
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
func (noPromiseExecutorReturn) Name() string { return "no-promise-executor-return" }
|
|
25
|
+
func (noPromiseExecutorReturn) Visits() []shimast.Kind {
|
|
26
|
+
return []shimast.Kind{shimast.KindNewExpression}
|
|
27
|
+
}
|
|
28
|
+
func (noPromiseExecutorReturn) NeedsTypeChecker() bool { return true }
|
|
29
|
+
func (noPromiseExecutorReturn) Check(ctx *Context, node *shimast.Node) {
|
|
30
|
+
expression := node.AsNewExpression()
|
|
31
|
+
if expression == nil || expression.Arguments == nil || len(expression.Arguments.Nodes) == 0 {
|
|
32
|
+
return
|
|
33
|
+
}
|
|
34
|
+
callee := stripParens(expression.Expression)
|
|
35
|
+
if !isGlobalPromiseConstructor(ctx, callee) {
|
|
36
|
+
return
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
executor := stripParens(expression.Arguments.Nodes[0])
|
|
40
|
+
if executor == nil || (executor.Kind != shimast.KindArrowFunction && executor.Kind != shimast.KindFunctionExpression) {
|
|
41
|
+
return
|
|
42
|
+
}
|
|
43
|
+
body := executor.Body()
|
|
44
|
+
if body == nil {
|
|
45
|
+
return
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
var options noPromiseExecutorReturnOptions
|
|
49
|
+
_ = ctx.DecodeOptions(&options)
|
|
50
|
+
if executor.Kind == shimast.KindArrowFunction && body.Kind != shimast.KindBlock {
|
|
51
|
+
reportPromiseExecutorReturn(ctx, body, body, options)
|
|
52
|
+
return
|
|
53
|
+
}
|
|
54
|
+
walkPromiseExecutorReturns(body, func(returnNode, value *shimast.Node) {
|
|
55
|
+
reportPromiseExecutorReturn(ctx, returnNode, value, options)
|
|
56
|
+
})
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// isGlobalPromiseConstructor proves that callee resolves to the program's
|
|
60
|
+
// global Promise value. The declaration guard handles script files whose
|
|
61
|
+
// top-level value declaration can merge into the checker global table: such a
|
|
62
|
+
// declaration still shadows the built-in for this source file and must not be
|
|
63
|
+
// treated as the standard Promise constructor.
|
|
64
|
+
func isGlobalPromiseConstructor(ctx *Context, callee *shimast.Node) bool {
|
|
65
|
+
if ctx == nil || ctx.Checker == nil || ctx.File == nil || identifierText(callee) != "Promise" {
|
|
66
|
+
return false
|
|
67
|
+
}
|
|
68
|
+
resolved := ctx.Checker.GetSymbolAtLocation(callee)
|
|
69
|
+
global := ctx.Checker.GetGlobalSymbol("Promise", shimast.SymbolFlagsValue, nil)
|
|
70
|
+
if resolved == nil || global == nil {
|
|
71
|
+
return false
|
|
72
|
+
}
|
|
73
|
+
resolved = ctx.Checker.GetMergedSymbol(resolved)
|
|
74
|
+
global = ctx.Checker.GetMergedSymbol(global)
|
|
75
|
+
if resolved != global {
|
|
76
|
+
return false
|
|
77
|
+
}
|
|
78
|
+
for _, declaration := range resolved.Declarations {
|
|
79
|
+
if declaration != nil && shimast.GetSourceFileOfNode(declaration) == ctx.File &&
|
|
80
|
+
promiseDeclarationIntroducesValue(declaration) {
|
|
81
|
+
return false
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return true
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// promiseDeclarationIntroducesValue distinguishes same-file value bindings
|
|
88
|
+
// from type-only declarations that legitimately merge with the global Promise
|
|
89
|
+
// interface. The checker already resolves nested lexical bindings to a
|
|
90
|
+
// different symbol; this list is only needed for declarations merged into a
|
|
91
|
+
// script's global symbol table.
|
|
92
|
+
func promiseDeclarationIntroducesValue(declaration *shimast.Node) bool {
|
|
93
|
+
switch declaration.Kind {
|
|
94
|
+
case
|
|
95
|
+
shimast.KindVariableDeclaration,
|
|
96
|
+
shimast.KindBindingElement,
|
|
97
|
+
shimast.KindFunctionDeclaration,
|
|
98
|
+
shimast.KindClassDeclaration,
|
|
99
|
+
shimast.KindEnumDeclaration,
|
|
100
|
+
shimast.KindModuleDeclaration,
|
|
101
|
+
shimast.KindImportEqualsDeclaration:
|
|
102
|
+
return true
|
|
103
|
+
}
|
|
104
|
+
return false
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// walkPromiseExecutorReturns visits all explicit return statements owned by
|
|
108
|
+
// root. Nested control-flow statements are traversed naturally; nested
|
|
109
|
+
// functions are pruned because their return values belong to another call.
|
|
110
|
+
func walkPromiseExecutorReturns(root *shimast.Node, visit func(returnNode, value *shimast.Node)) {
|
|
111
|
+
if root == nil {
|
|
112
|
+
return
|
|
113
|
+
}
|
|
114
|
+
var walk func(*shimast.Node)
|
|
115
|
+
walk = func(node *shimast.Node) {
|
|
116
|
+
if node == nil {
|
|
117
|
+
return
|
|
118
|
+
}
|
|
119
|
+
if node != root && isFunctionLikeKind(node) {
|
|
120
|
+
return
|
|
121
|
+
}
|
|
122
|
+
if node.Kind == shimast.KindReturnStatement {
|
|
123
|
+
statement := node.AsReturnStatement()
|
|
124
|
+
if statement != nil && statement.Expression != nil {
|
|
125
|
+
visit(node, statement.Expression)
|
|
126
|
+
}
|
|
127
|
+
return
|
|
128
|
+
}
|
|
129
|
+
node.ForEachChild(func(child *shimast.Node) bool {
|
|
130
|
+
walk(child)
|
|
131
|
+
return false
|
|
132
|
+
})
|
|
133
|
+
}
|
|
134
|
+
walk(root)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
func reportPromiseExecutorReturn(
|
|
138
|
+
ctx *Context,
|
|
139
|
+
reportNode *shimast.Node,
|
|
140
|
+
value *shimast.Node,
|
|
141
|
+
options noPromiseExecutorReturnOptions,
|
|
142
|
+
) {
|
|
143
|
+
if options.AllowVoid && isExplicitVoidExpression(value) {
|
|
144
|
+
return
|
|
145
|
+
}
|
|
146
|
+
ctx.Report(reportNode, noPromiseExecutorReturnMessage)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
func isExplicitVoidExpression(node *shimast.Node) bool {
|
|
150
|
+
node = stripParens(node)
|
|
151
|
+
return node != nil && node.Kind == shimast.KindVoidExpression
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
func init() {
|
|
155
|
+
Register(noPromiseExecutorReturn{})
|
|
156
|
+
}
|