@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,103 +1,1059 @@
|
|
|
1
|
-
// noRestrictedImports
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
// been renamed, a workspace internal that should not leak, or a heavy
|
|
5
|
-
// dependency that the project wants to discourage all share the same
|
|
6
|
-
// shape — flag the import at its source so the offending line is the
|
|
7
|
-
// one the developer fixes.
|
|
1
|
+
// noRestrictedImports enforces the exact paths and path patterns supplied by
|
|
2
|
+
// the user. With no options it is a no-op: the rule contains no project policy
|
|
3
|
+
// of its own.
|
|
8
4
|
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
// to the rule, so the Go unit test exercises the rule via a hard-coded
|
|
14
|
-
// default deny list (`lodash`, `underscore`) that fires whenever the
|
|
15
|
-
// rule is enabled with no options blob.
|
|
5
|
+
// The option surface mirrors ESLint's current core rule, including positional
|
|
6
|
+
// path entries, the {paths, patterns} object form, import-name restrictions,
|
|
7
|
+
// custom messages, case sensitivity, regular-expression patterns, and
|
|
8
|
+
// type-only exemptions.
|
|
16
9
|
// https://eslint.org/docs/latest/rules/no-restricted-imports
|
|
17
10
|
package linthost
|
|
18
11
|
|
|
19
12
|
import (
|
|
13
|
+
"bytes"
|
|
20
14
|
"encoding/json"
|
|
15
|
+
"fmt"
|
|
16
|
+
"regexp"
|
|
17
|
+
"sort"
|
|
18
|
+
"strings"
|
|
19
|
+
"unicode"
|
|
21
20
|
|
|
22
21
|
shimast "github.com/microsoft/typescript-go/shim/ast"
|
|
22
|
+
shimscanner "github.com/microsoft/typescript-go/shim/scanner"
|
|
23
23
|
)
|
|
24
24
|
|
|
25
|
-
type noRestrictedImports struct{}
|
|
25
|
+
type noRestrictedImports struct{ optionsRule }
|
|
26
26
|
|
|
27
|
-
// noRestrictedImportsOptions decodes the `paths` array from a user
|
|
28
|
-
// `[severity, options]` tuple. Anything else on the blob is ignored so
|
|
29
|
-
// future ESLint-compatible keys (e.g. `patterns`) can be layered in
|
|
30
|
-
// without changing existing configs.
|
|
31
27
|
type noRestrictedImportsOptions struct {
|
|
32
|
-
|
|
28
|
+
paths []noRestrictedImportsPath
|
|
29
|
+
patterns []noRestrictedImportsPattern
|
|
33
30
|
}
|
|
34
31
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
32
|
+
type noRestrictedImportsNameControl struct {
|
|
33
|
+
importNames []string
|
|
34
|
+
hasImportNames bool
|
|
35
|
+
allowImportNames []string
|
|
36
|
+
hasAllowImportNames bool
|
|
37
|
+
importNamePattern *regexp.Regexp
|
|
38
|
+
importNamePatternText string
|
|
39
|
+
allowImportNamePattern *regexp.Regexp
|
|
40
|
+
allowImportNameText string
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
type noRestrictedImportsPath struct {
|
|
44
|
+
name string
|
|
45
|
+
message string
|
|
46
|
+
allowTypeImports bool
|
|
47
|
+
names noRestrictedImportsNameControl
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
type noRestrictedImportsPattern struct {
|
|
51
|
+
group []noRestrictedImportsGlob
|
|
52
|
+
expression *regexp.Regexp
|
|
53
|
+
message string
|
|
54
|
+
allowTypeImports bool
|
|
55
|
+
names noRestrictedImportsNameControl
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
type noRestrictedImportsGlob struct {
|
|
59
|
+
expression *regexp.Regexp
|
|
60
|
+
negated bool
|
|
61
|
+
directoryOnly bool
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
type noRestrictedImportsReference struct {
|
|
65
|
+
source string
|
|
66
|
+
sourceNode *shimast.Node
|
|
67
|
+
names []noRestrictedImportsImportedName
|
|
68
|
+
wholeTypeOnly bool
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
type noRestrictedImportsImportedName struct {
|
|
72
|
+
name string
|
|
73
|
+
node *shimast.Node
|
|
74
|
+
pos int
|
|
75
|
+
end int
|
|
76
|
+
typeOnly bool
|
|
77
|
+
}
|
|
42
78
|
|
|
43
79
|
func (noRestrictedImports) Name() string { return "no-restricted-imports" }
|
|
44
80
|
func (noRestrictedImports) Visits() []shimast.Kind {
|
|
45
81
|
return []shimast.Kind{
|
|
46
82
|
shimast.KindImportDeclaration,
|
|
47
83
|
shimast.KindExportDeclaration,
|
|
84
|
+
shimast.KindImportEqualsDeclaration,
|
|
48
85
|
}
|
|
49
86
|
}
|
|
87
|
+
|
|
88
|
+
func (noRestrictedImports) ValidateOptions(raw json.RawMessage) error {
|
|
89
|
+
_, err := parseNoRestrictedImportsOptions(raw)
|
|
90
|
+
return err
|
|
91
|
+
}
|
|
92
|
+
|
|
50
93
|
func (noRestrictedImports) Check(ctx *Context, node *shimast.Node) {
|
|
51
|
-
|
|
52
|
-
|
|
94
|
+
if ctx == nil || node == nil {
|
|
95
|
+
return
|
|
96
|
+
}
|
|
97
|
+
options, err := parseNoRestrictedImportsOptions(ctx.Options)
|
|
98
|
+
if err != nil || (len(options.paths) == 0 && len(options.patterns) == 0) {
|
|
53
99
|
return
|
|
54
100
|
}
|
|
55
|
-
|
|
56
|
-
if
|
|
101
|
+
reference, ok := noRestrictedImportsReferenceOf(ctx.File, node)
|
|
102
|
+
if !ok {
|
|
57
103
|
return
|
|
58
104
|
}
|
|
59
|
-
|
|
60
|
-
for _,
|
|
61
|
-
if
|
|
62
|
-
ctx
|
|
63
|
-
|
|
105
|
+
|
|
106
|
+
for _, restricted := range options.paths {
|
|
107
|
+
if reference.source == restricted.name {
|
|
108
|
+
reportNoRestrictedImportsPath(ctx, reference, restricted)
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
for _, restricted := range options.patterns {
|
|
112
|
+
if restricted.matches(reference.source) {
|
|
113
|
+
reportNoRestrictedImportsPattern(ctx, reference, restricted)
|
|
64
114
|
}
|
|
65
115
|
}
|
|
66
116
|
}
|
|
67
117
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
118
|
+
func parseNoRestrictedImportsOptions(raw json.RawMessage) (noRestrictedImportsOptions, error) {
|
|
119
|
+
var options noRestrictedImportsOptions
|
|
120
|
+
raw = bytes.TrimSpace(raw)
|
|
121
|
+
if len(raw) == 0 {
|
|
122
|
+
return options, nil
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
switch raw[0] {
|
|
126
|
+
case '"':
|
|
127
|
+
name, err := noRestrictedImportsDecodeString(raw, "path")
|
|
128
|
+
if err != nil {
|
|
129
|
+
return options, err
|
|
130
|
+
}
|
|
131
|
+
options.paths = append(options.paths, noRestrictedImportsPath{name: name})
|
|
132
|
+
return options, nil
|
|
133
|
+
case '{':
|
|
134
|
+
object, err := noRestrictedImportsDecodeObject(raw, "options")
|
|
135
|
+
if err != nil {
|
|
136
|
+
return options, err
|
|
137
|
+
}
|
|
138
|
+
_, hasPaths := object["paths"]
|
|
139
|
+
_, hasPatterns := object["patterns"]
|
|
140
|
+
if len(object) == 0 || hasPaths || hasPatterns {
|
|
141
|
+
return parseNoRestrictedImportsObjectOptions(object)
|
|
142
|
+
}
|
|
143
|
+
path, err := parseNoRestrictedImportsPathObject(object)
|
|
144
|
+
if err != nil {
|
|
145
|
+
return options, err
|
|
146
|
+
}
|
|
147
|
+
options.paths = append(options.paths, path)
|
|
148
|
+
return options, nil
|
|
149
|
+
case '[':
|
|
150
|
+
entries, err := noRestrictedImportsDecodeArray(raw, "paths")
|
|
151
|
+
if err != nil {
|
|
152
|
+
return options, err
|
|
153
|
+
}
|
|
154
|
+
if err := noRestrictedImportsRequireUnique(entries, "paths"); err != nil {
|
|
155
|
+
return options, err
|
|
156
|
+
}
|
|
157
|
+
options.paths, err = parseNoRestrictedImportsPaths(entries)
|
|
158
|
+
return options, err
|
|
159
|
+
default:
|
|
160
|
+
return options, fmt.Errorf("options must be path entries or an object with %q and/or %q", "paths", "patterns")
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
func parseNoRestrictedImportsObjectOptions(object map[string]json.RawMessage) (noRestrictedImportsOptions, error) {
|
|
165
|
+
var options noRestrictedImportsOptions
|
|
166
|
+
if err := noRestrictedImportsRejectUnknown(object, "paths", "patterns"); err != nil {
|
|
167
|
+
return options, err
|
|
168
|
+
}
|
|
169
|
+
if raw, present := object["paths"]; present {
|
|
170
|
+
entries, err := noRestrictedImportsDecodeArray(raw, "paths")
|
|
171
|
+
if err != nil {
|
|
172
|
+
return options, err
|
|
173
|
+
}
|
|
174
|
+
if err := noRestrictedImportsRequireUnique(entries, "paths"); err != nil {
|
|
175
|
+
return options, err
|
|
176
|
+
}
|
|
177
|
+
options.paths, err = parseNoRestrictedImportsPaths(entries)
|
|
178
|
+
if err != nil {
|
|
179
|
+
return options, err
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
if raw, present := object["patterns"]; present {
|
|
183
|
+
entries, err := noRestrictedImportsDecodeArray(raw, "patterns")
|
|
184
|
+
if err != nil {
|
|
185
|
+
return options, err
|
|
186
|
+
}
|
|
187
|
+
if err := noRestrictedImportsRequireUnique(entries, "patterns"); err != nil {
|
|
188
|
+
return options, err
|
|
189
|
+
}
|
|
190
|
+
options.patterns, err = parseNoRestrictedImportsPatterns(entries)
|
|
191
|
+
if err != nil {
|
|
192
|
+
return options, err
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return options, nil
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
func parseNoRestrictedImportsPaths(entries []json.RawMessage) ([]noRestrictedImportsPath, error) {
|
|
199
|
+
paths := make([]noRestrictedImportsPath, 0, len(entries))
|
|
200
|
+
for index, raw := range entries {
|
|
201
|
+
raw = bytes.TrimSpace(raw)
|
|
202
|
+
if len(raw) == 0 {
|
|
203
|
+
return nil, fmt.Errorf("paths[%d] must be a string or object", index)
|
|
204
|
+
}
|
|
205
|
+
switch raw[0] {
|
|
206
|
+
case '"':
|
|
207
|
+
name, err := noRestrictedImportsDecodeString(raw, fmt.Sprintf("paths[%d]", index))
|
|
208
|
+
if err != nil {
|
|
209
|
+
return nil, err
|
|
210
|
+
}
|
|
211
|
+
paths = append(paths, noRestrictedImportsPath{name: name})
|
|
212
|
+
case '{':
|
|
213
|
+
object, err := noRestrictedImportsDecodeObject(raw, fmt.Sprintf("paths[%d]", index))
|
|
214
|
+
if err != nil {
|
|
215
|
+
return nil, err
|
|
216
|
+
}
|
|
217
|
+
path, err := parseNoRestrictedImportsPathObject(object)
|
|
218
|
+
if err != nil {
|
|
219
|
+
return nil, fmt.Errorf("paths[%d]: %w", index, err)
|
|
220
|
+
}
|
|
221
|
+
paths = append(paths, path)
|
|
222
|
+
default:
|
|
223
|
+
return nil, fmt.Errorf("paths[%d] must be a string or object", index)
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return paths, nil
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
func parseNoRestrictedImportsPathObject(object map[string]json.RawMessage) (noRestrictedImportsPath, error) {
|
|
230
|
+
var path noRestrictedImportsPath
|
|
231
|
+
if err := noRestrictedImportsRejectUnknown(
|
|
232
|
+
object,
|
|
233
|
+
"name",
|
|
234
|
+
"message",
|
|
235
|
+
"importNames",
|
|
236
|
+
"allowImportNames",
|
|
237
|
+
"allowTypeImports",
|
|
238
|
+
); err != nil {
|
|
239
|
+
return path, err
|
|
240
|
+
}
|
|
241
|
+
rawName, present := object["name"]
|
|
242
|
+
if !present {
|
|
243
|
+
return path, fmt.Errorf("path object requires %q", "name")
|
|
244
|
+
}
|
|
245
|
+
var err error
|
|
246
|
+
if path.name, err = noRestrictedImportsDecodeString(rawName, "name"); err != nil {
|
|
247
|
+
return path, err
|
|
248
|
+
}
|
|
249
|
+
if raw, present := object["message"]; present {
|
|
250
|
+
path.message, err = noRestrictedImportsDecodeString(raw, "message")
|
|
251
|
+
if err != nil {
|
|
252
|
+
return path, err
|
|
253
|
+
}
|
|
254
|
+
if path.message == "" {
|
|
255
|
+
return path, fmt.Errorf("option %q must not be empty", "message")
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
if raw, present := object["allowTypeImports"]; present {
|
|
259
|
+
path.allowTypeImports, err = noRestrictedImportsDecodeBool(raw, "allowTypeImports")
|
|
260
|
+
if err != nil {
|
|
261
|
+
return path, err
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
if raw, present := object["importNames"]; present {
|
|
265
|
+
path.names.importNames, err = noRestrictedImportsStringList(raw, "importNames", false, false)
|
|
266
|
+
if err != nil {
|
|
267
|
+
return path, err
|
|
268
|
+
}
|
|
269
|
+
path.names.hasImportNames = true
|
|
270
|
+
}
|
|
271
|
+
if raw, present := object["allowImportNames"]; present {
|
|
272
|
+
path.names.allowImportNames, err = noRestrictedImportsStringList(raw, "allowImportNames", false, false)
|
|
273
|
+
if err != nil {
|
|
274
|
+
return path, err
|
|
275
|
+
}
|
|
276
|
+
path.names.hasAllowImportNames = true
|
|
277
|
+
}
|
|
278
|
+
if path.names.hasImportNames && path.names.hasAllowImportNames {
|
|
279
|
+
return path, fmt.Errorf("options %q and %q cannot be combined", "importNames", "allowImportNames")
|
|
280
|
+
}
|
|
281
|
+
return path, nil
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
func parseNoRestrictedImportsPatterns(entries []json.RawMessage) ([]noRestrictedImportsPattern, error) {
|
|
285
|
+
if len(entries) == 0 {
|
|
286
|
+
return nil, nil
|
|
287
|
+
}
|
|
288
|
+
first := bytes.TrimSpace(entries[0])
|
|
289
|
+
if len(first) == 0 {
|
|
290
|
+
return nil, fmt.Errorf("patterns[0] must be a string or object")
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if first[0] == '"' {
|
|
294
|
+
group := make([]string, 0, len(entries))
|
|
295
|
+
for index, raw := range entries {
|
|
296
|
+
text, err := noRestrictedImportsDecodeString(raw, fmt.Sprintf("patterns[%d]", index))
|
|
297
|
+
if err != nil {
|
|
298
|
+
return nil, fmt.Errorf("patterns must contain only strings or only objects: %w", err)
|
|
299
|
+
}
|
|
300
|
+
group = append(group, text)
|
|
301
|
+
}
|
|
302
|
+
compiled, err := noRestrictedImportsCompileGlobGroup(group, false)
|
|
303
|
+
if err != nil {
|
|
304
|
+
return nil, err
|
|
305
|
+
}
|
|
306
|
+
return []noRestrictedImportsPattern{{group: compiled}}, nil
|
|
307
|
+
}
|
|
308
|
+
if first[0] != '{' {
|
|
309
|
+
return nil, fmt.Errorf("patterns[0] must be a string or object")
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
patterns := make([]noRestrictedImportsPattern, 0, len(entries))
|
|
313
|
+
for index, raw := range entries {
|
|
314
|
+
object, err := noRestrictedImportsDecodeObject(raw, fmt.Sprintf("patterns[%d]", index))
|
|
315
|
+
if err != nil {
|
|
316
|
+
return nil, fmt.Errorf("patterns must contain only strings or only objects: %w", err)
|
|
317
|
+
}
|
|
318
|
+
pattern, err := parseNoRestrictedImportsPatternObject(object)
|
|
319
|
+
if err != nil {
|
|
320
|
+
return nil, fmt.Errorf("patterns[%d]: %w", index, err)
|
|
321
|
+
}
|
|
322
|
+
patterns = append(patterns, pattern)
|
|
323
|
+
}
|
|
324
|
+
return patterns, nil
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
func parseNoRestrictedImportsPatternObject(object map[string]json.RawMessage) (noRestrictedImportsPattern, error) {
|
|
328
|
+
var pattern noRestrictedImportsPattern
|
|
329
|
+
if err := noRestrictedImportsRejectUnknown(
|
|
330
|
+
object,
|
|
331
|
+
"group",
|
|
332
|
+
"regex",
|
|
333
|
+
"message",
|
|
334
|
+
"caseSensitive",
|
|
335
|
+
"importNames",
|
|
336
|
+
"allowImportNames",
|
|
337
|
+
"importNamePattern",
|
|
338
|
+
"allowImportNamePattern",
|
|
339
|
+
"allowTypeImports",
|
|
340
|
+
); err != nil {
|
|
341
|
+
return pattern, err
|
|
342
|
+
}
|
|
343
|
+
rawGroup, hasGroup := object["group"]
|
|
344
|
+
rawRegex, hasRegex := object["regex"]
|
|
345
|
+
if hasGroup == hasRegex {
|
|
346
|
+
return pattern, fmt.Errorf("pattern object must contain exactly one of %q or %q", "group", "regex")
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
var err error
|
|
350
|
+
caseSensitive := false
|
|
351
|
+
if raw, present := object["caseSensitive"]; present {
|
|
352
|
+
caseSensitive, err = noRestrictedImportsDecodeBool(raw, "caseSensitive")
|
|
353
|
+
if err != nil {
|
|
354
|
+
return pattern, err
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
if raw, present := object["allowTypeImports"]; present {
|
|
358
|
+
pattern.allowTypeImports, err = noRestrictedImportsDecodeBool(raw, "allowTypeImports")
|
|
359
|
+
if err != nil {
|
|
360
|
+
return pattern, err
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
if hasGroup {
|
|
365
|
+
group, err := noRestrictedImportsStringList(rawGroup, "group", true, true)
|
|
366
|
+
if err != nil {
|
|
367
|
+
return pattern, err
|
|
368
|
+
}
|
|
369
|
+
pattern.group, err = noRestrictedImportsCompileGlobGroup(group, caseSensitive)
|
|
370
|
+
if err != nil {
|
|
371
|
+
return pattern, err
|
|
372
|
+
}
|
|
373
|
+
} else {
|
|
374
|
+
expression, err := noRestrictedImportsDecodeString(rawRegex, "regex")
|
|
375
|
+
if err != nil {
|
|
376
|
+
return pattern, err
|
|
377
|
+
}
|
|
378
|
+
pattern.expression, err = noRestrictedImportsCompileRegexp(expression, caseSensitive)
|
|
379
|
+
if err != nil {
|
|
380
|
+
return pattern, fmt.Errorf("option %q must be a valid regular expression: %w", "regex", err)
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
if raw, present := object["message"]; present {
|
|
384
|
+
pattern.message, err = noRestrictedImportsDecodeString(raw, "message")
|
|
385
|
+
if err != nil {
|
|
386
|
+
return pattern, err
|
|
387
|
+
}
|
|
388
|
+
if pattern.message == "" {
|
|
389
|
+
return pattern, fmt.Errorf("option %q must not be empty", "message")
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
if raw, present := object["importNames"]; present {
|
|
393
|
+
pattern.names.importNames, err = noRestrictedImportsStringList(raw, "importNames", true, true)
|
|
394
|
+
if err != nil {
|
|
395
|
+
return pattern, err
|
|
396
|
+
}
|
|
397
|
+
pattern.names.hasImportNames = true
|
|
398
|
+
}
|
|
399
|
+
if raw, present := object["allowImportNames"]; present {
|
|
400
|
+
pattern.names.allowImportNames, err = noRestrictedImportsStringList(raw, "allowImportNames", true, true)
|
|
401
|
+
if err != nil {
|
|
402
|
+
return pattern, err
|
|
403
|
+
}
|
|
404
|
+
pattern.names.hasAllowImportNames = true
|
|
405
|
+
}
|
|
406
|
+
if raw, present := object["importNamePattern"]; present {
|
|
407
|
+
pattern.names.importNamePatternText, err = noRestrictedImportsDecodeString(raw, "importNamePattern")
|
|
408
|
+
if err != nil {
|
|
409
|
+
return pattern, err
|
|
410
|
+
}
|
|
411
|
+
pattern.names.importNamePattern, err = noRestrictedImportsCompileRegexp(pattern.names.importNamePatternText, true)
|
|
412
|
+
if err != nil {
|
|
413
|
+
return pattern, fmt.Errorf("option %q must be a valid regular expression: %w", "importNamePattern", err)
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
if raw, present := object["allowImportNamePattern"]; present {
|
|
417
|
+
pattern.names.allowImportNameText, err = noRestrictedImportsDecodeString(raw, "allowImportNamePattern")
|
|
418
|
+
if err != nil {
|
|
419
|
+
return pattern, err
|
|
420
|
+
}
|
|
421
|
+
pattern.names.allowImportNamePattern, err = noRestrictedImportsCompileRegexp(pattern.names.allowImportNameText, true)
|
|
422
|
+
if err != nil {
|
|
423
|
+
return pattern, fmt.Errorf("option %q must be a valid regular expression: %w", "allowImportNamePattern", err)
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
if err := pattern.names.validatePatternCombinations(); err != nil {
|
|
427
|
+
return pattern, err
|
|
428
|
+
}
|
|
429
|
+
return pattern, nil
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
func (control noRestrictedImportsNameControl) validatePatternCombinations() error {
|
|
433
|
+
if control.hasImportNames && control.hasAllowImportNames {
|
|
434
|
+
return fmt.Errorf("options %q and %q cannot be combined", "importNames", "allowImportNames")
|
|
435
|
+
}
|
|
436
|
+
if control.importNamePattern != nil && control.allowImportNamePattern != nil {
|
|
437
|
+
return fmt.Errorf("options %q and %q cannot be combined", "importNamePattern", "allowImportNamePattern")
|
|
438
|
+
}
|
|
439
|
+
if control.hasImportNames && control.allowImportNamePattern != nil {
|
|
440
|
+
return fmt.Errorf("options %q and %q cannot be combined", "importNames", "allowImportNamePattern")
|
|
441
|
+
}
|
|
442
|
+
if control.importNamePattern != nil && control.hasAllowImportNames {
|
|
443
|
+
return fmt.Errorf("options %q and %q cannot be combined", "importNamePattern", "allowImportNames")
|
|
444
|
+
}
|
|
445
|
+
if control.hasAllowImportNames && control.allowImportNamePattern != nil {
|
|
446
|
+
return fmt.Errorf("options %q and %q cannot be combined", "allowImportNames", "allowImportNamePattern")
|
|
447
|
+
}
|
|
448
|
+
return nil
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
func noRestrictedImportsDecodeObject(raw json.RawMessage, name string) (map[string]json.RawMessage, error) {
|
|
452
|
+
var object map[string]json.RawMessage
|
|
453
|
+
if err := json.Unmarshal(raw, &object); err != nil || object == nil {
|
|
454
|
+
return nil, fmt.Errorf("%s must be an object", name)
|
|
455
|
+
}
|
|
456
|
+
return object, nil
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
func noRestrictedImportsDecodeArray(raw json.RawMessage, name string) ([]json.RawMessage, error) {
|
|
460
|
+
var entries []json.RawMessage
|
|
461
|
+
if err := json.Unmarshal(raw, &entries); err != nil || entries == nil {
|
|
462
|
+
return nil, fmt.Errorf("option %q must be an array", name)
|
|
463
|
+
}
|
|
464
|
+
return entries, nil
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
func noRestrictedImportsDecodeString(raw json.RawMessage, name string) (string, error) {
|
|
468
|
+
var decoded any
|
|
469
|
+
if err := json.Unmarshal(raw, &decoded); err != nil {
|
|
470
|
+
return "", fmt.Errorf("option %q must be a string", name)
|
|
471
|
+
}
|
|
472
|
+
value, ok := decoded.(string)
|
|
473
|
+
if !ok {
|
|
474
|
+
return "", fmt.Errorf("option %q must be a string", name)
|
|
475
|
+
}
|
|
476
|
+
return value, nil
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
func noRestrictedImportsDecodeBool(raw json.RawMessage, name string) (bool, error) {
|
|
480
|
+
var decoded any
|
|
481
|
+
if err := json.Unmarshal(raw, &decoded); err != nil {
|
|
482
|
+
return false, fmt.Errorf("option %q must be a boolean", name)
|
|
483
|
+
}
|
|
484
|
+
value, ok := decoded.(bool)
|
|
485
|
+
if !ok {
|
|
486
|
+
return false, fmt.Errorf("option %q must be a boolean", name)
|
|
487
|
+
}
|
|
488
|
+
return value, nil
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
func noRestrictedImportsStringList(raw json.RawMessage, name string, nonEmpty, unique bool) ([]string, error) {
|
|
492
|
+
entries, err := noRestrictedImportsDecodeArray(raw, name)
|
|
493
|
+
if err != nil {
|
|
494
|
+
return nil, err
|
|
495
|
+
}
|
|
496
|
+
if nonEmpty && len(entries) == 0 {
|
|
497
|
+
return nil, fmt.Errorf("option %q must contain at least one string", name)
|
|
498
|
+
}
|
|
499
|
+
values := make([]string, 0, len(entries))
|
|
500
|
+
seen := make(map[string]struct{}, len(entries))
|
|
501
|
+
for index, rawEntry := range entries {
|
|
502
|
+
value, err := noRestrictedImportsDecodeString(rawEntry, fmt.Sprintf("%s[%d]", name, index))
|
|
503
|
+
if err != nil {
|
|
504
|
+
return nil, err
|
|
505
|
+
}
|
|
506
|
+
if unique {
|
|
507
|
+
if _, duplicate := seen[value]; duplicate {
|
|
508
|
+
return nil, fmt.Errorf("option %q contains duplicate value %q", name, value)
|
|
509
|
+
}
|
|
510
|
+
seen[value] = struct{}{}
|
|
511
|
+
}
|
|
512
|
+
values = append(values, value)
|
|
513
|
+
}
|
|
514
|
+
return values, nil
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
func noRestrictedImportsRejectUnknown(object map[string]json.RawMessage, allowed ...string) error {
|
|
518
|
+
known := make(map[string]struct{}, len(allowed))
|
|
519
|
+
for _, name := range allowed {
|
|
520
|
+
known[name] = struct{}{}
|
|
521
|
+
}
|
|
522
|
+
unknown := make([]string, 0)
|
|
523
|
+
for name := range object {
|
|
524
|
+
if _, ok := known[name]; !ok {
|
|
525
|
+
unknown = append(unknown, name)
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
if len(unknown) == 0 {
|
|
73
529
|
return nil
|
|
74
530
|
}
|
|
531
|
+
sort.Strings(unknown)
|
|
532
|
+
return fmt.Errorf("unknown option %q", unknown[0])
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
func noRestrictedImportsRequireUnique(entries []json.RawMessage, name string) error {
|
|
536
|
+
seen := make(map[string]struct{}, len(entries))
|
|
537
|
+
for index, raw := range entries {
|
|
538
|
+
var value any
|
|
539
|
+
if err := json.Unmarshal(raw, &value); err != nil {
|
|
540
|
+
return fmt.Errorf("%s[%d] must be valid JSON: %w", name, index, err)
|
|
541
|
+
}
|
|
542
|
+
canonical, err := json.Marshal(value)
|
|
543
|
+
if err != nil {
|
|
544
|
+
return fmt.Errorf("%s[%d] cannot be normalized: %w", name, index, err)
|
|
545
|
+
}
|
|
546
|
+
key := string(canonical)
|
|
547
|
+
if _, duplicate := seen[key]; duplicate {
|
|
548
|
+
return fmt.Errorf("option %q contains a duplicate entry at index %d", name, index)
|
|
549
|
+
}
|
|
550
|
+
seen[key] = struct{}{}
|
|
551
|
+
}
|
|
552
|
+
return nil
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
func noRestrictedImportsCompileRegexp(expression string, caseSensitive bool) (*regexp.Regexp, error) {
|
|
556
|
+
if !caseSensitive {
|
|
557
|
+
expression = "(?i:" + expression + ")"
|
|
558
|
+
}
|
|
559
|
+
return regexp.Compile(expression)
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
func noRestrictedImportsCompileGlobGroup(patterns []string, caseSensitive bool) ([]noRestrictedImportsGlob, error) {
|
|
563
|
+
group := make([]noRestrictedImportsGlob, 0, len(patterns))
|
|
564
|
+
for index, pattern := range patterns {
|
|
565
|
+
compiled, active, err := noRestrictedImportsCompileGlob(pattern, caseSensitive)
|
|
566
|
+
if err != nil {
|
|
567
|
+
return nil, fmt.Errorf("option %q[%d] is not a valid pattern: %w", "group", index, err)
|
|
568
|
+
}
|
|
569
|
+
if active {
|
|
570
|
+
group = append(group, compiled)
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
return group, nil
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
func noRestrictedImportsCompileGlob(raw string, caseSensitive bool) (noRestrictedImportsGlob, bool, error) {
|
|
577
|
+
var glob noRestrictedImportsGlob
|
|
578
|
+
pattern := strings.TrimPrefix(raw, "\uFEFF")
|
|
579
|
+
pattern = noRestrictedImportsTrimPattern(pattern)
|
|
580
|
+
if pattern == "" || strings.HasPrefix(pattern, "#") {
|
|
581
|
+
return glob, false, nil
|
|
582
|
+
}
|
|
583
|
+
if strings.HasPrefix(pattern, `\#`) || strings.HasPrefix(pattern, `\!`) {
|
|
584
|
+
pattern = pattern[1:]
|
|
585
|
+
} else if strings.HasPrefix(pattern, "!") {
|
|
586
|
+
glob.negated = true
|
|
587
|
+
pattern = pattern[1:]
|
|
588
|
+
}
|
|
589
|
+
if pattern == "" {
|
|
590
|
+
return glob, false, nil
|
|
591
|
+
}
|
|
592
|
+
glob.directoryOnly = strings.HasSuffix(pattern, "/")
|
|
593
|
+
pattern = strings.TrimSuffix(pattern, "/")
|
|
594
|
+
rooted := strings.HasPrefix(pattern, "/")
|
|
595
|
+
pattern = strings.TrimPrefix(pattern, "/")
|
|
596
|
+
if pattern == "" {
|
|
597
|
+
return glob, false, nil
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
body, err := noRestrictedImportsGlobRegexp(pattern, rooted)
|
|
601
|
+
if err != nil {
|
|
602
|
+
return glob, false, err
|
|
603
|
+
}
|
|
604
|
+
if !rooted && !strings.Contains(pattern, "/") {
|
|
605
|
+
body = `(^|/)` + body
|
|
606
|
+
} else {
|
|
607
|
+
body = `^` + body
|
|
608
|
+
}
|
|
609
|
+
body += `$`
|
|
610
|
+
glob.expression, err = noRestrictedImportsCompileRegexp(body, caseSensitive)
|
|
611
|
+
if err != nil {
|
|
612
|
+
return glob, false, err
|
|
613
|
+
}
|
|
614
|
+
return glob, true, nil
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
func noRestrictedImportsTrimPattern(pattern string) string {
|
|
618
|
+
runes := []rune(pattern)
|
|
619
|
+
for len(runes) > 0 && unicode.IsSpace(runes[len(runes)-1]) {
|
|
620
|
+
slashCount := 0
|
|
621
|
+
for index := len(runes) - 2; index >= 0 && runes[index] == '\\'; index-- {
|
|
622
|
+
slashCount++
|
|
623
|
+
}
|
|
624
|
+
if slashCount%2 == 1 {
|
|
625
|
+
runes = append(runes[:len(runes)-2], runes[len(runes)-1])
|
|
626
|
+
break
|
|
627
|
+
}
|
|
628
|
+
runes = runes[:len(runes)-1]
|
|
629
|
+
}
|
|
630
|
+
return string(runes)
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
func noRestrictedImportsGlobRegexp(pattern string, rooted bool) (string, error) {
|
|
634
|
+
runes := []rune(pattern)
|
|
635
|
+
var expression strings.Builder
|
|
636
|
+
for index := 0; index < len(runes); index++ {
|
|
637
|
+
current := runes[index]
|
|
638
|
+
switch current {
|
|
639
|
+
case '\\':
|
|
640
|
+
if index+1 < len(runes) {
|
|
641
|
+
index++
|
|
642
|
+
expression.WriteString(regexp.QuoteMeta(string(runes[index])))
|
|
643
|
+
} else {
|
|
644
|
+
expression.WriteString(`\\`)
|
|
645
|
+
}
|
|
646
|
+
case '*':
|
|
647
|
+
start := index
|
|
648
|
+
for index+1 < len(runes) && runes[index+1] == '*' {
|
|
649
|
+
index++
|
|
650
|
+
}
|
|
651
|
+
count := index - start + 1
|
|
652
|
+
previousSlash := start > 0 && runes[start-1] == '/'
|
|
653
|
+
nextSlash := index+1 < len(runes) && runes[index+1] == '/'
|
|
654
|
+
if count == 2 && nextSlash && (start == 0 || previousSlash) {
|
|
655
|
+
index++
|
|
656
|
+
expression.WriteString(`(?:[^/]+/)*`)
|
|
657
|
+
} else if count == 2 && previousSlash && index == len(runes)-1 {
|
|
658
|
+
expression.WriteString(`.+`)
|
|
659
|
+
} else if count == 1 && index == len(runes)-1 && (previousSlash || (rooted && start == 0)) {
|
|
660
|
+
expression.WriteString(`[^/]+`)
|
|
661
|
+
} else {
|
|
662
|
+
expression.WriteString(`[^/]*`)
|
|
663
|
+
}
|
|
664
|
+
case '?':
|
|
665
|
+
expression.WriteString(`[^/]`)
|
|
666
|
+
case '[':
|
|
667
|
+
closeIndex := index + 1
|
|
668
|
+
for closeIndex < len(runes) && runes[closeIndex] != ']' {
|
|
669
|
+
closeIndex++
|
|
670
|
+
}
|
|
671
|
+
if closeIndex == len(runes) {
|
|
672
|
+
expression.WriteString(`\[`)
|
|
673
|
+
continue
|
|
674
|
+
}
|
|
675
|
+
class := string(runes[index+1 : closeIndex])
|
|
676
|
+
if strings.HasPrefix(class, "!") {
|
|
677
|
+
class = "^/" + class[1:]
|
|
678
|
+
} else if strings.HasPrefix(class, "^") {
|
|
679
|
+
class = `\^` + class[1:]
|
|
680
|
+
}
|
|
681
|
+
characterClass := "[" + class + "]"
|
|
682
|
+
if _, err := regexp.Compile(characterClass); err != nil {
|
|
683
|
+
// Gitignore accepts malformed or descending ranges as patterns and
|
|
684
|
+
// treats an unusable range as non-matching instead of rejecting the
|
|
685
|
+
// entire configuration.
|
|
686
|
+
expression.WriteString(`(?:\b\B)`)
|
|
687
|
+
} else {
|
|
688
|
+
expression.WriteString(characterClass)
|
|
689
|
+
}
|
|
690
|
+
index = closeIndex
|
|
691
|
+
default:
|
|
692
|
+
expression.WriteString(regexp.QuoteMeta(string(current)))
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
return expression.String(), nil
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
func (pattern noRestrictedImportsPattern) matches(source string) bool {
|
|
699
|
+
if pattern.expression != nil {
|
|
700
|
+
return pattern.expression.MatchString(source)
|
|
701
|
+
}
|
|
702
|
+
if len(pattern.group) == 0 {
|
|
703
|
+
return false
|
|
704
|
+
}
|
|
705
|
+
parts := strings.Split(source, "/")
|
|
706
|
+
for index := range parts {
|
|
707
|
+
candidate := strings.Join(parts[:index+1], "/")
|
|
708
|
+
directory := index < len(parts)-1
|
|
709
|
+
ignored := false
|
|
710
|
+
for _, glob := range pattern.group {
|
|
711
|
+
if glob.directoryOnly && !directory {
|
|
712
|
+
continue
|
|
713
|
+
}
|
|
714
|
+
if glob.expression.MatchString(candidate) {
|
|
715
|
+
ignored = !glob.negated
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
if ignored {
|
|
719
|
+
return true
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
return false
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
func noRestrictedImportsReferenceOf(file *shimast.SourceFile, node *shimast.Node) (noRestrictedImportsReference, bool) {
|
|
726
|
+
var reference noRestrictedImportsReference
|
|
727
|
+
if node == nil {
|
|
728
|
+
return reference, false
|
|
729
|
+
}
|
|
75
730
|
switch node.Kind {
|
|
76
731
|
case shimast.KindImportDeclaration:
|
|
77
|
-
|
|
78
|
-
|
|
732
|
+
declaration := node.AsImportDeclaration()
|
|
733
|
+
if declaration == nil || declaration.ModuleSpecifier == nil {
|
|
734
|
+
return reference, false
|
|
79
735
|
}
|
|
736
|
+
reference.sourceNode = declaration.ModuleSpecifier
|
|
737
|
+
reference.names, reference.wholeTypeOnly = noRestrictedImportsImportNames(declaration)
|
|
80
738
|
case shimast.KindExportDeclaration:
|
|
81
|
-
|
|
82
|
-
|
|
739
|
+
declaration := node.AsExportDeclaration()
|
|
740
|
+
if declaration == nil || declaration.ModuleSpecifier == nil {
|
|
741
|
+
return reference, false
|
|
83
742
|
}
|
|
743
|
+
reference.sourceNode = declaration.ModuleSpecifier
|
|
744
|
+
reference.names, reference.wholeTypeOnly = noRestrictedImportsExportNames(file, node, declaration)
|
|
745
|
+
case shimast.KindImportEqualsDeclaration:
|
|
746
|
+
declaration := node.AsImportEqualsDeclaration()
|
|
747
|
+
if declaration == nil || declaration.ModuleReference == nil ||
|
|
748
|
+
declaration.ModuleReference.Kind != shimast.KindExternalModuleReference {
|
|
749
|
+
return reference, false
|
|
750
|
+
}
|
|
751
|
+
module := declaration.ModuleReference.AsExternalModuleReference()
|
|
752
|
+
if module == nil || module.Expression == nil {
|
|
753
|
+
return reference, false
|
|
754
|
+
}
|
|
755
|
+
reference.sourceNode = module.Expression
|
|
756
|
+
reference.wholeTypeOnly = declaration.IsTypeOnly
|
|
757
|
+
default:
|
|
758
|
+
return reference, false
|
|
759
|
+
}
|
|
760
|
+
reference.source = stringLiteralText(reference.sourceNode)
|
|
761
|
+
return reference, true
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
func noRestrictedImportsImportNames(declaration *shimast.ImportDeclaration) ([]noRestrictedImportsImportedName, bool) {
|
|
765
|
+
if declaration == nil || declaration.ImportClause == nil {
|
|
766
|
+
return nil, false
|
|
767
|
+
}
|
|
768
|
+
clauseNode := declaration.ImportClause
|
|
769
|
+
clause := clauseNode.AsImportClause()
|
|
770
|
+
if clause == nil {
|
|
771
|
+
return nil, false
|
|
772
|
+
}
|
|
773
|
+
clauseTypeOnly := clauseNode.IsTypeOnly()
|
|
774
|
+
names := make([]noRestrictedImportsImportedName, 0)
|
|
775
|
+
if name := clause.Name(); name != nil {
|
|
776
|
+
names = append(names, noRestrictedImportsImportedName{name: "default", node: name, typeOnly: clauseTypeOnly})
|
|
777
|
+
}
|
|
778
|
+
if bindings := clause.NamedBindings; bindings != nil {
|
|
779
|
+
switch bindings.Kind {
|
|
780
|
+
case shimast.KindNamespaceImport:
|
|
781
|
+
names = append(names, noRestrictedImportsImportedName{name: "*", node: bindings, typeOnly: clauseTypeOnly})
|
|
782
|
+
case shimast.KindNamedImports:
|
|
783
|
+
named := bindings.AsNamedImports()
|
|
784
|
+
if named != nil && named.Elements != nil {
|
|
785
|
+
for _, element := range named.Elements.Nodes {
|
|
786
|
+
specifier := element.AsImportSpecifier()
|
|
787
|
+
if specifier == nil {
|
|
788
|
+
continue
|
|
789
|
+
}
|
|
790
|
+
imported := specifier.PropertyName
|
|
791
|
+
if imported == nil {
|
|
792
|
+
imported = specifier.Name()
|
|
793
|
+
}
|
|
794
|
+
names = append(names, noRestrictedImportsImportedName{
|
|
795
|
+
name: noRestrictedImportsModuleExportName(imported),
|
|
796
|
+
node: element,
|
|
797
|
+
typeOnly: clauseTypeOnly || specifier.IsTypeOnly,
|
|
798
|
+
})
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
return names, clauseTypeOnly || noRestrictedImportsAllNamesTypeOnly(names)
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
func noRestrictedImportsExportNames(
|
|
807
|
+
file *shimast.SourceFile,
|
|
808
|
+
node *shimast.Node,
|
|
809
|
+
declaration *shimast.ExportDeclaration,
|
|
810
|
+
) ([]noRestrictedImportsImportedName, bool) {
|
|
811
|
+
if declaration == nil {
|
|
812
|
+
return nil, false
|
|
813
|
+
}
|
|
814
|
+
declarationTypeOnly := declaration.IsTypeOnly
|
|
815
|
+
clause := declaration.ExportClause
|
|
816
|
+
if clause == nil {
|
|
817
|
+
pos, end := noRestrictedImportsExportStarRange(file, node, declaration.ModuleSpecifier)
|
|
818
|
+
return []noRestrictedImportsImportedName{{name: "*", pos: pos, end: end, typeOnly: declarationTypeOnly}}, declarationTypeOnly
|
|
819
|
+
}
|
|
820
|
+
if clause.Kind == shimast.KindNamespaceExport {
|
|
821
|
+
pos, end := noRestrictedImportsExportStarRange(file, node, declaration.ModuleSpecifier)
|
|
822
|
+
return []noRestrictedImportsImportedName{{name: "*", pos: pos, end: end, typeOnly: declarationTypeOnly}}, declarationTypeOnly
|
|
823
|
+
}
|
|
824
|
+
if clause.Kind != shimast.KindNamedExports {
|
|
825
|
+
return nil, declarationTypeOnly
|
|
826
|
+
}
|
|
827
|
+
named := clause.AsNamedExports()
|
|
828
|
+
if named == nil || named.Elements == nil {
|
|
829
|
+
return nil, declarationTypeOnly
|
|
830
|
+
}
|
|
831
|
+
names := make([]noRestrictedImportsImportedName, 0, len(named.Elements.Nodes))
|
|
832
|
+
for _, element := range named.Elements.Nodes {
|
|
833
|
+
specifier := element.AsExportSpecifier()
|
|
834
|
+
if specifier == nil {
|
|
835
|
+
continue
|
|
836
|
+
}
|
|
837
|
+
imported := specifier.PropertyName
|
|
838
|
+
if imported == nil {
|
|
839
|
+
imported = specifier.Name()
|
|
840
|
+
}
|
|
841
|
+
names = append(names, noRestrictedImportsImportedName{
|
|
842
|
+
name: noRestrictedImportsModuleExportName(imported),
|
|
843
|
+
node: element,
|
|
844
|
+
typeOnly: declarationTypeOnly || specifier.IsTypeOnly,
|
|
845
|
+
})
|
|
846
|
+
}
|
|
847
|
+
return names, declarationTypeOnly || noRestrictedImportsAllNamesTypeOnly(names)
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
func noRestrictedImportsModuleExportName(node *shimast.Node) string {
|
|
851
|
+
if name := identifierText(node); name != "" {
|
|
852
|
+
return name
|
|
853
|
+
}
|
|
854
|
+
return stringLiteralText(node)
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
func noRestrictedImportsAllNamesTypeOnly(names []noRestrictedImportsImportedName) bool {
|
|
858
|
+
if len(names) == 0 {
|
|
859
|
+
return false
|
|
860
|
+
}
|
|
861
|
+
for _, name := range names {
|
|
862
|
+
if !name.typeOnly {
|
|
863
|
+
return false
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
return true
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
func noRestrictedImportsExportStarRange(
|
|
870
|
+
file *shimast.SourceFile,
|
|
871
|
+
declaration *shimast.Node,
|
|
872
|
+
moduleSpecifier *shimast.Node,
|
|
873
|
+
) (int, int) {
|
|
874
|
+
if file == nil || declaration == nil {
|
|
875
|
+
return -1, -1
|
|
876
|
+
}
|
|
877
|
+
source := file.Text()
|
|
878
|
+
start, _ := tokenRange(file, declaration)
|
|
879
|
+
limit, _ := tokenRange(file, moduleSpecifier)
|
|
880
|
+
if start < 0 || limit < start || limit > len(source) {
|
|
881
|
+
return -1, -1
|
|
882
|
+
}
|
|
883
|
+
scanner := shimscanner.NewScanner()
|
|
884
|
+
scanner.SetText(source[start:limit])
|
|
885
|
+
scanner.SetSkipTrivia(true)
|
|
886
|
+
for {
|
|
887
|
+
switch scanner.Scan() {
|
|
888
|
+
case shimast.KindAsteriskToken:
|
|
889
|
+
return start + scanner.TokenStart(), start + scanner.TokenEnd()
|
|
890
|
+
case shimast.KindEndOfFile:
|
|
891
|
+
return -1, -1
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
func reportNoRestrictedImportsPath(ctx *Context, reference noRestrictedImportsReference, restricted noRestrictedImportsPath) {
|
|
897
|
+
if restricted.allowTypeImports && reference.wholeTypeOnly {
|
|
898
|
+
return
|
|
899
|
+
}
|
|
900
|
+
if !restricted.names.hasRestrictions() {
|
|
901
|
+
message := fmt.Sprintf("'%s' import is restricted from being used.", reference.source)
|
|
902
|
+
ctx.Report(reference.sourceNode, noRestrictedImportsCustomMessage(message, restricted.message))
|
|
903
|
+
return
|
|
904
|
+
}
|
|
905
|
+
reportNoRestrictedImportsNames(ctx, reference, restricted.names, restricted.message, false, restricted.allowTypeImports)
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
func reportNoRestrictedImportsPattern(ctx *Context, reference noRestrictedImportsReference, restricted noRestrictedImportsPattern) {
|
|
909
|
+
if restricted.allowTypeImports && reference.wholeTypeOnly {
|
|
910
|
+
return
|
|
911
|
+
}
|
|
912
|
+
if !restricted.names.hasRestrictions() {
|
|
913
|
+
message := fmt.Sprintf("'%s' import is restricted from being used by a pattern.", reference.source)
|
|
914
|
+
ctx.Report(reference.sourceNode, noRestrictedImportsCustomMessage(message, restricted.message))
|
|
915
|
+
return
|
|
916
|
+
}
|
|
917
|
+
reportNoRestrictedImportsNames(ctx, reference, restricted.names, restricted.message, true, restricted.allowTypeImports)
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
func (control noRestrictedImportsNameControl) hasRestrictions() bool {
|
|
921
|
+
return control.hasImportNames || control.hasAllowImportNames ||
|
|
922
|
+
control.importNamePattern != nil || control.allowImportNamePattern != nil
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
func reportNoRestrictedImportsNames(
|
|
926
|
+
ctx *Context,
|
|
927
|
+
reference noRestrictedImportsReference,
|
|
928
|
+
control noRestrictedImportsNameControl,
|
|
929
|
+
customMessage string,
|
|
930
|
+
pattern bool,
|
|
931
|
+
allowTypeImports bool,
|
|
932
|
+
) {
|
|
933
|
+
for _, imported := range reference.names {
|
|
934
|
+
if allowTypeImports && imported.typeOnly {
|
|
935
|
+
continue
|
|
936
|
+
}
|
|
937
|
+
if imported.name == "*" {
|
|
938
|
+
message := noRestrictedImportsNamespaceMessage(reference.source, control, pattern)
|
|
939
|
+
imported.report(ctx, noRestrictedImportsCustomMessage(message, customMessage))
|
|
940
|
+
continue
|
|
941
|
+
}
|
|
942
|
+
restricted := control.hasImportNames && noRestrictedImportsContains(control.importNames, imported.name)
|
|
943
|
+
restricted = restricted || (control.importNamePattern != nil && control.importNamePattern.MatchString(imported.name))
|
|
944
|
+
if restricted {
|
|
945
|
+
message := fmt.Sprintf("'%s' import from '%s' is restricted.", imported.name, reference.source)
|
|
946
|
+
if pattern {
|
|
947
|
+
message = fmt.Sprintf("'%s' import from '%s' is restricted from being used by a pattern.", imported.name, reference.source)
|
|
948
|
+
}
|
|
949
|
+
imported.report(ctx, noRestrictedImportsCustomMessage(message, customMessage))
|
|
950
|
+
}
|
|
951
|
+
if control.hasAllowImportNames && !noRestrictedImportsContains(control.allowImportNames, imported.name) {
|
|
952
|
+
message := fmt.Sprintf(
|
|
953
|
+
"'%s' import from '%s' is restricted because only %s %s allowed.",
|
|
954
|
+
imported.name,
|
|
955
|
+
reference.source,
|
|
956
|
+
noRestrictedImportsFormatNames(control.allowImportNames),
|
|
957
|
+
noRestrictedImportsIsOrAre(control.allowImportNames),
|
|
958
|
+
)
|
|
959
|
+
imported.report(ctx, noRestrictedImportsCustomMessage(message, customMessage))
|
|
960
|
+
} else if control.allowImportNamePattern != nil && !control.allowImportNamePattern.MatchString(imported.name) {
|
|
961
|
+
message := fmt.Sprintf(
|
|
962
|
+
"'%s' import from '%s' is restricted because only imports that match the pattern '%s' are allowed from '%s'.",
|
|
963
|
+
imported.name,
|
|
964
|
+
reference.source,
|
|
965
|
+
control.allowImportNameText,
|
|
966
|
+
reference.source,
|
|
967
|
+
)
|
|
968
|
+
imported.report(ctx, noRestrictedImportsCustomMessage(message, customMessage))
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
func noRestrictedImportsNamespaceMessage(source string, control noRestrictedImportsNameControl, pattern bool) string {
|
|
974
|
+
if control.hasImportNames {
|
|
975
|
+
suffix := "restricted"
|
|
976
|
+
if pattern {
|
|
977
|
+
suffix = "restricted from being used by a pattern"
|
|
978
|
+
}
|
|
979
|
+
return fmt.Sprintf(
|
|
980
|
+
"* import is invalid because %s from '%s' %s %s.",
|
|
981
|
+
noRestrictedImportsFormatNames(control.importNames),
|
|
982
|
+
source,
|
|
983
|
+
noRestrictedImportsIsOrAre(control.importNames),
|
|
984
|
+
suffix,
|
|
985
|
+
)
|
|
986
|
+
}
|
|
987
|
+
if control.hasAllowImportNames {
|
|
988
|
+
return fmt.Sprintf(
|
|
989
|
+
"* import is invalid because only %s from '%s' %s allowed.",
|
|
990
|
+
noRestrictedImportsFormatNames(control.allowImportNames),
|
|
991
|
+
source,
|
|
992
|
+
noRestrictedImportsIsOrAre(control.allowImportNames),
|
|
993
|
+
)
|
|
994
|
+
}
|
|
995
|
+
if control.allowImportNamePattern != nil {
|
|
996
|
+
return fmt.Sprintf(
|
|
997
|
+
"* import is invalid because only imports that match the pattern '%s' from '%s' are allowed.",
|
|
998
|
+
control.allowImportNameText,
|
|
999
|
+
source,
|
|
1000
|
+
)
|
|
1001
|
+
}
|
|
1002
|
+
return fmt.Sprintf(
|
|
1003
|
+
"* import is invalid because import name matching '%s' pattern from '%s' is restricted from being used.",
|
|
1004
|
+
control.importNamePatternText,
|
|
1005
|
+
source,
|
|
1006
|
+
)
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
func (imported noRestrictedImportsImportedName) report(ctx *Context, message string) {
|
|
1010
|
+
if imported.node != nil {
|
|
1011
|
+
ctx.Report(imported.node, message)
|
|
1012
|
+
return
|
|
1013
|
+
}
|
|
1014
|
+
if imported.pos >= 0 && imported.end > imported.pos {
|
|
1015
|
+
ctx.ReportRange(imported.pos, imported.end, message)
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
func noRestrictedImportsContains(values []string, value string) bool {
|
|
1020
|
+
for _, candidate := range values {
|
|
1021
|
+
if candidate == value {
|
|
1022
|
+
return true
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
return false
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
func noRestrictedImportsFormatNames(names []string) string {
|
|
1029
|
+
quoted := make([]string, len(names))
|
|
1030
|
+
for index, name := range names {
|
|
1031
|
+
quoted[index] = "'" + name + "'"
|
|
1032
|
+
}
|
|
1033
|
+
switch len(quoted) {
|
|
1034
|
+
case 0:
|
|
1035
|
+
return ""
|
|
1036
|
+
case 1:
|
|
1037
|
+
return quoted[0]
|
|
1038
|
+
case 2:
|
|
1039
|
+
return quoted[0] + " and " + quoted[1]
|
|
1040
|
+
default:
|
|
1041
|
+
return strings.Join(quoted[:len(quoted)-1], ", ") + ", and " + quoted[len(quoted)-1]
|
|
84
1042
|
}
|
|
85
|
-
return nil
|
|
86
1043
|
}
|
|
87
1044
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
// the AST-layer test fires deterministically.
|
|
92
|
-
func resolveNoRestrictedImportsPaths(ctx *Context) []string {
|
|
93
|
-
if ctx == nil || len(ctx.Options) == 0 {
|
|
94
|
-
return hardCodedRestrictedImports
|
|
1045
|
+
func noRestrictedImportsIsOrAre(names []string) string {
|
|
1046
|
+
if len(names) == 1 {
|
|
1047
|
+
return "is"
|
|
95
1048
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
1049
|
+
return "are"
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
func noRestrictedImportsCustomMessage(message, custom string) string {
|
|
1053
|
+
if custom == "" {
|
|
1054
|
+
return message
|
|
99
1055
|
}
|
|
100
|
-
return
|
|
1056
|
+
return message + " " + custom
|
|
101
1057
|
}
|
|
102
1058
|
|
|
103
1059
|
func init() {
|