@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.
Files changed (188) hide show
  1. package/README.md +125 -43
  2. package/go.mod +5 -4
  3. package/internal/rulecode/rulecode.go +88 -0
  4. package/lib/index.d.ts +10 -1
  5. package/lib/index.js +45 -17
  6. package/lib/index.js.map +1 -1
  7. package/lib/structures/ITtscLintConfig.d.ts +3 -0
  8. package/lib/structures/TtscLintRuleSetting.d.ts +3 -2
  9. package/lib/structures/format/ITtscLintFormat.d.ts +3 -2
  10. package/lib/structures/format/ITtscLintFormatSortImports.d.ts +20 -4
  11. package/lib/structures/rules/ITtscLintBoundariesRuleOptions.d.ts +80 -8
  12. package/lib/structures/rules/ITtscLintBoundariesRules.d.ts +6 -8
  13. package/lib/structures/rules/ITtscLintContributorRules.d.ts +18 -2
  14. package/lib/structures/rules/ITtscLintCoreRuleOptions.d.ts +347 -0
  15. package/lib/structures/rules/ITtscLintCoreRuleOptions.js +3 -0
  16. package/lib/structures/rules/ITtscLintCoreRuleOptions.js.map +1 -0
  17. package/lib/structures/rules/ITtscLintCoreRules.d.ts +101 -67
  18. package/lib/structures/rules/ITtscLintRegexpRules.d.ts +7 -2
  19. package/lib/structures/rules/ITtscLintRuleOptionsMap.d.ts +53 -7
  20. package/lib/structures/rules/ITtscLintRules.d.ts +6 -2
  21. package/lib/structures/rules/ITtscLintSolidRules.d.ts +0 -8
  22. package/lib/structures/rules/ITtscLintTypeScriptRuleOptions.d.ts +206 -0
  23. package/lib/structures/rules/ITtscLintTypeScriptRuleOptions.js +8 -0
  24. package/lib/structures/rules/ITtscLintTypeScriptRuleOptions.js.map +1 -0
  25. package/lib/structures/rules/ITtscLintTypeScriptRules.d.ts +55 -42
  26. package/lib/structures/rules/ITtscLintUnicornRuleOptions.d.ts +276 -0
  27. package/lib/structures/rules/ITtscLintUnicornRuleOptions.js +3 -0
  28. package/lib/structures/rules/ITtscLintUnicornRuleOptions.js.map +1 -0
  29. package/lib/structures/rules/ITtscLintUnicornRules.d.ts +44 -36
  30. package/lib/structures/rules/index.d.ts +3 -0
  31. package/lib/structures/rules/index.js +3 -0
  32. package/lib/structures/rules/index.js.map +1 -1
  33. package/linthost/ast_helpers.go +521 -23
  34. package/linthost/ast_selector_matcher.go +1628 -0
  35. package/linthost/ast_selector_parser.go +628 -0
  36. package/linthost/comment_scan.go +154 -0
  37. package/linthost/compile.go +129 -79
  38. package/linthost/config.go +352 -88
  39. package/linthost/config_format.go +15 -1
  40. package/linthost/contrib_adapter.go +47 -8
  41. package/linthost/declaration_rules.go +12 -11
  42. package/linthost/directives.go +9 -67
  43. package/linthost/dispatch.go +37 -3
  44. package/linthost/engine.go +368 -53
  45. package/linthost/escape_string.go +77 -0
  46. package/linthost/fix.go +133 -23
  47. package/linthost/flags_gen.go +18 -17
  48. package/linthost/format.go +73 -167
  49. package/linthost/format_editor_settings.go +134 -26
  50. package/linthost/grapheme_tables_gen.go +1920 -0
  51. package/linthost/host.go +159 -61
  52. package/linthost/literal_escape_scan.go +117 -0
  53. package/linthost/lsp.go +545 -98
  54. package/linthost/numeric_literal_precision.go +228 -0
  55. package/linthost/polyfill_browserslist.go +1439 -0
  56. package/linthost/polyfill_browserslist_config.go +603 -0
  57. package/linthost/polyfill_corejs.go +750 -0
  58. package/linthost/polyfill_data.go +113 -0
  59. package/linthost/polyfill_data_gen.json +39868 -0
  60. package/linthost/print_nodes_array.go +7 -1
  61. package/linthost/print_nodes_object.go +7 -1
  62. package/linthost/project_engine.go +231 -0
  63. package/linthost/project_rules.go +78 -0
  64. package/linthost/regex_clean.go +191 -0
  65. package/linthost/regex_tree.go +1544 -0
  66. package/linthost/regex_tree_optimizer.go +1685 -0
  67. package/linthost/regexp_cache.go +44 -0
  68. package/linthost/rule_codes.go +105 -0
  69. package/linthost/rule_codes.json +745 -0
  70. package/linthost/rules_ban_ts_comment.go +312 -0
  71. package/linthost/rules_boundaries.go +128 -43
  72. package/linthost/rules_boundaries_dependencies.go +1013 -0
  73. package/linthost/rules_core_extra.go +62 -92
  74. package/linthost/rules_cypress.go +1 -1
  75. package/linthost/rules_default_case.go +87 -11
  76. package/linthost/rules_dupes.go +38 -13
  77. package/linthost/rules_empty.go +188 -20
  78. package/linthost/rules_escape.go +62 -48
  79. package/linthost/rules_format_arrow_parens.go +19 -5
  80. package/linthost/rules_format_bracket_spacing.go +1 -1
  81. package/linthost/rules_format_clause_join.go +1 -1
  82. package/linthost/rules_format_declaration_header.go +31 -19
  83. package/linthost/rules_format_indent.go +1 -1
  84. package/linthost/rules_format_jsdoc.go +1 -1
  85. package/linthost/rules_format_orphan_semi.go +1 -1
  86. package/linthost/rules_format_parameter_properties.go +17 -7
  87. package/linthost/rules_format_print_width.go +1 -1
  88. package/linthost/rules_format_quote_props.go +1 -1
  89. package/linthost/rules_format_quotes.go +1 -1
  90. package/linthost/rules_format_semi.go +106 -79
  91. package/linthost/rules_format_sort_imports.go +79 -45
  92. package/linthost/rules_format_statement_split.go +1 -1
  93. package/linthost/rules_format_ternary_nullish_parens.go +1 -1
  94. package/linthost/rules_format_trailing_comma.go +64 -1
  95. package/linthost/rules_format_whitespace.go +1 -1
  96. package/linthost/rules_functional.go +21 -22
  97. package/linthost/rules_gap.go +80 -3
  98. package/linthost/rules_grouped_accessor_pairs.go +84 -25
  99. package/linthost/rules_guard_for_in.go +64 -147
  100. package/linthost/rules_jsx_a11y.go +232 -22
  101. package/linthost/rules_logic.go +84 -38
  102. package/linthost/rules_nextjs.go +105 -16
  103. package/linthost/rules_no_class_assign.go +70 -0
  104. package/linthost/rules_no_duplicate_imports.go +266 -0
  105. package/linthost/rules_no_else_return.go +156 -38
  106. package/linthost/rules_no_extend_native.go +113 -17
  107. package/linthost/rules_no_fallthrough.go +1013 -0
  108. package/linthost/rules_no_func_assign.go +70 -0
  109. package/linthost/rules_no_import_assign.go +278 -86
  110. package/linthost/rules_no_inner_declarations.go +186 -0
  111. package/linthost/rules_no_loop_func.go +438 -53
  112. package/linthost/rules_no_mixed_operators.go +208 -52
  113. package/linthost/rules_no_param_reassign.go +376 -61
  114. package/linthost/rules_no_promise_executor_return.go +156 -0
  115. package/linthost/rules_no_restricted_imports.go +1013 -57
  116. package/linthost/rules_no_restricted_syntax.go +190 -26
  117. package/linthost/rules_no_useless_constructor.go +6 -36
  118. package/linthost/rules_problems.go +154 -311
  119. package/linthost/rules_promise.go +2578 -109
  120. package/linthost/rules_react_extras.go +71 -45
  121. package/linthost/rules_react_hooks.go +18 -2
  122. package/linthost/rules_react_perf.go +1 -0
  123. package/linthost/rules_react_refresh.go +1 -1
  124. package/linthost/rules_regexp.go +230 -14
  125. package/linthost/rules_security.go +45 -19
  126. package/linthost/rules_self.go +21 -1
  127. package/linthost/rules_solid.go +13 -7
  128. package/linthost/rules_storybook.go +1 -1
  129. package/linthost/rules_suggestions.go +490 -84
  130. package/linthost/rules_testing_library.go +12 -1
  131. package/linthost/rules_ts.go +227 -54
  132. package/linthost/rules_ts_async.go +0 -131
  133. package/linthost/rules_ts_no_base_to_string.go +3 -4
  134. package/linthost/rules_ts_no_misused_promises.go +817 -0
  135. package/linthost/rules_ts_no_restricted_types.go +320 -34
  136. package/linthost/rules_ts_no_unnecessary_qualifier.go +132 -55
  137. package/linthost/rules_ts_no_unsafe_assignment.go +608 -60
  138. package/linthost/rules_ts_require_array_sort_compare.go +5 -5
  139. package/linthost/rules_ts_switch_exhaustiveness_check.go +556 -86
  140. package/linthost/rules_unicorn_better_regex.go +224 -16
  141. package/linthost/rules_unicorn_consistent_destructuring.go +1002 -7
  142. package/linthost/rules_unicorn_consistent_existence_index_check.go +208 -29
  143. package/linthost/rules_unicorn_consistent_function_scoping.go +1041 -8
  144. package/linthost/rules_unicorn_consistent_template_literal_escape.go +178 -7
  145. package/linthost/rules_unicorn_escape_case.go +30 -19
  146. package/linthost/rules_unicorn_filename_case.go +763 -16
  147. package/linthost/rules_unicorn_import_style.go +783 -17
  148. package/linthost/rules_unicorn_isolated_functions.go +934 -14
  149. package/linthost/rules_unicorn_no_hex_escape.go +26 -11
  150. package/linthost/rules_unicorn_no_typeof_undefined.go +234 -25
  151. package/linthost/rules_unicorn_no_unnecessary_polyfills.go +376 -21
  152. package/linthost/rules_unicorn_no_unused_properties.go +756 -19
  153. package/linthost/rules_unicorn_number_literal_case.go +58 -39
  154. package/linthost/rules_unicorn_prefer_at.go +7 -3
  155. package/linthost/rules_unicorn_prefer_number_properties.go +381 -48
  156. package/linthost/rules_unicorn_prefer_optional_catch_binding.go +24 -24
  157. package/linthost/rules_unicorn_prefer_simple_condition_first.go +597 -19
  158. package/linthost/rules_unicorn_prefer_string_raw.go +147 -16
  159. package/linthost/rules_unicorn_prevent_abbreviations.go +1963 -95
  160. package/linthost/rules_unicorn_string_content.go +611 -15
  161. package/linthost/rules_unicorn_switch_case_break_position.go +172 -15
  162. package/linthost/rules_unicorn_template_indent.go +923 -18
  163. package/linthost/rules_unicorn_text_encoding_identifier_case.go +202 -42
  164. package/linthost/rules_unicorn_throw_new_error.go +218 -26
  165. package/linthost/rules_var.go +829 -142
  166. package/linthost/string_length.go +222 -0
  167. package/package.json +4 -3
  168. package/rule/project.go +206 -0
  169. package/rule/rule.go +58 -1
  170. package/src/index.ts +58 -17
  171. package/src/structures/ITtscLintConfig.ts +3 -0
  172. package/src/structures/TtscLintRuleSetting.ts +3 -2
  173. package/src/structures/format/ITtscLintFormat.ts +3 -2
  174. package/src/structures/format/ITtscLintFormatSortImports.ts +21 -4
  175. package/src/structures/rules/ITtscLintBoundariesRuleOptions.ts +122 -8
  176. package/src/structures/rules/ITtscLintBoundariesRules.ts +6 -8
  177. package/src/structures/rules/ITtscLintContributorRules.ts +18 -2
  178. package/src/structures/rules/ITtscLintCoreRuleOptions.ts +465 -0
  179. package/src/structures/rules/ITtscLintCoreRules.ts +122 -67
  180. package/src/structures/rules/ITtscLintRegexpRules.ts +7 -2
  181. package/src/structures/rules/ITtscLintRuleOptionsMap.ts +88 -7
  182. package/src/structures/rules/ITtscLintRules.ts +7 -2
  183. package/src/structures/rules/ITtscLintSolidRules.ts +0 -9
  184. package/src/structures/rules/ITtscLintTypeScriptRuleOptions.ts +248 -0
  185. package/src/structures/rules/ITtscLintTypeScriptRules.ts +64 -42
  186. package/src/structures/rules/ITtscLintUnicornRuleOptions.ts +350 -0
  187. package/src/structures/rules/ITtscLintUnicornRules.ts +60 -36
  188. 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
+ }