@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.
- 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 +47 -8
- package/linthost/declaration_rules.go +12 -11
- package/linthost/directives.go +9 -67
- package/linthost/dispatch.go +37 -3
- package/linthost/engine.go +368 -53
- package/linthost/escape_string.go +77 -0
- package/linthost/fix.go +133 -23
- package/linthost/flags_gen.go +18 -17
- package/linthost/format.go +73 -167
- package/linthost/format_editor_settings.go +134 -26
- package/linthost/grapheme_tables_gen.go +1920 -0
- package/linthost/host.go +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
|
@@ -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",
|
package/linthost/rules_ts.go
CHANGED
|
@@ -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
|
|
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'` /
|
|
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{
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
187
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
196
|
-
|
|
197
|
-
|
|
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
|
-
|
|
200
|
-
|
|
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
|
-
|
|
203
|
-
pos, end := tokenRange(ctx.File, typeNode)
|
|
257
|
+
pos, end := tokenRange(file, typeNode)
|
|
204
258
|
if pos < 0 {
|
|
205
|
-
|
|
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.
|
|
209
|
-
|
|
280
|
+
ctx.ReportSuggestion(
|
|
281
|
+
typeNode,
|
|
210
282
|
message,
|
|
211
|
-
|
|
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
|
-
//
|
|
216
|
-
//
|
|
217
|
-
//
|
|
218
|
-
|
|
219
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
134
|
-
//
|
|
135
|
-
//
|
|
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 {
|