@ttsc/evidence 0.25.0
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/LICENSE +21 -0
- package/README.md +279 -0
- package/lib/index.d.ts +65 -0
- package/lib/index.js +87 -0
- package/lib/internal/loadPrismaModels.d.ts +1 -0
- package/lib/internal/loadPrismaModels.js +197 -0
- package/lib/internal/loadSwaggerOperations.d.ts +1 -0
- package/lib/internal/loadSwaggerOperations.js +156 -0
- package/lib/internal/normalizeSwaggerDocument.d.ts +1 -0
- package/lib/internal/normalizeSwaggerDocument.js +7 -0
- package/lib/structures/ITtscEvidenceDocumentedConfig.d.ts +32 -0
- package/lib/structures/ITtscEvidenceDocumentedConfig.js +2 -0
- package/lib/structures/ITtscEvidenceGraphClaim.d.ts +14 -0
- package/lib/structures/ITtscEvidenceGraphClaim.js +2 -0
- package/lib/structures/ITtscEvidenceGraphClaimBase.d.ts +102 -0
- package/lib/structures/ITtscEvidenceGraphClaimBase.js +2 -0
- package/lib/structures/ITtscEvidenceGraphConfig.d.ts +19 -0
- package/lib/structures/ITtscEvidenceGraphConfig.js +2 -0
- package/lib/structures/ITtscEvidenceGraphMarkdownClaim.d.ts +40 -0
- package/lib/structures/ITtscEvidenceGraphMarkdownClaim.js +2 -0
- package/lib/structures/ITtscEvidenceGraphMarkdownReference.d.ts +76 -0
- package/lib/structures/ITtscEvidenceGraphMarkdownReference.js +2 -0
- package/lib/structures/ITtscEvidenceGraphPrismaClaim.d.ts +65 -0
- package/lib/structures/ITtscEvidenceGraphPrismaClaim.js +2 -0
- package/lib/structures/ITtscEvidenceGraphPrismaReference.d.ts +75 -0
- package/lib/structures/ITtscEvidenceGraphPrismaReference.js +2 -0
- package/lib/structures/ITtscEvidenceGraphReference.d.ts +14 -0
- package/lib/structures/ITtscEvidenceGraphReference.js +2 -0
- package/lib/structures/ITtscEvidenceGraphReferenceBase.d.ts +73 -0
- package/lib/structures/ITtscEvidenceGraphReferenceBase.js +2 -0
- package/lib/structures/ITtscEvidenceGraphSwaggerReference.d.ts +39 -0
- package/lib/structures/ITtscEvidenceGraphSwaggerReference.js +2 -0
- package/lib/structures/ITtscEvidenceGraphTypeScriptClaim.d.ts +54 -0
- package/lib/structures/ITtscEvidenceGraphTypeScriptClaim.js +2 -0
- package/lib/structures/ITtscEvidenceGraphTypeScriptReference.d.ts +83 -0
- package/lib/structures/ITtscEvidenceGraphTypeScriptReference.js +2 -0
- package/lib/structures/index.d.ts +13 -0
- package/lib/structures/index.js +29 -0
- package/lib/typings/ITtscLintContributorRules.d.ts +34 -0
- package/lib/typings/ITtscLintContributorRules.js +2 -0
- package/lib/typings/ITtscLintRuleOptionsMap.d.ts +20 -0
- package/lib/typings/ITtscLintRuleOptionsMap.js +2 -0
- package/lib/typings/TtscEvidenceGraphMarkdownSymbol.d.ts +32 -0
- package/lib/typings/TtscEvidenceGraphMarkdownSymbol.js +2 -0
- package/lib/typings/TtscEvidenceGraphPrismaSymbol.d.ts +38 -0
- package/lib/typings/TtscEvidenceGraphPrismaSymbol.js +2 -0
- package/lib/typings/TtscEvidenceGraphTypeScriptSymbol.d.ts +64 -0
- package/lib/typings/TtscEvidenceGraphTypeScriptSymbol.js +2 -0
- package/lib/typings/index.d.ts +5 -0
- package/lib/typings/index.js +21 -0
- package/native/base.go +298 -0
- package/native/config.go +798 -0
- package/native/declaration.go +240 -0
- package/native/documented.go +353 -0
- package/native/exports.go +484 -0
- package/native/glob.go +207 -0
- package/native/graph.go +1409 -0
- package/native/health.go +45 -0
- package/native/hints.go +261 -0
- package/native/imports.go +373 -0
- package/native/inputs.go +125 -0
- package/native/loader.go +424 -0
- package/native/markdown.go +423 -0
- package/native/model.go +296 -0
- package/native/prisma.go +719 -0
- package/native/prisma_cache.go +213 -0
- package/native/prisma_locator.go +417 -0
- package/native/singular.go +369 -0
- package/native/swagger.go +331 -0
- package/native/swagger_cache.go +274 -0
- package/native/todo.go +138 -0
- package/native/typescript.go +1199 -0
- package/native/typescript_cache.go +96 -0
- package/package.json +64 -0
package/native/config.go
ADDED
|
@@ -0,0 +1,798 @@
|
|
|
1
|
+
package evidence
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"bytes"
|
|
5
|
+
"encoding/json"
|
|
6
|
+
"fmt"
|
|
7
|
+
"net/url"
|
|
8
|
+
"path"
|
|
9
|
+
"sort"
|
|
10
|
+
"strings"
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
func decodeGraphConfig(raw json.RawMessage) (graphConfig, []string) {
|
|
14
|
+
var config graphConfig
|
|
15
|
+
if len(bytes.TrimSpace(raw)) == 0 {
|
|
16
|
+
return config, []string{
|
|
17
|
+
"Invalid evidence/graph configuration: the rule requires an ITtscEvidenceGraphConfig options object. Configure it as ['error', { claims: [...] }].",
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
object, problem := decodeObject(raw, "configuration")
|
|
21
|
+
if problem != "" {
|
|
22
|
+
return config, []string{problem}
|
|
23
|
+
}
|
|
24
|
+
problems := rejectUnknownFields(object, []string{"claims"}, graphRuleName, "configuration")
|
|
25
|
+
claimRaw, exists := object["claims"]
|
|
26
|
+
if !exists {
|
|
27
|
+
problems = append(problems, "Invalid evidence/graph configuration at claims: the required claim array is missing.")
|
|
28
|
+
return config, problems
|
|
29
|
+
}
|
|
30
|
+
var claims []json.RawMessage
|
|
31
|
+
if err := json.Unmarshal(claimRaw, &claims); err != nil {
|
|
32
|
+
problems = append(problems, "Invalid evidence/graph configuration at claims: expected an array of Markdown or TypeScript claims.")
|
|
33
|
+
return config, problems
|
|
34
|
+
}
|
|
35
|
+
if len(claims) == 0 {
|
|
36
|
+
problems = append(problems, "Invalid evidence/graph configuration at claims: at least one claim is required; an empty graph cannot establish evidence coverage.")
|
|
37
|
+
return config, problems
|
|
38
|
+
}
|
|
39
|
+
for index, claimRaw := range claims {
|
|
40
|
+
claim, claimProblems := decodeClaim(claimRaw, index)
|
|
41
|
+
problems = append(problems, claimProblems...)
|
|
42
|
+
if len(claimProblems) == 0 {
|
|
43
|
+
config.Claims = append(config.Claims, claim)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return config, problems
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
func decodeClaim(raw json.RawMessage, index int) (claimSpec, []string) {
|
|
50
|
+
path := fmt.Sprintf("claims[%d]", index)
|
|
51
|
+
object, problem := decodeObject(raw, path)
|
|
52
|
+
if problem != "" {
|
|
53
|
+
return claimSpec{}, []string{problem}
|
|
54
|
+
}
|
|
55
|
+
problems := rejectUnknownFields(
|
|
56
|
+
object,
|
|
57
|
+
[]string{"type", "name", "disabled", "root", "files", "evidenceExcludeCarriers", "symbol", "reference"},
|
|
58
|
+
graphRuleName,
|
|
59
|
+
path,
|
|
60
|
+
)
|
|
61
|
+
kind, kindProblem := decodeArtifactKind(object["type"], path+".type", false)
|
|
62
|
+
if kindProblem != "" {
|
|
63
|
+
problems = append(problems, kindProblem)
|
|
64
|
+
}
|
|
65
|
+
name := ""
|
|
66
|
+
if rawName, exists := object["name"]; exists {
|
|
67
|
+
if err := json.Unmarshal(rawName, &name); err != nil {
|
|
68
|
+
problems = append(problems, "Invalid evidence/graph configuration at "+path+".name: expected a diagnostic-only string label.")
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
disabled := false
|
|
72
|
+
if rawDisabled, exists := object["disabled"]; exists {
|
|
73
|
+
switch string(bytes.TrimSpace(rawDisabled)) {
|
|
74
|
+
case "true":
|
|
75
|
+
disabled = true
|
|
76
|
+
case "false":
|
|
77
|
+
default:
|
|
78
|
+
problems = append(problems, "Invalid evidence/graph configuration at "+path+".disabled: expected a boolean.")
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
root, rootProblems := decodeClaimRoot(object["root"], kind, path+".root")
|
|
82
|
+
problems = append(problems, rootProblems...)
|
|
83
|
+
files, fileProblems := decodeFiles(object["files"], path+".files")
|
|
84
|
+
problems = append(problems, fileProblems...)
|
|
85
|
+
carriers := globSet{}
|
|
86
|
+
if raw, exists := object["evidenceExcludeCarriers"]; exists {
|
|
87
|
+
decoded, carrierProblems := decodeFiles(raw, path+".evidenceExcludeCarriers")
|
|
88
|
+
problems = append(problems, carrierProblems...)
|
|
89
|
+
carriers = decoded
|
|
90
|
+
}
|
|
91
|
+
symbols, symbolProblems := decodeSymbols(object["symbol"], kind, false, graphRuleName, path+".symbol")
|
|
92
|
+
problems = append(problems, symbolProblems...)
|
|
93
|
+
references, referenceProblems := decodeReferences(kind, object["reference"], path+".reference")
|
|
94
|
+
problems = append(problems, referenceProblems...)
|
|
95
|
+
if len(problems) != 0 {
|
|
96
|
+
return claimSpec{}, problems
|
|
97
|
+
}
|
|
98
|
+
return claimSpec{
|
|
99
|
+
Index: index,
|
|
100
|
+
Type: kind,
|
|
101
|
+
Name: name,
|
|
102
|
+
Disabled: disabled,
|
|
103
|
+
Root: root,
|
|
104
|
+
Files: files,
|
|
105
|
+
ExclusionCarriers: carriers,
|
|
106
|
+
Symbols: symbols,
|
|
107
|
+
References: references,
|
|
108
|
+
}, nil
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// enabledGraphConfig removes explicitly staged claims after their public
|
|
112
|
+
// configuration has been validated. Keeping the filter separate from decoding
|
|
113
|
+
// preserves original claim indexes and prevents disabled entries from hiding a
|
|
114
|
+
// malformed shape.
|
|
115
|
+
func enabledGraphConfig(config graphConfig) graphConfig {
|
|
116
|
+
claims := make([]claimSpec, 0, len(config.Claims))
|
|
117
|
+
for _, claim := range config.Claims {
|
|
118
|
+
if claim.Disabled {
|
|
119
|
+
continue
|
|
120
|
+
}
|
|
121
|
+
claims = append(claims, claim)
|
|
122
|
+
}
|
|
123
|
+
config.Claims = claims
|
|
124
|
+
return config
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
func decodeReferences(
|
|
128
|
+
claimKind artifactKind,
|
|
129
|
+
raw json.RawMessage,
|
|
130
|
+
path string,
|
|
131
|
+
) ([]referenceSpec, []string) {
|
|
132
|
+
if len(bytes.TrimSpace(raw)) == 0 {
|
|
133
|
+
return nil, []string{"Invalid evidence/graph configuration at " + path + ": the required evidence reference is missing."}
|
|
134
|
+
}
|
|
135
|
+
trimmed := bytes.TrimSpace(raw)
|
|
136
|
+
elements := []json.RawMessage{}
|
|
137
|
+
switch trimmed[0] {
|
|
138
|
+
case '{':
|
|
139
|
+
elements = append(elements, raw)
|
|
140
|
+
case '[':
|
|
141
|
+
if err := json.Unmarshal(raw, &elements); err != nil {
|
|
142
|
+
return nil, []string{"Invalid evidence/graph configuration at " + path + ": expected one reference object or an array of reference objects."}
|
|
143
|
+
}
|
|
144
|
+
if len(elements) == 0 {
|
|
145
|
+
return nil, []string{"Invalid evidence/graph configuration at " + path + ": an empty reference array creates no coverage obligation; provide at least one evidence reference."}
|
|
146
|
+
}
|
|
147
|
+
default:
|
|
148
|
+
return nil, []string{"Invalid evidence/graph configuration at " + path + ": expected one reference object or an array of reference objects."}
|
|
149
|
+
}
|
|
150
|
+
references := make([]referenceSpec, 0, len(elements))
|
|
151
|
+
problems := []string{}
|
|
152
|
+
for index, element := range elements {
|
|
153
|
+
referencePath := path
|
|
154
|
+
if len(elements) > 1 || trimmed[0] == '[' {
|
|
155
|
+
referencePath += "[" + decimal(index) + "]"
|
|
156
|
+
}
|
|
157
|
+
reference, referenceProblems := decodeReference(
|
|
158
|
+
claimKind,
|
|
159
|
+
element,
|
|
160
|
+
index,
|
|
161
|
+
referencePath,
|
|
162
|
+
)
|
|
163
|
+
problems = append(problems, referenceProblems...)
|
|
164
|
+
if len(referenceProblems) == 0 {
|
|
165
|
+
references = append(references, reference)
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return references, problems
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
func decodeReference(
|
|
172
|
+
claimKind artifactKind,
|
|
173
|
+
raw json.RawMessage,
|
|
174
|
+
index int,
|
|
175
|
+
path string,
|
|
176
|
+
) (referenceSpec, []string) {
|
|
177
|
+
object, problem := decodeObject(raw, path)
|
|
178
|
+
if problem != "" {
|
|
179
|
+
return referenceSpec{}, []string{problem}
|
|
180
|
+
}
|
|
181
|
+
problems := rejectUnknownFields(
|
|
182
|
+
object,
|
|
183
|
+
[]string{
|
|
184
|
+
"type",
|
|
185
|
+
"noEvidenceExclude",
|
|
186
|
+
"uniqueEvidence",
|
|
187
|
+
"singleEvidencePerSymbol",
|
|
188
|
+
"package",
|
|
189
|
+
"root",
|
|
190
|
+
"file",
|
|
191
|
+
"files",
|
|
192
|
+
"symbol",
|
|
193
|
+
},
|
|
194
|
+
graphRuleName,
|
|
195
|
+
path,
|
|
196
|
+
)
|
|
197
|
+
kind, kindProblem := decodeArtifactKind(object["type"], path+".type", true)
|
|
198
|
+
if kindProblem != "" {
|
|
199
|
+
problems = append(problems, kindProblem)
|
|
200
|
+
}
|
|
201
|
+
if problem := rejectForeignTypeScriptReference(claimKind, kind, path); problem != "" {
|
|
202
|
+
problems = append(problems, problem)
|
|
203
|
+
}
|
|
204
|
+
root, rootProblems := decodeRoot(object["root"], kind, path+".root")
|
|
205
|
+
problems = append(problems, rootProblems...)
|
|
206
|
+
policy, policyProblems := decodeReferencePolicy(object, path)
|
|
207
|
+
problems = append(problems, policyProblems...)
|
|
208
|
+
files := globSet{}
|
|
209
|
+
source := ""
|
|
210
|
+
packageName := ""
|
|
211
|
+
symbols := symbolSet{}
|
|
212
|
+
if kind != artifactTypeScript {
|
|
213
|
+
if _, exists := object["package"]; exists {
|
|
214
|
+
problems = append(
|
|
215
|
+
problems,
|
|
216
|
+
"Invalid evidence/graph configuration at "+path+".package: only a TypeScript reference can select an installed package; Markdown and Swagger evidence lives in this project.",
|
|
217
|
+
)
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
if kind == artifactTypeScript {
|
|
221
|
+
reference, referenceProblems := decodeTypeScriptReference(object, path)
|
|
222
|
+
problems = append(problems, referenceProblems...)
|
|
223
|
+
files = reference.Files
|
|
224
|
+
packageName = reference.Package
|
|
225
|
+
var symbolProblems []string
|
|
226
|
+
symbols, symbolProblems = decodeSymbols(object["symbol"], kind, true, graphRuleName, path+".symbol")
|
|
227
|
+
problems = append(problems, symbolProblems...)
|
|
228
|
+
} else if kind == artifactSwagger {
|
|
229
|
+
if _, exists := object["files"]; exists {
|
|
230
|
+
problems = append(
|
|
231
|
+
problems,
|
|
232
|
+
"Invalid evidence/graph configuration at "+path+".files: a Swagger reference owns one document; use singular 'file' and a reference array for multiple documents.",
|
|
233
|
+
)
|
|
234
|
+
}
|
|
235
|
+
if _, exists := object["symbol"]; exists {
|
|
236
|
+
problems = append(
|
|
237
|
+
problems,
|
|
238
|
+
"Invalid evidence/graph configuration at "+path+".symbol: Swagger references select every operation and do not accept a symbol selector.",
|
|
239
|
+
)
|
|
240
|
+
}
|
|
241
|
+
var sourceProblem string
|
|
242
|
+
source, sourceProblem = decodeSwaggerSource(object["file"], path+".file")
|
|
243
|
+
if sourceProblem != "" {
|
|
244
|
+
problems = append(problems, sourceProblem)
|
|
245
|
+
}
|
|
246
|
+
symbols["operation"] = true
|
|
247
|
+
} else {
|
|
248
|
+
if _, exists := object["file"]; exists {
|
|
249
|
+
problems = append(
|
|
250
|
+
problems,
|
|
251
|
+
"Invalid evidence/graph configuration at "+path+".file: singular 'file' is only supported by Swagger references; Markdown and TypeScript references use 'files' globs.",
|
|
252
|
+
)
|
|
253
|
+
}
|
|
254
|
+
var fileProblems []string
|
|
255
|
+
files, fileProblems = decodeFiles(object["files"], path+".files")
|
|
256
|
+
problems = append(problems, fileProblems...)
|
|
257
|
+
var symbolProblems []string
|
|
258
|
+
symbols, symbolProblems = decodeSymbols(object["symbol"], kind, true, graphRuleName, path+".symbol")
|
|
259
|
+
problems = append(problems, symbolProblems...)
|
|
260
|
+
}
|
|
261
|
+
if len(problems) != 0 {
|
|
262
|
+
return referenceSpec{}, problems
|
|
263
|
+
}
|
|
264
|
+
return referenceSpec{
|
|
265
|
+
Index: index,
|
|
266
|
+
Type: kind,
|
|
267
|
+
Policy: policy,
|
|
268
|
+
Root: root,
|
|
269
|
+
Files: files,
|
|
270
|
+
Source: source,
|
|
271
|
+
Package: packageName,
|
|
272
|
+
Symbols: symbols,
|
|
273
|
+
}, nil
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// decodeReferencePolicy validates the reference-local acknowledgement options
|
|
277
|
+
// before a disabled claim is filtered. Each option is a flat boolean whose
|
|
278
|
+
// false value means "not configured", so an omitted option and an explicit
|
|
279
|
+
// false preserve the original behavior identically.
|
|
280
|
+
func decodeReferencePolicy(
|
|
281
|
+
object map[string]json.RawMessage,
|
|
282
|
+
path string,
|
|
283
|
+
) (referencePolicy, []string) {
|
|
284
|
+
policy := referencePolicy{}
|
|
285
|
+
problems := []string{}
|
|
286
|
+
decodeFlag := func(name string, target *bool) {
|
|
287
|
+
value, exists := object[name]
|
|
288
|
+
if !exists {
|
|
289
|
+
return
|
|
290
|
+
}
|
|
291
|
+
// A JSON null decodes into Go's false without complaint, which would
|
|
292
|
+
// make a broken generator's output indistinguishable from an option
|
|
293
|
+
// nobody wrote. Only the two literals are the contract.
|
|
294
|
+
switch string(bytes.TrimSpace(value)) {
|
|
295
|
+
case "true":
|
|
296
|
+
*target = true
|
|
297
|
+
case "false":
|
|
298
|
+
default:
|
|
299
|
+
problems = append(problems, configurationProblem(
|
|
300
|
+
graphRuleName,
|
|
301
|
+
path+"."+name,
|
|
302
|
+
"expected a boolean.",
|
|
303
|
+
))
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
decodeFlag("noEvidenceExclude", &policy.NoExclude)
|
|
307
|
+
decodeFlag("uniqueEvidence", &policy.UniqueEvidence)
|
|
308
|
+
decodeFlag("singleEvidencePerSymbol", &policy.SingleEvidencePerSymbol)
|
|
309
|
+
return policy, problems
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// rejectForeignTypeScriptReference refuses a code population to a claim that
|
|
313
|
+
// cannot address one.
|
|
314
|
+
//
|
|
315
|
+
// A symbol is cited through an inline link, and that link resolves in the
|
|
316
|
+
// citing module's import scope — which only a TypeScript file has. Every other
|
|
317
|
+
// claim would have to fall back to matching the bare name against one
|
|
318
|
+
// repository-wide table, and that makes symbol-name uniqueness across the whole
|
|
319
|
+
// repository load-bearing: two modules exporting `IPage` make the citation
|
|
320
|
+
// impossible, and the only repair such a diagnostic can offer is renaming
|
|
321
|
+
// production code to suit a lint rule.
|
|
322
|
+
//
|
|
323
|
+
// The check is here rather than at resolution because a configuration error
|
|
324
|
+
// belongs where the configuration is read. Reported once per reference, before
|
|
325
|
+
// any file is opened, instead of once per citation that later fails to resolve.
|
|
326
|
+
//
|
|
327
|
+
// What this gives up is the decision it reverses: documentation can no longer
|
|
328
|
+
// cite code, and the inverse obligation is not the same one. Do not restore the fallback
|
|
329
|
+
// without restoring that record.
|
|
330
|
+
func rejectForeignTypeScriptReference(
|
|
331
|
+
claimKind artifactKind,
|
|
332
|
+
referenceKind artifactKind,
|
|
333
|
+
path string,
|
|
334
|
+
) string {
|
|
335
|
+
if referenceKind != artifactTypeScript ||
|
|
336
|
+
claimKind == artifactTypeScript ||
|
|
337
|
+
claimKind == "" {
|
|
338
|
+
return ""
|
|
339
|
+
}
|
|
340
|
+
return "Invalid evidence/graph configuration at " + path +
|
|
341
|
+
".type: only a TypeScript claim can cite TypeScript evidence, because a symbol citation resolves through the citing module's imports and a " +
|
|
342
|
+
string(claimKind) +
|
|
343
|
+
" comment has none. Invert the obligation so the code cites this artifact, or move the citation into TypeScript."
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// decodeTypeScriptReference reads the two ways a TypeScript population is
|
|
347
|
+
// selected: a local or package-relative glob set, or a package's own
|
|
348
|
+
// declaration entry.
|
|
349
|
+
//
|
|
350
|
+
// Both reduce to "produce a file set, then materialize its exported symbols";
|
|
351
|
+
// only the selection differs. `package` moves the base the globs resolve
|
|
352
|
+
// against, so it composes with them rather than replacing them.
|
|
353
|
+
func decodeTypeScriptReference(
|
|
354
|
+
object map[string]json.RawMessage,
|
|
355
|
+
path string,
|
|
356
|
+
) (referenceSpec, []string) {
|
|
357
|
+
problems := []string{}
|
|
358
|
+
reference := referenceSpec{}
|
|
359
|
+
if raw, exists := object["package"]; exists {
|
|
360
|
+
var value string
|
|
361
|
+
if err := json.Unmarshal(raw, &value); err != nil {
|
|
362
|
+
problems = append(problems, "Invalid evidence/graph configuration at "+path+".package: expected an installed package name.")
|
|
363
|
+
} else if name, problem := normalizePackageName(value); problem != "" {
|
|
364
|
+
problems = append(problems, "Invalid evidence/graph configuration at "+path+".package: "+problem)
|
|
365
|
+
} else {
|
|
366
|
+
reference.Package = name
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
if _, exists := object["file"]; exists {
|
|
370
|
+
problems = append(
|
|
371
|
+
problems,
|
|
372
|
+
"Invalid evidence/graph configuration at "+path+".file: singular 'file' is only supported by Swagger references; a TypeScript reference selects its population with 'files' globs.",
|
|
373
|
+
)
|
|
374
|
+
}
|
|
375
|
+
if _, exists := object["files"]; exists {
|
|
376
|
+
files, fileProblems := decodeFiles(object["files"], path+".files")
|
|
377
|
+
problems = append(problems, fileProblems...)
|
|
378
|
+
reference.Files = files
|
|
379
|
+
return reference, problems
|
|
380
|
+
}
|
|
381
|
+
if reference.Package == "" {
|
|
382
|
+
problems = append(
|
|
383
|
+
problems,
|
|
384
|
+
"Invalid evidence/graph configuration at "+path+": a local TypeScript reference needs 'files' globs. There is no implicit project population.",
|
|
385
|
+
)
|
|
386
|
+
}
|
|
387
|
+
// A package with no globs falls back to its own declaration entry, which is
|
|
388
|
+
// the only selection a package can make on the consumer's behalf.
|
|
389
|
+
return reference, problems
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
func normalizePackageName(value string) (string, string) {
|
|
393
|
+
if value == "" {
|
|
394
|
+
return "", "the package name must not be empty."
|
|
395
|
+
}
|
|
396
|
+
if strings.TrimSpace(value) != value {
|
|
397
|
+
return "", "the package name must not have leading or trailing whitespace."
|
|
398
|
+
}
|
|
399
|
+
normalized := strings.ReplaceAll(value, "\\", "/")
|
|
400
|
+
if strings.HasPrefix(normalized, ".") || strings.HasPrefix(normalized, "/") {
|
|
401
|
+
return "", "'" + value + "' is a path rather than a package name; use 'files' for a local population."
|
|
402
|
+
}
|
|
403
|
+
segments := strings.Split(normalized, "/")
|
|
404
|
+
limit := 1
|
|
405
|
+
if strings.HasPrefix(segments[0], "@") {
|
|
406
|
+
limit = 2
|
|
407
|
+
}
|
|
408
|
+
if len(segments) > limit {
|
|
409
|
+
return "", "'" + value + "' names a path inside a package; select the package and narrow it with 'files'."
|
|
410
|
+
}
|
|
411
|
+
if len(segments) < limit {
|
|
412
|
+
return "", "'" + value + "' is an incomplete scoped package name."
|
|
413
|
+
}
|
|
414
|
+
return normalized, ""
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
func decodeArtifactKind(
|
|
418
|
+
raw json.RawMessage,
|
|
419
|
+
path string,
|
|
420
|
+
allowSwagger bool,
|
|
421
|
+
) (artifactKind, string) {
|
|
422
|
+
if len(bytes.TrimSpace(raw)) == 0 {
|
|
423
|
+
return "", "Invalid evidence/graph configuration at " + path + ": the artifact discriminator is required."
|
|
424
|
+
}
|
|
425
|
+
var value string
|
|
426
|
+
if err := json.Unmarshal(raw, &value); err != nil {
|
|
427
|
+
return "", "Invalid evidence/graph configuration at " + path + ": expected a supported artifact type string."
|
|
428
|
+
}
|
|
429
|
+
switch artifactKind(value) {
|
|
430
|
+
case artifactMarkdown, artifactPrisma, artifactTypeScript:
|
|
431
|
+
return artifactKind(value), ""
|
|
432
|
+
case artifactSwagger:
|
|
433
|
+
if allowSwagger {
|
|
434
|
+
return artifactSwagger, ""
|
|
435
|
+
}
|
|
436
|
+
return "", "Invalid evidence/graph configuration at " + path + ": Swagger is evidence-only and cannot be a claim; expected 'markdown', 'prisma', or 'typescript'."
|
|
437
|
+
default:
|
|
438
|
+
expected := "'markdown', 'prisma', or 'typescript'"
|
|
439
|
+
if allowSwagger {
|
|
440
|
+
expected = "'markdown', 'prisma', 'swagger', or 'typescript'"
|
|
441
|
+
}
|
|
442
|
+
return "", "Invalid evidence/graph configuration at " + path + ": unsupported artifact type '" + value + "'; expected " + expected + "."
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// decodeClaimRoot admits a Program-backed TypeScript population without
|
|
447
|
+
// widening the Program itself.
|
|
448
|
+
//
|
|
449
|
+
// The root changes the address space and glob base only. The inventory still
|
|
450
|
+
// consists exclusively of ctx.Sources, so a sibling package must be an explicit
|
|
451
|
+
// tsconfig root and no configured directory becomes a filesystem scan.
|
|
452
|
+
func decodeClaimRoot(
|
|
453
|
+
raw json.RawMessage,
|
|
454
|
+
kind artifactKind,
|
|
455
|
+
configPath string,
|
|
456
|
+
) (string, []string) {
|
|
457
|
+
if len(bytes.TrimSpace(raw)) == 0 {
|
|
458
|
+
return "", nil
|
|
459
|
+
}
|
|
460
|
+
if kind != artifactTypeScript {
|
|
461
|
+
return decodeRoot(raw, kind, configPath)
|
|
462
|
+
}
|
|
463
|
+
return decodePopulationRoot(raw, configPath)
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// decodeRoot reads the directory a Markdown or Prisma population resolves
|
|
467
|
+
// against.
|
|
468
|
+
//
|
|
469
|
+
// The property is refused on the two artifact kinds that cannot use it, and
|
|
470
|
+
// each refusal names the channel that artifact does have. A TypeScript
|
|
471
|
+
// population is materialized from the ttsc Program, so a directory outside the
|
|
472
|
+
// project contains no file it could ever reach — `package` is the escape there,
|
|
473
|
+
// and it already exists. A Swagger reference names one exact document, so the
|
|
474
|
+
// location belongs in `file`, where it is visible without a second property.
|
|
475
|
+
func decodeRoot(
|
|
476
|
+
raw json.RawMessage,
|
|
477
|
+
kind artifactKind,
|
|
478
|
+
configPath string,
|
|
479
|
+
) (string, []string) {
|
|
480
|
+
if len(bytes.TrimSpace(raw)) == 0 {
|
|
481
|
+
return "", nil
|
|
482
|
+
}
|
|
483
|
+
switch kind {
|
|
484
|
+
case artifactMarkdown, artifactPrisma:
|
|
485
|
+
case artifactTypeScript:
|
|
486
|
+
return "", []string{
|
|
487
|
+
"Invalid evidence/graph configuration at " + configPath + ": a TypeScript reference selects the active ttsc program with 'files', or an installed package with 'package'; only a TypeScript claim accepts 'root'.",
|
|
488
|
+
}
|
|
489
|
+
case artifactSwagger:
|
|
490
|
+
return "", []string{
|
|
491
|
+
"Invalid evidence/graph configuration at " + configPath + ": a Swagger reference owns one exact document; write the ancestor-relative or absolute location in 'file' instead.",
|
|
492
|
+
}
|
|
493
|
+
default:
|
|
494
|
+
// The discriminator is already reported; a second message about a
|
|
495
|
+
// property of an unknown artifact kind would only add noise.
|
|
496
|
+
return "", nil
|
|
497
|
+
}
|
|
498
|
+
return decodePopulationRoot(raw, configPath)
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
func decodePopulationRoot(
|
|
502
|
+
raw json.RawMessage,
|
|
503
|
+
configPath string,
|
|
504
|
+
) (string, []string) {
|
|
505
|
+
var value string
|
|
506
|
+
if err := json.Unmarshal(raw, &value); err != nil {
|
|
507
|
+
return "", []string{
|
|
508
|
+
"Invalid evidence/graph configuration at " + configPath + ": expected one directory path this population's globs resolve against.",
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
normalized, problem := normalizeRootPath(value)
|
|
512
|
+
if problem != "" {
|
|
513
|
+
return "", []string{"Invalid evidence/graph configuration at " + configPath + ": " + problem}
|
|
514
|
+
}
|
|
515
|
+
return normalized, nil
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
func decodeSwaggerSource(raw json.RawMessage, configPath string) (string, string) {
|
|
519
|
+
if len(bytes.TrimSpace(raw)) == 0 {
|
|
520
|
+
return "", "Invalid evidence/graph configuration at " + configPath + ": the required Swagger file path or URL is missing."
|
|
521
|
+
}
|
|
522
|
+
var value string
|
|
523
|
+
if err := json.Unmarshal(raw, &value); err != nil {
|
|
524
|
+
return "", "Invalid evidence/graph configuration at " + configPath + ": expected one exact file path or http(s) URL."
|
|
525
|
+
}
|
|
526
|
+
source, problem := normalizeSwaggerSource(value)
|
|
527
|
+
if problem != "" {
|
|
528
|
+
return "", "Invalid evidence/graph configuration at " + configPath + ": " + problem
|
|
529
|
+
}
|
|
530
|
+
return source, ""
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// normalizeSwaggerSource reduces a declared Swagger location to one canonical
|
|
534
|
+
// spelling, without deciding where on the filesystem it may sit.
|
|
535
|
+
//
|
|
536
|
+
// A local path is free to ascend with `..` or to be absolute, and the ordering
|
|
537
|
+
// this restores is the point: the rule already accepts an arbitrary http(s) URL
|
|
538
|
+
// on any host, so refusing `../contracts/swagger.json` refused the one form the
|
|
539
|
+
// author can pin, version, and diff. An OpenAPI document is routinely generated
|
|
540
|
+
// somewhere with no relationship to the project that consumes it — a sibling
|
|
541
|
+
// package's output, a shared contract checkout, a CI artifact directory — and
|
|
542
|
+
// none of those is reachable through a project-relative path.
|
|
543
|
+
//
|
|
544
|
+
// A Windows drive prefix is recognized before the URL parse rather than after
|
|
545
|
+
// it. `url.Parse` reads the single letter of `C:/api/swagger.json` as a scheme,
|
|
546
|
+
// so an absolute Windows path would otherwise be rejected as an unsupported
|
|
547
|
+
// protocol — a diagnostic naming the wrong thing entirely. The drive-relative
|
|
548
|
+
// form stays refused for the reason `files` refuses it: it resolves against
|
|
549
|
+
// whatever directory that drive currently sits on.
|
|
550
|
+
func normalizeSwaggerSource(value string) (string, string) {
|
|
551
|
+
if value == "" {
|
|
552
|
+
return "", "Swagger sources must not be empty."
|
|
553
|
+
}
|
|
554
|
+
if strings.TrimSpace(value) != value {
|
|
555
|
+
return "", "Swagger sources must not have leading or trailing whitespace."
|
|
556
|
+
}
|
|
557
|
+
normalized := strings.ReplaceAll(value, "\\", "/")
|
|
558
|
+
drive := hasWindowsDrivePrefix(normalized)
|
|
559
|
+
if drive && !strings.HasPrefix(normalized[2:], "/") {
|
|
560
|
+
return "", "local Swagger path '" + value + "' is drive-relative, so it resolves against whatever directory that drive currently sits on rather than against a stable base. Write the full path."
|
|
561
|
+
}
|
|
562
|
+
parsed, err := url.Parse(value)
|
|
563
|
+
if err != nil {
|
|
564
|
+
return "", "invalid Swagger source '" + value + "': " + err.Error() + "."
|
|
565
|
+
}
|
|
566
|
+
if !drive && parsed.Scheme != "" {
|
|
567
|
+
if parsed.Scheme != "http" && parsed.Scheme != "https" {
|
|
568
|
+
return "", "unsupported URL scheme '" + parsed.Scheme + "'; only http: and https: are supported."
|
|
569
|
+
}
|
|
570
|
+
if parsed.Host == "" {
|
|
571
|
+
return "", "Swagger URL '" + value + "' has no host."
|
|
572
|
+
}
|
|
573
|
+
if parsed.Fragment != "" {
|
|
574
|
+
return "", "Swagger URL '" + value + "' must not contain a fragment."
|
|
575
|
+
}
|
|
576
|
+
return value, ""
|
|
577
|
+
}
|
|
578
|
+
if !drive && strings.Contains(value, "://") {
|
|
579
|
+
return "", "invalid Swagger source URL '" + value + "'."
|
|
580
|
+
}
|
|
581
|
+
// A trailing separator is read before cleaning, because `path.Clean` removes
|
|
582
|
+
// it — after which `docs/` and `docs` are one string and the author who
|
|
583
|
+
// meant a directory gets a missing-file diagnostic instead.
|
|
584
|
+
directory := strings.HasSuffix(normalized, "/")
|
|
585
|
+
// A UNC share is cleaned by hand, because `path.Clean` collapses its leading
|
|
586
|
+
// `//` into one slash and turns a network location into a local one that
|
|
587
|
+
// still reads like what was written.
|
|
588
|
+
if strings.HasPrefix(normalized, "//") {
|
|
589
|
+
normalized = "//" + strings.TrimPrefix(path.Clean(normalized), "/")
|
|
590
|
+
} else {
|
|
591
|
+
normalized = path.Clean(normalized)
|
|
592
|
+
}
|
|
593
|
+
switch {
|
|
594
|
+
case directory,
|
|
595
|
+
normalized == ".",
|
|
596
|
+
normalized == "..",
|
|
597
|
+
normalized == "/",
|
|
598
|
+
strings.HasSuffix(normalized, "/.."),
|
|
599
|
+
len(normalized) == 2 && hasWindowsDrivePrefix(normalized):
|
|
600
|
+
return "", "Swagger source '" + value + "' names a directory rather than a document; a reference owns one exact file."
|
|
601
|
+
}
|
|
602
|
+
return normalized, ""
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
func decodeFiles(raw json.RawMessage, path string) (globSet, []string) {
|
|
606
|
+
if len(bytes.TrimSpace(raw)) == 0 {
|
|
607
|
+
return globSet{}, []string{"Invalid evidence/graph configuration at " + path + ": the required project-relative glob array is missing."}
|
|
608
|
+
}
|
|
609
|
+
var patterns []string
|
|
610
|
+
if err := json.Unmarshal(raw, &patterns); err != nil {
|
|
611
|
+
return globSet{}, []string{"Invalid evidence/graph configuration at " + path + ": expected an array of project-relative glob strings."}
|
|
612
|
+
}
|
|
613
|
+
if len(patterns) == 0 {
|
|
614
|
+
return globSet{}, []string{"Invalid evidence/graph configuration at " + path + ": at least one positive glob is required."}
|
|
615
|
+
}
|
|
616
|
+
globs, err := newGlobSet(patterns)
|
|
617
|
+
if err != nil {
|
|
618
|
+
return globSet{}, []string{"Invalid evidence/graph configuration at " + path + ": " + err.Error()}
|
|
619
|
+
}
|
|
620
|
+
return globs, nil
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
func decodeSymbols(
|
|
624
|
+
raw json.RawMessage,
|
|
625
|
+
kind artifactKind,
|
|
626
|
+
unit bool,
|
|
627
|
+
ruleName string,
|
|
628
|
+
path string,
|
|
629
|
+
) (symbolSet, []string) {
|
|
630
|
+
if kind == "" {
|
|
631
|
+
return nil, nil
|
|
632
|
+
}
|
|
633
|
+
values := []string{}
|
|
634
|
+
if len(bytes.TrimSpace(raw)) == 0 {
|
|
635
|
+
switch {
|
|
636
|
+
case kind == artifactMarkdown:
|
|
637
|
+
values = []string{"file", "h1", "h2", "h3", "h4"}
|
|
638
|
+
// A Prisma reference owes coverage per model, not per column. The
|
|
639
|
+
// prior art cites at model granularity for the same reason
|
|
640
|
+
// (`AutoBeDatabase.IModel.evidence`), and defaulting to every member
|
|
641
|
+
// would put `id`, `created_at`, and every back-reference into the
|
|
642
|
+
// denominator — which teaches an author to write filler reasons. A
|
|
643
|
+
// claim keeps every host, because there the selector narrows where a
|
|
644
|
+
// tag may sit rather than what must be covered.
|
|
645
|
+
case kind == artifactPrisma && unit:
|
|
646
|
+
values = []string{"model"}
|
|
647
|
+
case kind == artifactPrisma:
|
|
648
|
+
values = []string{"model", "column", "relation"}
|
|
649
|
+
case kind == artifactTypeScript && unit:
|
|
650
|
+
values = []string{"type"}
|
|
651
|
+
default:
|
|
652
|
+
values = []string{"type", "function", "property"}
|
|
653
|
+
}
|
|
654
|
+
} else {
|
|
655
|
+
trimmed := bytes.TrimSpace(raw)
|
|
656
|
+
switch trimmed[0] {
|
|
657
|
+
case '"':
|
|
658
|
+
var value string
|
|
659
|
+
if err := json.Unmarshal(raw, &value); err != nil {
|
|
660
|
+
return nil, []string{configurationProblem(ruleName, path, "expected a supported symbol string or array.")}
|
|
661
|
+
}
|
|
662
|
+
values = []string{value}
|
|
663
|
+
case '[':
|
|
664
|
+
if err := json.Unmarshal(raw, &values); err != nil {
|
|
665
|
+
return nil, []string{configurationProblem(ruleName, path, "expected a supported symbol string or array.")}
|
|
666
|
+
}
|
|
667
|
+
if len(values) == 0 {
|
|
668
|
+
return nil, []string{configurationProblem(ruleName, path, "an empty symbol array selects no evidence units or declaration hosts.")}
|
|
669
|
+
}
|
|
670
|
+
default:
|
|
671
|
+
return nil, []string{configurationProblem(ruleName, path, "expected a supported symbol string or array.")}
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
allowed := map[string]bool{}
|
|
675
|
+
supported := []string{"type", "function", "property"}
|
|
676
|
+
switch kind {
|
|
677
|
+
case artifactMarkdown:
|
|
678
|
+
supported = []string{"file", "h1", "h2", "h3", "h4"}
|
|
679
|
+
case artifactPrisma:
|
|
680
|
+
supported = []string{"model", "column", "relation"}
|
|
681
|
+
}
|
|
682
|
+
for _, symbol := range supported {
|
|
683
|
+
allowed[symbol] = true
|
|
684
|
+
}
|
|
685
|
+
set := symbolSet{}
|
|
686
|
+
problems := []string{}
|
|
687
|
+
for _, value := range values {
|
|
688
|
+
if !allowed[value] {
|
|
689
|
+
problems = append(problems, configurationProblem(ruleName, path, "symbol '"+value+"' is not supported for "+string(kind)+"."))
|
|
690
|
+
continue
|
|
691
|
+
}
|
|
692
|
+
set[value] = true
|
|
693
|
+
}
|
|
694
|
+
return set, problems
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
func decodeObject(raw json.RawMessage, path string) (map[string]json.RawMessage, string) {
|
|
698
|
+
var object map[string]json.RawMessage
|
|
699
|
+
if err := json.Unmarshal(raw, &object); err != nil || object == nil {
|
|
700
|
+
return nil, "Invalid evidence/graph configuration at " + path + ": expected an object."
|
|
701
|
+
}
|
|
702
|
+
return object, ""
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
// configurationProblem opens a configuration diagnostic with the rule that owns
|
|
706
|
+
// the setting.
|
|
707
|
+
//
|
|
708
|
+
// The owning rule is a parameter because two rules share these decoders. A
|
|
709
|
+
// message naming a rule the reader did not configure sends them to edit a
|
|
710
|
+
// setting that is not wrong, and with several rules enabled it is not even
|
|
711
|
+
// obvious which one lied.
|
|
712
|
+
func configurationProblem(ruleName string, path string, message string) string {
|
|
713
|
+
return "Invalid " + ruleName + " configuration at " + path + ": " + message
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
func rejectUnknownFields(
|
|
717
|
+
object map[string]json.RawMessage,
|
|
718
|
+
allowed []string,
|
|
719
|
+
ruleName string,
|
|
720
|
+
path string,
|
|
721
|
+
) []string {
|
|
722
|
+
known := map[string]bool{}
|
|
723
|
+
for _, name := range allowed {
|
|
724
|
+
known[name] = true
|
|
725
|
+
}
|
|
726
|
+
unknown := []string{}
|
|
727
|
+
for name := range object {
|
|
728
|
+
if !known[name] {
|
|
729
|
+
unknown = append(unknown, name)
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
sort.Strings(unknown)
|
|
733
|
+
problems := make([]string, 0, len(unknown))
|
|
734
|
+
for _, name := range unknown {
|
|
735
|
+
if name == "severity" {
|
|
736
|
+
problems = append(problems, configurationProblem(
|
|
737
|
+
ruleName,
|
|
738
|
+
path+".severity",
|
|
739
|
+
"severity belongs only in the outer @ttsc/lint rule setting.",
|
|
740
|
+
))
|
|
741
|
+
continue
|
|
742
|
+
}
|
|
743
|
+
// The inverted-relation hints below name properties that only ever
|
|
744
|
+
// existed on the graph, so offering them elsewhere would invite a
|
|
745
|
+
// reader to migrate a setting their rule never had.
|
|
746
|
+
if name == "sources" && ruleName == graphRuleName {
|
|
747
|
+
problems = append(problems, configurationProblem(
|
|
748
|
+
ruleName,
|
|
749
|
+
path+".sources",
|
|
750
|
+
"the graph is now declared from the claiming side; declare 'claims', each citing its evidence under 'reference'.",
|
|
751
|
+
))
|
|
752
|
+
continue
|
|
753
|
+
}
|
|
754
|
+
if name == "citedBy" && ruleName == graphRuleName {
|
|
755
|
+
problems = append(problems, configurationProblem(
|
|
756
|
+
ruleName,
|
|
757
|
+
path+".citedBy",
|
|
758
|
+
"this relation was inverted; declare the evidence this claim cites under 'reference'.",
|
|
759
|
+
))
|
|
760
|
+
continue
|
|
761
|
+
}
|
|
762
|
+
problems = append(problems, configurationProblem(
|
|
763
|
+
ruleName,
|
|
764
|
+
path+"."+name,
|
|
765
|
+
"unknown property; expected only "+strings.Join(allowed, ", ")+".",
|
|
766
|
+
))
|
|
767
|
+
}
|
|
768
|
+
return problems
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
func describePatterns(globs globSet) string {
|
|
772
|
+
quoted := make([]string, 0, len(globs.Patterns))
|
|
773
|
+
for _, pattern := range globs.Patterns {
|
|
774
|
+
quoted = append(quoted, "'"+pattern.Raw+"'")
|
|
775
|
+
}
|
|
776
|
+
return "[" + strings.Join(quoted, ", ") + "]"
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
// describePopulation names the patterns a population selects with, and the base
|
|
780
|
+
// they were resolved against when that base is not the project root.
|
|
781
|
+
//
|
|
782
|
+
// The base is stated rather than assumed, because a citation that resolves
|
|
783
|
+
// outside the project has to be repairable from the diagnostic alone: patterns
|
|
784
|
+
// that look correct against a root the reader is imagining are the failure this
|
|
785
|
+
// property introduces, and naming the resolved base is what removes it.
|
|
786
|
+
func describePopulation(base populationBase, globs globSet) string {
|
|
787
|
+
if base.Default {
|
|
788
|
+
return describePatterns(globs)
|
|
789
|
+
}
|
|
790
|
+
return describePatterns(globs) + " under root '" + populationRootLabel(base) + "'"
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
func describeReferenceSources(reference referenceSpec) string {
|
|
794
|
+
if reference.Type != artifactSwagger {
|
|
795
|
+
return describePopulation(reference.Base, reference.Files)
|
|
796
|
+
}
|
|
797
|
+
return "'" + displaySwaggerSource(reference.Source) + "'"
|
|
798
|
+
}
|