@ttsc/lint 0.18.4 → 0.19.1
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 +125 -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 +159 -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 +231 -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 +206 -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
package/linthost/rules_gap.go
CHANGED
|
@@ -9,7 +9,11 @@
|
|
|
9
9
|
package linthost
|
|
10
10
|
|
|
11
11
|
import (
|
|
12
|
+
"path/filepath"
|
|
13
|
+
"strings"
|
|
14
|
+
|
|
12
15
|
shimast "github.com/microsoft/typescript-go/shim/ast"
|
|
16
|
+
shimcore "github.com/microsoft/typescript-go/shim/core"
|
|
13
17
|
shimscanner "github.com/microsoft/typescript-go/shim/scanner"
|
|
14
18
|
)
|
|
15
19
|
|
|
@@ -27,8 +31,11 @@ func (noEmptyStaticBlock) Check(ctx *Context, node *shimast.Node) {
|
|
|
27
31
|
return
|
|
28
32
|
}
|
|
29
33
|
body := block.Body.AsBlock()
|
|
30
|
-
if body == nil || body.Statements
|
|
31
|
-
|
|
34
|
+
if body == nil || body.Statements != nil && len(body.Statements.Nodes) != 0 {
|
|
35
|
+
return
|
|
36
|
+
}
|
|
37
|
+
if !emptyBracedBodyHasComment(ctx.File, block.Body) {
|
|
38
|
+
ctx.Report(block.Body, "Unexpected empty static block.")
|
|
32
39
|
}
|
|
33
40
|
}
|
|
34
41
|
|
|
@@ -177,13 +184,48 @@ func (noUnnecessaryTypeConstraint) Check(ctx *Context, node *shimast.Node) {
|
|
|
177
184
|
ctx.Report(param.Constraint, message)
|
|
178
185
|
return
|
|
179
186
|
}
|
|
187
|
+
replacement := ""
|
|
188
|
+
if requiresGenericArrowDisambiguation(ctx, node, param) {
|
|
189
|
+
replacement = ","
|
|
190
|
+
}
|
|
180
191
|
ctx.ReportFix(
|
|
181
192
|
param.Constraint,
|
|
182
193
|
message,
|
|
183
|
-
TextEdit{Pos: name.End(), End: param.Constraint.End(), Text:
|
|
194
|
+
TextEdit{Pos: name.End(), End: param.Constraint.End(), Text: replacement},
|
|
184
195
|
)
|
|
185
196
|
}
|
|
186
197
|
|
|
198
|
+
// requiresGenericArrowDisambiguation reports whether deleting a redundant
|
|
199
|
+
// constraint would leave a single generic arrow looking like JSX or another
|
|
200
|
+
// reserved generic declaration. TypeScript-ESLint uses this same extension
|
|
201
|
+
// boundary for TSX and the explicit Node module source modes.
|
|
202
|
+
func requiresGenericArrowDisambiguation(
|
|
203
|
+
ctx *Context,
|
|
204
|
+
node *shimast.Node,
|
|
205
|
+
param *shimast.TypeParameterDeclaration,
|
|
206
|
+
) bool {
|
|
207
|
+
if ctx == nil || ctx.File == nil || node == nil || param == nil ||
|
|
208
|
+
node.Parent == nil || node.Parent.Kind != shimast.KindArrowFunction {
|
|
209
|
+
return false
|
|
210
|
+
}
|
|
211
|
+
requiresDisambiguation := ctx.File.ScriptKind == shimcore.ScriptKindTSX
|
|
212
|
+
if !requiresDisambiguation {
|
|
213
|
+
switch strings.ToLower(filepath.Ext(ctx.File.FileName())) {
|
|
214
|
+
case ".mts", ".cts":
|
|
215
|
+
requiresDisambiguation = true
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
if !requiresDisambiguation {
|
|
219
|
+
return false
|
|
220
|
+
}
|
|
221
|
+
typeParameters := node.Parent.TypeParameterList()
|
|
222
|
+
return typeParameters != nil &&
|
|
223
|
+
len(typeParameters.Nodes) == 1 &&
|
|
224
|
+
typeParameters.Nodes[0] == node &&
|
|
225
|
+
!typeParameters.HasTrailingComma() &&
|
|
226
|
+
param.DefaultType == nil
|
|
227
|
+
}
|
|
228
|
+
|
|
187
229
|
// noUnsafeFunctionType: `Function` accepts any callable shape.
|
|
188
230
|
// typescript-eslint recommended: https://typescript-eslint.io/rules/no-unsafe-function-type/
|
|
189
231
|
type noUnsafeFunctionType struct{}
|
|
@@ -518,11 +560,28 @@ func (dotNotation) Check(ctx *Context, node *shimast.Node) {
|
|
|
518
560
|
} else {
|
|
519
561
|
replaceFrom = access.Expression.End()
|
|
520
562
|
replacement = "." + key
|
|
563
|
+
// A bare decimal-integer receiver lexes an immediately following `.` as
|
|
564
|
+
// the number's own decimal point, so `5["toString"]` → `5.toString` is a
|
|
565
|
+
// syntax error (TS1351). Emit a separating space (`5 .toString`) exactly
|
|
566
|
+
// where ESLint's dot-notation does. Parenthesized `(5)`, hex/octal/binary,
|
|
567
|
+
// float, exponent, and bigint receivers all take `.` as member access
|
|
568
|
+
// cleanly, so they keep the dot tight.
|
|
569
|
+
if access.Expression.Kind == shimast.KindNumericLiteral &&
|
|
570
|
+
isDecimalIntegerLiteralText(nodeText(ctx.File, access.Expression)) {
|
|
571
|
+
replacement = " " + replacement
|
|
572
|
+
}
|
|
521
573
|
}
|
|
522
574
|
if replaceFrom < 0 || replaceFrom >= node.End() {
|
|
523
575
|
ctx.Report(node, message)
|
|
524
576
|
return
|
|
525
577
|
}
|
|
578
|
+
// A comment inside the replaced `[…]` tail (`p1 /* keep */ ["foo"]`) would be
|
|
579
|
+
// silently deleted by the splice. Decline to a report-only diagnostic,
|
|
580
|
+
// matching ESLint dot-notation's `commentsExistBetween` guard.
|
|
581
|
+
if hasCommentBetween(src, replaceFrom, node.End()) {
|
|
582
|
+
ctx.Report(node, message)
|
|
583
|
+
return
|
|
584
|
+
}
|
|
526
585
|
ctx.ReportFix(
|
|
527
586
|
node,
|
|
528
587
|
message,
|
|
@@ -530,6 +589,24 @@ func (dotNotation) Check(ctx *Context, node *shimast.Node) {
|
|
|
530
589
|
)
|
|
531
590
|
}
|
|
532
591
|
|
|
592
|
+
// isDecimalIntegerLiteralText reports whether raw is a numeric literal written
|
|
593
|
+
// as a plain decimal integer — ASCII digits with optional `_` separators and
|
|
594
|
+
// no radix prefix, decimal point, exponent, or bigint `n` suffix. A `.`
|
|
595
|
+
// spliced directly after such a receiver is lexed as the number's own decimal
|
|
596
|
+
// point (`5["x"]` → `5.x` is a SyntaxError), so dot-notation inserts a
|
|
597
|
+
// separating space. Mirrors ESLint astUtils.isDecimalInteger.
|
|
598
|
+
func isDecimalIntegerLiteralText(raw string) bool {
|
|
599
|
+
if raw == "" {
|
|
600
|
+
return false
|
|
601
|
+
}
|
|
602
|
+
for i := 0; i < len(raw); i++ {
|
|
603
|
+
if c := raw[i]; (c < '0' || c > '9') && c != '_' {
|
|
604
|
+
return false
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
return true
|
|
608
|
+
}
|
|
609
|
+
|
|
533
610
|
// isReservedWord reports whether `value` is an ECMAScript reserved word. The
|
|
534
611
|
// `dot-notation` autofix uses this to skip the rewrite even though modern
|
|
535
612
|
// parsers accept reserved-word member names: leaving bracket access matches
|
|
@@ -1,31 +1,42 @@
|
|
|
1
1
|
// groupedAccessorPairs: a `get` and `set` accessor that share the same
|
|
2
|
-
// property name should be declared next to each other
|
|
3
|
-
// body. When the pair is split apart by
|
|
4
|
-
// scanning the
|
|
5
|
-
// — and patches to one half are easy to
|
|
6
|
-
// other.
|
|
2
|
+
// property name should be declared next to each other, whether they sit
|
|
3
|
+
// in a class body or an object literal. When the pair is split apart by
|
|
4
|
+
// unrelated members, a reader scanning the declaration has to chase the
|
|
5
|
+
// read and write halves separately — and patches to one half are easy to
|
|
6
|
+
// make without noticing the other.
|
|
7
7
|
//
|
|
8
|
-
// AST-only: walk every class declaration/expression
|
|
9
|
-
// (name, static-ness), and for any group that contains
|
|
10
|
-
// and a setter check that their member indices are
|
|
11
|
-
// second-encountered half is the one reported, because the
|
|
12
|
-
// reads correctly until the reader hits the second
|
|
8
|
+
// AST-only: walk every class declaration/expression and object literal,
|
|
9
|
+
// group members by (name, static-ness), and for any group that contains
|
|
10
|
+
// both a getter and a setter check that their member indices are
|
|
11
|
+
// adjacent. The second-encountered half is the one reported, because the
|
|
12
|
+
// first half reads correctly until the reader hits the second
|
|
13
|
+
// declaration.
|
|
14
|
+
//
|
|
15
|
+
// The optional order option (`anyOrder` (the default), `getBeforeSet`, or
|
|
16
|
+
// `setBeforeGet`) additionally pins the relative order of an already
|
|
17
|
+
// adjacent pair, mirroring upstream's `invalidOrder` diagnostic.
|
|
13
18
|
// https://eslint.org/docs/latest/rules/grouped-accessor-pairs
|
|
14
19
|
package linthost
|
|
15
20
|
|
|
16
21
|
import shimast "github.com/microsoft/typescript-go/shim/ast"
|
|
17
22
|
|
|
18
|
-
type groupedAccessorPairs struct{}
|
|
23
|
+
type groupedAccessorPairs struct{ optionsRule }
|
|
19
24
|
|
|
20
25
|
func (groupedAccessorPairs) Name() string { return "grouped-accessor-pairs" }
|
|
21
26
|
func (groupedAccessorPairs) Visits() []shimast.Kind {
|
|
22
|
-
return []shimast.Kind{
|
|
27
|
+
return []shimast.Kind{
|
|
28
|
+
shimast.KindClassDeclaration,
|
|
29
|
+
shimast.KindClassExpression,
|
|
30
|
+
shimast.KindObjectLiteralExpression,
|
|
31
|
+
}
|
|
23
32
|
}
|
|
24
33
|
func (groupedAccessorPairs) Check(ctx *Context, node *shimast.Node) {
|
|
25
|
-
members :=
|
|
34
|
+
members := groupedAccessorMembers(node)
|
|
26
35
|
if len(members) < 2 {
|
|
27
36
|
return
|
|
28
37
|
}
|
|
38
|
+
order := groupedAccessorOrder(ctx)
|
|
39
|
+
|
|
29
40
|
type slot struct {
|
|
30
41
|
name string
|
|
31
42
|
static bool
|
|
@@ -35,7 +46,10 @@ func (groupedAccessorPairs) Check(ctx *Context, node *shimast.Node) {
|
|
|
35
46
|
kind shimast.Kind
|
|
36
47
|
node *shimast.Node
|
|
37
48
|
}
|
|
38
|
-
|
|
49
|
+
// Track first-seen group order so multiple pairs report deterministically;
|
|
50
|
+
// a Go map's iteration order is randomized.
|
|
51
|
+
groups := map[slot]*[]entry{}
|
|
52
|
+
var groupOrder []slot
|
|
39
53
|
for i, member := range members {
|
|
40
54
|
if member == nil {
|
|
41
55
|
continue
|
|
@@ -48,9 +62,14 @@ func (groupedAccessorPairs) Check(ctx *Context, node *shimast.Node) {
|
|
|
48
62
|
continue
|
|
49
63
|
}
|
|
50
64
|
key := slot{name: name, static: hasModifier(member, shimast.KindStaticKeyword)}
|
|
51
|
-
groups[key]
|
|
65
|
+
if groups[key] == nil {
|
|
66
|
+
groups[key] = &[]entry{}
|
|
67
|
+
groupOrder = append(groupOrder, key)
|
|
68
|
+
}
|
|
69
|
+
*groups[key] = append(*groups[key], entry{index: i, kind: member.Kind, node: member})
|
|
52
70
|
}
|
|
53
|
-
for _,
|
|
71
|
+
for _, key := range groupOrder {
|
|
72
|
+
entries := *groups[key]
|
|
54
73
|
if len(entries) < 2 {
|
|
55
74
|
continue
|
|
56
75
|
}
|
|
@@ -67,21 +86,61 @@ func (groupedAccessorPairs) Check(ctx *Context, node *shimast.Node) {
|
|
|
67
86
|
if get == nil || set == nil {
|
|
68
87
|
continue
|
|
69
88
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
89
|
+
former, later := get, set
|
|
90
|
+
if set.index < get.index {
|
|
91
|
+
former, later = set, get
|
|
92
|
+
}
|
|
93
|
+
// Non-adjacent indices in the members list break the rule.
|
|
94
|
+
if later.index-former.index > 1 {
|
|
95
|
+
// Report on whichever half appears later — the earlier one
|
|
96
|
+
// reads fine in isolation; the later one is the surprise.
|
|
97
|
+
ctx.Report(later.node, "Accessor pair should be grouped.")
|
|
73
98
|
continue
|
|
74
99
|
}
|
|
75
|
-
//
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
later = set
|
|
100
|
+
// The pair is adjacent; enforce the requested order, if any.
|
|
101
|
+
if (order == "getBeforeSet" && former.kind == shimast.KindSetAccessor) ||
|
|
102
|
+
(order == "setBeforeGet" && former.kind == shimast.KindGetAccessor) {
|
|
103
|
+
ctx.Report(later.node, "Expected "+accessorRole(later.kind)+" to be before "+accessorRole(former.kind)+".")
|
|
80
104
|
}
|
|
81
|
-
ctx.Report(later.node, "Accessor pair should be grouped.")
|
|
82
105
|
}
|
|
83
106
|
}
|
|
84
107
|
|
|
108
|
+
// groupedAccessorMembers returns the ordered member list the rule scans for
|
|
109
|
+
// a class body (its members) or an object literal (its properties). Any
|
|
110
|
+
// other node kind yields nil so the multi-visit rule can bail cheaply.
|
|
111
|
+
func groupedAccessorMembers(node *shimast.Node) []*shimast.Node {
|
|
112
|
+
if node != nil && node.Kind == shimast.KindObjectLiteralExpression {
|
|
113
|
+
if obj := node.AsObjectLiteralExpression(); obj != nil && obj.Properties != nil {
|
|
114
|
+
return obj.Properties.Nodes
|
|
115
|
+
}
|
|
116
|
+
return nil
|
|
117
|
+
}
|
|
118
|
+
return classMembers(node)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// groupedAccessorOrder resolves the positional order option. A single
|
|
122
|
+
// positional slot is transported as a bare JSON string (see
|
|
123
|
+
// RuleOptionsMap); any unknown or absent value falls back to `anyOrder`,
|
|
124
|
+
// which disables the ordering check.
|
|
125
|
+
func groupedAccessorOrder(ctx *Context) string {
|
|
126
|
+
var order string
|
|
127
|
+
_ = ctx.DecodeOptions(&order)
|
|
128
|
+
switch order {
|
|
129
|
+
case "getBeforeSet", "setBeforeGet":
|
|
130
|
+
return order
|
|
131
|
+
default:
|
|
132
|
+
return "anyOrder"
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// accessorRole names an accessor half for the `invalidOrder` message.
|
|
137
|
+
func accessorRole(kind shimast.Kind) string {
|
|
138
|
+
if kind == shimast.KindSetAccessor {
|
|
139
|
+
return "setter"
|
|
140
|
+
}
|
|
141
|
+
return "getter"
|
|
142
|
+
}
|
|
143
|
+
|
|
85
144
|
func init() {
|
|
86
145
|
Register(groupedAccessorPairs{})
|
|
87
146
|
}
|
|
@@ -1,18 +1,24 @@
|
|
|
1
1
|
// guardForIn: `for (key in obj)` walks the prototype chain and yields
|
|
2
2
|
// every enumerable name, inherited or own. Most authors only ever care
|
|
3
3
|
// about own keys, so an unguarded body silently leaks work onto
|
|
4
|
-
// prototype-chain entries someone else attached.
|
|
5
|
-
// `if (Object.hasOwn(obj, key))` or the older
|
|
6
|
-
// `Object.prototype.hasOwnProperty.call(obj, key)`; an inverted-guard
|
|
7
|
-
// `continue` (`if (!Object.hasOwn(...)) continue;`) is the equivalent
|
|
8
|
-
// early-skip shape.
|
|
4
|
+
// prototype-chain entries someone else attached.
|
|
9
5
|
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
6
|
+
// Ported from ESLint's `guard-for-in`, the check is purely STRUCTURAL:
|
|
7
|
+
// it never inspects what the guard `if` tests, only the SHAPE of the
|
|
8
|
+
// loop body. The body is accepted when it is one of five shapes,
|
|
9
|
+
// mirroring upstream's ordered early-returns exactly:
|
|
10
|
+
//
|
|
11
|
+
// 1. an empty statement (`for (k in o);`);
|
|
12
|
+
// 2. a bare `if` statement (`for (k in o) if (...) ...;`);
|
|
13
|
+
// 3. an empty block (`{}`);
|
|
14
|
+
// 4. a block whose sole statement is an `if`;
|
|
15
|
+
// 5. a block whose leading statement is an `if` whose consequent is a
|
|
16
|
+
// `continue` (bare, or a block containing only `continue`).
|
|
17
|
+
//
|
|
18
|
+
// Anything else is reported. Because the condition is never examined,
|
|
19
|
+
// `if (obj.hasOwnProperty(key))`, `if (Object.hasOwn(obj, key))`,
|
|
20
|
+
// `if (cond)`, and `if (key.startsWith("_")) continue;` are all
|
|
21
|
+
// accepted, matching ESLint.
|
|
16
22
|
// https://eslint.org/docs/latest/rules/guard-for-in
|
|
17
23
|
package linthost
|
|
18
24
|
|
|
@@ -29,168 +35,79 @@ func (guardForIn) Check(ctx *Context, node *shimast.Node) {
|
|
|
29
35
|
if stmt == nil || stmt.Statement == nil {
|
|
30
36
|
return
|
|
31
37
|
}
|
|
32
|
-
|
|
33
|
-
if first == nil {
|
|
34
|
-
return
|
|
35
|
-
}
|
|
36
|
-
key := forInKeyName(stmt.Initializer)
|
|
37
|
-
if isHasOwnGuard(first, key) {
|
|
38
|
+
if isGuardedForInBody(stmt.Statement) {
|
|
38
39
|
return
|
|
39
40
|
}
|
|
40
|
-
ctx.Report(node, "The body of a `for...in` should be wrapped in an `if
|
|
41
|
+
ctx.Report(node, "The body of a `for...in` should be wrapped in an `if` statement to filter unwanted properties from the prototype.")
|
|
41
42
|
}
|
|
42
43
|
|
|
43
|
-
//
|
|
44
|
-
//
|
|
45
|
-
//
|
|
46
|
-
//
|
|
47
|
-
func
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
44
|
+
// isGuardedForInBody reports whether a `for...in` loop body has one of
|
|
45
|
+
// the five structural shapes ESLint's guard-for-in accepts. It mirrors
|
|
46
|
+
// upstream's ordered early-returns and deliberately never inspects the
|
|
47
|
+
// guard condition.
|
|
48
|
+
func isGuardedForInBody(body *shimast.Node) bool {
|
|
49
|
+
switch body.Kind {
|
|
50
|
+
case shimast.KindEmptyStatement:
|
|
51
|
+
// `for (k in o);` — an empty body cannot leak inherited keys.
|
|
52
|
+
return true
|
|
53
|
+
case shimast.KindIfStatement:
|
|
54
|
+
// `for (k in o) if (...) ...;` — a bare `if` body is itself the
|
|
55
|
+
// guard, whatever it tests.
|
|
56
|
+
return true
|
|
57
|
+
case shimast.KindBlock:
|
|
58
|
+
// Fall through to the block-shape analysis below.
|
|
59
|
+
default:
|
|
60
|
+
return false
|
|
53
61
|
}
|
|
62
|
+
|
|
54
63
|
block := body.AsBlock()
|
|
55
64
|
if block == nil || block.Statements == nil {
|
|
56
|
-
|
|
57
|
-
}
|
|
58
|
-
for _, s := range block.Statements.Nodes {
|
|
59
|
-
if s != nil {
|
|
60
|
-
return s
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
return nil
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// forInKeyName extracts the identifier text of the loop key from a
|
|
67
|
-
// `for (… in obj)` initializer. The initializer is either a
|
|
68
|
-
// VariableDeclarationList with one declaration (`for (const key in …)`)
|
|
69
|
-
// or a bare Identifier (`for (key in …)`). Destructuring patterns and
|
|
70
|
-
// other shapes return "" so the rule skips its key-identity check.
|
|
71
|
-
func forInKeyName(init *shimast.Node) string {
|
|
72
|
-
if init == nil {
|
|
73
|
-
return ""
|
|
74
|
-
}
|
|
75
|
-
if name := identifierText(init); name != "" {
|
|
76
|
-
return name
|
|
77
|
-
}
|
|
78
|
-
if init.Kind != shimast.KindVariableDeclarationList {
|
|
79
|
-
return ""
|
|
80
|
-
}
|
|
81
|
-
list := init.AsVariableDeclarationList()
|
|
82
|
-
if list == nil || list.Declarations == nil || len(list.Declarations.Nodes) != 1 {
|
|
83
|
-
return ""
|
|
84
|
-
}
|
|
85
|
-
decl := list.Declarations.Nodes[0]
|
|
86
|
-
if decl == nil {
|
|
87
|
-
return ""
|
|
88
|
-
}
|
|
89
|
-
v := decl.AsVariableDeclaration()
|
|
90
|
-
if v == nil {
|
|
91
|
-
return ""
|
|
92
|
-
}
|
|
93
|
-
return identifierText(v.Name())
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// isHasOwnGuard reports whether `stmt` is the canonical own-key guard
|
|
97
|
-
// that should open a `for...in` body. Two shapes count:
|
|
98
|
-
//
|
|
99
|
-
// 1. `if (<has-own>(obj, key)) { … }` — positive guard wrapping the
|
|
100
|
-
// real body. The else branch (if any) is allowed because the
|
|
101
|
-
// developer has explicitly handled the inherited-key case.
|
|
102
|
-
// 2. `if (!<has-own>(obj, key)) continue;` — inverted-guard early
|
|
103
|
-
// skip. The then branch must be a single `continue` (with or
|
|
104
|
-
// without a block wrapper) so the rest of the loop body only runs
|
|
105
|
-
// for own keys.
|
|
106
|
-
//
|
|
107
|
-
// `<has-own>` is either `Object.hasOwn(obj, key)` or
|
|
108
|
-
// `Object.prototype.hasOwnProperty.call(obj, key)`. The second
|
|
109
|
-
// argument must match `key` when one was extracted from the
|
|
110
|
-
// initializer; otherwise any second argument is accepted (the rule
|
|
111
|
-
// has no reliable handle on the key name).
|
|
112
|
-
func isHasOwnGuard(stmt *shimast.Node, key string) bool {
|
|
113
|
-
if stmt == nil || stmt.Kind != shimast.KindIfStatement {
|
|
114
|
-
return false
|
|
115
|
-
}
|
|
116
|
-
ifStmt := stmt.AsIfStatement()
|
|
117
|
-
if ifStmt == nil || ifStmt.Expression == nil {
|
|
118
|
-
return false
|
|
119
|
-
}
|
|
120
|
-
cond := stripParens(ifStmt.Expression)
|
|
121
|
-
if cond == nil {
|
|
122
|
-
return false
|
|
123
|
-
}
|
|
124
|
-
if isHasOwnCall(cond, key) {
|
|
65
|
+
// A block with no statement list behaves like an empty block.
|
|
125
66
|
return true
|
|
126
67
|
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
if pre == nil || pre.Operator != shimast.KindExclamationToken {
|
|
132
|
-
return false
|
|
133
|
-
}
|
|
134
|
-
if !isHasOwnCall(stripParens(pre.Operand), key) {
|
|
135
|
-
return false
|
|
136
|
-
}
|
|
137
|
-
// Negated guard: require the then branch to be a single `continue`
|
|
138
|
-
// so the rest of the loop runs only for own keys.
|
|
139
|
-
return isLoneContinue(ifStmt.ThenStatement)
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// isHasOwnCall reports whether `node` is `Object.hasOwn(obj, key)` or
|
|
143
|
-
// `Object.prototype.hasOwnProperty.call(obj, key)`. The second
|
|
144
|
-
// argument must match `key` when one is supplied.
|
|
145
|
-
func isHasOwnCall(node *shimast.Node, key string) bool {
|
|
146
|
-
if node == nil || node.Kind != shimast.KindCallExpression {
|
|
147
|
-
return false
|
|
68
|
+
stmts := block.Statements.Nodes
|
|
69
|
+
// Empty block.
|
|
70
|
+
if len(stmts) == 0 {
|
|
71
|
+
return true
|
|
148
72
|
}
|
|
149
|
-
|
|
150
|
-
|
|
73
|
+
// Every remaining accepted shape opens with an `if`.
|
|
74
|
+
lead := stmts[0]
|
|
75
|
+
if lead == nil || lead.Kind != shimast.KindIfStatement {
|
|
151
76
|
return false
|
|
152
77
|
}
|
|
153
|
-
|
|
154
|
-
if len(
|
|
155
|
-
return
|
|
78
|
+
// Block whose sole statement is an `if`.
|
|
79
|
+
if len(stmts) == 1 {
|
|
80
|
+
return true
|
|
156
81
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
// caller; the key check is the second argument.
|
|
162
|
-
case isMatchingPropertyAccess(callee, "Object", "prototype", "hasOwnProperty", "call"):
|
|
163
|
-
// `Object.prototype.hasOwnProperty.call(obj, key)` — same
|
|
164
|
-
// argument layout because `.call(obj, key)` passes the
|
|
165
|
-
// receiver explicitly.
|
|
166
|
-
default:
|
|
82
|
+
// Block that opens with an `if` whose consequent is a `continue`, so
|
|
83
|
+
// the statements after it run only for retained keys.
|
|
84
|
+
ifStmt := lead.AsIfStatement()
|
|
85
|
+
if ifStmt == nil {
|
|
167
86
|
return false
|
|
168
87
|
}
|
|
169
|
-
|
|
170
|
-
return true
|
|
171
|
-
}
|
|
172
|
-
return identifierText(stripParens(args[1])) == key
|
|
88
|
+
return isContinueConsequent(ifStmt.ThenStatement)
|
|
173
89
|
}
|
|
174
90
|
|
|
175
|
-
//
|
|
176
|
-
//
|
|
177
|
-
//
|
|
178
|
-
|
|
179
|
-
|
|
91
|
+
// isContinueConsequent reports whether an `if` consequent is a
|
|
92
|
+
// `continue` statement, either bare (`continue;`) or a block whose sole
|
|
93
|
+
// statement is `continue;`. Mirrors ESLint's check on the leading
|
|
94
|
+
// guard's consequent.
|
|
95
|
+
func isContinueConsequent(consequent *shimast.Node) bool {
|
|
96
|
+
if consequent == nil {
|
|
180
97
|
return false
|
|
181
98
|
}
|
|
182
|
-
if
|
|
99
|
+
if consequent.Kind == shimast.KindContinueStatement {
|
|
183
100
|
return true
|
|
184
101
|
}
|
|
185
|
-
if
|
|
102
|
+
if consequent.Kind != shimast.KindBlock {
|
|
186
103
|
return false
|
|
187
104
|
}
|
|
188
|
-
block :=
|
|
105
|
+
block := consequent.AsBlock()
|
|
189
106
|
if block == nil || block.Statements == nil || len(block.Statements.Nodes) != 1 {
|
|
190
107
|
return false
|
|
191
108
|
}
|
|
192
|
-
|
|
193
|
-
|
|
109
|
+
only := block.Statements.Nodes[0]
|
|
110
|
+
return only != nil && only.Kind == shimast.KindContinueStatement
|
|
194
111
|
}
|
|
195
112
|
|
|
196
113
|
func init() {
|