@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
|
@@ -1,26 +1,792 @@
|
|
|
1
|
-
// unicorn/import-style:
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
1
|
+
// unicorn/import-style: enforce specific import styles per module. A
|
|
2
|
+
// module's allowed styles (`unassigned`, `default`, `namespace`,
|
|
3
|
+
// `named`) come from the merged default table (`chalk`/`path` are
|
|
4
|
+
// default-only, `util` is named-only) and the user's `styles` option;
|
|
5
|
+
// static imports, dynamic imports, `export … from`, and `require`
|
|
6
|
+
// calls are classified into actual styles and reported when any of
|
|
7
|
+
// them falls outside the module's allowed set. `node:`-prefixed
|
|
8
|
+
// specifiers inherit the bare module's configuration while diagnostics
|
|
9
|
+
// keep the spelling the source used.
|
|
10
10
|
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
11
|
+
// Mirrors upstream's ESTree listeners: `import` declarations and
|
|
12
|
+
// unassigned dynamic imports / `require` statements are checked
|
|
13
|
+
// directly, while `const … = await import(…)` and `const … =
|
|
14
|
+
// require(…)` classify the declarator's binding target. Module names
|
|
15
|
+
// resolve through a static-string evaluator covering literals,
|
|
16
|
+
// parenthesized expressions, `+` concatenation, and template literals
|
|
17
|
+
// whose substitutions are themselves static strings; upstream's
|
|
18
|
+
// scope-based constant folding is intentionally out of scope, matching
|
|
19
|
+
// the literal-only convention of the other import rules in this host.
|
|
14
20
|
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/import-style.md
|
|
15
21
|
package linthost
|
|
16
22
|
|
|
17
|
-
import
|
|
23
|
+
import (
|
|
24
|
+
"bytes"
|
|
25
|
+
"encoding/json"
|
|
26
|
+
"errors"
|
|
27
|
+
"fmt"
|
|
28
|
+
"strings"
|
|
18
29
|
|
|
19
|
-
|
|
30
|
+
shimast "github.com/microsoft/typescript-go/shim/ast"
|
|
31
|
+
)
|
|
20
32
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
33
|
+
type unicornImportStyle struct{ optionsRule }
|
|
34
|
+
|
|
35
|
+
const (
|
|
36
|
+
unicornImportStyleUnassigned = "unassigned"
|
|
37
|
+
unicornImportStyleDefault = "default"
|
|
38
|
+
unicornImportStyleNamespace = "namespace"
|
|
39
|
+
unicornImportStyleNamed = "named"
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
// unicornImportStyleCanonicalStyles lists the four style names a module
|
|
43
|
+
// configuration must set to `false`, one and all, to count as banned.
|
|
44
|
+
var unicornImportStyleCanonicalStyles = []string{
|
|
45
|
+
unicornImportStyleUnassigned,
|
|
46
|
+
unicornImportStyleDefault,
|
|
47
|
+
unicornImportStyleNamespace,
|
|
48
|
+
unicornImportStyleNamed,
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// unicornImportStyleStylePair is one `style: allowed` entry of a module
|
|
52
|
+
// configuration. Order is preserved because the diagnostic lists the
|
|
53
|
+
// allowed styles in configuration order, exactly like upstream's
|
|
54
|
+
// JavaScript object spread.
|
|
55
|
+
type unicornImportStyleStylePair struct {
|
|
56
|
+
style string
|
|
57
|
+
allowed bool
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// unicornImportStyleDefaultStyles mirrors upstream's built-in table.
|
|
61
|
+
// Keep this alphabetically sorted for easier maintenance.
|
|
62
|
+
var unicornImportStyleDefaultStyles = map[string][]unicornImportStyleStylePair{
|
|
63
|
+
"chalk": {{style: unicornImportStyleDefault, allowed: true}},
|
|
64
|
+
"path": {{style: unicornImportStyleDefault, allowed: true}},
|
|
65
|
+
"util": {{style: unicornImportStyleNamed, allowed: true}},
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
var unicornImportStyleDefaultStyleNames = []string{"chalk", "path", "util"}
|
|
69
|
+
|
|
70
|
+
// unicornImportStyleModule is one module's resolved policy: the ordered
|
|
71
|
+
// allowed styles and whether every canonical style was explicitly
|
|
72
|
+
// disabled (the misuse upstream reports with a dedicated message).
|
|
73
|
+
type unicornImportStyleModule struct {
|
|
74
|
+
allowed []string
|
|
75
|
+
banned bool
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
type unicornImportStyleOptions struct {
|
|
79
|
+
checkImport bool
|
|
80
|
+
checkDynamicImport bool
|
|
81
|
+
checkExportFrom bool
|
|
82
|
+
checkRequire bool
|
|
83
|
+
styles map[string]unicornImportStyleModule
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// unicornImportStyleUserModule captures one raw `styles` entry before
|
|
87
|
+
// merging: `false` disables the module's restrictions, an object lists
|
|
88
|
+
// per-style booleans in declaration order.
|
|
89
|
+
type unicornImportStyleUserModule struct {
|
|
90
|
+
disabled bool
|
|
91
|
+
pairs []unicornImportStyleStylePair
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
func (unicornImportStyle) Name() string { return "unicorn/import-style" }
|
|
95
|
+
func (unicornImportStyle) Visits() []shimast.Kind {
|
|
96
|
+
return []shimast.Kind{
|
|
97
|
+
shimast.KindImportDeclaration,
|
|
98
|
+
shimast.KindExportDeclaration,
|
|
99
|
+
shimast.KindCallExpression,
|
|
100
|
+
shimast.KindVariableDeclaration,
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
func (unicornImportStyle) ValidateOptions(raw json.RawMessage) error {
|
|
105
|
+
_, err := parseUnicornImportStyleOptions(raw)
|
|
106
|
+
return err
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
func (unicornImportStyle) Check(ctx *Context, node *shimast.Node) {
|
|
110
|
+
if ctx == nil || node == nil {
|
|
111
|
+
return
|
|
112
|
+
}
|
|
113
|
+
options, err := parseUnicornImportStyleOptions(ctx.Options)
|
|
114
|
+
if err != nil || len(options.styles) == 0 {
|
|
115
|
+
return
|
|
116
|
+
}
|
|
117
|
+
switch node.Kind {
|
|
118
|
+
case shimast.KindImportDeclaration:
|
|
119
|
+
if options.checkImport {
|
|
120
|
+
checkUnicornImportStyleImportDeclaration(ctx, node, options)
|
|
121
|
+
}
|
|
122
|
+
case shimast.KindExportDeclaration:
|
|
123
|
+
if options.checkExportFrom {
|
|
124
|
+
checkUnicornImportStyleExportDeclaration(ctx, node, options)
|
|
125
|
+
}
|
|
126
|
+
case shimast.KindCallExpression:
|
|
127
|
+
checkUnicornImportStyleCallExpression(ctx, node, options)
|
|
128
|
+
case shimast.KindVariableDeclaration:
|
|
129
|
+
checkUnicornImportStyleVariableDeclaration(ctx, node, options)
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
func parseUnicornImportStyleOptions(raw json.RawMessage) (unicornImportStyleOptions, error) {
|
|
134
|
+
options := unicornImportStyleOptions{
|
|
135
|
+
checkImport: true,
|
|
136
|
+
checkDynamicImport: true,
|
|
137
|
+
checkRequire: true,
|
|
138
|
+
}
|
|
139
|
+
extendDefaultStyles := true
|
|
140
|
+
var userStyles map[string]unicornImportStyleUserModule
|
|
141
|
+
trimmed := bytes.TrimSpace(raw)
|
|
142
|
+
if len(trimmed) != 0 {
|
|
143
|
+
if trimmed[0] != '{' {
|
|
144
|
+
return options, errors.New("options must be an object")
|
|
145
|
+
}
|
|
146
|
+
var fields map[string]json.RawMessage
|
|
147
|
+
if err := json.Unmarshal(trimmed, &fields); err != nil {
|
|
148
|
+
return options, fmt.Errorf("options must be an object: %w", err)
|
|
149
|
+
}
|
|
150
|
+
for name, value := range fields {
|
|
151
|
+
var err error
|
|
152
|
+
switch name {
|
|
153
|
+
case "checkImport":
|
|
154
|
+
options.checkImport, err = unicornImportStyleDecodeBool(value, name)
|
|
155
|
+
case "checkDynamicImport":
|
|
156
|
+
options.checkDynamicImport, err = unicornImportStyleDecodeBool(value, name)
|
|
157
|
+
case "checkExportFrom":
|
|
158
|
+
options.checkExportFrom, err = unicornImportStyleDecodeBool(value, name)
|
|
159
|
+
case "checkRequire":
|
|
160
|
+
options.checkRequire, err = unicornImportStyleDecodeBool(value, name)
|
|
161
|
+
case "extendDefaultStyles":
|
|
162
|
+
extendDefaultStyles, err = unicornImportStyleDecodeBool(value, name)
|
|
163
|
+
case "styles":
|
|
164
|
+
userStyles, err = parseUnicornImportStyleStyles(value)
|
|
165
|
+
default:
|
|
166
|
+
err = fmt.Errorf("unknown option %q", name)
|
|
167
|
+
}
|
|
168
|
+
if err != nil {
|
|
169
|
+
return options, err
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
options.styles = resolveUnicornImportStyleStyles(userStyles, extendDefaultStyles)
|
|
174
|
+
return options, nil
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
func unicornImportStyleDecodeBool(raw json.RawMessage, name string) (bool, error) {
|
|
178
|
+
var decoded any
|
|
179
|
+
if err := json.Unmarshal(raw, &decoded); err != nil {
|
|
180
|
+
return false, fmt.Errorf("option %q must be a boolean", name)
|
|
181
|
+
}
|
|
182
|
+
value, ok := decoded.(bool)
|
|
183
|
+
if !ok {
|
|
184
|
+
return false, fmt.Errorf("option %q must be a boolean", name)
|
|
185
|
+
}
|
|
186
|
+
return value, nil
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
func parseUnicornImportStyleStyles(raw json.RawMessage) (map[string]unicornImportStyleUserModule, error) {
|
|
190
|
+
trimmed := bytes.TrimSpace(raw)
|
|
191
|
+
if len(trimmed) == 0 || trimmed[0] != '{' {
|
|
192
|
+
return nil, fmt.Errorf("option %q must be an object", "styles")
|
|
193
|
+
}
|
|
194
|
+
var modules map[string]json.RawMessage
|
|
195
|
+
if err := json.Unmarshal(trimmed, &modules); err != nil {
|
|
196
|
+
return nil, fmt.Errorf("option %q must be an object", "styles")
|
|
197
|
+
}
|
|
198
|
+
parsed := make(map[string]unicornImportStyleUserModule, len(modules))
|
|
199
|
+
for name, rawModule := range modules {
|
|
200
|
+
value := bytes.TrimSpace(rawModule)
|
|
201
|
+
if bytes.Equal(value, []byte("false")) {
|
|
202
|
+
parsed[name] = unicornImportStyleUserModule{disabled: true}
|
|
203
|
+
continue
|
|
204
|
+
}
|
|
205
|
+
if len(value) == 0 || value[0] != '{' {
|
|
206
|
+
return nil, fmt.Errorf("styles entry %q must be false or an object of booleans", name)
|
|
207
|
+
}
|
|
208
|
+
pairs, err := unicornImportStyleDecodeOrderedBooleans(value, name)
|
|
209
|
+
if err != nil {
|
|
210
|
+
return nil, err
|
|
211
|
+
}
|
|
212
|
+
parsed[name] = unicornImportStyleUserModule{pairs: pairs}
|
|
213
|
+
}
|
|
214
|
+
return parsed, nil
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// unicornImportStyleDecodeOrderedBooleans reads one module's style
|
|
218
|
+
// object while preserving key order. encoding/json maps are unordered,
|
|
219
|
+
// but the diagnostic message lists allowed styles in declaration order,
|
|
220
|
+
// so the pairs are decoded token by token. A duplicated key keeps its
|
|
221
|
+
// first position and takes the last value, mirroring JSON.parse.
|
|
222
|
+
func unicornImportStyleDecodeOrderedBooleans(raw []byte, moduleName string) ([]unicornImportStyleStylePair, error) {
|
|
223
|
+
decoder := json.NewDecoder(bytes.NewReader(raw))
|
|
224
|
+
if token, err := decoder.Token(); err != nil || token != json.Delim('{') {
|
|
225
|
+
return nil, fmt.Errorf("styles entry %q must be false or an object of booleans", moduleName)
|
|
226
|
+
}
|
|
227
|
+
var pairs []unicornImportStyleStylePair
|
|
228
|
+
for decoder.More() {
|
|
229
|
+
keyToken, err := decoder.Token()
|
|
230
|
+
if err != nil {
|
|
231
|
+
return nil, fmt.Errorf("styles entry %q must be false or an object of booleans", moduleName)
|
|
232
|
+
}
|
|
233
|
+
key, ok := keyToken.(string)
|
|
234
|
+
if !ok {
|
|
235
|
+
return nil, fmt.Errorf("styles entry %q must be false or an object of booleans", moduleName)
|
|
236
|
+
}
|
|
237
|
+
var decoded any
|
|
238
|
+
if err := decoder.Decode(&decoded); err != nil {
|
|
239
|
+
return nil, fmt.Errorf("style %q of module %q must be a boolean", key, moduleName)
|
|
240
|
+
}
|
|
241
|
+
value, isBool := decoded.(bool)
|
|
242
|
+
if !isBool {
|
|
243
|
+
return nil, fmt.Errorf("style %q of module %q must be a boolean", key, moduleName)
|
|
244
|
+
}
|
|
245
|
+
pairs = unicornImportStyleUpsertPair(pairs, key, value)
|
|
246
|
+
}
|
|
247
|
+
if token, err := decoder.Token(); err != nil || token != json.Delim('}') {
|
|
248
|
+
return nil, fmt.Errorf("styles entry %q must be false or an object of booleans", moduleName)
|
|
249
|
+
}
|
|
250
|
+
return pairs, nil
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
func unicornImportStyleUpsertPair(
|
|
254
|
+
pairs []unicornImportStyleStylePair,
|
|
255
|
+
style string,
|
|
256
|
+
allowed bool,
|
|
257
|
+
) []unicornImportStyleStylePair {
|
|
258
|
+
for index := range pairs {
|
|
259
|
+
if pairs[index].style == style {
|
|
260
|
+
pairs[index].allowed = allowed
|
|
261
|
+
return pairs
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
return append(pairs, unicornImportStyleStylePair{style: style, allowed: allowed})
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// resolveUnicornImportStyleStyles folds the default table and the
|
|
268
|
+
// user's `styles` into the final per-module policy, mirroring
|
|
269
|
+
// upstream's object spread: with `extendDefaultStyles` the defaults
|
|
270
|
+
// seed each shared module and user entries override style by style,
|
|
271
|
+
// while `moduleName: false` clears the module's restrictions entirely.
|
|
272
|
+
func resolveUnicornImportStyleStyles(
|
|
273
|
+
userStyles map[string]unicornImportStyleUserModule,
|
|
274
|
+
extendDefaultStyles bool,
|
|
275
|
+
) map[string]unicornImportStyleModule {
|
|
276
|
+
names := make([]string, 0, len(unicornImportStyleDefaultStyleNames)+len(userStyles))
|
|
277
|
+
if extendDefaultStyles {
|
|
278
|
+
names = append(names, unicornImportStyleDefaultStyleNames...)
|
|
279
|
+
}
|
|
280
|
+
for name := range userStyles {
|
|
281
|
+
if !extendDefaultStyles || unicornImportStyleDefaultStyles[name] == nil {
|
|
282
|
+
names = append(names, name)
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
resolved := make(map[string]unicornImportStyleModule, len(names))
|
|
286
|
+
for _, name := range names {
|
|
287
|
+
user, hasUser := userStyles[name]
|
|
288
|
+
if hasUser && user.disabled {
|
|
289
|
+
resolved[name] = unicornImportStyleModule{}
|
|
290
|
+
continue
|
|
291
|
+
}
|
|
292
|
+
var pairs []unicornImportStyleStylePair
|
|
293
|
+
if extendDefaultStyles {
|
|
294
|
+
pairs = append(pairs, unicornImportStyleDefaultStyles[name]...)
|
|
295
|
+
}
|
|
296
|
+
for _, pair := range user.pairs {
|
|
297
|
+
pairs = unicornImportStyleUpsertPair(pairs, pair.style, pair.allowed)
|
|
298
|
+
}
|
|
299
|
+
resolved[name] = unicornImportStyleFinalizeModule(pairs)
|
|
300
|
+
}
|
|
301
|
+
return resolved
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
func unicornImportStyleFinalizeModule(pairs []unicornImportStyleStylePair) unicornImportStyleModule {
|
|
305
|
+
module := unicornImportStyleModule{}
|
|
306
|
+
for _, pair := range pairs {
|
|
307
|
+
if pair.allowed {
|
|
308
|
+
module.allowed = append(module.allowed, pair.style)
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
banned := true
|
|
312
|
+
for _, style := range unicornImportStyleCanonicalStyles {
|
|
313
|
+
explicitlyFalse := false
|
|
314
|
+
for _, pair := range pairs {
|
|
315
|
+
if pair.style == style {
|
|
316
|
+
explicitlyFalse = !pair.allowed
|
|
317
|
+
break
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
if !explicitlyFalse {
|
|
321
|
+
banned = false
|
|
322
|
+
break
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
module.banned = banned
|
|
326
|
+
return module
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
func checkUnicornImportStyleImportDeclaration(ctx *Context, node *shimast.Node, options unicornImportStyleOptions) {
|
|
330
|
+
declaration := node.AsImportDeclaration()
|
|
331
|
+
if declaration == nil {
|
|
332
|
+
return
|
|
333
|
+
}
|
|
334
|
+
moduleName, ok := unicornImportStyleStaticString(declaration.ModuleSpecifier)
|
|
335
|
+
if !ok {
|
|
336
|
+
return
|
|
337
|
+
}
|
|
338
|
+
styles := unicornImportStyleImportDeclarationStyles(declaration)
|
|
339
|
+
reportUnicornImportStyle(ctx, node, moduleName, styles, false, options)
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// unicornImportStyleImportDeclarationStyles classifies one static
|
|
343
|
+
// import the way upstream reads ESTree specifiers: no specifiers is
|
|
344
|
+
// the unassigned style, a default binding or a named `default` alias
|
|
345
|
+
// is the default style, `* as ns` is namespace, and every other named
|
|
346
|
+
// specifier is named.
|
|
347
|
+
func unicornImportStyleImportDeclarationStyles(declaration *shimast.ImportDeclaration) []string {
|
|
348
|
+
clauseNode := declaration.ImportClause
|
|
349
|
+
if clauseNode == nil {
|
|
350
|
+
return []string{unicornImportStyleUnassigned}
|
|
351
|
+
}
|
|
352
|
+
clause := clauseNode.AsImportClause()
|
|
353
|
+
if clause == nil {
|
|
354
|
+
return []string{unicornImportStyleUnassigned}
|
|
355
|
+
}
|
|
356
|
+
var styles []string
|
|
357
|
+
if clause.Name() != nil {
|
|
358
|
+
styles = unicornImportStyleAppend(styles, unicornImportStyleDefault)
|
|
359
|
+
}
|
|
360
|
+
if bindings := clause.NamedBindings; bindings != nil {
|
|
361
|
+
switch bindings.Kind {
|
|
362
|
+
case shimast.KindNamespaceImport:
|
|
363
|
+
styles = unicornImportStyleAppend(styles, unicornImportStyleNamespace)
|
|
364
|
+
case shimast.KindNamedImports:
|
|
365
|
+
if named := bindings.AsNamedImports(); named != nil && named.Elements != nil {
|
|
366
|
+
for _, element := range named.Elements.Nodes {
|
|
367
|
+
specifier := element.AsImportSpecifier()
|
|
368
|
+
if specifier == nil {
|
|
369
|
+
continue
|
|
370
|
+
}
|
|
371
|
+
imported := specifier.PropertyName
|
|
372
|
+
if imported == nil {
|
|
373
|
+
imported = specifier.Name()
|
|
374
|
+
}
|
|
375
|
+
if identifierText(imported) == "default" {
|
|
376
|
+
styles = unicornImportStyleAppend(styles, unicornImportStyleDefault)
|
|
377
|
+
} else {
|
|
378
|
+
styles = unicornImportStyleAppend(styles, unicornImportStyleNamed)
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
if len(styles) == 0 {
|
|
385
|
+
return []string{unicornImportStyleUnassigned}
|
|
386
|
+
}
|
|
387
|
+
return styles
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
func checkUnicornImportStyleExportDeclaration(ctx *Context, node *shimast.Node, options unicornImportStyleOptions) {
|
|
391
|
+
declaration := node.AsExportDeclaration()
|
|
392
|
+
if declaration == nil || declaration.ModuleSpecifier == nil {
|
|
393
|
+
return
|
|
394
|
+
}
|
|
395
|
+
moduleName, ok := unicornImportStyleStaticString(declaration.ModuleSpecifier)
|
|
396
|
+
if !ok {
|
|
397
|
+
return
|
|
398
|
+
}
|
|
399
|
+
var styles []string
|
|
400
|
+
clause := declaration.ExportClause
|
|
401
|
+
switch {
|
|
402
|
+
case clause == nil || clause.Kind == shimast.KindNamespaceExport:
|
|
403
|
+
// `export * from` and `export * as ns from` are upstream's
|
|
404
|
+
// ExportAllDeclaration: always the namespace style.
|
|
405
|
+
styles = []string{unicornImportStyleNamespace}
|
|
406
|
+
case clause.Kind == shimast.KindNamedExports:
|
|
407
|
+
named := clause.AsNamedExports()
|
|
408
|
+
if named == nil || named.Elements == nil || len(named.Elements.Nodes) == 0 {
|
|
409
|
+
styles = []string{unicornImportStyleUnassigned}
|
|
410
|
+
break
|
|
411
|
+
}
|
|
412
|
+
for _, element := range named.Elements.Nodes {
|
|
413
|
+
specifier := element.AsExportSpecifier()
|
|
414
|
+
if specifier == nil {
|
|
415
|
+
continue
|
|
416
|
+
}
|
|
417
|
+
// Upstream classifies by the exported (outer) name, so
|
|
418
|
+
// `export {x as default} from "m"` is the default style.
|
|
419
|
+
if identifierText(specifier.Name()) == "default" {
|
|
420
|
+
styles = unicornImportStyleAppend(styles, unicornImportStyleDefault)
|
|
421
|
+
} else {
|
|
422
|
+
styles = unicornImportStyleAppend(styles, unicornImportStyleNamed)
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
default:
|
|
426
|
+
return
|
|
427
|
+
}
|
|
428
|
+
reportUnicornImportStyle(ctx, node, moduleName, styles, false, options)
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
func checkUnicornImportStyleCallExpression(ctx *Context, node *shimast.Node, options unicornImportStyleOptions) {
|
|
432
|
+
call := node.AsCallExpression()
|
|
433
|
+
if call == nil || call.Expression == nil {
|
|
434
|
+
return
|
|
435
|
+
}
|
|
436
|
+
if call.Expression.Kind == shimast.KindImportKeyword {
|
|
437
|
+
if !options.checkDynamicImport || unicornImportStyleIsAssignedDynamicImport(node) {
|
|
438
|
+
return
|
|
439
|
+
}
|
|
440
|
+
moduleName, ok := unicornImportStyleStaticString(unicornImportStyleFirstArgument(call))
|
|
441
|
+
if !ok {
|
|
442
|
+
return
|
|
443
|
+
}
|
|
444
|
+
reportUnicornImportStyle(ctx, node, moduleName, []string{unicornImportStyleUnassigned}, false, options)
|
|
445
|
+
return
|
|
446
|
+
}
|
|
447
|
+
if !options.checkRequire {
|
|
448
|
+
return
|
|
449
|
+
}
|
|
450
|
+
// Upstream's unassigned-require listener matches the exact
|
|
451
|
+
// statement-level shape: a non-optional bare `require` call with one
|
|
452
|
+
// argument whose parent is the expression statement itself.
|
|
453
|
+
if call.QuestionDotToken != nil || identifierText(stripParens(call.Expression)) != "require" {
|
|
454
|
+
return
|
|
455
|
+
}
|
|
456
|
+
if call.Arguments == nil || len(call.Arguments.Nodes) != 1 {
|
|
457
|
+
return
|
|
458
|
+
}
|
|
459
|
+
parent, child := unicornImportStyleEffectiveParent(node)
|
|
460
|
+
if parent == nil || parent.Kind != shimast.KindExpressionStatement {
|
|
461
|
+
return
|
|
462
|
+
}
|
|
463
|
+
statement := parent.AsExpressionStatement()
|
|
464
|
+
if statement == nil || statement.Expression != child {
|
|
465
|
+
return
|
|
466
|
+
}
|
|
467
|
+
moduleName, ok := unicornImportStyleStaticString(call.Arguments.Nodes[0])
|
|
468
|
+
if !ok {
|
|
469
|
+
return
|
|
470
|
+
}
|
|
471
|
+
reportUnicornImportStyle(ctx, node, moduleName, []string{unicornImportStyleUnassigned}, true, options)
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
func checkUnicornImportStyleVariableDeclaration(ctx *Context, node *shimast.Node, options unicornImportStyleOptions) {
|
|
475
|
+
declaration := node.AsVariableDeclaration()
|
|
476
|
+
if declaration == nil || declaration.Initializer == nil {
|
|
477
|
+
return
|
|
478
|
+
}
|
|
479
|
+
initializer := stripParens(declaration.Initializer)
|
|
480
|
+
if initializer == nil {
|
|
481
|
+
return
|
|
482
|
+
}
|
|
483
|
+
if initializer.Kind == shimast.KindAwaitExpression {
|
|
484
|
+
if !options.checkDynamicImport {
|
|
485
|
+
return
|
|
486
|
+
}
|
|
487
|
+
await := initializer.AsAwaitExpression()
|
|
488
|
+
if await == nil {
|
|
489
|
+
return
|
|
490
|
+
}
|
|
491
|
+
call := unicornImportStyleDynamicImportCall(await.Expression)
|
|
492
|
+
if call == nil {
|
|
493
|
+
return
|
|
494
|
+
}
|
|
495
|
+
moduleName, ok := unicornImportStyleStaticString(unicornImportStyleFirstArgument(call))
|
|
496
|
+
if !ok || moduleName == "" {
|
|
497
|
+
return
|
|
498
|
+
}
|
|
499
|
+
styles := unicornImportStyleAssignmentTargetStyles(declaration.Name())
|
|
500
|
+
reportUnicornImportStyle(ctx, node, moduleName, styles, false, options)
|
|
501
|
+
return
|
|
502
|
+
}
|
|
503
|
+
if !options.checkRequire || initializer.Kind != shimast.KindCallExpression {
|
|
504
|
+
return
|
|
505
|
+
}
|
|
506
|
+
call := initializer.AsCallExpression()
|
|
507
|
+
if call == nil || identifierText(stripParens(call.Expression)) != "require" {
|
|
508
|
+
return
|
|
509
|
+
}
|
|
510
|
+
// ESTree wraps `require?.(…)` in a ChainExpression, so upstream's
|
|
511
|
+
// `init.type === 'CallExpression'` listener never classifies an
|
|
512
|
+
// optional require declarator. QuestionDotToken is the tsgo AST's
|
|
513
|
+
// equivalent of that wrapper.
|
|
514
|
+
if call.QuestionDotToken != nil {
|
|
515
|
+
return
|
|
516
|
+
}
|
|
517
|
+
moduleName, ok := unicornImportStyleStaticString(unicornImportStyleFirstArgument(call))
|
|
518
|
+
if !ok || moduleName == "" {
|
|
519
|
+
return
|
|
520
|
+
}
|
|
521
|
+
styles := unicornImportStyleAssignmentTargetStyles(declaration.Name())
|
|
522
|
+
reportUnicornImportStyle(ctx, node, moduleName, styles, true, options)
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// unicornImportStyleDynamicImportCall returns the `import(…)` call
|
|
526
|
+
// behind `expression`, or nil when the expression is anything else.
|
|
527
|
+
func unicornImportStyleDynamicImportCall(expression *shimast.Node) *shimast.CallExpression {
|
|
528
|
+
expression = stripParens(expression)
|
|
529
|
+
if expression == nil || expression.Kind != shimast.KindCallExpression {
|
|
530
|
+
return nil
|
|
531
|
+
}
|
|
532
|
+
call := expression.AsCallExpression()
|
|
533
|
+
if call == nil || call.Expression == nil || call.Expression.Kind != shimast.KindImportKeyword {
|
|
534
|
+
return nil
|
|
535
|
+
}
|
|
536
|
+
return call
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
func unicornImportStyleFirstArgument(call *shimast.CallExpression) *shimast.Node {
|
|
540
|
+
if call == nil || call.Arguments == nil || len(call.Arguments.Nodes) == 0 {
|
|
541
|
+
return nil
|
|
542
|
+
}
|
|
543
|
+
return call.Arguments.Nodes[0]
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// unicornImportStyleIsAssignedDynamicImport mirrors upstream's
|
|
547
|
+
// isAssignedDynamicImport: `const x = await import("m")` is handled by
|
|
548
|
+
// the declarator path, so the bare dynamic-import listener skips an
|
|
549
|
+
// import expression awaited directly inside a declarator initializer.
|
|
550
|
+
func unicornImportStyleIsAssignedDynamicImport(importCall *shimast.Node) bool {
|
|
551
|
+
parent, child := unicornImportStyleEffectiveParent(importCall)
|
|
552
|
+
if parent == nil || parent.Kind != shimast.KindAwaitExpression {
|
|
553
|
+
return false
|
|
554
|
+
}
|
|
555
|
+
await := parent.AsAwaitExpression()
|
|
556
|
+
if await == nil || await.Expression != child {
|
|
557
|
+
return false
|
|
558
|
+
}
|
|
559
|
+
grandparent, awaited := unicornImportStyleEffectiveParent(parent)
|
|
560
|
+
if grandparent == nil || grandparent.Kind != shimast.KindVariableDeclaration {
|
|
561
|
+
return false
|
|
562
|
+
}
|
|
563
|
+
declaration := grandparent.AsVariableDeclaration()
|
|
564
|
+
return declaration != nil && stripParens(declaration.Initializer) == stripParens(awaited)
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
// unicornImportStyleEffectiveParent walks upward through parenthesized
|
|
568
|
+
// expressions and returns the first structural ancestor plus the child
|
|
569
|
+
// slot it holds, because ESTree (upstream's AST) has no parenthesized
|
|
570
|
+
// nodes at all.
|
|
571
|
+
func unicornImportStyleEffectiveParent(node *shimast.Node) (*shimast.Node, *shimast.Node) {
|
|
572
|
+
current := node
|
|
573
|
+
parent := node.Parent
|
|
574
|
+
for parent != nil && parent.Kind == shimast.KindParenthesizedExpression {
|
|
575
|
+
wrapper := parent.AsParenthesizedExpression()
|
|
576
|
+
if wrapper == nil || wrapper.Expression != current {
|
|
577
|
+
break
|
|
578
|
+
}
|
|
579
|
+
current = parent
|
|
580
|
+
parent = parent.Parent
|
|
581
|
+
}
|
|
582
|
+
return parent, current
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
// unicornImportStyleAssignmentTargetStyles classifies the binding
|
|
586
|
+
// target of an awaited dynamic import or a `require` declarator:
|
|
587
|
+
// identifiers and array patterns take the whole namespace, an empty
|
|
588
|
+
// object pattern is unassigned, and object-pattern properties are the
|
|
589
|
+
// default style for `default` keys, named otherwise (rest elements
|
|
590
|
+
// included).
|
|
591
|
+
func unicornImportStyleAssignmentTargetStyles(target *shimast.Node) []string {
|
|
592
|
+
if target == nil {
|
|
593
|
+
return nil
|
|
594
|
+
}
|
|
595
|
+
switch target.Kind {
|
|
596
|
+
case shimast.KindIdentifier, shimast.KindArrayBindingPattern:
|
|
597
|
+
return []string{unicornImportStyleNamespace}
|
|
598
|
+
case shimast.KindObjectBindingPattern:
|
|
599
|
+
pattern := target.AsBindingPattern()
|
|
600
|
+
if pattern == nil || pattern.Elements == nil || len(pattern.Elements.Nodes) == 0 {
|
|
601
|
+
return []string{unicornImportStyleUnassigned}
|
|
602
|
+
}
|
|
603
|
+
var styles []string
|
|
604
|
+
for _, elementNode := range pattern.Elements.Nodes {
|
|
605
|
+
element := elementNode.AsBindingElement()
|
|
606
|
+
if element == nil {
|
|
607
|
+
continue
|
|
608
|
+
}
|
|
609
|
+
if element.DotDotDotToken != nil {
|
|
610
|
+
styles = unicornImportStyleAppend(styles, unicornImportStyleNamed)
|
|
611
|
+
continue
|
|
612
|
+
}
|
|
613
|
+
key := element.PropertyName
|
|
614
|
+
if key == nil {
|
|
615
|
+
key = element.Name()
|
|
616
|
+
}
|
|
617
|
+
if key != nil && key.Kind == shimast.KindComputedPropertyName {
|
|
618
|
+
if computed := key.AsComputedPropertyName(); computed != nil {
|
|
619
|
+
key = stripParens(computed.Expression)
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
if key == nil || key.Kind != shimast.KindIdentifier {
|
|
623
|
+
// Upstream only classifies identifier keys; literal keys such
|
|
624
|
+
// as `{"x": y}` contribute no style at all.
|
|
625
|
+
continue
|
|
626
|
+
}
|
|
627
|
+
if identifierText(key) == "default" {
|
|
628
|
+
styles = unicornImportStyleAppend(styles, unicornImportStyleDefault)
|
|
629
|
+
} else {
|
|
630
|
+
styles = unicornImportStyleAppend(styles, unicornImportStyleNamed)
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
return styles
|
|
634
|
+
}
|
|
635
|
+
return nil
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
func unicornImportStyleAppend(styles []string, style string) []string {
|
|
639
|
+
for _, existing := range styles {
|
|
640
|
+
if existing == style {
|
|
641
|
+
return styles
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
return append(styles, style)
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
// unicornImportStyleStaticString evaluates a compile-time-constant
|
|
648
|
+
// string expression: string literals, no-substitution templates,
|
|
649
|
+
// parenthesized expressions, `+` concatenations of static strings, and
|
|
650
|
+
// template literals whose substitutions are static strings.
|
|
651
|
+
func unicornImportStyleStaticString(node *shimast.Node) (string, bool) {
|
|
652
|
+
node = stripParens(node)
|
|
653
|
+
if node == nil {
|
|
654
|
+
return "", false
|
|
655
|
+
}
|
|
656
|
+
switch node.Kind {
|
|
657
|
+
case shimast.KindStringLiteral:
|
|
658
|
+
if literal := node.AsStringLiteral(); literal != nil {
|
|
659
|
+
return literal.Text, true
|
|
660
|
+
}
|
|
661
|
+
case shimast.KindNoSubstitutionTemplateLiteral:
|
|
662
|
+
if literal := node.AsNoSubstitutionTemplateLiteral(); literal != nil {
|
|
663
|
+
return literal.Text, true
|
|
664
|
+
}
|
|
665
|
+
case shimast.KindTemplateExpression:
|
|
666
|
+
template := node.AsTemplateExpression()
|
|
667
|
+
if template == nil || template.Head == nil || template.TemplateSpans == nil {
|
|
668
|
+
return "", false
|
|
669
|
+
}
|
|
670
|
+
head := template.Head.AsTemplateHead()
|
|
671
|
+
if head == nil {
|
|
672
|
+
return "", false
|
|
673
|
+
}
|
|
674
|
+
var builder strings.Builder
|
|
675
|
+
builder.WriteString(head.Text)
|
|
676
|
+
for _, spanNode := range template.TemplateSpans.Nodes {
|
|
677
|
+
span := spanNode.AsTemplateSpan()
|
|
678
|
+
if span == nil {
|
|
679
|
+
return "", false
|
|
680
|
+
}
|
|
681
|
+
value, ok := unicornImportStyleStaticString(span.Expression)
|
|
682
|
+
if !ok || span.Literal == nil {
|
|
683
|
+
return "", false
|
|
684
|
+
}
|
|
685
|
+
builder.WriteString(value)
|
|
686
|
+
switch span.Literal.Kind {
|
|
687
|
+
case shimast.KindTemplateMiddle:
|
|
688
|
+
middle := span.Literal.AsTemplateMiddle()
|
|
689
|
+
if middle == nil {
|
|
690
|
+
return "", false
|
|
691
|
+
}
|
|
692
|
+
builder.WriteString(middle.Text)
|
|
693
|
+
case shimast.KindTemplateTail:
|
|
694
|
+
tail := span.Literal.AsTemplateTail()
|
|
695
|
+
if tail == nil {
|
|
696
|
+
return "", false
|
|
697
|
+
}
|
|
698
|
+
builder.WriteString(tail.Text)
|
|
699
|
+
default:
|
|
700
|
+
return "", false
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
return builder.String(), true
|
|
704
|
+
case shimast.KindBinaryExpression:
|
|
705
|
+
binary := node.AsBinaryExpression()
|
|
706
|
+
if binary == nil || binary.OperatorToken == nil ||
|
|
707
|
+
binary.OperatorToken.Kind != shimast.KindPlusToken {
|
|
708
|
+
return "", false
|
|
709
|
+
}
|
|
710
|
+
left, ok := unicornImportStyleStaticString(binary.Left)
|
|
711
|
+
if !ok {
|
|
712
|
+
return "", false
|
|
713
|
+
}
|
|
714
|
+
right, ok := unicornImportStyleStaticString(binary.Right)
|
|
715
|
+
if !ok {
|
|
716
|
+
return "", false
|
|
717
|
+
}
|
|
718
|
+
return left + right, true
|
|
719
|
+
}
|
|
720
|
+
return "", false
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
// reportUnicornImportStyle applies one module's policy to the actual
|
|
724
|
+
// styles of a reference. `node:`-prefixed specifiers resolve to the
|
|
725
|
+
// bare module's configuration; the diagnostic keeps the original
|
|
726
|
+
// spelling. For `require`, an allowed `default` style implies
|
|
727
|
+
// `namespace` because CommonJS interop cannot distinguish them.
|
|
728
|
+
func reportUnicornImportStyle(
|
|
729
|
+
ctx *Context,
|
|
730
|
+
node *shimast.Node,
|
|
731
|
+
moduleName string,
|
|
732
|
+
actualStyles []string,
|
|
733
|
+
isRequire bool,
|
|
734
|
+
options unicornImportStyleOptions,
|
|
735
|
+
) {
|
|
736
|
+
module, exists := options.styles[strings.TrimPrefix(moduleName, "node:")]
|
|
737
|
+
if !exists {
|
|
738
|
+
return
|
|
739
|
+
}
|
|
740
|
+
if len(module.allowed) == 0 {
|
|
741
|
+
if module.banned {
|
|
742
|
+
ctx.Report(node, fmt.Sprintf(
|
|
743
|
+
"All import styles are disabled for module `%s`. Use the `no-restricted-imports` rule to disallow a module.",
|
|
744
|
+
moduleName,
|
|
745
|
+
))
|
|
746
|
+
}
|
|
747
|
+
return
|
|
748
|
+
}
|
|
749
|
+
effective := module.allowed
|
|
750
|
+
if isRequire &&
|
|
751
|
+
unicornImportStyleContains(effective, unicornImportStyleDefault) &&
|
|
752
|
+
!unicornImportStyleContains(effective, unicornImportStyleNamespace) {
|
|
753
|
+
effective = append(append([]string(nil), module.allowed...), unicornImportStyleNamespace)
|
|
754
|
+
}
|
|
755
|
+
for _, style := range actualStyles {
|
|
756
|
+
if !unicornImportStyleContains(effective, style) {
|
|
757
|
+
ctx.Report(node, fmt.Sprintf(
|
|
758
|
+
"Use %s import for module `%s`.",
|
|
759
|
+
unicornImportStyleDisjunction(module.allowed),
|
|
760
|
+
moduleName,
|
|
761
|
+
))
|
|
762
|
+
return
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
func unicornImportStyleContains(styles []string, style string) bool {
|
|
768
|
+
for _, candidate := range styles {
|
|
769
|
+
if candidate == style {
|
|
770
|
+
return true
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
return false
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
// unicornImportStyleDisjunction renders the allowed styles the way
|
|
777
|
+
// upstream's `Intl.ListFormat("en-US", {type: "disjunction"})` does:
|
|
778
|
+
// "a", "a or b", "a, b, or c".
|
|
779
|
+
func unicornImportStyleDisjunction(items []string) string {
|
|
780
|
+
switch len(items) {
|
|
781
|
+
case 0:
|
|
782
|
+
return ""
|
|
783
|
+
case 1:
|
|
784
|
+
return items[0]
|
|
785
|
+
case 2:
|
|
786
|
+
return items[0] + " or " + items[1]
|
|
787
|
+
default:
|
|
788
|
+
return strings.Join(items[:len(items)-1], ", ") + ", or " + items[len(items)-1]
|
|
789
|
+
}
|
|
24
790
|
}
|
|
25
791
|
|
|
26
792
|
func init() {
|