@ttsc/lint 0.18.4 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (188) hide show
  1. package/README.md +95 -43
  2. package/go.mod +5 -4
  3. package/internal/rulecode/rulecode.go +88 -0
  4. package/lib/index.d.ts +10 -1
  5. package/lib/index.js +45 -17
  6. package/lib/index.js.map +1 -1
  7. package/lib/structures/ITtscLintConfig.d.ts +3 -0
  8. package/lib/structures/TtscLintRuleSetting.d.ts +3 -2
  9. package/lib/structures/format/ITtscLintFormat.d.ts +3 -2
  10. package/lib/structures/format/ITtscLintFormatSortImports.d.ts +20 -4
  11. package/lib/structures/rules/ITtscLintBoundariesRuleOptions.d.ts +80 -8
  12. package/lib/structures/rules/ITtscLintBoundariesRules.d.ts +6 -8
  13. package/lib/structures/rules/ITtscLintContributorRules.d.ts +18 -2
  14. package/lib/structures/rules/ITtscLintCoreRuleOptions.d.ts +347 -0
  15. package/lib/structures/rules/ITtscLintCoreRuleOptions.js +3 -0
  16. package/lib/structures/rules/ITtscLintCoreRuleOptions.js.map +1 -0
  17. package/lib/structures/rules/ITtscLintCoreRules.d.ts +101 -67
  18. package/lib/structures/rules/ITtscLintRegexpRules.d.ts +7 -2
  19. package/lib/structures/rules/ITtscLintRuleOptionsMap.d.ts +53 -7
  20. package/lib/structures/rules/ITtscLintRules.d.ts +6 -2
  21. package/lib/structures/rules/ITtscLintSolidRules.d.ts +0 -8
  22. package/lib/structures/rules/ITtscLintTypeScriptRuleOptions.d.ts +206 -0
  23. package/lib/structures/rules/ITtscLintTypeScriptRuleOptions.js +8 -0
  24. package/lib/structures/rules/ITtscLintTypeScriptRuleOptions.js.map +1 -0
  25. package/lib/structures/rules/ITtscLintTypeScriptRules.d.ts +55 -42
  26. package/lib/structures/rules/ITtscLintUnicornRuleOptions.d.ts +276 -0
  27. package/lib/structures/rules/ITtscLintUnicornRuleOptions.js +3 -0
  28. package/lib/structures/rules/ITtscLintUnicornRuleOptions.js.map +1 -0
  29. package/lib/structures/rules/ITtscLintUnicornRules.d.ts +44 -36
  30. package/lib/structures/rules/index.d.ts +3 -0
  31. package/lib/structures/rules/index.js +3 -0
  32. package/lib/structures/rules/index.js.map +1 -1
  33. package/linthost/ast_helpers.go +521 -23
  34. package/linthost/ast_selector_matcher.go +1628 -0
  35. package/linthost/ast_selector_parser.go +628 -0
  36. package/linthost/comment_scan.go +154 -0
  37. package/linthost/compile.go +129 -79
  38. package/linthost/config.go +352 -88
  39. package/linthost/config_format.go +15 -1
  40. package/linthost/contrib_adapter.go +47 -8
  41. package/linthost/declaration_rules.go +12 -11
  42. package/linthost/directives.go +9 -67
  43. package/linthost/dispatch.go +37 -3
  44. package/linthost/engine.go +368 -53
  45. package/linthost/escape_string.go +77 -0
  46. package/linthost/fix.go +133 -23
  47. package/linthost/flags_gen.go +18 -17
  48. package/linthost/format.go +73 -167
  49. package/linthost/format_editor_settings.go +134 -26
  50. package/linthost/grapheme_tables_gen.go +1920 -0
  51. package/linthost/host.go +160 -61
  52. package/linthost/literal_escape_scan.go +117 -0
  53. package/linthost/lsp.go +545 -98
  54. package/linthost/numeric_literal_precision.go +228 -0
  55. package/linthost/polyfill_browserslist.go +1439 -0
  56. package/linthost/polyfill_browserslist_config.go +603 -0
  57. package/linthost/polyfill_corejs.go +750 -0
  58. package/linthost/polyfill_data.go +113 -0
  59. package/linthost/polyfill_data_gen.json +39868 -0
  60. package/linthost/print_nodes_array.go +7 -1
  61. package/linthost/print_nodes_object.go +7 -1
  62. package/linthost/project_engine.go +151 -0
  63. package/linthost/project_rules.go +78 -0
  64. package/linthost/regex_clean.go +191 -0
  65. package/linthost/regex_tree.go +1544 -0
  66. package/linthost/regex_tree_optimizer.go +1685 -0
  67. package/linthost/regexp_cache.go +44 -0
  68. package/linthost/rule_codes.go +105 -0
  69. package/linthost/rule_codes.json +745 -0
  70. package/linthost/rules_ban_ts_comment.go +312 -0
  71. package/linthost/rules_boundaries.go +128 -43
  72. package/linthost/rules_boundaries_dependencies.go +1013 -0
  73. package/linthost/rules_core_extra.go +62 -92
  74. package/linthost/rules_cypress.go +1 -1
  75. package/linthost/rules_default_case.go +87 -11
  76. package/linthost/rules_dupes.go +38 -13
  77. package/linthost/rules_empty.go +188 -20
  78. package/linthost/rules_escape.go +62 -48
  79. package/linthost/rules_format_arrow_parens.go +19 -5
  80. package/linthost/rules_format_bracket_spacing.go +1 -1
  81. package/linthost/rules_format_clause_join.go +1 -1
  82. package/linthost/rules_format_declaration_header.go +31 -19
  83. package/linthost/rules_format_indent.go +1 -1
  84. package/linthost/rules_format_jsdoc.go +1 -1
  85. package/linthost/rules_format_orphan_semi.go +1 -1
  86. package/linthost/rules_format_parameter_properties.go +17 -7
  87. package/linthost/rules_format_print_width.go +1 -1
  88. package/linthost/rules_format_quote_props.go +1 -1
  89. package/linthost/rules_format_quotes.go +1 -1
  90. package/linthost/rules_format_semi.go +106 -79
  91. package/linthost/rules_format_sort_imports.go +79 -45
  92. package/linthost/rules_format_statement_split.go +1 -1
  93. package/linthost/rules_format_ternary_nullish_parens.go +1 -1
  94. package/linthost/rules_format_trailing_comma.go +64 -1
  95. package/linthost/rules_format_whitespace.go +1 -1
  96. package/linthost/rules_functional.go +21 -22
  97. package/linthost/rules_gap.go +80 -3
  98. package/linthost/rules_grouped_accessor_pairs.go +84 -25
  99. package/linthost/rules_guard_for_in.go +64 -147
  100. package/linthost/rules_jsx_a11y.go +232 -22
  101. package/linthost/rules_logic.go +84 -38
  102. package/linthost/rules_nextjs.go +105 -16
  103. package/linthost/rules_no_class_assign.go +70 -0
  104. package/linthost/rules_no_duplicate_imports.go +266 -0
  105. package/linthost/rules_no_else_return.go +156 -38
  106. package/linthost/rules_no_extend_native.go +113 -17
  107. package/linthost/rules_no_fallthrough.go +1013 -0
  108. package/linthost/rules_no_func_assign.go +70 -0
  109. package/linthost/rules_no_import_assign.go +278 -86
  110. package/linthost/rules_no_inner_declarations.go +186 -0
  111. package/linthost/rules_no_loop_func.go +438 -53
  112. package/linthost/rules_no_mixed_operators.go +208 -52
  113. package/linthost/rules_no_param_reassign.go +376 -61
  114. package/linthost/rules_no_promise_executor_return.go +156 -0
  115. package/linthost/rules_no_restricted_imports.go +1013 -57
  116. package/linthost/rules_no_restricted_syntax.go +190 -26
  117. package/linthost/rules_no_useless_constructor.go +6 -36
  118. package/linthost/rules_problems.go +154 -311
  119. package/linthost/rules_promise.go +2578 -109
  120. package/linthost/rules_react_extras.go +71 -45
  121. package/linthost/rules_react_hooks.go +18 -2
  122. package/linthost/rules_react_perf.go +1 -0
  123. package/linthost/rules_react_refresh.go +1 -1
  124. package/linthost/rules_regexp.go +230 -14
  125. package/linthost/rules_security.go +45 -19
  126. package/linthost/rules_self.go +21 -1
  127. package/linthost/rules_solid.go +13 -7
  128. package/linthost/rules_storybook.go +1 -1
  129. package/linthost/rules_suggestions.go +490 -84
  130. package/linthost/rules_testing_library.go +12 -1
  131. package/linthost/rules_ts.go +227 -54
  132. package/linthost/rules_ts_async.go +0 -131
  133. package/linthost/rules_ts_no_base_to_string.go +3 -4
  134. package/linthost/rules_ts_no_misused_promises.go +817 -0
  135. package/linthost/rules_ts_no_restricted_types.go +320 -34
  136. package/linthost/rules_ts_no_unnecessary_qualifier.go +132 -55
  137. package/linthost/rules_ts_no_unsafe_assignment.go +608 -60
  138. package/linthost/rules_ts_require_array_sort_compare.go +5 -5
  139. package/linthost/rules_ts_switch_exhaustiveness_check.go +556 -86
  140. package/linthost/rules_unicorn_better_regex.go +224 -16
  141. package/linthost/rules_unicorn_consistent_destructuring.go +1002 -7
  142. package/linthost/rules_unicorn_consistent_existence_index_check.go +208 -29
  143. package/linthost/rules_unicorn_consistent_function_scoping.go +1041 -8
  144. package/linthost/rules_unicorn_consistent_template_literal_escape.go +178 -7
  145. package/linthost/rules_unicorn_escape_case.go +30 -19
  146. package/linthost/rules_unicorn_filename_case.go +763 -16
  147. package/linthost/rules_unicorn_import_style.go +783 -17
  148. package/linthost/rules_unicorn_isolated_functions.go +934 -14
  149. package/linthost/rules_unicorn_no_hex_escape.go +26 -11
  150. package/linthost/rules_unicorn_no_typeof_undefined.go +234 -25
  151. package/linthost/rules_unicorn_no_unnecessary_polyfills.go +376 -21
  152. package/linthost/rules_unicorn_no_unused_properties.go +756 -19
  153. package/linthost/rules_unicorn_number_literal_case.go +58 -39
  154. package/linthost/rules_unicorn_prefer_at.go +7 -3
  155. package/linthost/rules_unicorn_prefer_number_properties.go +381 -48
  156. package/linthost/rules_unicorn_prefer_optional_catch_binding.go +24 -24
  157. package/linthost/rules_unicorn_prefer_simple_condition_first.go +597 -19
  158. package/linthost/rules_unicorn_prefer_string_raw.go +147 -16
  159. package/linthost/rules_unicorn_prevent_abbreviations.go +1963 -95
  160. package/linthost/rules_unicorn_string_content.go +611 -15
  161. package/linthost/rules_unicorn_switch_case_break_position.go +172 -15
  162. package/linthost/rules_unicorn_template_indent.go +923 -18
  163. package/linthost/rules_unicorn_text_encoding_identifier_case.go +202 -42
  164. package/linthost/rules_unicorn_throw_new_error.go +218 -26
  165. package/linthost/rules_var.go +829 -142
  166. package/linthost/string_length.go +222 -0
  167. package/package.json +4 -3
  168. package/rule/project.go +145 -0
  169. package/rule/rule.go +58 -1
  170. package/src/index.ts +58 -17
  171. package/src/structures/ITtscLintConfig.ts +3 -0
  172. package/src/structures/TtscLintRuleSetting.ts +3 -2
  173. package/src/structures/format/ITtscLintFormat.ts +3 -2
  174. package/src/structures/format/ITtscLintFormatSortImports.ts +21 -4
  175. package/src/structures/rules/ITtscLintBoundariesRuleOptions.ts +122 -8
  176. package/src/structures/rules/ITtscLintBoundariesRules.ts +6 -8
  177. package/src/structures/rules/ITtscLintContributorRules.ts +18 -2
  178. package/src/structures/rules/ITtscLintCoreRuleOptions.ts +465 -0
  179. package/src/structures/rules/ITtscLintCoreRules.ts +122 -67
  180. package/src/structures/rules/ITtscLintRegexpRules.ts +7 -2
  181. package/src/structures/rules/ITtscLintRuleOptionsMap.ts +88 -7
  182. package/src/structures/rules/ITtscLintRules.ts +7 -2
  183. package/src/structures/rules/ITtscLintSolidRules.ts +0 -9
  184. package/src/structures/rules/ITtscLintTypeScriptRuleOptions.ts +248 -0
  185. package/src/structures/rules/ITtscLintTypeScriptRules.ts +64 -42
  186. package/src/structures/rules/ITtscLintUnicornRuleOptions.ts +350 -0
  187. package/src/structures/rules/ITtscLintUnicornRules.ts +60 -36
  188. package/src/structures/rules/index.ts +3 -0
@@ -1,96 +1,411 @@
1
- // noParamReassign: reassigning a function parameter inside the body of
2
- // the function it belongs to. Mutating the binding makes the declared
3
- // name stop pointing at the caller's argument and is the usual ESLint
4
- // baseline of `no-param-reassign` without the `props: true` extension
5
- // that would also flag `param.foo = …`.
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
- // Conservative scope tracking: collect simple-identifier parameter
8
- // names, walk the body until a nested function-like introduces a fresh
9
- // scope, and flag `name = …` / `name op= …` / `++name` / `name--` at
10
- // any depth in between. A local `const name = 1` that shadows a
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 shimast "github.com/microsoft/typescript-go/shim/ast"
14
+ import (
15
+ "encoding/json"
16
+ "fmt"
17
+ "regexp"
18
+ "sort"
18
19
 
19
- type noParamReassign struct{}
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) Visits() []shimast.Kind {
23
- return []shimast.Kind{
24
- shimast.KindFunctionDeclaration,
25
- shimast.KindFunctionExpression,
26
- shimast.KindArrowFunction,
27
- shimast.KindMethodDeclaration,
28
- shimast.KindConstructor,
29
- shimast.KindGetAccessor,
30
- shimast.KindSetAccessor,
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
- body := node.Body()
35
- if body == nil {
138
+ if ctx == nil || ctx.Checker == nil || node == nil {
36
139
  return
37
140
  }
38
- names := map[string]bool{}
39
- for _, p := range node.Parameters() {
40
- if decl := p.AsParameterDeclaration(); decl != nil {
41
- if name := identifierText(decl.Name()); name != "" {
42
- names[name] = true
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(names) == 0 {
168
+ })
169
+ if len(parameters) == 0 {
47
170
  return
48
171
  }
49
- walkParamReassignBody(body, names, func(target *shimast.Node) {
50
- ctx.Report(target, "Assignment to function parameter.")
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
- // walkParamReassignBody visits every assignment-like write in `root`
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
- var walk func(*shimast.Node)
62
- hits := func(operand *shimast.Node) bool {
63
- name := identifierText(stripParens(operand))
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
- walk = func(node *shimast.Node) {
67
- if node == nil || (node != root && isFunctionLikeKind(node)) {
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
- switch node.Kind {
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
- expr := node.AsBinaryExpression()
73
- if expr != nil && expr.OperatorToken != nil &&
74
- isAssignmentOperator(expr.OperatorToken.Kind) && hits(expr.Left) {
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
- pre := node.AsPrefixUnaryExpression()
79
- if pre != nil && (pre.Operator == shimast.KindPlusPlusToken || pre.Operator == shimast.KindMinusMinusToken) && hits(pre.Operand) {
80
- report(node)
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
- post := node.AsPostfixUnaryExpression()
84
- if post != nil && (post.Operator == shimast.KindPlusPlusToken || post.Operator == shimast.KindMinusMinusToken) && hits(post.Operand) {
85
- report(node)
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
- node.ForEachChild(func(child *shimast.Node) bool {
89
- walk(child)
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
- walk(root)
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
+ }