@ttsc/lint 0.18.4 → 0.19.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +125 -43
- package/go.mod +5 -4
- package/internal/rulecode/rulecode.go +88 -0
- package/lib/index.d.ts +10 -1
- package/lib/index.js +45 -17
- package/lib/index.js.map +1 -1
- package/lib/structures/ITtscLintConfig.d.ts +3 -0
- package/lib/structures/TtscLintRuleSetting.d.ts +3 -2
- package/lib/structures/format/ITtscLintFormat.d.ts +3 -2
- package/lib/structures/format/ITtscLintFormatSortImports.d.ts +20 -4
- package/lib/structures/rules/ITtscLintBoundariesRuleOptions.d.ts +80 -8
- package/lib/structures/rules/ITtscLintBoundariesRules.d.ts +6 -8
- package/lib/structures/rules/ITtscLintContributorRules.d.ts +18 -2
- package/lib/structures/rules/ITtscLintCoreRuleOptions.d.ts +347 -0
- package/lib/structures/rules/ITtscLintCoreRuleOptions.js +3 -0
- package/lib/structures/rules/ITtscLintCoreRuleOptions.js.map +1 -0
- package/lib/structures/rules/ITtscLintCoreRules.d.ts +101 -67
- package/lib/structures/rules/ITtscLintRegexpRules.d.ts +7 -2
- package/lib/structures/rules/ITtscLintRuleOptionsMap.d.ts +53 -7
- package/lib/structures/rules/ITtscLintRules.d.ts +6 -2
- package/lib/structures/rules/ITtscLintSolidRules.d.ts +0 -8
- package/lib/structures/rules/ITtscLintTypeScriptRuleOptions.d.ts +206 -0
- package/lib/structures/rules/ITtscLintTypeScriptRuleOptions.js +8 -0
- package/lib/structures/rules/ITtscLintTypeScriptRuleOptions.js.map +1 -0
- package/lib/structures/rules/ITtscLintTypeScriptRules.d.ts +55 -42
- package/lib/structures/rules/ITtscLintUnicornRuleOptions.d.ts +276 -0
- package/lib/structures/rules/ITtscLintUnicornRuleOptions.js +3 -0
- package/lib/structures/rules/ITtscLintUnicornRuleOptions.js.map +1 -0
- package/lib/structures/rules/ITtscLintUnicornRules.d.ts +44 -36
- package/lib/structures/rules/index.d.ts +3 -0
- package/lib/structures/rules/index.js +3 -0
- package/lib/structures/rules/index.js.map +1 -1
- package/linthost/ast_helpers.go +521 -23
- package/linthost/ast_selector_matcher.go +1628 -0
- package/linthost/ast_selector_parser.go +628 -0
- package/linthost/comment_scan.go +154 -0
- package/linthost/compile.go +129 -79
- package/linthost/config.go +352 -88
- package/linthost/config_format.go +15 -1
- package/linthost/contrib_adapter.go +47 -8
- package/linthost/declaration_rules.go +12 -11
- package/linthost/directives.go +9 -67
- package/linthost/dispatch.go +37 -3
- package/linthost/engine.go +368 -53
- package/linthost/escape_string.go +77 -0
- package/linthost/fix.go +133 -23
- package/linthost/flags_gen.go +18 -17
- package/linthost/format.go +73 -167
- package/linthost/format_editor_settings.go +134 -26
- package/linthost/grapheme_tables_gen.go +1920 -0
- package/linthost/host.go +159 -61
- package/linthost/literal_escape_scan.go +117 -0
- package/linthost/lsp.go +545 -98
- package/linthost/numeric_literal_precision.go +228 -0
- package/linthost/polyfill_browserslist.go +1439 -0
- package/linthost/polyfill_browserslist_config.go +603 -0
- package/linthost/polyfill_corejs.go +750 -0
- package/linthost/polyfill_data.go +113 -0
- package/linthost/polyfill_data_gen.json +39868 -0
- package/linthost/print_nodes_array.go +7 -1
- package/linthost/print_nodes_object.go +7 -1
- package/linthost/project_engine.go +231 -0
- package/linthost/project_rules.go +78 -0
- package/linthost/regex_clean.go +191 -0
- package/linthost/regex_tree.go +1544 -0
- package/linthost/regex_tree_optimizer.go +1685 -0
- package/linthost/regexp_cache.go +44 -0
- package/linthost/rule_codes.go +105 -0
- package/linthost/rule_codes.json +745 -0
- package/linthost/rules_ban_ts_comment.go +312 -0
- package/linthost/rules_boundaries.go +128 -43
- package/linthost/rules_boundaries_dependencies.go +1013 -0
- package/linthost/rules_core_extra.go +62 -92
- package/linthost/rules_cypress.go +1 -1
- package/linthost/rules_default_case.go +87 -11
- package/linthost/rules_dupes.go +38 -13
- package/linthost/rules_empty.go +188 -20
- package/linthost/rules_escape.go +62 -48
- package/linthost/rules_format_arrow_parens.go +19 -5
- package/linthost/rules_format_bracket_spacing.go +1 -1
- package/linthost/rules_format_clause_join.go +1 -1
- package/linthost/rules_format_declaration_header.go +31 -19
- package/linthost/rules_format_indent.go +1 -1
- package/linthost/rules_format_jsdoc.go +1 -1
- package/linthost/rules_format_orphan_semi.go +1 -1
- package/linthost/rules_format_parameter_properties.go +17 -7
- package/linthost/rules_format_print_width.go +1 -1
- package/linthost/rules_format_quote_props.go +1 -1
- package/linthost/rules_format_quotes.go +1 -1
- package/linthost/rules_format_semi.go +106 -79
- package/linthost/rules_format_sort_imports.go +79 -45
- package/linthost/rules_format_statement_split.go +1 -1
- package/linthost/rules_format_ternary_nullish_parens.go +1 -1
- package/linthost/rules_format_trailing_comma.go +64 -1
- package/linthost/rules_format_whitespace.go +1 -1
- package/linthost/rules_functional.go +21 -22
- package/linthost/rules_gap.go +80 -3
- package/linthost/rules_grouped_accessor_pairs.go +84 -25
- package/linthost/rules_guard_for_in.go +64 -147
- package/linthost/rules_jsx_a11y.go +232 -22
- package/linthost/rules_logic.go +84 -38
- package/linthost/rules_nextjs.go +105 -16
- package/linthost/rules_no_class_assign.go +70 -0
- package/linthost/rules_no_duplicate_imports.go +266 -0
- package/linthost/rules_no_else_return.go +156 -38
- package/linthost/rules_no_extend_native.go +113 -17
- package/linthost/rules_no_fallthrough.go +1013 -0
- package/linthost/rules_no_func_assign.go +70 -0
- package/linthost/rules_no_import_assign.go +278 -86
- package/linthost/rules_no_inner_declarations.go +186 -0
- package/linthost/rules_no_loop_func.go +438 -53
- package/linthost/rules_no_mixed_operators.go +208 -52
- package/linthost/rules_no_param_reassign.go +376 -61
- package/linthost/rules_no_promise_executor_return.go +156 -0
- package/linthost/rules_no_restricted_imports.go +1013 -57
- package/linthost/rules_no_restricted_syntax.go +190 -26
- package/linthost/rules_no_useless_constructor.go +6 -36
- package/linthost/rules_problems.go +154 -311
- package/linthost/rules_promise.go +2578 -109
- package/linthost/rules_react_extras.go +71 -45
- package/linthost/rules_react_hooks.go +18 -2
- package/linthost/rules_react_perf.go +1 -0
- package/linthost/rules_react_refresh.go +1 -1
- package/linthost/rules_regexp.go +230 -14
- package/linthost/rules_security.go +45 -19
- package/linthost/rules_self.go +21 -1
- package/linthost/rules_solid.go +13 -7
- package/linthost/rules_storybook.go +1 -1
- package/linthost/rules_suggestions.go +490 -84
- package/linthost/rules_testing_library.go +12 -1
- package/linthost/rules_ts.go +227 -54
- package/linthost/rules_ts_async.go +0 -131
- package/linthost/rules_ts_no_base_to_string.go +3 -4
- package/linthost/rules_ts_no_misused_promises.go +817 -0
- package/linthost/rules_ts_no_restricted_types.go +320 -34
- package/linthost/rules_ts_no_unnecessary_qualifier.go +132 -55
- package/linthost/rules_ts_no_unsafe_assignment.go +608 -60
- package/linthost/rules_ts_require_array_sort_compare.go +5 -5
- package/linthost/rules_ts_switch_exhaustiveness_check.go +556 -86
- package/linthost/rules_unicorn_better_regex.go +224 -16
- package/linthost/rules_unicorn_consistent_destructuring.go +1002 -7
- package/linthost/rules_unicorn_consistent_existence_index_check.go +208 -29
- package/linthost/rules_unicorn_consistent_function_scoping.go +1041 -8
- package/linthost/rules_unicorn_consistent_template_literal_escape.go +178 -7
- package/linthost/rules_unicorn_escape_case.go +30 -19
- package/linthost/rules_unicorn_filename_case.go +763 -16
- package/linthost/rules_unicorn_import_style.go +783 -17
- package/linthost/rules_unicorn_isolated_functions.go +934 -14
- package/linthost/rules_unicorn_no_hex_escape.go +26 -11
- package/linthost/rules_unicorn_no_typeof_undefined.go +234 -25
- package/linthost/rules_unicorn_no_unnecessary_polyfills.go +376 -21
- package/linthost/rules_unicorn_no_unused_properties.go +756 -19
- package/linthost/rules_unicorn_number_literal_case.go +58 -39
- package/linthost/rules_unicorn_prefer_at.go +7 -3
- package/linthost/rules_unicorn_prefer_number_properties.go +381 -48
- package/linthost/rules_unicorn_prefer_optional_catch_binding.go +24 -24
- package/linthost/rules_unicorn_prefer_simple_condition_first.go +597 -19
- package/linthost/rules_unicorn_prefer_string_raw.go +147 -16
- package/linthost/rules_unicorn_prevent_abbreviations.go +1963 -95
- package/linthost/rules_unicorn_string_content.go +611 -15
- package/linthost/rules_unicorn_switch_case_break_position.go +172 -15
- package/linthost/rules_unicorn_template_indent.go +923 -18
- package/linthost/rules_unicorn_text_encoding_identifier_case.go +202 -42
- package/linthost/rules_unicorn_throw_new_error.go +218 -26
- package/linthost/rules_var.go +829 -142
- package/linthost/string_length.go +222 -0
- package/package.json +4 -3
- package/rule/project.go +206 -0
- package/rule/rule.go +58 -1
- package/src/index.ts +58 -17
- package/src/structures/ITtscLintConfig.ts +3 -0
- package/src/structures/TtscLintRuleSetting.ts +3 -2
- package/src/structures/format/ITtscLintFormat.ts +3 -2
- package/src/structures/format/ITtscLintFormatSortImports.ts +21 -4
- package/src/structures/rules/ITtscLintBoundariesRuleOptions.ts +122 -8
- package/src/structures/rules/ITtscLintBoundariesRules.ts +6 -8
- package/src/structures/rules/ITtscLintContributorRules.ts +18 -2
- package/src/structures/rules/ITtscLintCoreRuleOptions.ts +465 -0
- package/src/structures/rules/ITtscLintCoreRules.ts +122 -67
- package/src/structures/rules/ITtscLintRegexpRules.ts +7 -2
- package/src/structures/rules/ITtscLintRuleOptionsMap.ts +88 -7
- package/src/structures/rules/ITtscLintRules.ts +7 -2
- package/src/structures/rules/ITtscLintSolidRules.ts +0 -9
- package/src/structures/rules/ITtscLintTypeScriptRuleOptions.ts +248 -0
- package/src/structures/rules/ITtscLintTypeScriptRules.ts +64 -42
- package/src/structures/rules/ITtscLintUnicornRuleOptions.ts +350 -0
- package/src/structures/rules/ITtscLintUnicornRules.ts +60 -36
- package/src/structures/rules/index.ts +3 -0
|
@@ -0,0 +1,1013 @@
|
|
|
1
|
+
// boundaries/dependencies is the unified, direction-aware boundary policy.
|
|
2
|
+
// It keeps the repository's project-local element declarations while following
|
|
3
|
+
// upstream's policy order: the last matching policy wins, and a disallow match
|
|
4
|
+
// takes precedence over allow within one policy. String selectors remain the
|
|
5
|
+
// shorthand for element types; object selectors add origin, source, path,
|
|
6
|
+
// entry/private/unknown state, and dependency metadata without coupling the
|
|
7
|
+
// rule to a particular repository layout.
|
|
8
|
+
//
|
|
9
|
+
// Module targets come from the TypeScript checker when available. That makes
|
|
10
|
+
// tsconfig path aliases and re-exports classify by their resolved source file,
|
|
11
|
+
// while the filesystem resolver remains the AST-only fallback used by the
|
|
12
|
+
// legacy boundary rules and focused tests.
|
|
13
|
+
// https://github.com/javierbrea/eslint-plugin-boundaries/blob/master/packages/website/docs/rules/dependencies.md
|
|
14
|
+
package linthost
|
|
15
|
+
|
|
16
|
+
import (
|
|
17
|
+
"bytes"
|
|
18
|
+
"encoding/json"
|
|
19
|
+
"fmt"
|
|
20
|
+
"path/filepath"
|
|
21
|
+
"sort"
|
|
22
|
+
"strings"
|
|
23
|
+
|
|
24
|
+
shimast "github.com/microsoft/typescript-go/shim/ast"
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
type boundariesDependencies struct{ optionsRule }
|
|
28
|
+
|
|
29
|
+
type boundaryDependenciesOptions struct {
|
|
30
|
+
Elements []boundaryElement
|
|
31
|
+
Policies []boundaryDependenciesPolicy
|
|
32
|
+
Default string
|
|
33
|
+
Message string
|
|
34
|
+
CheckAllOrigins bool
|
|
35
|
+
CheckUnknownLocals bool
|
|
36
|
+
CheckInternals bool
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
type boundaryDependenciesPolicy struct {
|
|
40
|
+
From []boundaryDependenciesEntitySelector
|
|
41
|
+
To []boundaryDependenciesEntitySelector
|
|
42
|
+
Dependency []boundaryDependenciesInfoSelector
|
|
43
|
+
Allow []boundaryDependenciesSelector
|
|
44
|
+
Disallow []boundaryDependenciesSelector
|
|
45
|
+
ImportKind string
|
|
46
|
+
Message string
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
type boundaryDependenciesEntitySelector struct {
|
|
50
|
+
Types boundaryStringList
|
|
51
|
+
Origins boundaryStringList
|
|
52
|
+
Sources boundaryStringList
|
|
53
|
+
Paths boundaryStringList
|
|
54
|
+
Entry *bool
|
|
55
|
+
Private *bool
|
|
56
|
+
Unknown *bool
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
type boundaryDependenciesInfoSelector struct {
|
|
60
|
+
Kinds boundaryStringList
|
|
61
|
+
Sources boundaryStringList
|
|
62
|
+
NodeKinds boundaryStringList
|
|
63
|
+
Specifiers boundaryStringList
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
type boundaryDependenciesSelector struct {
|
|
67
|
+
From []boundaryDependenciesEntitySelector
|
|
68
|
+
To []boundaryDependenciesEntitySelector
|
|
69
|
+
Dependency []boundaryDependenciesInfoSelector
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
type boundaryDependenciesEntity struct {
|
|
73
|
+
Type string
|
|
74
|
+
Origin string
|
|
75
|
+
Source string
|
|
76
|
+
Path string
|
|
77
|
+
Entry bool
|
|
78
|
+
Private bool
|
|
79
|
+
Unknown bool
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
type boundaryDependenciesDescription struct {
|
|
83
|
+
From boundaryDependenciesEntity
|
|
84
|
+
To boundaryDependenciesEntity
|
|
85
|
+
Dependency boundaryDependency
|
|
86
|
+
Internal bool
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
type boundaryDependenciesEvaluation struct {
|
|
90
|
+
allowed bool
|
|
91
|
+
policyIndex int
|
|
92
|
+
policy *boundaryDependenciesPolicy
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
func (boundariesDependencies) NeedsTypeChecker() bool { return true }
|
|
96
|
+
|
|
97
|
+
func (boundariesDependencies) ValidateOptions(raw json.RawMessage) error {
|
|
98
|
+
_, err := parseBoundaryDependenciesOptions(raw)
|
|
99
|
+
return err
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
func (boundariesDependencies) Check(ctx *Context, node *shimast.Node) {
|
|
103
|
+
if ctx == nil || ctx.File == nil || node == nil {
|
|
104
|
+
return
|
|
105
|
+
}
|
|
106
|
+
options, err := parseBoundaryDependenciesOptions(ctx.Options)
|
|
107
|
+
if err != nil {
|
|
108
|
+
return
|
|
109
|
+
}
|
|
110
|
+
source := classifyBoundaryFile(ctx.File.FileName(), options.Elements)
|
|
111
|
+
if source == nil {
|
|
112
|
+
return
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
for _, dependency := range collectBoundaryPolicyDependencies(node) {
|
|
116
|
+
if boundaryDependenciesShadowedRequire(ctx, dependency) {
|
|
117
|
+
continue
|
|
118
|
+
}
|
|
119
|
+
description := describeBoundaryDependency(ctx, options, source, dependency)
|
|
120
|
+
if description.To.Origin != "local" && !options.CheckAllOrigins {
|
|
121
|
+
continue
|
|
122
|
+
}
|
|
123
|
+
if description.To.Origin == "local" && description.To.Unknown && !options.CheckUnknownLocals {
|
|
124
|
+
continue
|
|
125
|
+
}
|
|
126
|
+
if description.Internal && !options.CheckInternals {
|
|
127
|
+
continue
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
evaluation := evaluateBoundaryDependencies(options, description)
|
|
131
|
+
if evaluation.allowed {
|
|
132
|
+
continue
|
|
133
|
+
}
|
|
134
|
+
message := boundaryDependenciesMessage(options, description, evaluation)
|
|
135
|
+
reportBoundaryDependency(ctx, dependency, message)
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
func parseBoundaryDependenciesOptions(raw json.RawMessage) (boundaryDependenciesOptions, error) {
|
|
140
|
+
options := boundaryDependenciesOptions{Default: "disallow"}
|
|
141
|
+
raw = bytes.TrimSpace(raw)
|
|
142
|
+
if len(raw) == 0 {
|
|
143
|
+
return options, nil
|
|
144
|
+
}
|
|
145
|
+
fields, err := boundaryDependenciesObject(raw, "options")
|
|
146
|
+
if err != nil {
|
|
147
|
+
return options, err
|
|
148
|
+
}
|
|
149
|
+
if err := boundaryDependenciesRejectUnknown(
|
|
150
|
+
fields,
|
|
151
|
+
"elements",
|
|
152
|
+
"policies",
|
|
153
|
+
"rules",
|
|
154
|
+
"default",
|
|
155
|
+
"message",
|
|
156
|
+
"checkAllOrigins",
|
|
157
|
+
"checkUnknownLocals",
|
|
158
|
+
"checkInternals",
|
|
159
|
+
); err != nil {
|
|
160
|
+
return options, err
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if value, present := fields["default"]; present {
|
|
164
|
+
options.Default, err = boundaryDependenciesString(value, "default")
|
|
165
|
+
if err != nil {
|
|
166
|
+
return options, err
|
|
167
|
+
}
|
|
168
|
+
if options.Default != "allow" && options.Default != "disallow" {
|
|
169
|
+
return options, fmt.Errorf("option %q must be %q or %q", "default", "allow", "disallow")
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
if value, present := fields["message"]; present {
|
|
173
|
+
options.Message, err = boundaryDependenciesString(value, "message")
|
|
174
|
+
if err != nil {
|
|
175
|
+
return options, err
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
if value, present := fields["checkAllOrigins"]; present {
|
|
179
|
+
options.CheckAllOrigins, err = boundaryDependenciesBool(value, "checkAllOrigins")
|
|
180
|
+
if err != nil {
|
|
181
|
+
return options, err
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
if value, present := fields["checkUnknownLocals"]; present {
|
|
185
|
+
options.CheckUnknownLocals, err = boundaryDependenciesBool(value, "checkUnknownLocals")
|
|
186
|
+
if err != nil {
|
|
187
|
+
return options, err
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
if value, present := fields["checkInternals"]; present {
|
|
191
|
+
options.CheckInternals, err = boundaryDependenciesBool(value, "checkInternals")
|
|
192
|
+
if err != nil {
|
|
193
|
+
return options, err
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
if value, present := fields["elements"]; present {
|
|
197
|
+
options.Elements, err = parseBoundaryDependenciesElements(value)
|
|
198
|
+
if err != nil {
|
|
199
|
+
return options, err
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
policiesRaw, hasPolicies := fields["policies"]
|
|
204
|
+
rulesRaw, hasRules := fields["rules"]
|
|
205
|
+
if hasPolicies && hasRules {
|
|
206
|
+
return options, fmt.Errorf("options %q and %q cannot be combined", "policies", "rules")
|
|
207
|
+
}
|
|
208
|
+
policiesKey := "policies"
|
|
209
|
+
if !hasPolicies {
|
|
210
|
+
policiesRaw = rulesRaw
|
|
211
|
+
policiesKey = "rules"
|
|
212
|
+
}
|
|
213
|
+
if hasPolicies || hasRules {
|
|
214
|
+
options.Policies, err = parseBoundaryDependenciesPolicies(policiesRaw, policiesKey)
|
|
215
|
+
if err != nil {
|
|
216
|
+
return options, err
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return options, nil
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
func parseBoundaryDependenciesElements(raw json.RawMessage) ([]boundaryElement, error) {
|
|
223
|
+
entries, err := boundaryDependenciesArray(raw, "elements")
|
|
224
|
+
if err != nil {
|
|
225
|
+
return nil, err
|
|
226
|
+
}
|
|
227
|
+
elements := make([]boundaryElement, 0, len(entries))
|
|
228
|
+
for index, entry := range entries {
|
|
229
|
+
path := fmt.Sprintf("elements[%d]", index)
|
|
230
|
+
fields, err := boundaryDependenciesObject(entry, path)
|
|
231
|
+
if err != nil {
|
|
232
|
+
return nil, err
|
|
233
|
+
}
|
|
234
|
+
if err := boundaryDependenciesRejectUnknown(fields, "type", "pattern", "entry", "private"); err != nil {
|
|
235
|
+
return nil, fmt.Errorf("%s: %w", path, err)
|
|
236
|
+
}
|
|
237
|
+
typeRaw, hasType := fields["type"]
|
|
238
|
+
patternRaw, hasPattern := fields["pattern"]
|
|
239
|
+
if !hasType || !hasPattern {
|
|
240
|
+
return nil, fmt.Errorf("%s requires non-empty %q and %q", path, "type", "pattern")
|
|
241
|
+
}
|
|
242
|
+
element := boundaryElement{}
|
|
243
|
+
if element.Type, err = boundaryDependenciesString(typeRaw, path+".type"); err != nil {
|
|
244
|
+
return nil, err
|
|
245
|
+
}
|
|
246
|
+
if element.Pattern, err = boundaryDependenciesString(patternRaw, path+".pattern"); err != nil {
|
|
247
|
+
return nil, err
|
|
248
|
+
}
|
|
249
|
+
if element.Type == "" || element.Pattern == "" {
|
|
250
|
+
return nil, fmt.Errorf("%s requires non-empty %q and %q", path, "type", "pattern")
|
|
251
|
+
}
|
|
252
|
+
if value, present := fields["entry"]; present {
|
|
253
|
+
element.Entry, err = boundaryDependenciesStringList(value, path+".entry")
|
|
254
|
+
if err != nil {
|
|
255
|
+
return nil, err
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
if value, present := fields["private"]; present {
|
|
259
|
+
element.Private, err = boundaryDependenciesStringList(value, path+".private")
|
|
260
|
+
if err != nil {
|
|
261
|
+
return nil, err
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
elements = append(elements, element)
|
|
265
|
+
}
|
|
266
|
+
return elements, nil
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
func parseBoundaryDependenciesPolicies(raw json.RawMessage, key string) ([]boundaryDependenciesPolicy, error) {
|
|
270
|
+
entries, err := boundaryDependenciesArray(raw, key)
|
|
271
|
+
if err != nil {
|
|
272
|
+
return nil, err
|
|
273
|
+
}
|
|
274
|
+
policies := make([]boundaryDependenciesPolicy, 0, len(entries))
|
|
275
|
+
for index, entry := range entries {
|
|
276
|
+
policy, err := parseBoundaryDependenciesPolicy(entry, fmt.Sprintf("%s[%d]", key, index))
|
|
277
|
+
if err != nil {
|
|
278
|
+
return nil, err
|
|
279
|
+
}
|
|
280
|
+
policies = append(policies, policy)
|
|
281
|
+
}
|
|
282
|
+
return policies, nil
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
func parseBoundaryDependenciesPolicy(raw json.RawMessage, path string) (boundaryDependenciesPolicy, error) {
|
|
286
|
+
var policy boundaryDependenciesPolicy
|
|
287
|
+
fields, err := boundaryDependenciesObject(raw, path)
|
|
288
|
+
if err != nil {
|
|
289
|
+
return policy, err
|
|
290
|
+
}
|
|
291
|
+
if err := boundaryDependenciesRejectUnknown(
|
|
292
|
+
fields,
|
|
293
|
+
"from",
|
|
294
|
+
"to",
|
|
295
|
+
"dependency",
|
|
296
|
+
"allow",
|
|
297
|
+
"disallow",
|
|
298
|
+
"importKind",
|
|
299
|
+
"message",
|
|
300
|
+
); err != nil {
|
|
301
|
+
return policy, fmt.Errorf("%s: %w", path, err)
|
|
302
|
+
}
|
|
303
|
+
if value, present := fields["from"]; present {
|
|
304
|
+
policy.From, err = parseBoundaryDependenciesEntitySelectors(value, path+".from")
|
|
305
|
+
if err != nil {
|
|
306
|
+
return policy, err
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
if value, present := fields["to"]; present {
|
|
310
|
+
policy.To, err = parseBoundaryDependenciesEntitySelectors(value, path+".to")
|
|
311
|
+
if err != nil {
|
|
312
|
+
return policy, err
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
if value, present := fields["dependency"]; present {
|
|
316
|
+
policy.Dependency, err = parseBoundaryDependenciesInfoSelectors(value, path+".dependency")
|
|
317
|
+
if err != nil {
|
|
318
|
+
return policy, err
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
_, hasFrom := fields["from"]
|
|
322
|
+
if value, present := fields["allow"]; present {
|
|
323
|
+
policy.Allow, err = parseBoundaryDependenciesEffect(value, path+".allow", hasFrom)
|
|
324
|
+
if err != nil {
|
|
325
|
+
return policy, err
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
if value, present := fields["disallow"]; present {
|
|
329
|
+
policy.Disallow, err = parseBoundaryDependenciesEffect(value, path+".disallow", hasFrom)
|
|
330
|
+
if err != nil {
|
|
331
|
+
return policy, err
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
if len(policy.Allow) == 0 && len(policy.Disallow) == 0 {
|
|
335
|
+
return policy, fmt.Errorf("%s requires at least one of %q or %q", path, "allow", "disallow")
|
|
336
|
+
}
|
|
337
|
+
if value, present := fields["importKind"]; present {
|
|
338
|
+
policy.ImportKind, err = boundaryDependenciesString(value, path+".importKind")
|
|
339
|
+
if err != nil {
|
|
340
|
+
return policy, err
|
|
341
|
+
}
|
|
342
|
+
if !boundaryDependenciesValidKind(policy.ImportKind) {
|
|
343
|
+
return policy, fmt.Errorf("%s.importKind must be %q, %q, or %q", path, "value", "type", "typeof")
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
if value, present := fields["message"]; present {
|
|
347
|
+
policy.Message, err = boundaryDependenciesString(value, path+".message")
|
|
348
|
+
if err != nil {
|
|
349
|
+
return policy, err
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
return policy, nil
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
func parseBoundaryDependenciesEntitySelectors(raw json.RawMessage, path string) ([]boundaryDependenciesEntitySelector, error) {
|
|
356
|
+
raw = bytes.TrimSpace(raw)
|
|
357
|
+
if len(raw) == 0 {
|
|
358
|
+
return nil, fmt.Errorf("%s must be an entity selector", path)
|
|
359
|
+
}
|
|
360
|
+
switch raw[0] {
|
|
361
|
+
case '"':
|
|
362
|
+
value, err := boundaryDependenciesString(raw, path)
|
|
363
|
+
if err != nil {
|
|
364
|
+
return nil, err
|
|
365
|
+
}
|
|
366
|
+
if value == "" {
|
|
367
|
+
return nil, fmt.Errorf("%s must not be empty", path)
|
|
368
|
+
}
|
|
369
|
+
return []boundaryDependenciesEntitySelector{{Types: boundaryStringList{value}}}, nil
|
|
370
|
+
case '[':
|
|
371
|
+
entries, err := boundaryDependenciesArray(raw, path)
|
|
372
|
+
if err != nil {
|
|
373
|
+
return nil, err
|
|
374
|
+
}
|
|
375
|
+
if len(entries) == 0 {
|
|
376
|
+
return nil, fmt.Errorf("%s must not be empty", path)
|
|
377
|
+
}
|
|
378
|
+
selectors := make([]boundaryDependenciesEntitySelector, 0, len(entries))
|
|
379
|
+
for index, entry := range entries {
|
|
380
|
+
parsed, err := parseBoundaryDependenciesEntitySelectors(entry, fmt.Sprintf("%s[%d]", path, index))
|
|
381
|
+
if err != nil {
|
|
382
|
+
return nil, err
|
|
383
|
+
}
|
|
384
|
+
selectors = append(selectors, parsed...)
|
|
385
|
+
}
|
|
386
|
+
return selectors, nil
|
|
387
|
+
case '{':
|
|
388
|
+
selector, err := parseBoundaryDependenciesEntitySelectorObject(raw, path)
|
|
389
|
+
if err != nil {
|
|
390
|
+
return nil, err
|
|
391
|
+
}
|
|
392
|
+
return []boundaryDependenciesEntitySelector{selector}, nil
|
|
393
|
+
default:
|
|
394
|
+
return nil, fmt.Errorf("%s must be a string, object, or array of selectors", path)
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
func parseBoundaryDependenciesEntitySelectorObject(raw json.RawMessage, path string) (boundaryDependenciesEntitySelector, error) {
|
|
399
|
+
var selector boundaryDependenciesEntitySelector
|
|
400
|
+
fields, err := boundaryDependenciesObject(raw, path)
|
|
401
|
+
if err != nil {
|
|
402
|
+
return selector, err
|
|
403
|
+
}
|
|
404
|
+
if err := boundaryDependenciesRejectUnknown(fields, "type", "origin", "source", "path", "entry", "private", "unknown"); err != nil {
|
|
405
|
+
return selector, fmt.Errorf("%s: %w", path, err)
|
|
406
|
+
}
|
|
407
|
+
if value, present := fields["type"]; present {
|
|
408
|
+
selector.Types, err = boundaryDependenciesStringList(value, path+".type")
|
|
409
|
+
if err != nil {
|
|
410
|
+
return selector, err
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
if value, present := fields["origin"]; present {
|
|
414
|
+
selector.Origins, err = boundaryDependenciesStringList(value, path+".origin")
|
|
415
|
+
if err != nil {
|
|
416
|
+
return selector, err
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
if value, present := fields["source"]; present {
|
|
420
|
+
selector.Sources, err = boundaryDependenciesStringList(value, path+".source")
|
|
421
|
+
if err != nil {
|
|
422
|
+
return selector, err
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
if value, present := fields["path"]; present {
|
|
426
|
+
selector.Paths, err = boundaryDependenciesStringList(value, path+".path")
|
|
427
|
+
if err != nil {
|
|
428
|
+
return selector, err
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
if value, present := fields["entry"]; present {
|
|
432
|
+
selected, err := boundaryDependenciesBool(value, path+".entry")
|
|
433
|
+
if err != nil {
|
|
434
|
+
return selector, err
|
|
435
|
+
}
|
|
436
|
+
selector.Entry = &selected
|
|
437
|
+
}
|
|
438
|
+
if value, present := fields["private"]; present {
|
|
439
|
+
selected, err := boundaryDependenciesBool(value, path+".private")
|
|
440
|
+
if err != nil {
|
|
441
|
+
return selector, err
|
|
442
|
+
}
|
|
443
|
+
selector.Private = &selected
|
|
444
|
+
}
|
|
445
|
+
if value, present := fields["unknown"]; present {
|
|
446
|
+
selected, err := boundaryDependenciesBool(value, path+".unknown")
|
|
447
|
+
if err != nil {
|
|
448
|
+
return selector, err
|
|
449
|
+
}
|
|
450
|
+
selector.Unknown = &selected
|
|
451
|
+
}
|
|
452
|
+
return selector, nil
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
func parseBoundaryDependenciesInfoSelectors(raw json.RawMessage, path string) ([]boundaryDependenciesInfoSelector, error) {
|
|
456
|
+
raw = bytes.TrimSpace(raw)
|
|
457
|
+
if len(raw) == 0 {
|
|
458
|
+
return nil, fmt.Errorf("%s must be a dependency selector", path)
|
|
459
|
+
}
|
|
460
|
+
if raw[0] == '[' {
|
|
461
|
+
entries, err := boundaryDependenciesArray(raw, path)
|
|
462
|
+
if err != nil {
|
|
463
|
+
return nil, err
|
|
464
|
+
}
|
|
465
|
+
if len(entries) == 0 {
|
|
466
|
+
return nil, fmt.Errorf("%s must not be empty", path)
|
|
467
|
+
}
|
|
468
|
+
selectors := make([]boundaryDependenciesInfoSelector, 0, len(entries))
|
|
469
|
+
for index, entry := range entries {
|
|
470
|
+
parsed, err := parseBoundaryDependenciesInfoSelectors(entry, fmt.Sprintf("%s[%d]", path, index))
|
|
471
|
+
if err != nil {
|
|
472
|
+
return nil, err
|
|
473
|
+
}
|
|
474
|
+
selectors = append(selectors, parsed...)
|
|
475
|
+
}
|
|
476
|
+
return selectors, nil
|
|
477
|
+
}
|
|
478
|
+
fields, err := boundaryDependenciesObject(raw, path)
|
|
479
|
+
if err != nil {
|
|
480
|
+
return nil, err
|
|
481
|
+
}
|
|
482
|
+
if err := boundaryDependenciesRejectUnknown(fields, "kind", "source", "nodeKind", "specifiers"); err != nil {
|
|
483
|
+
return nil, fmt.Errorf("%s: %w", path, err)
|
|
484
|
+
}
|
|
485
|
+
selector := boundaryDependenciesInfoSelector{}
|
|
486
|
+
if value, present := fields["kind"]; present {
|
|
487
|
+
selector.Kinds, err = boundaryDependenciesStringList(value, path+".kind")
|
|
488
|
+
if err != nil {
|
|
489
|
+
return nil, err
|
|
490
|
+
}
|
|
491
|
+
for _, kind := range selector.Kinds {
|
|
492
|
+
if kind != "*" && !boundaryDependenciesValidKind(kind) {
|
|
493
|
+
return nil, fmt.Errorf("%s.kind contains unsupported value %q", path, kind)
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
if value, present := fields["source"]; present {
|
|
498
|
+
selector.Sources, err = boundaryDependenciesStringList(value, path+".source")
|
|
499
|
+
if err != nil {
|
|
500
|
+
return nil, err
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
if value, present := fields["nodeKind"]; present {
|
|
504
|
+
selector.NodeKinds, err = boundaryDependenciesStringList(value, path+".nodeKind")
|
|
505
|
+
if err != nil {
|
|
506
|
+
return nil, err
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
if value, present := fields["specifiers"]; present {
|
|
510
|
+
selector.Specifiers, err = boundaryDependenciesStringList(value, path+".specifiers")
|
|
511
|
+
if err != nil {
|
|
512
|
+
return nil, err
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
return []boundaryDependenciesInfoSelector{selector}, nil
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
func parseBoundaryDependenciesEffect(raw json.RawMessage, path string, targetsDestination bool) ([]boundaryDependenciesSelector, error) {
|
|
519
|
+
raw = bytes.TrimSpace(raw)
|
|
520
|
+
if len(raw) == 0 {
|
|
521
|
+
return nil, fmt.Errorf("%s must be a selector", path)
|
|
522
|
+
}
|
|
523
|
+
if raw[0] == '[' {
|
|
524
|
+
entries, err := boundaryDependenciesArray(raw, path)
|
|
525
|
+
if err != nil {
|
|
526
|
+
return nil, err
|
|
527
|
+
}
|
|
528
|
+
if len(entries) == 0 {
|
|
529
|
+
return nil, fmt.Errorf("%s must not be empty", path)
|
|
530
|
+
}
|
|
531
|
+
selectors := make([]boundaryDependenciesSelector, 0, len(entries))
|
|
532
|
+
for index, entry := range entries {
|
|
533
|
+
parsed, err := parseBoundaryDependenciesEffect(entry, fmt.Sprintf("%s[%d]", path, index), targetsDestination)
|
|
534
|
+
if err != nil {
|
|
535
|
+
return nil, err
|
|
536
|
+
}
|
|
537
|
+
selectors = append(selectors, parsed...)
|
|
538
|
+
}
|
|
539
|
+
return selectors, nil
|
|
540
|
+
}
|
|
541
|
+
if raw[0] == '{' {
|
|
542
|
+
fields, err := boundaryDependenciesObject(raw, path)
|
|
543
|
+
if err != nil {
|
|
544
|
+
return nil, err
|
|
545
|
+
}
|
|
546
|
+
_, hasFrom := fields["from"]
|
|
547
|
+
_, hasTo := fields["to"]
|
|
548
|
+
_, hasDependency := fields["dependency"]
|
|
549
|
+
if hasFrom || hasTo || hasDependency {
|
|
550
|
+
if err := boundaryDependenciesRejectUnknown(fields, "from", "to", "dependency"); err != nil {
|
|
551
|
+
return nil, fmt.Errorf("%s: %w", path, err)
|
|
552
|
+
}
|
|
553
|
+
selector := boundaryDependenciesSelector{}
|
|
554
|
+
if hasFrom {
|
|
555
|
+
selector.From, err = parseBoundaryDependenciesEntitySelectors(fields["from"], path+".from")
|
|
556
|
+
if err != nil {
|
|
557
|
+
return nil, err
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
if hasTo {
|
|
561
|
+
selector.To, err = parseBoundaryDependenciesEntitySelectors(fields["to"], path+".to")
|
|
562
|
+
if err != nil {
|
|
563
|
+
return nil, err
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
if hasDependency {
|
|
567
|
+
selector.Dependency, err = parseBoundaryDependenciesInfoSelectors(fields["dependency"], path+".dependency")
|
|
568
|
+
if err != nil {
|
|
569
|
+
return nil, err
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
return []boundaryDependenciesSelector{selector}, nil
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
entities, err := parseBoundaryDependenciesEntitySelectors(raw, path)
|
|
576
|
+
if err != nil {
|
|
577
|
+
return nil, err
|
|
578
|
+
}
|
|
579
|
+
selectors := make([]boundaryDependenciesSelector, 0, len(entities))
|
|
580
|
+
for _, entity := range entities {
|
|
581
|
+
selector := boundaryDependenciesSelector{}
|
|
582
|
+
if targetsDestination {
|
|
583
|
+
selector.To = []boundaryDependenciesEntitySelector{entity}
|
|
584
|
+
} else {
|
|
585
|
+
selector.From = []boundaryDependenciesEntitySelector{entity}
|
|
586
|
+
}
|
|
587
|
+
selectors = append(selectors, selector)
|
|
588
|
+
}
|
|
589
|
+
return selectors, nil
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
func describeBoundaryDependency(
|
|
593
|
+
ctx *Context,
|
|
594
|
+
options boundaryDependenciesOptions,
|
|
595
|
+
source *boundaryFile,
|
|
596
|
+
dependency boundaryDependency,
|
|
597
|
+
) boundaryDependenciesDescription {
|
|
598
|
+
from := boundaryDependenciesEntityFromFile(source, ctx.File.FileName(), "local")
|
|
599
|
+
resolvedPath, resolved := resolveBoundaryDependencyPath(ctx, dependency)
|
|
600
|
+
local := dependency.relative || (resolved && boundaryDependenciesProjectLocalPath(ctx.CurrentDirectory, resolvedPath))
|
|
601
|
+
if local {
|
|
602
|
+
if !resolved && dependency.relative {
|
|
603
|
+
resolvedPath = filepath.Clean(filepath.Join(filepath.Dir(ctx.File.FileName()), filepath.FromSlash(dependency.specifier)))
|
|
604
|
+
}
|
|
605
|
+
target := classifyBoundaryFile(resolvedPath, options.Elements)
|
|
606
|
+
to := boundaryDependenciesEntityFromFile(target, resolvedPath, "local")
|
|
607
|
+
to.Source = dependency.specifier
|
|
608
|
+
to.Unknown = target == nil
|
|
609
|
+
return boundaryDependenciesDescription{
|
|
610
|
+
From: from,
|
|
611
|
+
To: to,
|
|
612
|
+
Dependency: dependency,
|
|
613
|
+
Internal: target != nil && source.RootPath != "" && source.RootPath == target.RootPath,
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
origin := boundaryDependenciesOrigin(dependency.specifier)
|
|
618
|
+
return boundaryDependenciesDescription{
|
|
619
|
+
From: from,
|
|
620
|
+
To: boundaryDependenciesEntity{
|
|
621
|
+
Origin: origin,
|
|
622
|
+
Source: dependency.specifier,
|
|
623
|
+
Path: dependency.specifier,
|
|
624
|
+
},
|
|
625
|
+
Dependency: dependency,
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
func boundaryDependenciesEntityFromFile(file *boundaryFile, path, origin string) boundaryDependenciesEntity {
|
|
630
|
+
entity := boundaryDependenciesEntity{
|
|
631
|
+
Origin: origin,
|
|
632
|
+
Source: normalizeBoundaryPath(path),
|
|
633
|
+
Path: boundaryDisplayPath(path),
|
|
634
|
+
}
|
|
635
|
+
if file == nil {
|
|
636
|
+
return entity
|
|
637
|
+
}
|
|
638
|
+
entity.Type = file.Type
|
|
639
|
+
entity.Path = file.LocalPath
|
|
640
|
+
if entity.Path == "" {
|
|
641
|
+
entity.Path = file.RelativePath
|
|
642
|
+
}
|
|
643
|
+
entity.Entry = matchBoundaryElementLocalPattern(file.Entry, file)
|
|
644
|
+
entity.Private = matchBoundaryElementLocalPattern(file.Private, file)
|
|
645
|
+
return entity
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
func resolveBoundaryDependencyPath(ctx *Context, dependency boundaryDependency) (string, bool) {
|
|
649
|
+
if ctx != nil && ctx.Checker != nil && dependency.node != nil {
|
|
650
|
+
symbol := ctx.Checker.GetSymbolAtLocation(dependency.node)
|
|
651
|
+
if symbol != nil && symbol.Flags&shimast.SymbolFlagsAlias != 0 {
|
|
652
|
+
symbol = ctx.Checker.GetAliasedSymbol(symbol)
|
|
653
|
+
}
|
|
654
|
+
if symbol != nil {
|
|
655
|
+
for _, declaration := range symbol.Declarations {
|
|
656
|
+
source := sourceFileForBoundaryDeclaration(declaration)
|
|
657
|
+
if source == nil || source.FileName() == "" {
|
|
658
|
+
continue
|
|
659
|
+
}
|
|
660
|
+
return filepath.Clean(source.FileName()), true
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
if ctx != nil && ctx.File != nil {
|
|
665
|
+
return resolveBoundaryImport(ctx.File.FileName(), dependency.specifier)
|
|
666
|
+
}
|
|
667
|
+
return "", false
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
func sourceFileForBoundaryDeclaration(node *shimast.Node) *shimast.SourceFile {
|
|
671
|
+
for current := node; current != nil; current = current.Parent {
|
|
672
|
+
if current.Kind == shimast.KindSourceFile {
|
|
673
|
+
return current.AsSourceFile()
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
return nil
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
func boundaryDependenciesProjectLocalPath(root, path string) bool {
|
|
680
|
+
if root == "" || path == "" || boundaryDependenciesPathHasSegment(path, "node_modules") {
|
|
681
|
+
return false
|
|
682
|
+
}
|
|
683
|
+
relative, err := filepath.Rel(filepath.Clean(root), filepath.Clean(path))
|
|
684
|
+
if err != nil || filepath.IsAbs(relative) {
|
|
685
|
+
return false
|
|
686
|
+
}
|
|
687
|
+
return relative != ".." && !strings.HasPrefix(relative, ".."+string(filepath.Separator))
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
func boundaryDependenciesPathHasSegment(path, segment string) bool {
|
|
691
|
+
normalized := strings.ToLower(normalizeBoundaryPath(path))
|
|
692
|
+
segment = "/" + strings.ToLower(strings.Trim(segment, "/")) + "/"
|
|
693
|
+
return strings.Contains("/"+strings.Trim(normalized, "/")+"/", segment)
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
func boundaryDependenciesOrigin(specifier string) string {
|
|
697
|
+
if strings.HasPrefix(specifier, "node:") {
|
|
698
|
+
return "core"
|
|
699
|
+
}
|
|
700
|
+
if _, ok := unicornPreferNodeProtocolBuiltins[specifier]; ok {
|
|
701
|
+
return "core"
|
|
702
|
+
}
|
|
703
|
+
return "external"
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
func boundaryDependenciesShadowedRequire(ctx *Context, dependency boundaryDependency) bool {
|
|
707
|
+
if ctx == nil || ctx.Checker == nil || dependency.nodeKind != "RequireCall" || dependency.node == nil || dependency.node.Parent == nil {
|
|
708
|
+
return false
|
|
709
|
+
}
|
|
710
|
+
call := dependency.node.Parent.AsCallExpression()
|
|
711
|
+
if call == nil || call.Expression == nil {
|
|
712
|
+
return false
|
|
713
|
+
}
|
|
714
|
+
symbol := ctx.Checker.GetSymbolAtLocation(call.Expression)
|
|
715
|
+
if symbol == nil {
|
|
716
|
+
return false
|
|
717
|
+
}
|
|
718
|
+
for _, declaration := range symbol.Declarations {
|
|
719
|
+
source := sourceFileForBoundaryDeclaration(declaration)
|
|
720
|
+
if source != nil && boundaryDependenciesProjectLocalPath(ctx.CurrentDirectory, source.FileName()) {
|
|
721
|
+
return true
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
return false
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
func evaluateBoundaryDependencies(
|
|
728
|
+
options boundaryDependenciesOptions,
|
|
729
|
+
description boundaryDependenciesDescription,
|
|
730
|
+
) boundaryDependenciesEvaluation {
|
|
731
|
+
evaluation := boundaryDependenciesEvaluation{
|
|
732
|
+
allowed: options.Default == "allow",
|
|
733
|
+
policyIndex: -1,
|
|
734
|
+
}
|
|
735
|
+
for index := range options.Policies {
|
|
736
|
+
policy := &options.Policies[index]
|
|
737
|
+
if !boundaryDependenciesEntitiesMatch(policy.From, description.From) ||
|
|
738
|
+
!boundaryDependenciesEntitiesMatch(policy.To, description.To) ||
|
|
739
|
+
!boundaryDependenciesInfoMatches(policy.Dependency, description.Dependency) {
|
|
740
|
+
continue
|
|
741
|
+
}
|
|
742
|
+
if boundaryDependenciesEffectsMatch(policy, policy.Disallow, description) {
|
|
743
|
+
evaluation.allowed = false
|
|
744
|
+
evaluation.policyIndex = index
|
|
745
|
+
evaluation.policy = policy
|
|
746
|
+
continue
|
|
747
|
+
}
|
|
748
|
+
if boundaryDependenciesEffectsMatch(policy, policy.Allow, description) {
|
|
749
|
+
evaluation.allowed = true
|
|
750
|
+
evaluation.policyIndex = index
|
|
751
|
+
evaluation.policy = policy
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
return evaluation
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
func boundaryDependenciesEffectsMatch(
|
|
758
|
+
policy *boundaryDependenciesPolicy,
|
|
759
|
+
selectors []boundaryDependenciesSelector,
|
|
760
|
+
description boundaryDependenciesDescription,
|
|
761
|
+
) bool {
|
|
762
|
+
for _, selector := range selectors {
|
|
763
|
+
if !boundaryDependenciesEntitiesMatch(selector.From, description.From) ||
|
|
764
|
+
!boundaryDependenciesEntitiesMatch(selector.To, description.To) ||
|
|
765
|
+
!boundaryDependenciesInfoMatches(selector.Dependency, description.Dependency) {
|
|
766
|
+
continue
|
|
767
|
+
}
|
|
768
|
+
if policy.ImportKind != "" &&
|
|
769
|
+
!boundaryDependenciesInfoHasKind(policy.Dependency) &&
|
|
770
|
+
!boundaryDependenciesInfoHasKind(selector.Dependency) &&
|
|
771
|
+
policy.ImportKind != description.Dependency.kind {
|
|
772
|
+
continue
|
|
773
|
+
}
|
|
774
|
+
return true
|
|
775
|
+
}
|
|
776
|
+
return false
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
func boundaryDependenciesEntitiesMatch(selectors []boundaryDependenciesEntitySelector, entity boundaryDependenciesEntity) bool {
|
|
780
|
+
if len(selectors) == 0 {
|
|
781
|
+
return true
|
|
782
|
+
}
|
|
783
|
+
for _, selector := range selectors {
|
|
784
|
+
if boundaryDependenciesEntityMatches(selector, entity) {
|
|
785
|
+
return true
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
return false
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
func boundaryDependenciesEntityMatches(selector boundaryDependenciesEntitySelector, entity boundaryDependenciesEntity) bool {
|
|
792
|
+
if len(selector.Types) > 0 && !matchAnyBoundaryPattern(selector.Types, entity.Type) {
|
|
793
|
+
return false
|
|
794
|
+
}
|
|
795
|
+
if len(selector.Origins) > 0 && !matchAnyBoundaryPattern(selector.Origins, entity.Origin) {
|
|
796
|
+
return false
|
|
797
|
+
}
|
|
798
|
+
if len(selector.Sources) > 0 && !matchAnyBoundaryPattern(
|
|
799
|
+
selector.Sources,
|
|
800
|
+
entity.Source,
|
|
801
|
+
boundaryPackageName(entity.Source),
|
|
802
|
+
) {
|
|
803
|
+
return false
|
|
804
|
+
}
|
|
805
|
+
if len(selector.Paths) > 0 && !matchAnyBoundaryPattern(
|
|
806
|
+
selector.Paths,
|
|
807
|
+
entity.Path,
|
|
808
|
+
filepath.Base(entity.Path),
|
|
809
|
+
) {
|
|
810
|
+
return false
|
|
811
|
+
}
|
|
812
|
+
if selector.Entry != nil && *selector.Entry != entity.Entry {
|
|
813
|
+
return false
|
|
814
|
+
}
|
|
815
|
+
if selector.Private != nil && *selector.Private != entity.Private {
|
|
816
|
+
return false
|
|
817
|
+
}
|
|
818
|
+
if selector.Unknown != nil && *selector.Unknown != entity.Unknown {
|
|
819
|
+
return false
|
|
820
|
+
}
|
|
821
|
+
return true
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
func boundaryDependenciesInfoMatches(selectors []boundaryDependenciesInfoSelector, dependency boundaryDependency) bool {
|
|
825
|
+
if len(selectors) == 0 {
|
|
826
|
+
return true
|
|
827
|
+
}
|
|
828
|
+
for _, selector := range selectors {
|
|
829
|
+
if len(selector.Kinds) > 0 && !matchAnyBoundaryPattern(selector.Kinds, dependency.kind) {
|
|
830
|
+
continue
|
|
831
|
+
}
|
|
832
|
+
if len(selector.Sources) > 0 && !matchAnyBoundaryPattern(
|
|
833
|
+
selector.Sources,
|
|
834
|
+
dependency.specifier,
|
|
835
|
+
boundaryPackageName(dependency.specifier),
|
|
836
|
+
) {
|
|
837
|
+
continue
|
|
838
|
+
}
|
|
839
|
+
if len(selector.NodeKinds) > 0 && !matchAnyBoundaryPattern(selector.NodeKinds, dependency.nodeKind) {
|
|
840
|
+
continue
|
|
841
|
+
}
|
|
842
|
+
if len(selector.Specifiers) > 0 && !matchAnyBoundaryPattern(selector.Specifiers, dependency.specifiers...) {
|
|
843
|
+
continue
|
|
844
|
+
}
|
|
845
|
+
return true
|
|
846
|
+
}
|
|
847
|
+
return false
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
func boundaryDependenciesInfoHasKind(selectors []boundaryDependenciesInfoSelector) bool {
|
|
851
|
+
for _, selector := range selectors {
|
|
852
|
+
if len(selector.Kinds) > 0 {
|
|
853
|
+
return true
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
return false
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
func boundaryDependenciesMessage(
|
|
860
|
+
options boundaryDependenciesOptions,
|
|
861
|
+
description boundaryDependenciesDescription,
|
|
862
|
+
evaluation boundaryDependenciesEvaluation,
|
|
863
|
+
) string {
|
|
864
|
+
custom := options.Message
|
|
865
|
+
if evaluation.policy != nil && evaluation.policy.Message != "" {
|
|
866
|
+
custom = evaluation.policy.Message
|
|
867
|
+
}
|
|
868
|
+
if custom != "" {
|
|
869
|
+
return boundaryDependenciesRenderMessage(custom, description, evaluation.policyIndex)
|
|
870
|
+
}
|
|
871
|
+
from := boundaryDependenciesEntityLabel(description.From)
|
|
872
|
+
to := boundaryDependenciesEntityLabel(description.To)
|
|
873
|
+
if evaluation.policyIndex >= 0 {
|
|
874
|
+
return fmt.Sprintf("Dependency from %s to %s is not allowed by policy at index %d.", from, to, evaluation.policyIndex)
|
|
875
|
+
}
|
|
876
|
+
return fmt.Sprintf("There is no policy allowing dependencies from %s to %s.", from, to)
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
func boundaryDependenciesEntityLabel(entity boundaryDependenciesEntity) string {
|
|
880
|
+
switch entity.Origin {
|
|
881
|
+
case "external", "core":
|
|
882
|
+
return fmt.Sprintf("%s module %q", entity.Origin, entity.Source)
|
|
883
|
+
}
|
|
884
|
+
if entity.Unknown {
|
|
885
|
+
return fmt.Sprintf("unknown local target %q", entity.Source)
|
|
886
|
+
}
|
|
887
|
+
return fmt.Sprintf("boundary element %q", entity.Type)
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
func boundaryDependenciesRenderMessage(
|
|
891
|
+
message string,
|
|
892
|
+
description boundaryDependenciesDescription,
|
|
893
|
+
policyIndex int,
|
|
894
|
+
) string {
|
|
895
|
+
return strings.NewReplacer(
|
|
896
|
+
"{{from.type}}", description.From.Type,
|
|
897
|
+
"{{from.path}}", description.From.Path,
|
|
898
|
+
"{{from.origin}}", description.From.Origin,
|
|
899
|
+
"{{to.type}}", description.To.Type,
|
|
900
|
+
"{{to.path}}", description.To.Path,
|
|
901
|
+
"{{to.origin}}", description.To.Origin,
|
|
902
|
+
"{{dependency.source}}", description.Dependency.specifier,
|
|
903
|
+
"{{dependency.kind}}", description.Dependency.kind,
|
|
904
|
+
"{{dependency.nodeKind}}", description.Dependency.nodeKind,
|
|
905
|
+
"{{policy.index}}", fmt.Sprintf("%d", policyIndex),
|
|
906
|
+
).Replace(message)
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
func boundaryDependenciesObject(raw json.RawMessage, path string) (map[string]json.RawMessage, error) {
|
|
910
|
+
raw = bytes.TrimSpace(raw)
|
|
911
|
+
if len(raw) == 0 || raw[0] != '{' {
|
|
912
|
+
return nil, fmt.Errorf("%s must be an object", path)
|
|
913
|
+
}
|
|
914
|
+
var fields map[string]json.RawMessage
|
|
915
|
+
if err := json.Unmarshal(raw, &fields); err != nil {
|
|
916
|
+
return nil, fmt.Errorf("%s must be an object: %w", path, err)
|
|
917
|
+
}
|
|
918
|
+
if fields == nil {
|
|
919
|
+
return nil, fmt.Errorf("%s must be an object", path)
|
|
920
|
+
}
|
|
921
|
+
return fields, nil
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
func boundaryDependenciesArray(raw json.RawMessage, path string) ([]json.RawMessage, error) {
|
|
925
|
+
raw = bytes.TrimSpace(raw)
|
|
926
|
+
if len(raw) == 0 || raw[0] != '[' {
|
|
927
|
+
return nil, fmt.Errorf("%s must be an array", path)
|
|
928
|
+
}
|
|
929
|
+
var values []json.RawMessage
|
|
930
|
+
if err := json.Unmarshal(raw, &values); err != nil {
|
|
931
|
+
return nil, fmt.Errorf("%s must be an array: %w", path, err)
|
|
932
|
+
}
|
|
933
|
+
return values, nil
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
func boundaryDependenciesString(raw json.RawMessage, path string) (string, error) {
|
|
937
|
+
var value string
|
|
938
|
+
if err := json.Unmarshal(raw, &value); err != nil {
|
|
939
|
+
return "", fmt.Errorf("%s must be a string", path)
|
|
940
|
+
}
|
|
941
|
+
return value, nil
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
func boundaryDependenciesStringList(raw json.RawMessage, path string) (boundaryStringList, error) {
|
|
945
|
+
raw = bytes.TrimSpace(raw)
|
|
946
|
+
if len(raw) == 0 {
|
|
947
|
+
return nil, fmt.Errorf("%s must be a string or array of strings", path)
|
|
948
|
+
}
|
|
949
|
+
if raw[0] == '"' {
|
|
950
|
+
value, err := boundaryDependenciesString(raw, path)
|
|
951
|
+
if err != nil {
|
|
952
|
+
return nil, err
|
|
953
|
+
}
|
|
954
|
+
if value == "" {
|
|
955
|
+
return nil, fmt.Errorf("%s must not be empty", path)
|
|
956
|
+
}
|
|
957
|
+
return boundaryStringList{value}, nil
|
|
958
|
+
}
|
|
959
|
+
entries, err := boundaryDependenciesArray(raw, path)
|
|
960
|
+
if err != nil {
|
|
961
|
+
return nil, fmt.Errorf("%s must be a string or array of strings", path)
|
|
962
|
+
}
|
|
963
|
+
if len(entries) == 0 {
|
|
964
|
+
return nil, fmt.Errorf("%s must not be empty", path)
|
|
965
|
+
}
|
|
966
|
+
values := make(boundaryStringList, 0, len(entries))
|
|
967
|
+
seen := make(map[string]struct{}, len(entries))
|
|
968
|
+
for index, entry := range entries {
|
|
969
|
+
value, err := boundaryDependenciesString(entry, fmt.Sprintf("%s[%d]", path, index))
|
|
970
|
+
if err != nil {
|
|
971
|
+
return nil, err
|
|
972
|
+
}
|
|
973
|
+
if value == "" {
|
|
974
|
+
return nil, fmt.Errorf("%s[%d] must not be empty", path, index)
|
|
975
|
+
}
|
|
976
|
+
if _, duplicate := seen[value]; duplicate {
|
|
977
|
+
return nil, fmt.Errorf("%s contains duplicate value %q", path, value)
|
|
978
|
+
}
|
|
979
|
+
seen[value] = struct{}{}
|
|
980
|
+
values = append(values, value)
|
|
981
|
+
}
|
|
982
|
+
return values, nil
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
func boundaryDependenciesBool(raw json.RawMessage, path string) (bool, error) {
|
|
986
|
+
var value bool
|
|
987
|
+
if err := json.Unmarshal(raw, &value); err != nil {
|
|
988
|
+
return false, fmt.Errorf("%s must be a boolean", path)
|
|
989
|
+
}
|
|
990
|
+
return value, nil
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
func boundaryDependenciesRejectUnknown(fields map[string]json.RawMessage, allowed ...string) error {
|
|
994
|
+
known := make(map[string]struct{}, len(allowed))
|
|
995
|
+
for _, name := range allowed {
|
|
996
|
+
known[name] = struct{}{}
|
|
997
|
+
}
|
|
998
|
+
unknown := make([]string, 0)
|
|
999
|
+
for name := range fields {
|
|
1000
|
+
if _, ok := known[name]; !ok {
|
|
1001
|
+
unknown = append(unknown, name)
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
if len(unknown) == 0 {
|
|
1005
|
+
return nil
|
|
1006
|
+
}
|
|
1007
|
+
sort.Strings(unknown)
|
|
1008
|
+
return fmt.Errorf("unknown option %q", unknown[0])
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
func boundaryDependenciesValidKind(kind string) bool {
|
|
1012
|
+
return kind == "value" || kind == "type" || kind == "typeof"
|
|
1013
|
+
}
|