@ttsc/lint 0.18.3 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +95 -43
- package/go.mod +5 -4
- package/internal/rulecode/rulecode.go +88 -0
- package/lib/index.d.ts +10 -1
- package/lib/index.js +45 -17
- package/lib/index.js.map +1 -1
- package/lib/structures/ITtscLintConfig.d.ts +3 -0
- package/lib/structures/TtscLintRuleSetting.d.ts +3 -2
- package/lib/structures/format/ITtscLintFormat.d.ts +3 -2
- package/lib/structures/format/ITtscLintFormatSortImports.d.ts +20 -4
- package/lib/structures/rules/ITtscLintBoundariesRuleOptions.d.ts +80 -8
- package/lib/structures/rules/ITtscLintBoundariesRules.d.ts +6 -8
- package/lib/structures/rules/ITtscLintContributorRules.d.ts +18 -2
- package/lib/structures/rules/ITtscLintCoreRuleOptions.d.ts +347 -0
- package/lib/structures/rules/ITtscLintCoreRuleOptions.js +3 -0
- package/lib/structures/rules/ITtscLintCoreRuleOptions.js.map +1 -0
- package/lib/structures/rules/ITtscLintCoreRules.d.ts +101 -67
- package/lib/structures/rules/ITtscLintRegexpRules.d.ts +7 -2
- package/lib/structures/rules/ITtscLintRuleOptionsMap.d.ts +53 -7
- package/lib/structures/rules/ITtscLintRules.d.ts +6 -2
- package/lib/structures/rules/ITtscLintSolidRules.d.ts +0 -8
- package/lib/structures/rules/ITtscLintTypeScriptRuleOptions.d.ts +206 -0
- package/lib/structures/rules/ITtscLintTypeScriptRuleOptions.js +8 -0
- package/lib/structures/rules/ITtscLintTypeScriptRuleOptions.js.map +1 -0
- package/lib/structures/rules/ITtscLintTypeScriptRules.d.ts +55 -42
- package/lib/structures/rules/ITtscLintUnicornRuleOptions.d.ts +276 -0
- package/lib/structures/rules/ITtscLintUnicornRuleOptions.js +3 -0
- package/lib/structures/rules/ITtscLintUnicornRuleOptions.js.map +1 -0
- package/lib/structures/rules/ITtscLintUnicornRules.d.ts +44 -36
- package/lib/structures/rules/index.d.ts +3 -0
- package/lib/structures/rules/index.js +3 -0
- package/lib/structures/rules/index.js.map +1 -1
- package/linthost/ast_helpers.go +521 -23
- package/linthost/ast_selector_matcher.go +1628 -0
- package/linthost/ast_selector_parser.go +628 -0
- package/linthost/comment_scan.go +154 -0
- package/linthost/compile.go +129 -79
- package/linthost/config.go +352 -88
- package/linthost/config_format.go +15 -1
- package/linthost/contrib_adapter.go +129 -25
- package/linthost/declaration_rules.go +12 -11
- package/linthost/directives.go +9 -67
- package/linthost/dispatch.go +37 -3
- package/linthost/engine.go +368 -53
- package/linthost/escape_string.go +77 -0
- package/linthost/fix.go +133 -23
- package/linthost/flags_gen.go +18 -17
- package/linthost/format.go +73 -167
- package/linthost/format_editor_settings.go +134 -26
- package/linthost/grapheme_tables_gen.go +1920 -0
- package/linthost/host.go +160 -61
- package/linthost/literal_escape_scan.go +117 -0
- package/linthost/lsp.go +545 -98
- package/linthost/numeric_literal_precision.go +228 -0
- package/linthost/polyfill_browserslist.go +1439 -0
- package/linthost/polyfill_browserslist_config.go +603 -0
- package/linthost/polyfill_corejs.go +750 -0
- package/linthost/polyfill_data.go +113 -0
- package/linthost/polyfill_data_gen.json +39868 -0
- package/linthost/print_nodes_array.go +7 -1
- package/linthost/print_nodes_object.go +7 -1
- package/linthost/project_engine.go +151 -0
- package/linthost/project_rules.go +78 -0
- package/linthost/regex_clean.go +191 -0
- package/linthost/regex_tree.go +1544 -0
- package/linthost/regex_tree_optimizer.go +1685 -0
- package/linthost/regexp_cache.go +44 -0
- package/linthost/rule_codes.go +105 -0
- package/linthost/rule_codes.json +745 -0
- package/linthost/rules_ban_ts_comment.go +312 -0
- package/linthost/rules_boundaries.go +128 -43
- package/linthost/rules_boundaries_dependencies.go +1013 -0
- package/linthost/rules_core_extra.go +62 -92
- package/linthost/rules_cypress.go +1 -1
- package/linthost/rules_default_case.go +87 -11
- package/linthost/rules_dupes.go +38 -13
- package/linthost/rules_empty.go +188 -20
- package/linthost/rules_escape.go +62 -48
- package/linthost/rules_format_arrow_parens.go +19 -5
- package/linthost/rules_format_bracket_spacing.go +1 -1
- package/linthost/rules_format_clause_join.go +1 -1
- package/linthost/rules_format_declaration_header.go +31 -19
- package/linthost/rules_format_indent.go +1 -1
- package/linthost/rules_format_jsdoc.go +1 -1
- package/linthost/rules_format_orphan_semi.go +1 -1
- package/linthost/rules_format_parameter_properties.go +17 -7
- package/linthost/rules_format_print_width.go +1 -1
- package/linthost/rules_format_quote_props.go +1 -1
- package/linthost/rules_format_quotes.go +1 -1
- package/linthost/rules_format_semi.go +106 -79
- package/linthost/rules_format_sort_imports.go +79 -45
- package/linthost/rules_format_statement_split.go +1 -1
- package/linthost/rules_format_ternary_nullish_parens.go +1 -1
- package/linthost/rules_format_trailing_comma.go +64 -1
- package/linthost/rules_format_whitespace.go +1 -1
- package/linthost/rules_functional.go +21 -22
- package/linthost/rules_gap.go +80 -3
- package/linthost/rules_grouped_accessor_pairs.go +84 -25
- package/linthost/rules_guard_for_in.go +64 -147
- package/linthost/rules_jsx_a11y.go +232 -22
- package/linthost/rules_logic.go +84 -38
- package/linthost/rules_nextjs.go +105 -16
- package/linthost/rules_no_class_assign.go +70 -0
- package/linthost/rules_no_duplicate_imports.go +266 -0
- package/linthost/rules_no_else_return.go +156 -38
- package/linthost/rules_no_extend_native.go +113 -17
- package/linthost/rules_no_fallthrough.go +1013 -0
- package/linthost/rules_no_func_assign.go +70 -0
- package/linthost/rules_no_import_assign.go +278 -86
- package/linthost/rules_no_inner_declarations.go +186 -0
- package/linthost/rules_no_loop_func.go +438 -53
- package/linthost/rules_no_mixed_operators.go +208 -52
- package/linthost/rules_no_param_reassign.go +376 -61
- package/linthost/rules_no_promise_executor_return.go +156 -0
- package/linthost/rules_no_restricted_imports.go +1013 -57
- package/linthost/rules_no_restricted_syntax.go +190 -26
- package/linthost/rules_no_useless_constructor.go +6 -36
- package/linthost/rules_problems.go +154 -311
- package/linthost/rules_promise.go +2578 -109
- package/linthost/rules_react_extras.go +71 -45
- package/linthost/rules_react_hooks.go +18 -2
- package/linthost/rules_react_perf.go +1 -0
- package/linthost/rules_react_refresh.go +1 -1
- package/linthost/rules_regexp.go +230 -14
- package/linthost/rules_security.go +45 -19
- package/linthost/rules_self.go +21 -1
- package/linthost/rules_solid.go +13 -7
- package/linthost/rules_storybook.go +1 -1
- package/linthost/rules_suggestions.go +490 -84
- package/linthost/rules_testing_library.go +12 -1
- package/linthost/rules_ts.go +227 -54
- package/linthost/rules_ts_async.go +0 -131
- package/linthost/rules_ts_no_base_to_string.go +3 -4
- package/linthost/rules_ts_no_misused_promises.go +817 -0
- package/linthost/rules_ts_no_restricted_types.go +320 -34
- package/linthost/rules_ts_no_unnecessary_qualifier.go +132 -55
- package/linthost/rules_ts_no_unsafe_assignment.go +608 -60
- package/linthost/rules_ts_require_array_sort_compare.go +5 -5
- package/linthost/rules_ts_switch_exhaustiveness_check.go +556 -86
- package/linthost/rules_unicorn_better_regex.go +224 -16
- package/linthost/rules_unicorn_consistent_destructuring.go +1002 -7
- package/linthost/rules_unicorn_consistent_existence_index_check.go +208 -29
- package/linthost/rules_unicorn_consistent_function_scoping.go +1041 -8
- package/linthost/rules_unicorn_consistent_template_literal_escape.go +178 -7
- package/linthost/rules_unicorn_escape_case.go +30 -19
- package/linthost/rules_unicorn_filename_case.go +763 -16
- package/linthost/rules_unicorn_import_style.go +783 -17
- package/linthost/rules_unicorn_isolated_functions.go +934 -14
- package/linthost/rules_unicorn_no_hex_escape.go +26 -11
- package/linthost/rules_unicorn_no_typeof_undefined.go +234 -25
- package/linthost/rules_unicorn_no_unnecessary_polyfills.go +376 -21
- package/linthost/rules_unicorn_no_unused_properties.go +756 -19
- package/linthost/rules_unicorn_number_literal_case.go +58 -39
- package/linthost/rules_unicorn_prefer_at.go +7 -3
- package/linthost/rules_unicorn_prefer_number_properties.go +381 -48
- package/linthost/rules_unicorn_prefer_optional_catch_binding.go +24 -24
- package/linthost/rules_unicorn_prefer_simple_condition_first.go +597 -19
- package/linthost/rules_unicorn_prefer_string_raw.go +147 -16
- package/linthost/rules_unicorn_prevent_abbreviations.go +1963 -95
- package/linthost/rules_unicorn_string_content.go +611 -15
- package/linthost/rules_unicorn_switch_case_break_position.go +172 -15
- package/linthost/rules_unicorn_template_indent.go +923 -18
- package/linthost/rules_unicorn_text_encoding_identifier_case.go +202 -42
- package/linthost/rules_unicorn_throw_new_error.go +218 -26
- package/linthost/rules_var.go +829 -142
- package/linthost/string_length.go +222 -0
- package/package.json +4 -3
- package/rule/project.go +145 -0
- package/rule/rule.go +58 -1
- package/src/index.ts +58 -17
- package/src/structures/ITtscLintConfig.ts +3 -0
- package/src/structures/TtscLintRuleSetting.ts +3 -2
- package/src/structures/format/ITtscLintFormat.ts +3 -2
- package/src/structures/format/ITtscLintFormatSortImports.ts +21 -4
- package/src/structures/rules/ITtscLintBoundariesRuleOptions.ts +122 -8
- package/src/structures/rules/ITtscLintBoundariesRules.ts +6 -8
- package/src/structures/rules/ITtscLintContributorRules.ts +18 -2
- package/src/structures/rules/ITtscLintCoreRuleOptions.ts +465 -0
- package/src/structures/rules/ITtscLintCoreRules.ts +122 -67
- package/src/structures/rules/ITtscLintRegexpRules.ts +7 -2
- package/src/structures/rules/ITtscLintRuleOptionsMap.ts +88 -7
- package/src/structures/rules/ITtscLintRules.ts +7 -2
- package/src/structures/rules/ITtscLintSolidRules.ts +0 -9
- package/src/structures/rules/ITtscLintTypeScriptRuleOptions.ts +248 -0
- package/src/structures/rules/ITtscLintTypeScriptRules.ts +64 -42
- package/src/structures/rules/ITtscLintUnicornRuleOptions.ts +350 -0
- package/src/structures/rules/ITtscLintUnicornRules.ts +60 -36
- package/src/structures/rules/index.ts +3 -0
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// hadn't migrated yet.
|
|
4
4
|
//
|
|
5
5
|
// Implemented here:
|
|
6
|
-
// - no-await-in-loop: await
|
|
6
|
+
// - no-await-in-loop: explicit or implicit await in a repeated loop position
|
|
7
7
|
// - no-dupe-class-members: duplicate class member declarations
|
|
8
8
|
// - no-this-before-super: `this` (or `super.x`) before `super()` in
|
|
9
9
|
// derived constructors
|
|
@@ -14,75 +14,85 @@ import (
|
|
|
14
14
|
shimast "github.com/microsoft/typescript-go/shim/ast"
|
|
15
15
|
)
|
|
16
16
|
|
|
17
|
-
// noAwaitInLoop reports
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
// operations are independent and could overlap with Promise.all.
|
|
17
|
+
// noAwaitInLoop reports explicit and implicit awaits evaluated in a repeated
|
|
18
|
+
// loop position. Initializers and iterable expressions run only once per loop,
|
|
19
|
+
// while tests, updates, bodies, nested for-await statements, and await-using
|
|
20
|
+
// declarations in repeated positions serialize iterations.
|
|
22
21
|
// https://eslint.org/docs/latest/rules/no-await-in-loop
|
|
23
|
-
//
|
|
24
|
-
// `for await … of` loops are exempt — the for-await iterator protocol
|
|
25
|
-
// is the whole reason the loop exists, and rejecting its `await` would
|
|
26
|
-
// just suggest the developer abandon the iterator.
|
|
27
22
|
type noAwaitInLoop struct{}
|
|
28
23
|
|
|
29
24
|
func (noAwaitInLoop) Name() string { return "no-await-in-loop" }
|
|
30
25
|
func (noAwaitInLoop) Visits() []shimast.Kind {
|
|
31
|
-
return []shimast.Kind{
|
|
26
|
+
return []shimast.Kind{
|
|
27
|
+
shimast.KindAwaitExpression,
|
|
28
|
+
shimast.KindForOfStatement,
|
|
29
|
+
shimast.KindVariableDeclarationList,
|
|
30
|
+
}
|
|
32
31
|
}
|
|
33
32
|
func (noAwaitInLoop) Check(ctx *Context, node *shimast.Node) {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
shimast.KindWhileStatement,
|
|
41
|
-
shimast.KindDoStatement,
|
|
42
|
-
shimast.KindForInStatement:
|
|
43
|
-
ctx.Report(node, "Unexpected `await` inside a loop — iterations run sequentially; prefer `Promise.all` when independent.")
|
|
44
|
-
return
|
|
45
|
-
case shimast.KindForOfStatement:
|
|
46
|
-
// `for await … of` loops are exempt by design — the await
|
|
47
|
-
// is the iteration itself, not a sequential block.
|
|
48
|
-
if isForAwaitOfStatement(ctx.File, cur) {
|
|
49
|
-
return
|
|
50
|
-
}
|
|
33
|
+
if node == nil || !isAwaitInLoopCandidate(node) {
|
|
34
|
+
return
|
|
35
|
+
}
|
|
36
|
+
child := node
|
|
37
|
+
for parent := node.Parent; parent != nil && !isAwaitInLoopBoundary(parent); parent = parent.Parent {
|
|
38
|
+
if isRepeatedLoopPosition(child, parent) {
|
|
51
39
|
ctx.Report(node, "Unexpected `await` inside a loop — iterations run sequentially; prefer `Promise.all` when independent.")
|
|
52
40
|
return
|
|
53
41
|
}
|
|
42
|
+
child = parent
|
|
54
43
|
}
|
|
55
44
|
}
|
|
56
45
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
return
|
|
66
|
-
|
|
67
|
-
forPos := keywordStart(file, node, "for")
|
|
68
|
-
if forPos < 0 {
|
|
46
|
+
func isAwaitInLoopCandidate(node *shimast.Node) bool {
|
|
47
|
+
switch node.Kind {
|
|
48
|
+
case shimast.KindAwaitExpression:
|
|
49
|
+
return true
|
|
50
|
+
case shimast.KindForOfStatement:
|
|
51
|
+
statement := node.AsForInOrOfStatement()
|
|
52
|
+
return statement != nil && statement.AwaitModifier != nil
|
|
53
|
+
case shimast.KindVariableDeclarationList:
|
|
54
|
+
return isAwaitUsingDeclarationList(node)
|
|
55
|
+
default:
|
|
69
56
|
return false
|
|
70
57
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
func isAwaitInLoopBoundary(node *shimast.Node) bool {
|
|
61
|
+
if isFunctionLikeKind(node) {
|
|
62
|
+
return true
|
|
75
63
|
}
|
|
76
|
-
if node.
|
|
77
|
-
|
|
64
|
+
if node == nil || node.Kind != shimast.KindForOfStatement {
|
|
65
|
+
return false
|
|
78
66
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
67
|
+
statement := node.AsForInOrOfStatement()
|
|
68
|
+
return statement != nil && statement.AwaitModifier != nil
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
func isRepeatedLoopPosition(child, parent *shimast.Node) bool {
|
|
72
|
+
switch parent.Kind {
|
|
73
|
+
case shimast.KindForStatement:
|
|
74
|
+
statement := parent.AsForStatement()
|
|
75
|
+
return statement != nil &&
|
|
76
|
+
(child == statement.Condition || child == statement.Incrementor || child == statement.Statement)
|
|
77
|
+
case shimast.KindForInStatement, shimast.KindForOfStatement:
|
|
78
|
+
statement := parent.AsForInOrOfStatement()
|
|
79
|
+
return statement != nil &&
|
|
80
|
+
(child == statement.Statement ||
|
|
81
|
+
(child == statement.Initializer && isAwaitUsingDeclarationList(child)))
|
|
82
|
+
case shimast.KindWhileStatement:
|
|
83
|
+
statement := parent.AsWhileStatement()
|
|
84
|
+
return statement != nil && (child == statement.Expression || child == statement.Statement)
|
|
85
|
+
case shimast.KindDoStatement:
|
|
86
|
+
statement := parent.AsDoStatement()
|
|
87
|
+
return statement != nil && (child == statement.Expression || child == statement.Statement)
|
|
88
|
+
default:
|
|
89
|
+
return false
|
|
84
90
|
}
|
|
85
|
-
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
func isAwaitUsingDeclarationList(node *shimast.Node) bool {
|
|
94
|
+
return node != nil && node.Kind == shimast.KindVariableDeclarationList &&
|
|
95
|
+
shimast.GetCombinedNodeFlags(node)&shimast.NodeFlagsBlockScoped == shimast.NodeFlagsAwaitUsing
|
|
86
96
|
}
|
|
87
97
|
|
|
88
98
|
// noDupeClassMembers reports two declarations of the same member on a
|
|
@@ -377,45 +387,6 @@ func objectLiteralIsEmpty(node *shimast.Node) bool {
|
|
|
377
387
|
return len(lit.Properties.Nodes) == 0
|
|
378
388
|
}
|
|
379
389
|
|
|
380
|
-
// noDuplicateImports reports two import declarations that resolve to
|
|
381
|
-
// the same module specifier. Consolidating them into one import keeps
|
|
382
|
-
// the dependency graph at the head of the file readable and avoids
|
|
383
|
-
// surprising load-order interactions when the import has side effects.
|
|
384
|
-
// https://eslint.org/docs/latest/rules/no-duplicate-imports
|
|
385
|
-
//
|
|
386
|
-
// The check is textual on the module specifier string — two imports
|
|
387
|
-
// with the same exact string literal collide. `import type { … } from`
|
|
388
|
-
// is folded together with value imports because the runtime sees only
|
|
389
|
-
// one module load.
|
|
390
|
-
type noDuplicateImports struct{}
|
|
391
|
-
|
|
392
|
-
func (noDuplicateImports) Name() string { return "no-duplicate-imports" }
|
|
393
|
-
func (noDuplicateImports) Visits() []shimast.Kind {
|
|
394
|
-
return []shimast.Kind{shimast.KindSourceFile}
|
|
395
|
-
}
|
|
396
|
-
func (noDuplicateImports) Check(ctx *Context, node *shimast.Node) {
|
|
397
|
-
seen := map[string]bool{}
|
|
398
|
-
node.ForEachChild(func(child *shimast.Node) bool {
|
|
399
|
-
if child == nil || child.Kind != shimast.KindImportDeclaration {
|
|
400
|
-
return false
|
|
401
|
-
}
|
|
402
|
-
decl := child.AsImportDeclaration()
|
|
403
|
-
if decl == nil || decl.ModuleSpecifier == nil {
|
|
404
|
-
return false
|
|
405
|
-
}
|
|
406
|
-
spec := stringLiteralText(decl.ModuleSpecifier)
|
|
407
|
-
if spec == "" {
|
|
408
|
-
return false
|
|
409
|
-
}
|
|
410
|
-
if seen[spec] {
|
|
411
|
-
ctx.Report(child, "Module `"+spec+"` is already imported above; consolidate the imports.")
|
|
412
|
-
return false
|
|
413
|
-
}
|
|
414
|
-
seen[spec] = true
|
|
415
|
-
return false
|
|
416
|
-
})
|
|
417
|
-
}
|
|
418
|
-
|
|
419
390
|
// getterReturn reports a `get` accessor whose body completes without
|
|
420
391
|
// returning a value. The runtime returns `undefined` from such a
|
|
421
392
|
// getter; in practice that is always a bug — the caller expects the
|
|
@@ -728,7 +699,6 @@ func init() {
|
|
|
728
699
|
Register(noAwaitInLoop{})
|
|
729
700
|
Register(noConstructorReturn{})
|
|
730
701
|
Register(noDupeClassMembers{})
|
|
731
|
-
Register(noDuplicateImports{})
|
|
732
702
|
Register(noImplicitCoercion{})
|
|
733
703
|
Register(noNewSymbol{})
|
|
734
704
|
Register(noThisBeforeSuper{})
|
|
@@ -12,7 +12,7 @@ type cypressNoUnnecessaryWaiting struct{}
|
|
|
12
12
|
type cypressNoForce struct{}
|
|
13
13
|
type cypressNoPause struct{}
|
|
14
14
|
type cypressNoDebug struct{}
|
|
15
|
-
type cypressUnsafeToChainCommand struct{}
|
|
15
|
+
type cypressUnsafeToChainCommand struct{ optionsRule }
|
|
16
16
|
type cypressAssertionBeforeScreenshot struct{}
|
|
17
17
|
type cypressNoAsyncTests struct{}
|
|
18
18
|
type cypressNoAsyncBefore struct{}
|
|
@@ -1,39 +1,115 @@
|
|
|
1
1
|
// defaultCase: a `switch` statement that omits the `default` clause
|
|
2
2
|
// silently drops every discriminant value not matched by an explicit
|
|
3
|
-
// `case` label. The rule wants the catch-all path written out
|
|
4
|
-
//
|
|
5
|
-
// branch is a `// no default` comment marker.
|
|
3
|
+
// `case` label. The rule wants the catch-all path written out — unless the
|
|
4
|
+
// omission is deliberately marked by a trailing `// no default` comment.
|
|
6
5
|
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
// switch
|
|
10
|
-
//
|
|
6
|
+
// Behavior mirrors ESLint's `default-case`:
|
|
7
|
+
//
|
|
8
|
+
// - An empty switch (`switch (x) {}`, zero clauses) is skipped: an empty
|
|
9
|
+
// case block has no clause to attach the marker comment to, so upstream
|
|
10
|
+
// bails with `if (!node.cases.length) return;`.
|
|
11
|
+
// - A `default` clause anywhere in the block satisfies the rule.
|
|
12
|
+
// - Otherwise the LAST comment trailing the final clause (between its end
|
|
13
|
+
// and the case block's `}`) is tested against `commentPattern`. When the
|
|
14
|
+
// comment's trimmed text matches, the omission is intentional and the
|
|
15
|
+
// switch is left alone; otherwise the rule reports on the `switch`
|
|
16
|
+
// keyword so the diagnostic lands at the head of the statement.
|
|
17
|
+
//
|
|
18
|
+
// `commentPattern` replaces the default marker pattern `/^no default$/i`
|
|
19
|
+
// (ESLint's DEFAULT_COMMENT_PATTERN). The trailing-comment scan reuses
|
|
20
|
+
// comment_scan.go's parser-aware gap scanner over the trivia-only region
|
|
21
|
+
// after the last clause.
|
|
11
22
|
// https://eslint.org/docs/latest/rules/default-case
|
|
12
23
|
package linthost
|
|
13
24
|
|
|
14
|
-
import
|
|
25
|
+
import (
|
|
26
|
+
"regexp"
|
|
27
|
+
"strings"
|
|
28
|
+
|
|
29
|
+
shimast "github.com/microsoft/typescript-go/shim/ast"
|
|
30
|
+
shimscanner "github.com/microsoft/typescript-go/shim/scanner"
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
// defaultCaseDefaultCommentPattern is ESLint's DEFAULT_COMMENT_PATTERN
|
|
34
|
+
// (`/^no default$/iu`) in RE2: the trailing comment's trimmed text must be
|
|
35
|
+
// exactly `no default`, in any letter case.
|
|
36
|
+
var defaultCaseDefaultCommentPattern = regexp.MustCompile(`(?i)^no default$`)
|
|
37
|
+
|
|
38
|
+
// defaultCaseOptions mirrors the upstream rule's single-object schema.
|
|
39
|
+
type defaultCaseOptions struct {
|
|
40
|
+
// CommentPattern replaces the default `no default` marker pattern when
|
|
41
|
+
// non-empty. Upstream compiles it with the `u` flag and no `i`, so the
|
|
42
|
+
// custom pattern is case-sensitive unless it opts in. An uncompilable
|
|
43
|
+
// pattern falls back to the default marker (matching no-fallthrough) so a
|
|
44
|
+
// config typo cannot silently turn every marked switch into a finding.
|
|
45
|
+
CommentPattern string `json:"commentPattern"`
|
|
46
|
+
}
|
|
15
47
|
|
|
16
|
-
type defaultCase struct{}
|
|
48
|
+
type defaultCase struct{ optionsRule }
|
|
17
49
|
|
|
18
50
|
func (defaultCase) Name() string { return "default-case" }
|
|
19
51
|
func (defaultCase) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindSwitchStatement} }
|
|
20
52
|
func (defaultCase) Check(ctx *Context, node *shimast.Node) {
|
|
21
53
|
sw := node.AsSwitchStatement()
|
|
22
|
-
if sw == nil || sw.CaseBlock == nil {
|
|
54
|
+
if sw == nil || sw.CaseBlock == nil || ctx.File == nil {
|
|
23
55
|
return
|
|
24
56
|
}
|
|
25
57
|
block := sw.CaseBlock.AsCaseBlock()
|
|
26
58
|
if block == nil || block.Clauses == nil {
|
|
27
59
|
return
|
|
28
60
|
}
|
|
29
|
-
|
|
61
|
+
clauses := block.Clauses.Nodes
|
|
62
|
+
// Empty switch: upstream skips it because an empty case block offers no
|
|
63
|
+
// clause to read the marker comment from.
|
|
64
|
+
if len(clauses) == 0 {
|
|
65
|
+
return
|
|
66
|
+
}
|
|
67
|
+
for _, clause := range clauses {
|
|
30
68
|
if clause != nil && clause.Kind == shimast.KindDefaultClause {
|
|
31
69
|
return
|
|
32
70
|
}
|
|
33
71
|
}
|
|
72
|
+
|
|
73
|
+
var opts defaultCaseOptions
|
|
74
|
+
ctx.DecodeOptions(&opts)
|
|
75
|
+
pattern := defaultCaseDefaultCommentPattern
|
|
76
|
+
if opts.CommentPattern != "" {
|
|
77
|
+
if custom, err := compileUserPattern(opts.CommentPattern); err == nil {
|
|
78
|
+
pattern = custom
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ESLint's sourceCode.getCommentsAfter(lastCase).at(-1): the LAST comment
|
|
83
|
+
// between the final clause and the case block's closing brace marks the
|
|
84
|
+
// omission intentional when its trimmed text matches the pattern. A
|
|
85
|
+
// trailing non-marker comment after a `// no default` therefore invalidates
|
|
86
|
+
// the marker.
|
|
87
|
+
if lastClause := clauses[len(clauses)-1]; lastClause != nil {
|
|
88
|
+
text := ctx.File.Text()
|
|
89
|
+
closeBrace := sw.CaseBlock.End() - 1 // exclusive End covers the `}` token
|
|
90
|
+
if pos, end, ok := defaultCaseLastTrailingComment(text, lastClause.End(), closeBrace); ok &&
|
|
91
|
+
pattern.MatchString(strings.TrimSpace(commentInnerText(text[pos:end]))) {
|
|
92
|
+
return
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
34
96
|
ctx.Report(node, "Expected a default case.")
|
|
35
97
|
}
|
|
36
98
|
|
|
99
|
+
// defaultCaseLastTrailingComment returns the source range of the LAST comment
|
|
100
|
+
// in the trivia region [from, to), reusing comment_scan.go's parser-aware gap
|
|
101
|
+
// scanner. The region between a switch's last clause and its closing brace is
|
|
102
|
+
// trivia-only by construction, so scanning the isolated gap is exact.
|
|
103
|
+
func defaultCaseLastTrailingComment(text string, from, to int) (pos, end int, ok bool) {
|
|
104
|
+
if from < 0 || to > len(text) || from >= to {
|
|
105
|
+
return 0, 0, false
|
|
106
|
+
}
|
|
107
|
+
scanCommentGap(shimscanner.NewScanner(), text, from, to, func(_ shimast.Kind, p, e int) {
|
|
108
|
+
pos, end, ok = p, e, true
|
|
109
|
+
})
|
|
110
|
+
return pos, end, ok
|
|
111
|
+
}
|
|
112
|
+
|
|
37
113
|
func init() {
|
|
38
114
|
Register(defaultCase{})
|
|
39
115
|
}
|
package/linthost/rules_dupes.go
CHANGED
|
@@ -99,11 +99,11 @@ func (noDupeArgs) Check(ctx *Context, node *shimast.Node) {
|
|
|
99
99
|
}
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
-
// propertyKey returns a stable key for a property in an object literal
|
|
103
|
-
//
|
|
104
|
-
//
|
|
105
|
-
//
|
|
106
|
-
//
|
|
102
|
+
// propertyKey returns a stable dedupe key for a property in an object literal,
|
|
103
|
+
// mirroring ESLint's getStaticPropertyName. A property whose name is not
|
|
104
|
+
// statically known — a computed name built from a non-constant expression —
|
|
105
|
+
// returns "" so the dedupe pass skips it and never treats two distinct dynamic
|
|
106
|
+
// keys as duplicates.
|
|
107
107
|
func propertyKey(file *shimast.SourceFile, prop *shimast.Node) string {
|
|
108
108
|
if prop == nil {
|
|
109
109
|
return ""
|
|
@@ -152,11 +152,15 @@ func propertyKey(file *shimast.SourceFile, prop *shimast.Node) string {
|
|
|
152
152
|
return ""
|
|
153
153
|
}
|
|
154
154
|
|
|
155
|
-
// staticPropertyKey extracts a comparable string key from a property name node
|
|
156
|
-
//
|
|
157
|
-
//
|
|
158
|
-
//
|
|
159
|
-
|
|
155
|
+
// staticPropertyKey extracts a comparable string key from a property name node,
|
|
156
|
+
// mirroring ESLint's getStaticPropertyName. A bare identifier yields its name,
|
|
157
|
+
// and string / numeric / bigint literals yield their text. A computed name
|
|
158
|
+
// yields a key only when its bracketed expression is itself a constant literal
|
|
159
|
+
// (resolved like getStaticStringValue): `["a"]` resolves to `a` so it collides
|
|
160
|
+
// with the identifier key `a`, while a non-constant computed name such as
|
|
161
|
+
// `[f()]` or `[x]` resolves to "" and the caller skips it. The file argument is
|
|
162
|
+
// unused here but kept so the whole property-key family shares one signature.
|
|
163
|
+
func staticPropertyKey(_ *shimast.SourceFile, name *shimast.Node) string {
|
|
160
164
|
if name == nil {
|
|
161
165
|
return ""
|
|
162
166
|
}
|
|
@@ -168,9 +172,30 @@ func staticPropertyKey(file *shimast.SourceFile, name *shimast.Node) string {
|
|
|
168
172
|
case shimast.KindNumericLiteral, shimast.KindBigIntLiteral:
|
|
169
173
|
return numericLiteralText(name)
|
|
170
174
|
case shimast.KindComputedPropertyName:
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
175
|
+
computed := name.AsComputedPropertyName()
|
|
176
|
+
if computed == nil {
|
|
177
|
+
return ""
|
|
178
|
+
}
|
|
179
|
+
return staticComputedKey(computed.Expression)
|
|
180
|
+
}
|
|
181
|
+
return ""
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// staticComputedKey resolves the expression inside a `[...]` computed property
|
|
185
|
+
// name to a static key, mirroring ESLint's getStaticStringValue: only a
|
|
186
|
+
// constant literal (string, template without substitutions, numeric, or bigint)
|
|
187
|
+
// contributes a key. Parentheses are transparent, matching ESTree, which has no
|
|
188
|
+
// parenthesized-expression node. Any other expression contributes "".
|
|
189
|
+
func staticComputedKey(expr *shimast.Node) string {
|
|
190
|
+
expr = stripParens(expr)
|
|
191
|
+
if expr == nil {
|
|
192
|
+
return ""
|
|
193
|
+
}
|
|
194
|
+
switch expr.Kind {
|
|
195
|
+
case shimast.KindStringLiteral, shimast.KindNoSubstitutionTemplateLiteral:
|
|
196
|
+
return stringLiteralText(expr)
|
|
197
|
+
case shimast.KindNumericLiteral, shimast.KindBigIntLiteral:
|
|
198
|
+
return numericLiteralText(expr)
|
|
174
199
|
}
|
|
175
200
|
return ""
|
|
176
201
|
}
|
package/linthost/rules_empty.go
CHANGED
|
@@ -1,35 +1,67 @@
|
|
|
1
1
|
package linthost
|
|
2
2
|
|
|
3
|
-
import
|
|
3
|
+
import (
|
|
4
|
+
shimast "github.com/microsoft/typescript-go/shim/ast"
|
|
5
|
+
shimscanner "github.com/microsoft/typescript-go/shim/scanner"
|
|
6
|
+
)
|
|
4
7
|
|
|
5
|
-
// noEmpty
|
|
6
|
-
//
|
|
8
|
+
// noEmpty rejects empty block and switch statements unless an interior
|
|
9
|
+
// comment documents the intentional no-op.
|
|
7
10
|
// https://eslint.org/docs/latest/rules/no-empty
|
|
8
|
-
type noEmpty struct{}
|
|
11
|
+
type noEmpty struct{ optionsRule }
|
|
9
12
|
|
|
10
|
-
|
|
11
|
-
|
|
13
|
+
type noEmptyOptions struct {
|
|
14
|
+
AllowEmptyCatch bool `json:"allowEmptyCatch"`
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
func (noEmpty) Name() string { return "no-empty" }
|
|
18
|
+
func (noEmpty) Visits() []shimast.Kind {
|
|
19
|
+
return []shimast.Kind{shimast.KindBlock, shimast.KindSwitchStatement}
|
|
20
|
+
}
|
|
12
21
|
func (noEmpty) Check(ctx *Context, node *shimast.Node) {
|
|
13
|
-
|
|
14
|
-
|
|
22
|
+
if node.Kind == shimast.KindSwitchStatement {
|
|
23
|
+
statement := node.AsSwitchStatement()
|
|
24
|
+
if statement == nil || statement.CaseBlock == nil {
|
|
25
|
+
return
|
|
26
|
+
}
|
|
27
|
+
caseBlock := statement.CaseBlock.AsCaseBlock()
|
|
28
|
+
if caseBlock == nil || caseBlock.Clauses != nil && len(caseBlock.Clauses.Nodes) != 0 {
|
|
29
|
+
return
|
|
30
|
+
}
|
|
31
|
+
if !emptyBracedBodyHasComment(ctx.File, statement.CaseBlock) {
|
|
32
|
+
ctx.Report(statement.CaseBlock, "Empty switch statement.")
|
|
33
|
+
}
|
|
15
34
|
return
|
|
16
35
|
}
|
|
17
|
-
|
|
36
|
+
|
|
37
|
+
block := node.AsBlock()
|
|
38
|
+
if block == nil || block.Statements != nil && len(block.Statements.Nodes) != 0 {
|
|
18
39
|
return
|
|
19
40
|
}
|
|
20
41
|
parent := node.Parent
|
|
21
|
-
if parent != nil && parent.Kind == shimast.KindCatchClause {
|
|
22
|
-
return // tolerated — see ESLint default options
|
|
23
|
-
}
|
|
24
42
|
if isFunctionLikeKind(parent) {
|
|
25
43
|
return // empty function body is `no-empty-function`'s job
|
|
26
44
|
}
|
|
27
|
-
|
|
45
|
+
if parent != nil && parent.Kind == shimast.KindClassStaticBlockDeclaration {
|
|
46
|
+
return // static blocks have their own rule and are not BlockStatement in ESTree
|
|
47
|
+
}
|
|
48
|
+
var options noEmptyOptions
|
|
49
|
+
_ = ctx.DecodeOptions(&options)
|
|
50
|
+
if options.AllowEmptyCatch && parent != nil && parent.Kind == shimast.KindCatchClause {
|
|
51
|
+
return
|
|
52
|
+
}
|
|
53
|
+
if !emptyBracedBodyHasComment(ctx.File, node) {
|
|
54
|
+
ctx.Report(node, "Empty block statement.")
|
|
55
|
+
}
|
|
28
56
|
}
|
|
29
57
|
|
|
30
58
|
// noEmptyFunction: empty function / method / arrow / accessor bodies.
|
|
31
59
|
// https://eslint.org/docs/latest/rules/no-empty-function
|
|
32
|
-
type noEmptyFunction struct{}
|
|
60
|
+
type noEmptyFunction struct{ optionsRule }
|
|
61
|
+
|
|
62
|
+
type noEmptyFunctionOptions struct {
|
|
63
|
+
Allow []string `json:"allow"`
|
|
64
|
+
}
|
|
33
65
|
|
|
34
66
|
func (noEmptyFunction) Name() string { return "no-empty-function" }
|
|
35
67
|
func (noEmptyFunction) Visits() []shimast.Kind {
|
|
@@ -45,19 +77,155 @@ func (noEmptyFunction) Visits() []shimast.Kind {
|
|
|
45
77
|
}
|
|
46
78
|
func (noEmptyFunction) Check(ctx *Context, node *shimast.Node) {
|
|
47
79
|
body := node.Body()
|
|
48
|
-
if body == nil {
|
|
80
|
+
if body == nil || body.Kind != shimast.KindBlock {
|
|
49
81
|
return
|
|
50
82
|
}
|
|
51
|
-
|
|
83
|
+
block := body.AsBlock()
|
|
84
|
+
if block == nil || block.Statements != nil && len(block.Statements.Nodes) != 0 {
|
|
52
85
|
return
|
|
53
86
|
}
|
|
54
|
-
|
|
55
|
-
|
|
87
|
+
var options noEmptyFunctionOptions
|
|
88
|
+
_ = ctx.DecodeOptions(&options)
|
|
89
|
+
kind := noEmptyFunctionKind(node)
|
|
90
|
+
if kind == "" || noEmptyFunctionAllowed(node, kind, options.Allow) || emptyBracedBodyHasComment(ctx.File, body) {
|
|
56
91
|
return
|
|
57
92
|
}
|
|
58
|
-
|
|
59
|
-
|
|
93
|
+
ctx.Report(body, "Unexpected empty "+noEmptyFunctionLabel(kind)+".")
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
func noEmptyFunctionKind(node *shimast.Node) string {
|
|
97
|
+
if node == nil {
|
|
98
|
+
return ""
|
|
99
|
+
}
|
|
100
|
+
switch node.Kind {
|
|
101
|
+
case shimast.KindArrowFunction:
|
|
102
|
+
return "arrowFunctions"
|
|
103
|
+
case shimast.KindConstructor:
|
|
104
|
+
return "constructors"
|
|
105
|
+
case shimast.KindGetAccessor:
|
|
106
|
+
return "getters"
|
|
107
|
+
case shimast.KindSetAccessor:
|
|
108
|
+
return "setters"
|
|
109
|
+
case shimast.KindMethodDeclaration:
|
|
110
|
+
if noEmptyFunctionIsGenerator(node) {
|
|
111
|
+
return "generatorMethods"
|
|
112
|
+
}
|
|
113
|
+
if node.ModifierFlags()&shimast.ModifierFlagsAsync != 0 {
|
|
114
|
+
return "asyncMethods"
|
|
115
|
+
}
|
|
116
|
+
return "methods"
|
|
117
|
+
case shimast.KindFunctionDeclaration, shimast.KindFunctionExpression:
|
|
118
|
+
if noEmptyFunctionIsGenerator(node) {
|
|
119
|
+
return "generatorFunctions"
|
|
120
|
+
}
|
|
121
|
+
if node.ModifierFlags()&shimast.ModifierFlagsAsync != 0 {
|
|
122
|
+
return "asyncFunctions"
|
|
123
|
+
}
|
|
124
|
+
return "functions"
|
|
125
|
+
default:
|
|
126
|
+
return ""
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
func noEmptyFunctionIsGenerator(node *shimast.Node) bool {
|
|
131
|
+
switch node.Kind {
|
|
132
|
+
case shimast.KindFunctionDeclaration:
|
|
133
|
+
declaration := node.AsFunctionDeclaration()
|
|
134
|
+
return declaration != nil && declaration.AsteriskToken != nil
|
|
135
|
+
case shimast.KindFunctionExpression:
|
|
136
|
+
expression := node.AsFunctionExpression()
|
|
137
|
+
return expression != nil && expression.AsteriskToken != nil
|
|
138
|
+
case shimast.KindMethodDeclaration:
|
|
139
|
+
declaration := node.AsMethodDeclaration()
|
|
140
|
+
return declaration != nil && declaration.AsteriskToken != nil
|
|
141
|
+
default:
|
|
142
|
+
return false
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
func noEmptyFunctionAllowed(node *shimast.Node, kind string, allow []string) bool {
|
|
147
|
+
if noEmptyFunctionOptionIncludes(allow, kind) {
|
|
148
|
+
return true
|
|
149
|
+
}
|
|
150
|
+
if kind == "constructors" {
|
|
151
|
+
if hasParameterProperty(node) {
|
|
152
|
+
return true
|
|
153
|
+
}
|
|
154
|
+
modifiers := node.ModifierFlags()
|
|
155
|
+
if modifiers&shimast.ModifierFlagsPrivate != 0 && noEmptyFunctionOptionIncludes(allow, "privateConstructors") {
|
|
156
|
+
return true
|
|
157
|
+
}
|
|
158
|
+
if modifiers&shimast.ModifierFlagsProtected != 0 && noEmptyFunctionOptionIncludes(allow, "protectedConstructors") {
|
|
159
|
+
return true
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
switch kind {
|
|
163
|
+
case "methods", "generatorMethods", "asyncMethods", "getters", "setters":
|
|
164
|
+
if len(node.Decorators()) != 0 && noEmptyFunctionOptionIncludes(allow, "decoratedFunctions") {
|
|
165
|
+
return true
|
|
166
|
+
}
|
|
167
|
+
if node.ModifierFlags()&shimast.ModifierFlagsOverride != 0 && noEmptyFunctionOptionIncludes(allow, "overrideMethods") {
|
|
168
|
+
return true
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return false
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
func noEmptyFunctionOptionIncludes(allow []string, candidate string) bool {
|
|
175
|
+
for _, option := range allow {
|
|
176
|
+
if option == candidate {
|
|
177
|
+
return true
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return false
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
func noEmptyFunctionLabel(kind string) string {
|
|
184
|
+
switch kind {
|
|
185
|
+
case "arrowFunctions":
|
|
186
|
+
return "arrow function"
|
|
187
|
+
case "generatorFunctions":
|
|
188
|
+
return "generator function"
|
|
189
|
+
case "methods":
|
|
190
|
+
return "method"
|
|
191
|
+
case "generatorMethods":
|
|
192
|
+
return "generator method"
|
|
193
|
+
case "getters":
|
|
194
|
+
return "getter"
|
|
195
|
+
case "setters":
|
|
196
|
+
return "setter"
|
|
197
|
+
case "constructors":
|
|
198
|
+
return "constructor"
|
|
199
|
+
case "asyncFunctions":
|
|
200
|
+
return "async function"
|
|
201
|
+
case "asyncMethods":
|
|
202
|
+
return "async method"
|
|
203
|
+
default:
|
|
204
|
+
return "function"
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// emptyBracedBodyHasComment scans only the trivia between the parser-owned
|
|
209
|
+
// opening and closing brace tokens of an already-proven empty body. Exterior
|
|
210
|
+
// comments cannot enter this range, and context-sensitive expression tokens
|
|
211
|
+
// cannot occur because the body has no statements or switch clauses.
|
|
212
|
+
func emptyBracedBodyHasComment(file *shimast.SourceFile, body *shimast.Node) bool {
|
|
213
|
+
if file == nil || body == nil {
|
|
214
|
+
return false
|
|
215
|
+
}
|
|
216
|
+
opening := shimscanner.GetScannerForSourceFile(file, body.Pos())
|
|
217
|
+
if opening.Token() != shimast.KindOpenBraceToken || opening.TokenEnd() > body.End() {
|
|
218
|
+
return false
|
|
219
|
+
}
|
|
220
|
+
closing := shimscanner.GetScannerForSourceFile(file, opening.TokenEnd())
|
|
221
|
+
if closing.Token() != shimast.KindCloseBraceToken || closing.TokenEnd() > body.End() {
|
|
222
|
+
return false
|
|
60
223
|
}
|
|
224
|
+
found := false
|
|
225
|
+
scanCommentGap(shimscanner.NewScanner(), file.Text(), opening.TokenEnd(), closing.TokenStart(), func(_ shimast.Kind, _, _ int) {
|
|
226
|
+
found = true
|
|
227
|
+
})
|
|
228
|
+
return found
|
|
61
229
|
}
|
|
62
230
|
|
|
63
231
|
// noEmptyPattern: `({}) => x` or `function ({}) {}` — destructuring
|