@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
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
// noDuplicateImports reports an import (and, with `includeExports`, a
|
|
2
|
+
// re-export) declaration whose module specifier already appeared above,
|
|
3
|
+
// but only when the two declarations could be consolidated into one
|
|
4
|
+
// legal declaration. Same-module declarations TypeScript cannot merge —
|
|
5
|
+
// named next to namespace bindings, or (since ESLint 9.30.1) a
|
|
6
|
+
// type-only default next to type-only named bindings — are not
|
|
7
|
+
// duplicates, and `allowSeparateTypeImports` additionally keeps one
|
|
8
|
+
// clause-level `import type` declaration apart from value-bearing
|
|
9
|
+
// declarations.
|
|
10
|
+
// https://eslint.org/docs/latest/rules/no-duplicate-imports
|
|
11
|
+
package linthost
|
|
12
|
+
|
|
13
|
+
import (
|
|
14
|
+
"strings"
|
|
15
|
+
|
|
16
|
+
shimast "github.com/microsoft/typescript-go/shim/ast"
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
// noDuplicateImportsOptions mirrors the official rule's options object.
|
|
20
|
+
// Both keys default to false, matching ESLint's `defaultOptions`.
|
|
21
|
+
type noDuplicateImportsOptions struct {
|
|
22
|
+
// AllowSeparateTypeImports keeps clause-level `import type` / `export
|
|
23
|
+
// type` declarations out of the duplicate comparison with
|
|
24
|
+
// value-bearing declarations of the same module. Inline type
|
|
25
|
+
// specifiers (`import { type Foo }`) stay on the value side because
|
|
26
|
+
// their import clause is not type-only.
|
|
27
|
+
AllowSeparateTypeImports bool `json:"allowSeparateTypeImports"`
|
|
28
|
+
|
|
29
|
+
// IncludeExports folds `export … from` declarations into the same
|
|
30
|
+
// duplicate/mergeability analysis, adding the official
|
|
31
|
+
// duplicated-as-export / duplicated-as-import pairings.
|
|
32
|
+
IncludeExports bool `json:"includeExports"`
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// importExportCategory mirrors the specifier categories the official
|
|
36
|
+
// implementation derives from ESTree specifier types
|
|
37
|
+
// (`getImportExportType`).
|
|
38
|
+
type importExportCategory int
|
|
39
|
+
|
|
40
|
+
const (
|
|
41
|
+
// `import def from "m"` — ImportDefaultSpecifier.
|
|
42
|
+
importExportDefault importExportCategory = iota
|
|
43
|
+
// `import { a } from "m"` / `export { a } from "m"` —
|
|
44
|
+
// ImportSpecifier / ExportSpecifier.
|
|
45
|
+
importExportNamed
|
|
46
|
+
// `import * as ns from "m"` / `export * as ns from "m"` —
|
|
47
|
+
// ImportNamespaceSpecifier / ExportNamespaceSpecifier.
|
|
48
|
+
importExportNamespace
|
|
49
|
+
// `export * from "m"` — ExportAllDeclaration without an alias.
|
|
50
|
+
importExportAll
|
|
51
|
+
// `import "m"` and specifier-less clauses such as `import {} from
|
|
52
|
+
// "m"` / `export {} from "m"` — SideEffectImport.
|
|
53
|
+
importExportSideEffect
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
// importExportEntry retains the shape of one earlier import/re-export
|
|
57
|
+
// declaration so later declarations of the same module can answer the
|
|
58
|
+
// mergeability question against it.
|
|
59
|
+
type importExportEntry struct {
|
|
60
|
+
node *shimast.Node
|
|
61
|
+
category importExportCategory
|
|
62
|
+
typeOnly bool
|
|
63
|
+
isExport bool
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
type noDuplicateImports struct{ optionsRule }
|
|
67
|
+
|
|
68
|
+
func (noDuplicateImports) Name() string { return "no-duplicate-imports" }
|
|
69
|
+
func (noDuplicateImports) Visits() []shimast.Kind {
|
|
70
|
+
return []shimast.Kind{shimast.KindSourceFile}
|
|
71
|
+
}
|
|
72
|
+
func (noDuplicateImports) Check(ctx *Context, node *shimast.Node) {
|
|
73
|
+
var opts noDuplicateImportsOptions
|
|
74
|
+
_ = ctx.DecodeOptions(&opts)
|
|
75
|
+
modules := map[string][]importExportEntry{}
|
|
76
|
+
node.ForEachChild(func(child *shimast.Node) bool {
|
|
77
|
+
if child == nil {
|
|
78
|
+
return false
|
|
79
|
+
}
|
|
80
|
+
switch child.Kind {
|
|
81
|
+
case shimast.KindImportDeclaration:
|
|
82
|
+
decl := child.AsImportDeclaration()
|
|
83
|
+
if decl == nil {
|
|
84
|
+
return false
|
|
85
|
+
}
|
|
86
|
+
module := duplicateImportsModule(decl.ModuleSpecifier)
|
|
87
|
+
if module == "" {
|
|
88
|
+
return false
|
|
89
|
+
}
|
|
90
|
+
entry := duplicateImportsImportEntry(child, decl)
|
|
91
|
+
reportDuplicateImports(ctx, opts, modules[module], entry, module)
|
|
92
|
+
modules[module] = append(modules[module], entry)
|
|
93
|
+
case shimast.KindExportDeclaration:
|
|
94
|
+
// Without `includeExports` the official rule does not even
|
|
95
|
+
// record re-exports, so they can neither be reported nor make
|
|
96
|
+
// a later import count as duplicated-as-export.
|
|
97
|
+
if !opts.IncludeExports {
|
|
98
|
+
return false
|
|
99
|
+
}
|
|
100
|
+
decl := child.AsExportDeclaration()
|
|
101
|
+
if decl == nil {
|
|
102
|
+
return false
|
|
103
|
+
}
|
|
104
|
+
module := duplicateImportsModule(decl.ModuleSpecifier)
|
|
105
|
+
if module == "" {
|
|
106
|
+
return false
|
|
107
|
+
}
|
|
108
|
+
entry := duplicateImportsExportEntry(child, decl)
|
|
109
|
+
reportDuplicateImports(ctx, opts, modules[module], entry, module)
|
|
110
|
+
modules[module] = append(modules[module], entry)
|
|
111
|
+
}
|
|
112
|
+
return false
|
|
113
|
+
})
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// duplicateImportsModule returns the trimmed module specifier text, or
|
|
117
|
+
// "" when the declaration has no usable string specifier (`export { x
|
|
118
|
+
// }` without `from`, an empty string module). Trimming mirrors the
|
|
119
|
+
// official implementation, which compares `source.value.trim()`.
|
|
120
|
+
func duplicateImportsModule(spec *shimast.Node) string {
|
|
121
|
+
return strings.TrimSpace(stringLiteralText(spec))
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// duplicateImportsImportEntry categorizes one `import` declaration the
|
|
125
|
+
// way the official rule reads ESTree specifiers: the first named or
|
|
126
|
+
// namespace specifier decides the category, a lone default binding is
|
|
127
|
+
// the default category, and a clause-less or specifier-less import is a
|
|
128
|
+
// side-effect import.
|
|
129
|
+
func duplicateImportsImportEntry(node *shimast.Node, decl *shimast.ImportDeclaration) importExportEntry {
|
|
130
|
+
entry := importExportEntry{node: node, category: importExportSideEffect}
|
|
131
|
+
clause := decl.ImportClause
|
|
132
|
+
if clause == nil {
|
|
133
|
+
return entry
|
|
134
|
+
}
|
|
135
|
+
entry.typeOnly = clause.IsTypeOnly()
|
|
136
|
+
clauseData := clause.AsImportClause()
|
|
137
|
+
if clauseData == nil {
|
|
138
|
+
return entry
|
|
139
|
+
}
|
|
140
|
+
if bindings := clauseData.NamedBindings; bindings != nil {
|
|
141
|
+
switch bindings.Kind {
|
|
142
|
+
case shimast.KindNamespaceImport:
|
|
143
|
+
entry.category = importExportNamespace
|
|
144
|
+
return entry
|
|
145
|
+
case shimast.KindNamedImports:
|
|
146
|
+
// `import def, {} from "m"` has no named specifier in ESTree;
|
|
147
|
+
// the empty block falls through to the default binding below.
|
|
148
|
+
if named := bindings.AsNamedImports(); named != nil && named.Elements != nil && len(named.Elements.Nodes) > 0 {
|
|
149
|
+
entry.category = importExportNamed
|
|
150
|
+
return entry
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
if clauseData.Name() != nil {
|
|
155
|
+
entry.category = importExportDefault
|
|
156
|
+
}
|
|
157
|
+
return entry
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// duplicateImportsExportEntry categorizes one `export … from`
|
|
161
|
+
// declaration. `export * from` is the export-all category, `export * as
|
|
162
|
+
// ns from` is a namespace specifier, non-empty `export { … } from` is
|
|
163
|
+
// named, and the degenerate `export {} from "m"` matches the official
|
|
164
|
+
// specifier-less SideEffectImport category.
|
|
165
|
+
func duplicateImportsExportEntry(node *shimast.Node, decl *shimast.ExportDeclaration) importExportEntry {
|
|
166
|
+
entry := importExportEntry{
|
|
167
|
+
node: node,
|
|
168
|
+
category: importExportSideEffect,
|
|
169
|
+
typeOnly: decl.IsTypeOnly,
|
|
170
|
+
isExport: true,
|
|
171
|
+
}
|
|
172
|
+
clause := decl.ExportClause
|
|
173
|
+
if clause == nil {
|
|
174
|
+
entry.category = importExportAll
|
|
175
|
+
return entry
|
|
176
|
+
}
|
|
177
|
+
switch clause.Kind {
|
|
178
|
+
case shimast.KindNamespaceExport:
|
|
179
|
+
entry.category = importExportNamespace
|
|
180
|
+
case shimast.KindNamedExports:
|
|
181
|
+
if named := clause.AsNamedExports(); named != nil && named.Elements != nil && len(named.Elements.Nodes) > 0 {
|
|
182
|
+
entry.category = importExportNamed
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return entry
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// reportDuplicateImports mirrors the official checkAndReport: an import
|
|
189
|
+
// is reported once against earlier imports and once more against
|
|
190
|
+
// earlier re-exports, a re-export against earlier re-exports and
|
|
191
|
+
// earlier imports, each pairing with its own message. Earlier
|
|
192
|
+
// re-exports only exist when `includeExports` recorded them.
|
|
193
|
+
func reportDuplicateImports(ctx *Context, opts noDuplicateImportsOptions, previous []importExportEntry, entry importExportEntry, module string) {
|
|
194
|
+
if len(previous) == 0 {
|
|
195
|
+
return
|
|
196
|
+
}
|
|
197
|
+
if entry.isExport {
|
|
198
|
+
if duplicateImportsShouldReport(entry, previous, true, opts.AllowSeparateTypeImports) {
|
|
199
|
+
ctx.Report(entry.node, "`"+module+"` export is duplicated.")
|
|
200
|
+
}
|
|
201
|
+
if duplicateImportsShouldReport(entry, previous, false, opts.AllowSeparateTypeImports) {
|
|
202
|
+
ctx.Report(entry.node, "`"+module+"` export is duplicated as import.")
|
|
203
|
+
}
|
|
204
|
+
return
|
|
205
|
+
}
|
|
206
|
+
if duplicateImportsShouldReport(entry, previous, false, opts.AllowSeparateTypeImports) {
|
|
207
|
+
ctx.Report(entry.node, "`"+module+"` import is duplicated.")
|
|
208
|
+
}
|
|
209
|
+
if duplicateImportsShouldReport(entry, previous, true, opts.AllowSeparateTypeImports) {
|
|
210
|
+
ctx.Report(entry.node, "`"+module+"` import is duplicated as export.")
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// duplicateImportsShouldReport mirrors the official
|
|
215
|
+
// shouldReportImportExport: the new declaration is a duplicate when at
|
|
216
|
+
// least one earlier declaration of the requested kind could legally
|
|
217
|
+
// absorb it. With `allowSeparateTypeImports`, earlier declarations
|
|
218
|
+
// whose clause-level type-ness differs from the new declaration's are
|
|
219
|
+
// exempt from the comparison.
|
|
220
|
+
func duplicateImportsShouldReport(entry importExportEntry, previous []importExportEntry, wantExport bool, allowSeparateTypeImports bool) bool {
|
|
221
|
+
for _, prev := range previous {
|
|
222
|
+
if prev.isExport != wantExport {
|
|
223
|
+
continue
|
|
224
|
+
}
|
|
225
|
+
if allowSeparateTypeImports && entry.typeOnly != prev.typeOnly {
|
|
226
|
+
continue
|
|
227
|
+
}
|
|
228
|
+
if duplicateImportsCanMerge(entry, prev) {
|
|
229
|
+
return true
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return false
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// duplicateImportsCanMerge mirrors the official
|
|
236
|
+
// isImportExportCanBeMerged: two same-module declarations are duplicates
|
|
237
|
+
// only when one legal declaration could carry both clauses.
|
|
238
|
+
func duplicateImportsCanMerge(a, b importExportEntry) bool {
|
|
239
|
+
// ESLint 9.30.1 correction: a type-only default and type-only named
|
|
240
|
+
// bindings cannot be merged, because `import type Def, { Named }` is
|
|
241
|
+
// not legal TypeScript.
|
|
242
|
+
if a.typeOnly && b.typeOnly {
|
|
243
|
+
if (a.category == importExportDefault && b.category == importExportNamed) ||
|
|
244
|
+
(b.category == importExportDefault && a.category == importExportNamed) {
|
|
245
|
+
return false
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
// `export * from` merges only with another export-all or with a bare
|
|
249
|
+
// side-effect import; default, named, and namespace forms all need a
|
|
250
|
+
// different declaration shape.
|
|
251
|
+
if (a.category == importExportAll && b.category != importExportAll && b.category != importExportSideEffect) ||
|
|
252
|
+
(b.category == importExportAll && a.category != importExportAll && a.category != importExportSideEffect) {
|
|
253
|
+
return false
|
|
254
|
+
}
|
|
255
|
+
// A namespace binding and named bindings cannot share one
|
|
256
|
+
// declaration.
|
|
257
|
+
if (a.category == importExportNamespace && b.category == importExportNamed) ||
|
|
258
|
+
(b.category == importExportNamespace && a.category == importExportNamed) {
|
|
259
|
+
return false
|
|
260
|
+
}
|
|
261
|
+
return true
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
func init() {
|
|
265
|
+
Register(noDuplicateImports{})
|
|
266
|
+
}
|
|
@@ -1,71 +1,189 @@
|
|
|
1
|
-
// noElseReturn:
|
|
2
|
-
// `
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
// nesting and one less branch to read.
|
|
1
|
+
// noElseReturn: an `else` block whose preceding `if` branch ends in a
|
|
2
|
+
// `return` is redundant — control already left the function on that
|
|
3
|
+
// branch, so the `else` body can be flattened into the surrounding
|
|
4
|
+
// scope for one less level of nesting and one less branch to read.
|
|
6
5
|
// https://eslint.org/docs/latest/rules/no-else-return
|
|
7
6
|
//
|
|
8
|
-
// AST-only
|
|
9
|
-
//
|
|
10
|
-
// keyword. A `then` branch terminates when:
|
|
7
|
+
// This is an AST-only port of the ESLint rule, faithful to its
|
|
8
|
+
// analysis:
|
|
11
9
|
//
|
|
12
|
-
// -
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
// -
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
//
|
|
10
|
+
// - Only `return` terminates a branch (`checkForReturn`). `throw`,
|
|
11
|
+
// `break`, and `continue` do NOT count — matching upstream, which
|
|
12
|
+
// leaves those shapes alone.
|
|
13
|
+
// - `allowElseIf` (default `true`) walks the `if / else if` chain and
|
|
14
|
+
// bails when it ends without a plain `else`, so the very common
|
|
15
|
+
// `return` + `else if` early-return chain stays valid. Set it to
|
|
16
|
+
// `false` to also flag an `else if`.
|
|
17
|
+
// - Analysis starts only at an `if` that sits directly in a statement
|
|
18
|
+
// list (source file, block, class static block, or switch clause),
|
|
19
|
+
// mirroring upstream's `STATEMENT_LIST_PARENTS` gate. A nested
|
|
20
|
+
// `else if` is reached through its chain head's walk, so the rule
|
|
21
|
+
// reports exactly ONCE — on the chain's terminal `else`.
|
|
20
22
|
package linthost
|
|
21
23
|
|
|
22
24
|
import (
|
|
23
25
|
shimast "github.com/microsoft/typescript-go/shim/ast"
|
|
24
26
|
)
|
|
25
27
|
|
|
26
|
-
|
|
28
|
+
const noElseReturnMessage = "Remove the `else` — the preceding `if` branch already returns."
|
|
29
|
+
|
|
30
|
+
type noElseReturn struct{ optionsRule }
|
|
31
|
+
|
|
32
|
+
// noElseReturnOptions mirrors ESLint's single object option. `AllowElseIf`
|
|
33
|
+
// is a pointer so a missing option decodes to the upstream default (`true`)
|
|
34
|
+
// rather than Go's zero value (`false`).
|
|
35
|
+
type noElseReturnOptions struct {
|
|
36
|
+
AllowElseIf *bool `json:"allowElseIf"`
|
|
37
|
+
}
|
|
27
38
|
|
|
28
39
|
func (noElseReturn) Name() string { return "no-else-return" }
|
|
29
40
|
func (noElseReturn) Visits() []shimast.Kind {
|
|
30
41
|
return []shimast.Kind{shimast.KindIfStatement}
|
|
31
42
|
}
|
|
43
|
+
|
|
32
44
|
func (noElseReturn) Check(ctx *Context, node *shimast.Node) {
|
|
45
|
+
if node == nil || node.AsIfStatement() == nil {
|
|
46
|
+
return
|
|
47
|
+
}
|
|
48
|
+
// Only analyze a chain head: an `if` whose parent is a statement list.
|
|
49
|
+
// A nested `else if` (parent is the outer `if`) is folded into its head's
|
|
50
|
+
// walk, and a bare loop/label body `if` is left alone — both match
|
|
51
|
+
// upstream's STATEMENT_LIST_PARENTS gate.
|
|
52
|
+
if !noElseReturnStatementListParent(node.Parent) {
|
|
53
|
+
return
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
allowElseIf := true
|
|
57
|
+
var opts noElseReturnOptions
|
|
58
|
+
_ = ctx.DecodeOptions(&opts)
|
|
59
|
+
if opts.AllowElseIf != nil {
|
|
60
|
+
allowElseIf = *opts.AllowElseIf
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if allowElseIf {
|
|
64
|
+
noElseReturnCheckWithoutElse(ctx, node)
|
|
65
|
+
} else {
|
|
66
|
+
noElseReturnCheckWithElse(ctx, node)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// noElseReturnCheckWithoutElse ports upstream `checkIfWithoutElse`
|
|
71
|
+
// (`allowElseIf: true`). Starting at the chain head `node`, it walks the
|
|
72
|
+
// `if / else if` chain; if the chain ends without a plain `else` it bails,
|
|
73
|
+
// otherwise it reports once on the terminal `else` when every consequent
|
|
74
|
+
// always returns.
|
|
75
|
+
func noElseReturnCheckWithoutElse(ctx *Context, node *shimast.Node) {
|
|
76
|
+
consequents := make([]*shimast.Node, 0)
|
|
77
|
+
var alternate *shimast.Node
|
|
78
|
+
for current := node; current != nil && current.Kind == shimast.KindIfStatement; {
|
|
79
|
+
ifStmt := current.AsIfStatement()
|
|
80
|
+
if ifStmt == nil || ifStmt.ElseStatement == nil {
|
|
81
|
+
return
|
|
82
|
+
}
|
|
83
|
+
consequents = append(consequents, ifStmt.ThenStatement)
|
|
84
|
+
alternate = ifStmt.ElseStatement
|
|
85
|
+
current = ifStmt.ElseStatement
|
|
86
|
+
}
|
|
87
|
+
for _, consequent := range consequents {
|
|
88
|
+
if !noElseReturnAlwaysReturns(consequent) {
|
|
89
|
+
return
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
ctx.Report(alternate, noElseReturnMessage)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// noElseReturnCheckWithElse ports upstream `checkIfWithElse`
|
|
96
|
+
// (`allowElseIf: false`): a single `if` with an `else` (which may itself be
|
|
97
|
+
// an `else if`) reports when its consequent always returns.
|
|
98
|
+
func noElseReturnCheckWithElse(ctx *Context, node *shimast.Node) {
|
|
33
99
|
ifStmt := node.AsIfStatement()
|
|
34
100
|
if ifStmt == nil || ifStmt.ElseStatement == nil {
|
|
35
101
|
return
|
|
36
102
|
}
|
|
37
|
-
if
|
|
38
|
-
|
|
103
|
+
if noElseReturnAlwaysReturns(ifStmt.ThenStatement) {
|
|
104
|
+
ctx.Report(ifStmt.ElseStatement, noElseReturnMessage)
|
|
39
105
|
}
|
|
40
|
-
ctx.Report(ifStmt.ElseStatement, "Remove the `else` — the preceding branch already terminates.")
|
|
41
106
|
}
|
|
42
107
|
|
|
43
|
-
|
|
44
|
-
|
|
108
|
+
// noElseReturnAlwaysReturns ports upstream `alwaysReturns`: a block returns
|
|
109
|
+
// when ANY of its statements returns (or is an `if/else` that returns on both
|
|
110
|
+
// paths); a non-block returns when it is itself such a statement.
|
|
111
|
+
func noElseReturnAlwaysReturns(node *shimast.Node) bool {
|
|
112
|
+
if node == nil {
|
|
45
113
|
return false
|
|
46
114
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
shimast.KindThrowStatement,
|
|
50
|
-
shimast.KindBreakStatement,
|
|
51
|
-
shimast.KindContinueStatement:
|
|
52
|
-
return true
|
|
53
|
-
case shimast.KindBlock:
|
|
54
|
-
block := stmt.AsBlock()
|
|
115
|
+
if node.Kind == shimast.KindBlock {
|
|
116
|
+
block := node.AsBlock()
|
|
55
117
|
if block == nil || block.Statements == nil {
|
|
56
118
|
return false
|
|
57
119
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
120
|
+
for _, stmt := range block.Statements.Nodes {
|
|
121
|
+
if noElseReturnCheckForReturnOrIf(stmt) {
|
|
122
|
+
return true
|
|
123
|
+
}
|
|
61
124
|
}
|
|
62
|
-
return
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
125
|
+
return false
|
|
126
|
+
}
|
|
127
|
+
return noElseReturnCheckForReturnOrIf(node)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// noElseReturnCheckForReturnOrIf ports upstream `checkForReturnOrIf`.
|
|
131
|
+
func noElseReturnCheckForReturnOrIf(node *shimast.Node) bool {
|
|
132
|
+
return noElseReturnCheckForReturn(node) || noElseReturnCheckForIf(node)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// noElseReturnCheckForReturn ports upstream `checkForReturn`: the terminator
|
|
136
|
+
// is a `return`, and only a `return`.
|
|
137
|
+
func noElseReturnCheckForReturn(node *shimast.Node) bool {
|
|
138
|
+
return node != nil && node.Kind == shimast.KindReturnStatement
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// noElseReturnCheckForIf ports upstream `checkForIf`: an `if` with an `else`
|
|
142
|
+
// whose then AND else branches each naively return (chained early returns).
|
|
143
|
+
func noElseReturnCheckForIf(node *shimast.Node) bool {
|
|
144
|
+
if node == nil || node.Kind != shimast.KindIfStatement {
|
|
145
|
+
return false
|
|
146
|
+
}
|
|
147
|
+
ifStmt := node.AsIfStatement()
|
|
148
|
+
if ifStmt == nil || ifStmt.ThenStatement == nil || ifStmt.ElseStatement == nil {
|
|
149
|
+
return false
|
|
150
|
+
}
|
|
151
|
+
return noElseReturnNaiveHasReturn(ifStmt.ElseStatement) &&
|
|
152
|
+
noElseReturnNaiveHasReturn(ifStmt.ThenStatement)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// noElseReturnNaiveHasReturn ports upstream `naiveHasReturn`: a block returns
|
|
156
|
+
// when its LAST statement is a `return`; a non-block returns when it is itself
|
|
157
|
+
// a `return`.
|
|
158
|
+
func noElseReturnNaiveHasReturn(node *shimast.Node) bool {
|
|
159
|
+
if node == nil {
|
|
160
|
+
return false
|
|
161
|
+
}
|
|
162
|
+
if node.Kind == shimast.KindBlock {
|
|
163
|
+
block := node.AsBlock()
|
|
164
|
+
if block == nil || block.Statements == nil || len(block.Statements.Nodes) == 0 {
|
|
66
165
|
return false
|
|
67
166
|
}
|
|
68
|
-
|
|
167
|
+
stmts := block.Statements.Nodes
|
|
168
|
+
return noElseReturnCheckForReturn(stmts[len(stmts)-1])
|
|
169
|
+
}
|
|
170
|
+
return noElseReturnCheckForReturn(node)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// noElseReturnStatementListParent reports whether `parent` is a node whose
|
|
174
|
+
// body is a statement list, the TypeScript-AST equivalents of upstream's
|
|
175
|
+
// STATEMENT_LIST_PARENTS (Program, BlockStatement, StaticBlock, SwitchCase).
|
|
176
|
+
func noElseReturnStatementListParent(parent *shimast.Node) bool {
|
|
177
|
+
if parent == nil {
|
|
178
|
+
return false
|
|
179
|
+
}
|
|
180
|
+
switch parent.Kind {
|
|
181
|
+
case shimast.KindSourceFile,
|
|
182
|
+
shimast.KindBlock,
|
|
183
|
+
shimast.KindClassStaticBlockDeclaration,
|
|
184
|
+
shimast.KindCaseClause,
|
|
185
|
+
shimast.KindDefaultClause:
|
|
186
|
+
return true
|
|
69
187
|
}
|
|
70
188
|
return false
|
|
71
189
|
}
|
|
@@ -2,38 +2,134 @@ package linthost
|
|
|
2
2
|
|
|
3
3
|
import shimast "github.com/microsoft/typescript-go/shim/ast"
|
|
4
4
|
|
|
5
|
-
// noExtendNative
|
|
6
|
-
//
|
|
5
|
+
// noExtendNative forbids extending a native builtin's prototype, which mutates a
|
|
6
|
+
// shared global and leaks across the entire realm. Mirroring ESLint's
|
|
7
|
+
// no-extend-native, it flags every shape that adds to `<Builtin>.prototype`:
|
|
8
|
+
// direct member assignment (`X.prototype.y = …` and `X.prototype["y"] = …`) and
|
|
9
|
+
// `Object.defineProperty` / `Object.defineProperties` calls whose target is a
|
|
10
|
+
// native prototype. The `exceptions` option removes builtins from the set.
|
|
7
11
|
// https://eslint.org/docs/latest/rules/no-extend-native
|
|
8
|
-
type noExtendNative struct{}
|
|
12
|
+
type noExtendNative struct{ optionsRule }
|
|
9
13
|
|
|
10
|
-
|
|
11
|
-
|
|
14
|
+
type noExtendNativeOptions struct {
|
|
15
|
+
Exceptions []string `json:"exceptions"`
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
func (noExtendNative) Name() string { return "no-extend-native" }
|
|
19
|
+
func (noExtendNative) Visits() []shimast.Kind {
|
|
20
|
+
return []shimast.Kind{shimast.KindBinaryExpression, shimast.KindCallExpression}
|
|
21
|
+
}
|
|
12
22
|
func (noExtendNative) Check(ctx *Context, node *shimast.Node) {
|
|
23
|
+
switch node.Kind {
|
|
24
|
+
case shimast.KindBinaryExpression:
|
|
25
|
+
checkExtendNativeAssignment(ctx, node)
|
|
26
|
+
case shimast.KindCallExpression:
|
|
27
|
+
checkExtendNativeDefineProperty(ctx, node)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// checkExtendNativeAssignment reports `<Builtin>.prototype.<key> = …` and
|
|
32
|
+
// `<Builtin>.prototype["<key>"] = …`. Mirroring upstream's isAssigningToPropertyOf,
|
|
33
|
+
// the assigned member key itself is not inspected: any write to a member of the
|
|
34
|
+
// prototype extends it.
|
|
35
|
+
func checkExtendNativeAssignment(ctx *Context, node *shimast.Node) {
|
|
13
36
|
expr := node.AsBinaryExpression()
|
|
14
|
-
if expr == nil || expr.OperatorToken == nil {
|
|
37
|
+
if expr == nil || expr.OperatorToken == nil || expr.OperatorToken.Kind != shimast.KindEqualsToken {
|
|
15
38
|
return
|
|
16
39
|
}
|
|
17
|
-
|
|
40
|
+
target := stripParens(expr.Left)
|
|
41
|
+
if target == nil {
|
|
18
42
|
return
|
|
19
43
|
}
|
|
20
|
-
|
|
21
|
-
|
|
44
|
+
var object *shimast.Node
|
|
45
|
+
switch target.Kind {
|
|
46
|
+
case shimast.KindPropertyAccessExpression:
|
|
47
|
+
if access := target.AsPropertyAccessExpression(); access != nil {
|
|
48
|
+
object = access.Expression
|
|
49
|
+
}
|
|
50
|
+
case shimast.KindElementAccessExpression:
|
|
51
|
+
if access := target.AsElementAccessExpression(); access != nil {
|
|
52
|
+
object = access.Expression
|
|
53
|
+
}
|
|
54
|
+
default:
|
|
22
55
|
return
|
|
23
56
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
return
|
|
57
|
+
if builtin := extendNativePrototypeBuiltin(ctx, object); builtin != "" {
|
|
58
|
+
ctx.Report(node, builtin+" prototype is read only, properties should not be added.")
|
|
27
59
|
}
|
|
28
|
-
|
|
29
|
-
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// checkExtendNativeDefineProperty reports
|
|
63
|
+
// `Object.defineProperty(<Builtin>.prototype, …)` and the `defineProperties`
|
|
64
|
+
// variant, mirroring upstream's isInDefinePropertyCall: the extended prototype
|
|
65
|
+
// is the call's first argument.
|
|
66
|
+
func checkExtendNativeDefineProperty(ctx *Context, node *shimast.Node) {
|
|
67
|
+
call := node.AsCallExpression()
|
|
68
|
+
if call == nil || call.Arguments == nil || len(call.Arguments.Nodes) == 0 {
|
|
30
69
|
return
|
|
31
70
|
}
|
|
32
|
-
|
|
33
|
-
if !
|
|
71
|
+
callee := stripParens(call.Expression)
|
|
72
|
+
if !isMatchingPropertyAccess(callee, "Object", "defineProperty") &&
|
|
73
|
+
!isMatchingPropertyAccess(callee, "Object", "defineProperties") {
|
|
34
74
|
return
|
|
35
75
|
}
|
|
36
|
-
|
|
76
|
+
if builtin := extendNativePrototypeBuiltin(ctx, call.Arguments.Nodes[0]); builtin != "" {
|
|
77
|
+
ctx.Report(node, builtin+" prototype is read only, properties should not be added.")
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// extendNativePrototypeBuiltin returns the native builtin name when `node` reads
|
|
82
|
+
// `<Builtin>.prototype` — through `.prototype` property access or a static
|
|
83
|
+
// `["prototype"]` element access — and the builtin is protected (a known native
|
|
84
|
+
// not listed in the rule's `exceptions` option). Otherwise it returns "".
|
|
85
|
+
// Mirrors upstream's isPrototypePropertyAccessed, which resolves the accessed
|
|
86
|
+
// key with getStaticPropertyName, combined with the exceptions filter.
|
|
87
|
+
func extendNativePrototypeBuiltin(ctx *Context, node *shimast.Node) string {
|
|
88
|
+
node = stripParens(node)
|
|
89
|
+
if node == nil {
|
|
90
|
+
return ""
|
|
91
|
+
}
|
|
92
|
+
var object *shimast.Node
|
|
93
|
+
var key string
|
|
94
|
+
switch node.Kind {
|
|
95
|
+
case shimast.KindPropertyAccessExpression:
|
|
96
|
+
access := node.AsPropertyAccessExpression()
|
|
97
|
+
if access == nil {
|
|
98
|
+
return ""
|
|
99
|
+
}
|
|
100
|
+
object = access.Expression
|
|
101
|
+
key = identifierText(access.Name())
|
|
102
|
+
case shimast.KindElementAccessExpression:
|
|
103
|
+
access := node.AsElementAccessExpression()
|
|
104
|
+
if access == nil {
|
|
105
|
+
return ""
|
|
106
|
+
}
|
|
107
|
+
object = access.Expression
|
|
108
|
+
key = stringLiteralText(access.ArgumentExpression)
|
|
109
|
+
default:
|
|
110
|
+
return ""
|
|
111
|
+
}
|
|
112
|
+
if key != "prototype" {
|
|
113
|
+
return ""
|
|
114
|
+
}
|
|
115
|
+
builtin := identifierText(stripParens(object))
|
|
116
|
+
if !isExtendNativeBuiltin(builtin) || extendNativeExcepted(ctx, builtin) {
|
|
117
|
+
return ""
|
|
118
|
+
}
|
|
119
|
+
return builtin
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// extendNativeExcepted reports whether the rule's `exceptions` option opts the
|
|
123
|
+
// given builtin out of the protected set.
|
|
124
|
+
func extendNativeExcepted(ctx *Context, builtin string) bool {
|
|
125
|
+
var options noExtendNativeOptions
|
|
126
|
+
_ = ctx.DecodeOptions(&options)
|
|
127
|
+
for _, name := range options.Exceptions {
|
|
128
|
+
if name == builtin {
|
|
129
|
+
return true
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return false
|
|
37
133
|
}
|
|
38
134
|
|
|
39
135
|
func isExtendNativeBuiltin(name string) bool {
|