@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
|
@@ -8,45 +8,168 @@
|
|
|
8
8
|
package linthost
|
|
9
9
|
|
|
10
10
|
import (
|
|
11
|
-
"strconv"
|
|
12
11
|
"strings"
|
|
13
12
|
|
|
14
13
|
shimast "github.com/microsoft/typescript-go/shim/ast"
|
|
14
|
+
shimscanner "github.com/microsoft/typescript-go/shim/scanner"
|
|
15
15
|
)
|
|
16
16
|
|
|
17
|
-
// noDupeElseIf
|
|
18
|
-
//
|
|
17
|
+
// noDupeElseIf rejects an else-if branch when earlier conditions in the
|
|
18
|
+
// same chain already cover every path that can make its condition true.
|
|
19
19
|
type noDupeElseIf struct{}
|
|
20
20
|
|
|
21
21
|
func (noDupeElseIf) Name() string { return "no-dupe-else-if" }
|
|
22
22
|
func (noDupeElseIf) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindIfStatement} }
|
|
23
23
|
func (noDupeElseIf) Check(ctx *Context, node *shimast.Node) {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
24
|
+
statement := node.AsIfStatement()
|
|
25
|
+
if statement == nil || statement.Expression == nil {
|
|
26
|
+
return
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
test := stripParens(statement.Expression)
|
|
30
|
+
conditionsToCheck := []*shimast.Node{test}
|
|
31
|
+
if noDupeElseIfLogicalOperator(test) == shimast.KindAmpersandAmpersandToken {
|
|
32
|
+
conditionsToCheck = append(conditionsToCheck, noDupeElseIfSplit(test, shimast.KindAmpersandAmpersandToken)...)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
candidates := make([][][]*shimast.Node, 0, len(conditionsToCheck))
|
|
36
|
+
for _, condition := range conditionsToCheck {
|
|
37
|
+
orOperands := noDupeElseIfSplit(condition, shimast.KindBarBarToken)
|
|
38
|
+
conjunctions := make([][]*shimast.Node, 0, len(orOperands))
|
|
39
|
+
for _, operand := range orOperands {
|
|
40
|
+
conjunctions = append(conjunctions, noDupeElseIfSplit(operand, shimast.KindAmpersandAmpersandToken))
|
|
32
41
|
}
|
|
42
|
+
candidates = append(candidates, conjunctions)
|
|
33
43
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
for
|
|
37
|
-
|
|
38
|
-
|
|
44
|
+
|
|
45
|
+
current := node
|
|
46
|
+
for current.Parent != nil && current.Parent.Kind == shimast.KindIfStatement {
|
|
47
|
+
parent := current.Parent
|
|
48
|
+
parentStatement := parent.AsIfStatement()
|
|
49
|
+
if parentStatement == nil || parentStatement.ElseStatement != current || parentStatement.Expression == nil {
|
|
39
50
|
break
|
|
40
51
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
52
|
+
|
|
53
|
+
earlierOrOperands := noDupeElseIfSplit(stripParens(parentStatement.Expression), shimast.KindBarBarToken)
|
|
54
|
+
earlierConjunctions := make([][]*shimast.Node, 0, len(earlierOrOperands))
|
|
55
|
+
for _, operand := range earlierOrOperands {
|
|
56
|
+
earlierConjunctions = append(earlierConjunctions, noDupeElseIfSplit(operand, shimast.KindAmpersandAmpersandToken))
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
for i, disjunction := range candidates {
|
|
60
|
+
remaining := disjunction[:0]
|
|
61
|
+
for _, conjunction := range disjunction {
|
|
62
|
+
covered := false
|
|
63
|
+
for _, earlier := range earlierConjunctions {
|
|
64
|
+
if noDupeElseIfSubset(ctx.File, earlier, conjunction) {
|
|
65
|
+
covered = true
|
|
66
|
+
break
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if !covered {
|
|
70
|
+
remaining = append(remaining, conjunction)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
candidates[i] = remaining
|
|
74
|
+
if len(remaining) == 0 {
|
|
75
|
+
ctx.Report(statement.Expression, "This branch can never execute. Its condition is a duplicate or covered by previous conditions in the if-else-if chain.")
|
|
76
|
+
return
|
|
47
77
|
}
|
|
48
78
|
}
|
|
49
|
-
|
|
79
|
+
|
|
80
|
+
current = parent
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
func noDupeElseIfLogicalOperator(node *shimast.Node) shimast.Kind {
|
|
85
|
+
node = stripParens(node)
|
|
86
|
+
if node == nil || node.Kind != shimast.KindBinaryExpression {
|
|
87
|
+
return shimast.KindUnknown
|
|
88
|
+
}
|
|
89
|
+
expression := node.AsBinaryExpression()
|
|
90
|
+
if expression == nil || expression.OperatorToken == nil {
|
|
91
|
+
return shimast.KindUnknown
|
|
92
|
+
}
|
|
93
|
+
switch expression.OperatorToken.Kind {
|
|
94
|
+
case shimast.KindAmpersandAmpersandToken, shimast.KindBarBarToken:
|
|
95
|
+
return expression.OperatorToken.Kind
|
|
96
|
+
default:
|
|
97
|
+
return shimast.KindUnknown
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
func noDupeElseIfSplit(node *shimast.Node, operator shimast.Kind) []*shimast.Node {
|
|
102
|
+
node = stripParens(node)
|
|
103
|
+
if noDupeElseIfLogicalOperator(node) != operator {
|
|
104
|
+
return []*shimast.Node{node}
|
|
105
|
+
}
|
|
106
|
+
expression := node.AsBinaryExpression()
|
|
107
|
+
operands := noDupeElseIfSplit(expression.Left, operator)
|
|
108
|
+
return append(operands, noDupeElseIfSplit(expression.Right, operator)...)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
func noDupeElseIfSubset(file *shimast.SourceFile, subset, set []*shimast.Node) bool {
|
|
112
|
+
for _, candidate := range subset {
|
|
113
|
+
matched := false
|
|
114
|
+
for _, element := range set {
|
|
115
|
+
if noDupeElseIfEqual(file, candidate, element) {
|
|
116
|
+
matched = true
|
|
117
|
+
break
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
if !matched {
|
|
121
|
+
return false
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return true
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// noDupeElseIfEqual compares boolean expressions structurally. Logical AND
|
|
128
|
+
// and OR are commutative in a condition's truth table; every other expression
|
|
129
|
+
// must retain the same token kinds and values.
|
|
130
|
+
func noDupeElseIfEqual(file *shimast.SourceFile, left, right *shimast.Node) bool {
|
|
131
|
+
left = stripParens(left)
|
|
132
|
+
right = stripParens(right)
|
|
133
|
+
if left == nil || right == nil || left.Kind != right.Kind {
|
|
134
|
+
return false
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
operator := noDupeElseIfLogicalOperator(left)
|
|
138
|
+
if operator != shimast.KindUnknown && operator == noDupeElseIfLogicalOperator(right) {
|
|
139
|
+
leftExpression := left.AsBinaryExpression()
|
|
140
|
+
rightExpression := right.AsBinaryExpression()
|
|
141
|
+
return noDupeElseIfEqual(file, leftExpression.Left, rightExpression.Left) &&
|
|
142
|
+
noDupeElseIfEqual(file, leftExpression.Right, rightExpression.Right) ||
|
|
143
|
+
noDupeElseIfEqual(file, leftExpression.Left, rightExpression.Right) &&
|
|
144
|
+
noDupeElseIfEqual(file, leftExpression.Right, rightExpression.Left)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return noDupeElseIfEqualTokens(file, left, right)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
func noDupeElseIfEqualTokens(file *shimast.SourceFile, left, right *shimast.Node) bool {
|
|
151
|
+
leftText := nodeText(file, left)
|
|
152
|
+
rightText := nodeText(file, right)
|
|
153
|
+
if leftText == "" || rightText == "" {
|
|
154
|
+
return false
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
leftScanner := shimscanner.NewScanner()
|
|
158
|
+
leftScanner.SetText(leftText)
|
|
159
|
+
leftScanner.SetSkipTrivia(true)
|
|
160
|
+
rightScanner := shimscanner.NewScanner()
|
|
161
|
+
rightScanner.SetText(rightText)
|
|
162
|
+
rightScanner.SetSkipTrivia(true)
|
|
163
|
+
|
|
164
|
+
for {
|
|
165
|
+
leftKind := leftScanner.Scan()
|
|
166
|
+
rightKind := rightScanner.Scan()
|
|
167
|
+
if leftKind != rightKind || leftScanner.TokenText() != rightScanner.TokenText() {
|
|
168
|
+
return false
|
|
169
|
+
}
|
|
170
|
+
if leftKind == shimast.KindEndOfFile {
|
|
171
|
+
return true
|
|
172
|
+
}
|
|
50
173
|
}
|
|
51
174
|
}
|
|
52
175
|
|
|
@@ -75,9 +198,9 @@ func (noExAssign) Check(ctx *Context, node *shimast.Node) {
|
|
|
75
198
|
}
|
|
76
199
|
|
|
77
200
|
// walkAssignments invokes `report` on every `<name> = ...` shape inside
|
|
78
|
-
// `root`.
|
|
79
|
-
//
|
|
80
|
-
//
|
|
201
|
+
// `root`. It is retained for noExAssign's catch-block-local scan; class and
|
|
202
|
+
// function assignment rules require checker binding identity and live in
|
|
203
|
+
// their dedicated files.
|
|
81
204
|
func walkAssignments(root *shimast.Node, name string, report func(*shimast.Node)) {
|
|
82
205
|
if root == nil {
|
|
83
206
|
return
|
|
@@ -107,33 +230,12 @@ func (noEmptyCharacterClass) Visits() []shimast.Kind {
|
|
|
107
230
|
return []shimast.Kind{shimast.KindRegularExpressionLiteral}
|
|
108
231
|
}
|
|
109
232
|
func (noEmptyCharacterClass) Check(ctx *Context, node *shimast.Node) {
|
|
110
|
-
|
|
111
|
-
if
|
|
233
|
+
parts, ok := parseRegexpLiteralParts(ctx, node)
|
|
234
|
+
if ok && regexpHasEmptyCharacterClass(parts) {
|
|
112
235
|
ctx.Report(node, "Empty class.")
|
|
113
236
|
}
|
|
114
237
|
}
|
|
115
238
|
|
|
116
|
-
// hasEmptyCharClass reports whether the regex source text src contains an
|
|
117
|
-
// empty character class (`[]` or `[^]`). Respects backslash escapes.
|
|
118
|
-
func hasEmptyCharClass(src string) bool {
|
|
119
|
-
// Walk the regex literal source manually, respecting escapes.
|
|
120
|
-
for i := 0; i < len(src); i++ {
|
|
121
|
-
switch src[i] {
|
|
122
|
-
case '\\':
|
|
123
|
-
i++ // skip escape
|
|
124
|
-
case '[':
|
|
125
|
-
j := i + 1
|
|
126
|
-
if j < len(src) && src[j] == '^' {
|
|
127
|
-
j++
|
|
128
|
-
}
|
|
129
|
-
if j < len(src) && src[j] == ']' {
|
|
130
|
-
return true
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
return false
|
|
135
|
-
}
|
|
136
|
-
|
|
137
239
|
// noMisleadingCharacterClass: `/[👍]/` — surrogate pairs in regex.
|
|
138
240
|
type noMisleadingCharacterClass struct{}
|
|
139
241
|
|
|
@@ -179,10 +281,9 @@ func regexHasSurrogatePair(src string) bool {
|
|
|
179
281
|
return false
|
|
180
282
|
}
|
|
181
283
|
|
|
182
|
-
// noLossOfPrecision:
|
|
183
|
-
//
|
|
184
|
-
//
|
|
185
|
-
// precision.
|
|
284
|
+
// noLossOfPrecision: a Number literal whose requested significant digits
|
|
285
|
+
// change during IEEE-754 conversion. We read the source form instead of the
|
|
286
|
+
// parser's normalized .Text, which has already lost spelling and precision.
|
|
186
287
|
type noLossOfPrecision struct{}
|
|
187
288
|
|
|
188
289
|
func (noLossOfPrecision) Name() string { return "no-loss-of-precision" }
|
|
@@ -197,128 +298,6 @@ func (noLossOfPrecision) Check(ctx *Context, node *shimast.Node) {
|
|
|
197
298
|
}
|
|
198
299
|
}
|
|
199
300
|
|
|
200
|
-
// numericLiteralLosesPrecision reports whether the decimal integer literal text
|
|
201
|
-
// changes when parsed as a JavaScript Number. Non-decimal literals (hex, octal,
|
|
202
|
-
// binary) and literals with exponents or decimal points are exempt because
|
|
203
|
-
// their precision loss is caller-visible and intentional.
|
|
204
|
-
func numericLiteralLosesPrecision(text string) bool {
|
|
205
|
-
// Strip underscore separators, exponents, decimal/hex/oct/binary
|
|
206
|
-
// markers — for the simple-base-10 integer case the round-trip
|
|
207
|
-
// parse → format check is sufficient.
|
|
208
|
-
clean := strings.ReplaceAll(text, "_", "")
|
|
209
|
-
if strings.ContainsAny(clean, "eE.xXoObB") {
|
|
210
|
-
return false
|
|
211
|
-
}
|
|
212
|
-
if len(clean) < 16 {
|
|
213
|
-
return false
|
|
214
|
-
}
|
|
215
|
-
// Trim leading zeros for comparison.
|
|
216
|
-
trimmed := strings.TrimLeft(clean, "0")
|
|
217
|
-
if trimmed == "" {
|
|
218
|
-
return false
|
|
219
|
-
}
|
|
220
|
-
// 2^53 is unsafe for arithmetic comparisons but still exactly representable.
|
|
221
|
-
// 2^53+1 is the first plain decimal integer that changes when parsed.
|
|
222
|
-
const firstPossibleLoss = "9007199254740993"
|
|
223
|
-
if len(trimmed) < len(firstPossibleLoss) {
|
|
224
|
-
return false
|
|
225
|
-
}
|
|
226
|
-
if len(trimmed) == len(firstPossibleLoss) && trimmed < firstPossibleLoss {
|
|
227
|
-
return false
|
|
228
|
-
}
|
|
229
|
-
// Number.MAX_VALUE is around 1.79e308, so any integer with more than 309
|
|
230
|
-
// decimal digits cannot round-trip through a finite JavaScript Number.
|
|
231
|
-
const maxFiniteDecimalDigits = 309
|
|
232
|
-
if len(trimmed) > maxFiniteDecimalDigits {
|
|
233
|
-
return true
|
|
234
|
-
}
|
|
235
|
-
parsed, err := strconv.ParseFloat(trimmed, 64)
|
|
236
|
-
if err != nil && parsed == 0 {
|
|
237
|
-
return false
|
|
238
|
-
}
|
|
239
|
-
return strconv.FormatFloat(parsed, 'f', 0, 64) != trimmed
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
// noClassAssign: assigning to a class declaration's name.
|
|
243
|
-
type noClassAssign struct{}
|
|
244
|
-
|
|
245
|
-
func (noClassAssign) Name() string { return "no-class-assign" }
|
|
246
|
-
func (noClassAssign) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindSourceFile} }
|
|
247
|
-
func (noClassAssign) Check(ctx *Context, node *shimast.Node) {
|
|
248
|
-
reportAssignmentsToDeclarations(ctx, node, shimast.KindClassDeclaration, "is a class.")
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
// noFuncAssign: same idea, but for function declarations.
|
|
252
|
-
type noFuncAssign struct{}
|
|
253
|
-
|
|
254
|
-
func (noFuncAssign) Name() string { return "no-func-assign" }
|
|
255
|
-
func (noFuncAssign) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindSourceFile} }
|
|
256
|
-
func (noFuncAssign) Check(ctx *Context, node *shimast.Node) {
|
|
257
|
-
reportAssignmentsToDeclarations(ctx, node, shimast.KindFunctionDeclaration, "is a function.")
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
// reportAssignmentsToDeclarations flags every `<name> = …` assignment whose
|
|
261
|
-
// target identifier names a `declKind` declaration found anywhere in the
|
|
262
|
-
// file. It walks the file exactly once — gathering declared names and
|
|
263
|
-
// assignment targets in the same pass — so the cost is linear in file size.
|
|
264
|
-
//
|
|
265
|
-
// The earlier shape registered for `declKind` directly and, on every
|
|
266
|
-
// declaration, re-scanned the whole file for assignments: O(declarations ×
|
|
267
|
-
// file size), which blows up quadratically on a file with many top-level
|
|
268
|
-
// functions. Visiting `KindSourceFile` once and cross-referencing afterward
|
|
269
|
-
// keeps the same findings without the repeated scans.
|
|
270
|
-
func reportAssignmentsToDeclarations(
|
|
271
|
-
ctx *Context,
|
|
272
|
-
file *shimast.Node,
|
|
273
|
-
declKind shimast.Kind,
|
|
274
|
-
noun string,
|
|
275
|
-
) {
|
|
276
|
-
if ctx == nil || file == nil {
|
|
277
|
-
return
|
|
278
|
-
}
|
|
279
|
-
declared := map[string]struct{}{}
|
|
280
|
-
var targets []*shimast.Node
|
|
281
|
-
walkDescendants(file, func(n *shimast.Node) {
|
|
282
|
-
switch n.Kind {
|
|
283
|
-
case declKind:
|
|
284
|
-
if name := declarationName(n); name != "" {
|
|
285
|
-
declared[name] = struct{}{}
|
|
286
|
-
}
|
|
287
|
-
case shimast.KindBinaryExpression:
|
|
288
|
-
if expr := n.AsBinaryExpression(); expr != nil &&
|
|
289
|
-
expr.OperatorToken != nil && isAssignmentOperator(expr.OperatorToken.Kind) &&
|
|
290
|
-
expr.Left != nil && expr.Left.Kind == shimast.KindIdentifier {
|
|
291
|
-
targets = append(targets, expr.Left)
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
})
|
|
295
|
-
if len(declared) == 0 || len(targets) == 0 {
|
|
296
|
-
return
|
|
297
|
-
}
|
|
298
|
-
for _, target := range targets {
|
|
299
|
-
name := identifierText(target)
|
|
300
|
-
if _, ok := declared[name]; ok {
|
|
301
|
-
ctx.Report(target, "'"+name+"' "+noun)
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
// declarationName returns the bound name of a class or function declaration
|
|
307
|
-
// node, or "" when the node is neither (or is anonymous).
|
|
308
|
-
func declarationName(n *shimast.Node) string {
|
|
309
|
-
switch n.Kind {
|
|
310
|
-
case shimast.KindFunctionDeclaration:
|
|
311
|
-
if d := n.AsFunctionDeclaration(); d != nil {
|
|
312
|
-
return identifierText(d.Name())
|
|
313
|
-
}
|
|
314
|
-
case shimast.KindClassDeclaration:
|
|
315
|
-
if d := n.AsClassDeclaration(); d != nil {
|
|
316
|
-
return identifierText(d.Name())
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
return ""
|
|
320
|
-
}
|
|
321
|
-
|
|
322
301
|
// noPrototypeBuiltins: `obj.hasOwnProperty(x)` — should be
|
|
323
302
|
// `Object.prototype.hasOwnProperty.call(obj, x)` or `Object.hasOwn`.
|
|
324
303
|
type noPrototypeBuiltins struct{}
|
|
@@ -371,36 +350,6 @@ func (noAsyncPromiseExecutor) Check(ctx *Context, node *shimast.Node) {
|
|
|
371
350
|
}
|
|
372
351
|
}
|
|
373
352
|
|
|
374
|
-
// noPromiseExecutorReturn: `new Promise(() => 1)` — the value is
|
|
375
|
-
// thrown away.
|
|
376
|
-
type noPromiseExecutorReturn struct{}
|
|
377
|
-
|
|
378
|
-
func (noPromiseExecutorReturn) Name() string { return "no-promise-executor-return" }
|
|
379
|
-
func (noPromiseExecutorReturn) Visits() []shimast.Kind {
|
|
380
|
-
return []shimast.Kind{shimast.KindNewExpression}
|
|
381
|
-
}
|
|
382
|
-
func (noPromiseExecutorReturn) Check(ctx *Context, node *shimast.Node) {
|
|
383
|
-
ne := node.AsNewExpression()
|
|
384
|
-
if ne == nil || identifierText(ne.Expression) != "Promise" {
|
|
385
|
-
return
|
|
386
|
-
}
|
|
387
|
-
if ne.Arguments == nil || len(ne.Arguments.Nodes) == 0 {
|
|
388
|
-
return
|
|
389
|
-
}
|
|
390
|
-
executor := ne.Arguments.Nodes[0]
|
|
391
|
-
if executor == nil || executor.Kind != shimast.KindArrowFunction {
|
|
392
|
-
return
|
|
393
|
-
}
|
|
394
|
-
arrow := executor.AsArrowFunction()
|
|
395
|
-
if arrow == nil || arrow.Body == nil {
|
|
396
|
-
return
|
|
397
|
-
}
|
|
398
|
-
// Concise arrow body returns the value implicitly.
|
|
399
|
-
if arrow.Body.Kind != shimast.KindBlock {
|
|
400
|
-
ctx.Report(arrow.Body, "Return values from promise executor functions cannot be read.")
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
|
|
404
353
|
// noControlRegex: `/\x00/` — control characters in regex are usually
|
|
405
354
|
// the result of accidentally typing the escape rather than the printable
|
|
406
355
|
// counterpart.
|
|
@@ -502,107 +451,6 @@ func isIrregularWhitespace(r rune) bool {
|
|
|
502
451
|
return false
|
|
503
452
|
}
|
|
504
453
|
|
|
505
|
-
// noFallthrough: `switch` cases that fall through to the next label
|
|
506
|
-
// without an explicit `break` / `return` / `throw` / `continue`.
|
|
507
|
-
type noFallthrough struct{}
|
|
508
|
-
|
|
509
|
-
func (noFallthrough) Name() string { return "no-fallthrough" }
|
|
510
|
-
func (noFallthrough) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindSwitchStatement} }
|
|
511
|
-
func (noFallthrough) Check(ctx *Context, node *shimast.Node) {
|
|
512
|
-
sw := node.AsSwitchStatement()
|
|
513
|
-
if sw == nil || sw.CaseBlock == nil {
|
|
514
|
-
return
|
|
515
|
-
}
|
|
516
|
-
block := sw.CaseBlock.AsCaseBlock()
|
|
517
|
-
if block == nil || block.Clauses == nil {
|
|
518
|
-
return
|
|
519
|
-
}
|
|
520
|
-
clauses := block.Clauses.Nodes
|
|
521
|
-
for i := 0; i+1 < len(clauses); i++ {
|
|
522
|
-
clause := clauses[i].AsCaseOrDefaultClause()
|
|
523
|
-
if clause == nil || clause.Statements == nil {
|
|
524
|
-
continue
|
|
525
|
-
}
|
|
526
|
-
stmts := clause.Statements.Nodes
|
|
527
|
-
if len(stmts) == 0 {
|
|
528
|
-
continue // empty case is intentional, never a fallthrough.
|
|
529
|
-
}
|
|
530
|
-
if !isTerminating(stmts[len(stmts)-1]) {
|
|
531
|
-
ctx.Report(clauses[i+1], "Expected a 'break' statement before this case.")
|
|
532
|
-
}
|
|
533
|
-
}
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
// isTerminating reports whether stmt is a statement that unconditionally
|
|
537
|
-
// transfers control out of the current block: break, continue, return,
|
|
538
|
-
// throw, or a block whose last statement is terminating.
|
|
539
|
-
func isTerminating(stmt *shimast.Node) bool {
|
|
540
|
-
if stmt == nil {
|
|
541
|
-
return false
|
|
542
|
-
}
|
|
543
|
-
switch stmt.Kind {
|
|
544
|
-
case shimast.KindBreakStatement,
|
|
545
|
-
shimast.KindContinueStatement,
|
|
546
|
-
shimast.KindReturnStatement,
|
|
547
|
-
shimast.KindThrowStatement:
|
|
548
|
-
return true
|
|
549
|
-
case shimast.KindBlock:
|
|
550
|
-
block := stmt.AsBlock()
|
|
551
|
-
if block == nil || block.Statements == nil {
|
|
552
|
-
return false
|
|
553
|
-
}
|
|
554
|
-
nodes := block.Statements.Nodes
|
|
555
|
-
if len(nodes) == 0 {
|
|
556
|
-
return false
|
|
557
|
-
}
|
|
558
|
-
return isTerminating(nodes[len(nodes)-1])
|
|
559
|
-
}
|
|
560
|
-
return false
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
// noInnerDeclarations: `function foo() { if (x) { function bar() {} } }`
|
|
564
|
-
// — inner function declarations are hoisted differently in strict mode
|
|
565
|
-
// vs sloppy and are confusing.
|
|
566
|
-
type noInnerDeclarations struct{}
|
|
567
|
-
|
|
568
|
-
func (noInnerDeclarations) Name() string { return "no-inner-declarations" }
|
|
569
|
-
func (noInnerDeclarations) Visits() []shimast.Kind {
|
|
570
|
-
return []shimast.Kind{shimast.KindFunctionDeclaration, shimast.KindVariableStatement}
|
|
571
|
-
}
|
|
572
|
-
func (noInnerDeclarations) Check(ctx *Context, node *shimast.Node) {
|
|
573
|
-
if node.Kind == shimast.KindVariableStatement {
|
|
574
|
-
stmt := node.AsVariableStatement()
|
|
575
|
-
if stmt == nil || stmt.DeclarationList == nil {
|
|
576
|
-
return
|
|
577
|
-
}
|
|
578
|
-
// Only `var` is hoisted oddly.
|
|
579
|
-
if !shimast.IsVar(stmt.DeclarationList) {
|
|
580
|
-
return
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
|
-
parent := node.Parent
|
|
584
|
-
if parent == nil {
|
|
585
|
-
return
|
|
586
|
-
}
|
|
587
|
-
switch parent.Kind {
|
|
588
|
-
case shimast.KindSourceFile, shimast.KindModuleBlock:
|
|
589
|
-
return
|
|
590
|
-
case shimast.KindBlock:
|
|
591
|
-
grand := parent.Parent
|
|
592
|
-
if grand == nil {
|
|
593
|
-
return
|
|
594
|
-
}
|
|
595
|
-
if isFunctionLikeKind(grand) {
|
|
596
|
-
return
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
|
-
what := "function"
|
|
600
|
-
if node.Kind == shimast.KindVariableStatement {
|
|
601
|
-
what = "variable"
|
|
602
|
-
}
|
|
603
|
-
ctx.Report(node, "Move "+what+" declaration to the function scope.")
|
|
604
|
-
}
|
|
605
|
-
|
|
606
454
|
// noObjCalls: `Math()`, `JSON()` — these globals are objects, not
|
|
607
455
|
// callables. ESLint catches a small list.
|
|
608
456
|
type noObjCalls struct{}
|
|
@@ -656,14 +504,9 @@ func init() {
|
|
|
656
504
|
Register(noEmptyCharacterClass{})
|
|
657
505
|
Register(noMisleadingCharacterClass{})
|
|
658
506
|
Register(noLossOfPrecision{})
|
|
659
|
-
Register(noClassAssign{})
|
|
660
|
-
Register(noFuncAssign{})
|
|
661
507
|
Register(noPrototypeBuiltins{})
|
|
662
508
|
Register(noAsyncPromiseExecutor{})
|
|
663
|
-
Register(noPromiseExecutorReturn{})
|
|
664
509
|
Register(noControlRegex{})
|
|
665
510
|
Register(noIrregularWhitespace{})
|
|
666
|
-
Register(noFallthrough{})
|
|
667
|
-
Register(noInnerDeclarations{})
|
|
668
511
|
Register(noObjCalls{})
|
|
669
512
|
}
|