@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.
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 +129 -25
  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
@@ -13,6 +13,15 @@ type testingLibraryRule struct {
13
13
  name string
14
14
  }
15
15
 
16
+ // testingLibraryOptionsRule marks the one rule in the shared SourceFile
17
+ // dispatcher whose public contract includes an options object. Embedding the
18
+ // base rule keeps dispatch shared while leaving every sibling genuinely
19
+ // optionless in the registry.
20
+ type testingLibraryOptionsRule struct {
21
+ testingLibraryRule
22
+ optionsRule
23
+ }
24
+
16
25
  func (r testingLibraryRule) Name() string { return "testing-library/" + r.name }
17
26
  func (r testingLibraryRule) Visits() []shimast.Kind {
18
27
  return []shimast.Kind{shimast.KindSourceFile}
@@ -1168,11 +1177,13 @@ func testingLibraryFileName(fileName string) string {
1168
1177
  }
1169
1178
 
1170
1179
  func init() {
1180
+ Register(testingLibraryOptionsRule{
1181
+ testingLibraryRule: testingLibraryRule{name: "consistent-data-testid"},
1182
+ })
1171
1183
  for _, name := range []string{
1172
1184
  "await-async-events",
1173
1185
  "await-async-queries",
1174
1186
  "await-async-utils",
1175
- "consistent-data-testid",
1176
1187
  "no-await-sync-events",
1177
1188
  "no-await-sync-queries",
1178
1189
  "no-container",
@@ -4,7 +4,12 @@
4
4
  // Each rule is registered in the package init function at the bottom.
5
5
  package linthost
6
6
 
7
- import shimast "github.com/microsoft/typescript-go/shim/ast"
7
+ import (
8
+ "strings"
9
+
10
+ shimast "github.com/microsoft/typescript-go/shim/ast"
11
+ shimscanner "github.com/microsoft/typescript-go/shim/scanner"
12
+ )
8
13
 
9
14
  // noExplicitAny: ban `: any` annotations. Loud equivalent of
10
15
  // `@typescript-eslint/no-explicit-any`.
@@ -161,68 +166,257 @@ func (noThisAlias) Check(ctx *Context, node *shimast.Node) {
161
166
  }
162
167
  }
163
168
 
164
- // preferAsConst: `as 'foo'` / `as 1` should be `as const`.
169
+ // preferAsConst: `as 'foo'` / `<'foo'>` assertions and `let x: 'foo' = 'foo'`
170
+ // variable / class-property annotations should use `as const`. Port of
171
+ // `@typescript-eslint/prefer-as-const`, which visits the same four AST
172
+ // families and compares the literals' raw source spelling.
165
173
  type preferAsConst struct{}
166
174
 
167
175
  func (preferAsConst) Name() string { return "typescript/prefer-as-const" }
168
176
  func (preferAsConst) Visits() []shimast.Kind {
169
- return []shimast.Kind{shimast.KindAsExpression, shimast.KindTypeAssertionExpression}
177
+ return []shimast.Kind{
178
+ shimast.KindAsExpression,
179
+ shimast.KindTypeAssertionExpression,
180
+ shimast.KindVariableDeclaration,
181
+ shimast.KindPropertyDeclaration,
182
+ }
170
183
  }
171
184
  func (preferAsConst) Check(ctx *Context, node *shimast.Node) {
172
- var expr, typeNode *shimast.Node
173
185
  switch node.Kind {
174
186
  case shimast.KindAsExpression:
175
- as := node.AsAsExpression()
176
- if as == nil {
177
- return
187
+ if as := node.AsAsExpression(); as != nil {
188
+ preferAsConstCheckAssertion(ctx, as.Expression, as.Type)
178
189
  }
179
- expr = as.Expression
180
- typeNode = as.Type
181
190
  case shimast.KindTypeAssertionExpression:
182
- ta := node.AsTypeAssertion()
183
- if ta == nil {
191
+ if ta := node.AsTypeAssertion(); ta != nil {
192
+ preferAsConstCheckAssertion(ctx, ta.Expression, ta.Type)
193
+ }
194
+ case shimast.KindVariableDeclaration:
195
+ if decl := node.AsVariableDeclaration(); decl != nil {
196
+ preferAsConstCheckAnnotation(ctx, node, decl.Initializer, decl.Type)
197
+ }
198
+ case shimast.KindPropertyDeclaration:
199
+ decl := node.AsPropertyDeclaration()
200
+ if decl == nil {
184
201
  return
185
202
  }
186
- expr = ta.Expression
187
- typeNode = ta.Type
203
+ // `accessor` fields surface upstream as AccessorProperty nodes, which
204
+ // the rule's PropertyDefinition visitor never receives; get/set
205
+ // accessors and methods are separate AST kinds already.
206
+ if node.ModifierFlags()&shimast.ModifierFlagsAccessor != 0 {
207
+ return
208
+ }
209
+ preferAsConstCheckAnnotation(ctx, node, decl.Initializer, decl.Type)
188
210
  }
189
- if expr == nil || typeNode == nil {
211
+ }
212
+
213
+ // preferAsConstCheckAssertion handles the `expr as T` / `<T>expr` assertion
214
+ // forms. These are directly autofixable: the literal type becomes `const`, and
215
+ // any type-only parentheses are removed without disturbing their comments.
216
+ func preferAsConstCheckAssertion(ctx *Context, expr, typeNode *shimast.Node) {
217
+ if !preferAsConstLiteralsMatch(ctx.File, expr, typeNode) {
190
218
  return
191
219
  }
192
- if typeNode.Kind != shimast.KindLiteralType {
220
+ message := "Expected `as const` instead of `as` literal type."
221
+ edits := preferAsConstAssertionEdits(ctx.File, typeNode)
222
+ if len(edits) == 0 {
223
+ ctx.Report(typeNode, message)
193
224
  return
194
225
  }
195
- literalType := typeNode.AsLiteralTypeNode()
196
- if literalType == nil || literalType.Literal == nil {
197
- return
226
+ ctx.ReportFix(typeNode, message, edits...)
227
+ }
228
+
229
+ // preferAsConstAssertionEdits replaces the innermost literal type and removes
230
+ // only the syntactic parentheses around it. Keeping the trivia between those
231
+ // tokens preserves comments while producing valid `as const` / `<const>`
232
+ // syntax instead of the invalid `as (const)` shape.
233
+ func preferAsConstAssertionEdits(file *shimast.SourceFile, typeNode *shimast.Node) []TextEdit {
234
+ if file == nil || typeNode == nil {
235
+ return nil
198
236
  }
199
- if !literalsMatchSourceText(ctx.File, expr, literalType.Literal) {
200
- return
237
+ edits := []TextEdit{}
238
+ for typeNode.Kind == shimast.KindParenthesizedType {
239
+ parenthesized := typeNode.AsParenthesizedTypeNode()
240
+ if parenthesized == nil || parenthesized.Type == nil {
241
+ return nil
242
+ }
243
+ openScanner := shimscanner.GetScannerForSourceFile(file, typeNode.Pos())
244
+ if openScanner.Token() != shimast.KindOpenParenToken {
245
+ return nil
246
+ }
247
+ closeScanner := shimscanner.GetScannerForSourceFile(file, parenthesized.Type.End())
248
+ if closeScanner.Token() != shimast.KindCloseParenToken || closeScanner.TokenEnd() > typeNode.End() {
249
+ return nil
250
+ }
251
+ edits = append(edits,
252
+ TextEdit{Pos: openScanner.TokenStart(), End: openScanner.TokenEnd()},
253
+ TextEdit{Pos: closeScanner.TokenStart(), End: closeScanner.TokenEnd()},
254
+ )
255
+ typeNode = parenthesized.Type
201
256
  }
202
- message := "Expected `as const` instead of `as` literal type."
203
- pos, end := tokenRange(ctx.File, typeNode)
257
+ pos, end := tokenRange(file, typeNode)
204
258
  if pos < 0 {
205
- ctx.Report(node, message)
259
+ return nil
260
+ }
261
+ return append(edits, TextEdit{Pos: pos, End: end, Text: "const"})
262
+ }
263
+
264
+ // preferAsConstCheckAnnotation handles variable declarators and class
265
+ // property declarations whose literal type annotation repeats the
266
+ // initializer literal. The upstream rule pairs this report with a manual
267
+ // suggestion, not an autofix: it removes the annotation and appends `as const`
268
+ // after the initializer while `eslint --fix` leaves the declaration alone.
269
+ func preferAsConstCheckAnnotation(ctx *Context, declaration, init, typeNode *shimast.Node) {
270
+ if init == nil || !preferAsConstLiteralsMatch(ctx.File, init, typeNode) {
271
+ return
272
+ }
273
+ annotationStart := preferAsConstAnnotationStart(ctx.File, declaration, typeNode)
274
+ _, annotationEnd := tokenRange(ctx.File, typeNode)
275
+ message := "Expected a `const` assertion instead of a literal type annotation."
276
+ if annotationStart < 0 || annotationEnd < annotationStart || init.End() < annotationEnd {
277
+ ctx.Report(typeNode, message)
206
278
  return
207
279
  }
208
- ctx.ReportFix(
209
- node,
280
+ ctx.ReportSuggestion(
281
+ typeNode,
210
282
  message,
211
- TextEdit{Pos: pos, End: end, Text: "const"},
283
+ "Replace the literal type annotation with `as const`.",
284
+ TextEdit{
285
+ Pos: annotationStart,
286
+ End: annotationEnd,
287
+ Text: preferAsConstPreservedAnnotationComments(ctx.File, annotationStart, annotationEnd),
288
+ },
289
+ TextEdit{
290
+ Pos: init.End(),
291
+ End: init.End(),
292
+ Text: " as const",
293
+ },
212
294
  )
213
295
  }
214
296
 
215
- // literalsMatchSourceText reports whether lhs and rhs are both literal
216
- // expressions whose source text is identical. Used by preferAsConst to
217
- // detect `x as "foo"` where "foo" matches x's literal value.
218
- func literalsMatchSourceText(file *shimast.SourceFile, lhs, rhs *shimast.Node) bool {
219
- if lhs == nil || rhs == nil {
297
+ // preferAsConstPreservedAnnotationComments keeps comments embedded in the
298
+ // removed annotation. Multiline comments remain space-separated; line comments
299
+ // retain their line ending so the following initializer cannot be commented
300
+ // out. Syntax tokens and whitespace are intentionally discarded.
301
+ func preferAsConstPreservedAnnotationComments(file *shimast.SourceFile, start, end int) string {
302
+ if file == nil || start < 0 || end <= start || end > len(file.Text()) {
303
+ return ""
304
+ }
305
+ src := file.Text()
306
+ scanner := shimscanner.GetScannerForSourceFile(file, start)
307
+ scanner.SetSkipTrivia(false)
308
+ var preserved strings.Builder
309
+ lineEnded := false
310
+ for scanner.TokenStart() < end && scanner.Token() != shimast.KindEndOfFile {
311
+ kind := scanner.Token()
312
+ if kind == shimast.KindSingleLineCommentTrivia || kind == shimast.KindMultiLineCommentTrivia {
313
+ if preserved.Len() == 0 || !lineEnded {
314
+ preserved.WriteByte(' ')
315
+ }
316
+ preserved.WriteString(scanner.TokenText())
317
+ lineEnded = false
318
+ if kind == shimast.KindSingleLineCommentTrivia {
319
+ switch tokenEnd := scanner.TokenEnd(); {
320
+ case tokenEnd+1 < len(src) && src[tokenEnd:tokenEnd+2] == "\r\n":
321
+ preserved.WriteString("\r\n")
322
+ case tokenEnd < len(src) && (src[tokenEnd] == '\r' || src[tokenEnd] == '\n'):
323
+ preserved.WriteByte(src[tokenEnd])
324
+ default:
325
+ preserved.WriteByte('\n')
326
+ }
327
+ lineEnded = true
328
+ }
329
+ }
330
+ scanner.Scan()
331
+ }
332
+ return preserved.String()
333
+ }
334
+
335
+ // preferAsConstAnnotationStart locates the annotation's colon by tokenizing
336
+ // only the declaration segment between its name and type. This preserves
337
+ // modifiers, computed names, and postfix `?` / `!` markers without relying on
338
+ // source-text regular expressions.
339
+ func preferAsConstAnnotationStart(file *shimast.SourceFile, declaration, typeNode *shimast.Node) int {
340
+ if file == nil || declaration == nil || typeNode == nil || declaration.Name() == nil {
341
+ return -1
342
+ }
343
+ scanner := shimscanner.GetScannerForSourceFile(file, declaration.Name().End())
344
+ for {
345
+ kind := scanner.Token()
346
+ if kind == shimast.KindColonToken {
347
+ return scanner.TokenStart()
348
+ }
349
+ if kind == shimast.KindEndOfFile || scanner.TokenStart() >= typeNode.End() {
350
+ return -1
351
+ }
352
+ scanner.Scan()
353
+ }
354
+ }
355
+
356
+ // preferAsConstLiteralsMatch reports whether typeNode is a literal type
357
+ // whose literal token repeats expr's exact source spelling. Mirrors the
358
+ // upstream check `valueNode.raw === typeNode.literal.raw` between ESTree
359
+ // `Literal` nodes: string, numeric, bigint, and boolean literals qualify on
360
+ // both sides. Template literals (ESTree `TemplateLiteral`) and `null`
361
+ // (whose type position surfaces upstream as `TSNullKeyword`, not
362
+ // `TSLiteralType`) are excluded, so a template literal asserted to its
363
+ // identically spelled template literal type and `null as null` stay clean
364
+ // exactly like the upstream fixtures.
365
+ func preferAsConstLiteralsMatch(file *shimast.SourceFile, expr, typeNode *shimast.Node) bool {
366
+ if expr == nil || typeNode == nil {
220
367
  return false
221
368
  }
222
- if !isLiteralExpression(lhs) {
369
+ // typescript-estree erases both expression and type parentheses, so tsgo's
370
+ // ParenthesizedType wrappers must not affect the upstream raw-literal check.
371
+ typeNode = preferAsConstUnwrapType(typeNode)
372
+ if typeNode == nil || typeNode.Kind != shimast.KindLiteralType {
223
373
  return false
224
374
  }
225
- return nodeText(file, lhs) == nodeText(file, rhs)
375
+ literalType := typeNode.AsLiteralTypeNode()
376
+ if literalType == nil || literalType.Literal == nil {
377
+ return false
378
+ }
379
+ // ESTree does not represent expression parentheses, so upstream sees the
380
+ // bare literal in `('a') as 'a'` and reports it; descend to the same
381
+ // canonical node.
382
+ expr = stripParens(expr)
383
+ if !isPreferAsConstLiteral(expr) || !isPreferAsConstLiteral(literalType.Literal) {
384
+ return false
385
+ }
386
+ return nodeText(file, expr) == nodeText(file, literalType.Literal)
387
+ }
388
+
389
+ func preferAsConstUnwrapType(typeNode *shimast.Node) *shimast.Node {
390
+ for typeNode != nil && typeNode.Kind == shimast.KindParenthesizedType {
391
+ parenthesized := typeNode.AsParenthesizedTypeNode()
392
+ if parenthesized == nil || parenthesized.Type == nil {
393
+ return nil
394
+ }
395
+ typeNode = parenthesized.Type
396
+ }
397
+ return typeNode
398
+ }
399
+
400
+ // isPreferAsConstLiteral reports whether node is one of the literal token
401
+ // kinds ts-estree maps to an ESTree `Literal` in both the expression and
402
+ // the literal-type position. `null` and regular-expression literals map to
403
+ // `Literal` in expression position only; neither can raw-match a literal
404
+ // type (a `null` annotation is `TSNullKeyword` upstream and regexes cannot
405
+ // appear in type position), so excluding them here is equivalent to the
406
+ // upstream shape check.
407
+ func isPreferAsConstLiteral(node *shimast.Node) bool {
408
+ if node == nil {
409
+ return false
410
+ }
411
+ switch node.Kind {
412
+ case shimast.KindStringLiteral,
413
+ shimast.KindNumericLiteral,
414
+ shimast.KindBigIntLiteral,
415
+ shimast.KindTrueKeyword,
416
+ shimast.KindFalseKeyword:
417
+ return true
418
+ }
419
+ return false
226
420
  }
227
421
 
228
422
  // noRequireImports: ban `require(...)` calls in TS source. Use
@@ -250,26 +444,6 @@ func (noRequireImports) Check(ctx *Context, node *shimast.Node) {
250
444
  }
251
445
  }
252
446
 
253
- // banTsComment: `// @ts-ignore` / `// @ts-expect-error` silence the type
254
- // checker. The rule flags `@ts-ignore` and `@ts-expect-error`.
255
- type banTsComment struct{}
256
-
257
- func (banTsComment) Name() string { return "typescript/ban-ts-comment" }
258
- func (banTsComment) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindSourceFile} }
259
- func (banTsComment) Check(ctx *Context, node *shimast.Node) {
260
- if ctx.File == nil {
261
- return
262
- }
263
- for _, directive := range ctx.File.CommentDirectives {
264
- switch directive.Kind {
265
- case shimast.CommentDirectiveKindIgnore:
266
- ctx.ReportRange(directive.Loc.Pos(), directive.Loc.End(), "Do not use `@ts-ignore` because it alters compilation errors.")
267
- case shimast.CommentDirectiveKindExpectError:
268
- ctx.ReportRange(directive.Loc.Pos(), directive.Loc.End(), "Do not use `@ts-expect-error` because it alters compilation errors.")
269
- }
270
- }
271
- }
272
-
273
447
  func init() {
274
448
  Register(noExplicitAny{})
275
449
  Register(noNonNullAssertion{})
@@ -279,5 +453,4 @@ func init() {
279
453
  Register(noThisAlias{})
280
454
  Register(preferAsConst{})
281
455
  Register(noRequireImports{})
282
- Register(banTsComment{})
283
456
  }
@@ -7,8 +7,6 @@
7
7
  // Implemented here:
8
8
  // - typescript/require-await
9
9
  // (AST-only; async function with no `await` in its body)
10
- // - typescript/no-misused-promises
11
- // (type-aware; Promise in conditional / logical / forEach position)
12
10
  // - typescript/use-unknown-in-catch-callback-variable
13
11
  // (type-aware; `.catch(err)` / `.then(_, err)` must annotate `unknown`)
14
12
  // - typescript/only-throw-error
@@ -110,134 +108,6 @@ func requireAwaitBodyHasAwait(body *shimast.Node) bool {
110
108
  return found
111
109
  }
112
110
 
113
- // noMisusedPromises reports Promises supplied where a boolean or a
114
- // `void`-returning callback is expected. The Promise is truthy as a
115
- // value, so the conditional silently passes; the discarded Promise then
116
- // leaks any rejection. typescript-eslint:
117
- // https://typescript-eslint.io/rules/no-misused-promises/
118
- //
119
- // Type-aware. Covers the three highest-signal positions:
120
- // - conditional positions: `if (p)`, `while (p)`, `do … while (p)`,
121
- // `for (; p; …)`, `p ? a : b`, `!p`;
122
- // - short-circuit / nullish operators: `p && x`, `p || x`, `p ?? x`;
123
- // - array iteration: `.forEach(async fn)` where the callback returns
124
- // a Promise the iteration never observes.
125
- //
126
- // JSX event-handler positions and arbitrary user-defined
127
- // void-callback contracts are deferred — they need a richer expected-
128
- // type inspection than this baseline.
129
- type noMisusedPromises struct{}
130
-
131
- func (noMisusedPromises) Name() string { return "typescript/no-misused-promises" }
132
- func (noMisusedPromises) NeedsTypeChecker() bool {
133
- return true
134
- }
135
- func (noMisusedPromises) Visits() []shimast.Kind {
136
- return []shimast.Kind{
137
- shimast.KindIfStatement,
138
- shimast.KindWhileStatement,
139
- shimast.KindDoStatement,
140
- shimast.KindForStatement,
141
- shimast.KindConditionalExpression,
142
- shimast.KindPrefixUnaryExpression,
143
- shimast.KindBinaryExpression,
144
- shimast.KindCallExpression,
145
- }
146
- }
147
- func (noMisusedPromises) Check(ctx *Context, node *shimast.Node) {
148
- if ctx.Checker == nil {
149
- return
150
- }
151
- switch node.Kind {
152
- case shimast.KindIfStatement:
153
- if stmt := node.AsIfStatement(); stmt != nil {
154
- noMisusedPromisesReportConditional(ctx, stmt.Expression)
155
- }
156
- case shimast.KindWhileStatement:
157
- if stmt := node.AsWhileStatement(); stmt != nil {
158
- noMisusedPromisesReportConditional(ctx, stmt.Expression)
159
- }
160
- case shimast.KindDoStatement:
161
- if stmt := node.AsDoStatement(); stmt != nil {
162
- noMisusedPromisesReportConditional(ctx, stmt.Expression)
163
- }
164
- case shimast.KindForStatement:
165
- if stmt := node.AsForStatement(); stmt != nil {
166
- noMisusedPromisesReportConditional(ctx, stmt.Condition)
167
- }
168
- case shimast.KindConditionalExpression:
169
- if expr := node.AsConditionalExpression(); expr != nil {
170
- noMisusedPromisesReportConditional(ctx, expr.Condition)
171
- }
172
- case shimast.KindPrefixUnaryExpression:
173
- expr := node.AsPrefixUnaryExpression()
174
- if expr != nil && expr.Operator == shimast.KindExclamationToken {
175
- noMisusedPromisesReportConditional(ctx, expr.Operand)
176
- }
177
- case shimast.KindBinaryExpression:
178
- bin := node.AsBinaryExpression()
179
- if bin == nil || bin.OperatorToken == nil {
180
- return
181
- }
182
- switch bin.OperatorToken.Kind {
183
- case shimast.KindAmpersandAmpersandToken,
184
- shimast.KindBarBarToken,
185
- shimast.KindQuestionQuestionToken:
186
- noMisusedPromisesReportConditional(ctx, bin.Left)
187
- }
188
- case shimast.KindCallExpression:
189
- if call := node.AsCallExpression(); call != nil {
190
- noMisusedPromisesCheckVoidCallback(ctx, call)
191
- }
192
- }
193
- }
194
-
195
- // noMisusedPromisesReportConditional reports expr if it carries a
196
- // Promise type. `await x` and `void x` are author-acknowledged opt-outs
197
- // and pass through. `any` / `unknown` / `never` are intentionally NOT
198
- // flagged because they propagate from generic helpers and would
199
- // produce overwhelming false-positive volume in real codebases.
200
- func noMisusedPromisesReportConditional(ctx *Context, expr *shimast.Node) {
201
- target := stripParens(expr)
202
- if target == nil {
203
- return
204
- }
205
- switch target.Kind {
206
- case shimast.KindAwaitExpression, shimast.KindVoidExpression:
207
- return
208
- }
209
- t := ctx.Checker.GetTypeAtLocation(target)
210
- if t == nil || !isPromiseTypedExpression(ctx.Checker, t) {
211
- return
212
- }
213
- ctx.Report(target, "Expected non-Promise value in a boolean conditional position; did you forget `await`?")
214
- }
215
-
216
- // noMisusedPromisesCheckVoidCallback fires when an async callback is
217
- // passed to a method that expects a void-returning callback. The
218
- // baseline targets the most common offender — `Array.prototype.forEach`
219
- // — by name rather than by signature inspection. Restricting to
220
- // `forEach` keeps the false-positive rate low; richer void-callback
221
- // detection (e.g., JSX event handlers, user methods with `void` in
222
- // their signature) is deferred.
223
- func noMisusedPromisesCheckVoidCallback(ctx *Context, call *shimast.CallExpression) {
224
- if call == nil || call.Arguments == nil || len(call.Arguments.Nodes) == 0 {
225
- return
226
- }
227
- _, method, ok := promisePropertyAccessParts(call.Expression)
228
- if !ok || method != "forEach" {
229
- return
230
- }
231
- callback := stripParens(call.Arguments.Nodes[0])
232
- if callback == nil || !isFunctionLikeKind(callback) {
233
- return
234
- }
235
- if !hasAsyncModifier(callback) {
236
- return
237
- }
238
- ctx.Report(callback, "Async callback passed to forEach — the returned Promise will be discarded; use `for ... of` instead.")
239
- }
240
-
241
111
  // useUnknownInCatchCallbackVariable requires the parameter of `.catch`
242
112
  // and the second parameter of `.then` to be explicitly typed `unknown`.
243
113
  // TypeScript 4.4+ types `try { } catch (e)` as `unknown` by default;
@@ -393,7 +263,6 @@ func onlyThrowErrorIsPrimitive(checker *shimchecker.Checker, t *shimchecker.Type
393
263
 
394
264
  func init() {
395
265
  Register(requireAwait{})
396
- Register(noMisusedPromises{})
397
266
  Register(useUnknownInCatchCallbackVariable{})
398
267
  Register(onlyThrowError{})
399
268
  }
@@ -130,10 +130,9 @@ func isStringLikeType(t *shimchecker.Type) bool {
130
130
 
131
131
  // isSafeToStringType reports whether `t` can be implicitly coerced to a
132
132
  // string without producing the default `Object.prototype.toString`
133
- // output. The shape mirrors `isAwaitable` in rules_promise.go: union
134
- // constituents must all be safe; intersection constituents are safe
135
- // when any one of them is. The `any` / `unknown` / `never` cases pass
136
- // because flagging them would explode at generic-helper boundaries —
133
+ // output. Union constituents must all be safe; intersection constituents
134
+ // are safe when any one of them is. The `any` / `unknown` / `never` cases
135
+ // pass because flagging them would explode at generic-helper boundaries
137
136
  // the same conservatism `no-floating-promises` adopts.
138
137
  func isSafeToStringType(checker *shimchecker.Checker, t *shimchecker.Type) bool {
139
138
  if checker == nil || t == nil {