@ttsc/evidence 0.27.0 → 0.28.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/README.md +82 -169
- package/lib/structures/ITtscEvidenceGraphClaimBase.d.ts +15 -5
- package/lib/structures/ITtscEvidenceGraphMarkdownReference.d.ts +70 -6
- package/lib/structures/ITtscEvidenceGraphPrismaClaim.d.ts +5 -0
- package/lib/structures/ITtscEvidenceGraphPrismaReference.d.ts +16 -5
- package/lib/structures/ITtscEvidenceGraphReferenceBase.d.ts +8 -0
- package/native/base.go +517 -22
- package/native/config.go +114 -5
- package/native/graph.go +332 -70
- package/native/graph_nodes.go +121 -0
- package/native/loader.go +30 -7
- package/native/markdown.go +91 -28
- package/native/model.go +49 -16
- package/native/prisma.go +167 -48
- package/native/prisma_cache.go +40 -16
- package/native/swagger.go +1 -1
- package/native/swagger_cache.go +1 -1
- package/native/typescript.go +120 -9
- package/package.json +5 -5
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
package evidence
|
|
2
|
+
|
|
3
|
+
import "github.com/samchon/ttsc/packages/lint/rule"
|
|
4
|
+
|
|
5
|
+
// GraphNodes publishes the artifacts this project's configured references
|
|
6
|
+
// selected, so a consumer can answer what a citation names rather than only who
|
|
7
|
+
// wrote it.
|
|
8
|
+
//
|
|
9
|
+
// It is a projection of the same corpus Hints projects, and it decides nothing:
|
|
10
|
+
// no coverage, no cardinality, no policy, no diagnostic crosses this boundary.
|
|
11
|
+
// Those are this rule's product and it already delivers them as compile errors;
|
|
12
|
+
// a consumer holding a second copy would hold an unmaintained one.
|
|
13
|
+
//
|
|
14
|
+
// Only selected units are published, which is what makes a document nobody
|
|
15
|
+
// configured a reference for contribute nothing, and what withdraws a unit that
|
|
16
|
+
// named the tag it hid itself behind: selection is where the rule's own answer
|
|
17
|
+
// about its surface already lives, so this asks it rather than repeating it.
|
|
18
|
+
//
|
|
19
|
+
// TypeScript units are not published. The graph already holds every TypeScript
|
|
20
|
+
// declaration as a real node resolved by the checker, and a second node for the
|
|
21
|
+
// same symbol under an evidence address would make one declaration two things.
|
|
22
|
+
func (graphRule) GraphNodes(ctx *rule.GraphContext) []rule.GraphNode {
|
|
23
|
+
if ctx == nil {
|
|
24
|
+
return nil
|
|
25
|
+
}
|
|
26
|
+
cycle, published := ctx.State.(*graphCycleState)
|
|
27
|
+
if !published || cycle == nil {
|
|
28
|
+
return nil
|
|
29
|
+
}
|
|
30
|
+
corpus := cycle.Corpus
|
|
31
|
+
// Addresses of every materialized unit, so a parent can be named by the
|
|
32
|
+
// address a citation would use rather than by the rule's internal id. A
|
|
33
|
+
// parent outside the selected set resolves to nothing and the host clears it,
|
|
34
|
+
// which leaves the child at the top of its chain instead of inventing one.
|
|
35
|
+
addresses := map[string]string{}
|
|
36
|
+
for _, inventories := range []map[string]*artifactInventory{
|
|
37
|
+
corpus.Markdown,
|
|
38
|
+
corpus.Prisma,
|
|
39
|
+
corpus.Swagger,
|
|
40
|
+
} {
|
|
41
|
+
for _, inventory := range inventories {
|
|
42
|
+
if inventory == nil {
|
|
43
|
+
continue
|
|
44
|
+
}
|
|
45
|
+
for _, unit := range inventory.Units {
|
|
46
|
+
if unit != nil {
|
|
47
|
+
addresses[unit.ID] = unit.Target
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
selected := selectedCompletionUnits(
|
|
54
|
+
corpus.Config,
|
|
55
|
+
corpus.Markdown,
|
|
56
|
+
corpus.Prisma,
|
|
57
|
+
corpus.Swagger,
|
|
58
|
+
false,
|
|
59
|
+
)
|
|
60
|
+
nodes := make([]rule.GraphNode, 0, len(selected))
|
|
61
|
+
for _, unit := range selected {
|
|
62
|
+
// No withdrawal check here on purpose. `selectedCompletionUnits` already
|
|
63
|
+
// excludes a unit that named the tag it hid itself behind, so a second check
|
|
64
|
+
// would be a branch nothing can reach — and an unreachable guard reads as a
|
|
65
|
+
// load-bearing one to the next author, who then trusts it instead of the
|
|
66
|
+
// selection that actually enforces it.
|
|
67
|
+
if unit == nil {
|
|
68
|
+
continue
|
|
69
|
+
}
|
|
70
|
+
kind, ok := graphNodeKindOf(unit)
|
|
71
|
+
if !ok {
|
|
72
|
+
continue
|
|
73
|
+
}
|
|
74
|
+
nodes = append(nodes, rule.GraphNode{
|
|
75
|
+
Address: unit.Target,
|
|
76
|
+
Kind: kind,
|
|
77
|
+
Readable: unit.Readable,
|
|
78
|
+
Parent: addresses[unit.ParentID],
|
|
79
|
+
File: unit.Path,
|
|
80
|
+
Line: unit.Line,
|
|
81
|
+
Aliases: unit.Aliases,
|
|
82
|
+
})
|
|
83
|
+
}
|
|
84
|
+
return nodes
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// graphNodeKindOf maps one materialized unit onto the host's node vocabulary.
|
|
88
|
+
//
|
|
89
|
+
// The symbol is the rule's own selector, so this is a translation rather than a
|
|
90
|
+
// re-derivation: a Prisma relation is its own kind because the parser already
|
|
91
|
+
// separated it from a column, and a Markdown file is its own kind because the
|
|
92
|
+
// heading walk already separated it from a heading.
|
|
93
|
+
//
|
|
94
|
+
// A TypeScript unit reports false. So does an unrecognized symbol: guessing
|
|
95
|
+
// would publish a node under a kind a consumer would then rank and contain as
|
|
96
|
+
// something it is not.
|
|
97
|
+
func graphNodeKindOf(unit *evidenceUnit) (rule.GraphNodeKind, bool) {
|
|
98
|
+
switch unit.Type {
|
|
99
|
+
case artifactMarkdown:
|
|
100
|
+
if unit.Symbol == "file" {
|
|
101
|
+
return rule.GraphNodeMarkdownDocument, true
|
|
102
|
+
}
|
|
103
|
+
return rule.GraphNodeMarkdownSection, true
|
|
104
|
+
case artifactPrisma:
|
|
105
|
+
switch unit.Symbol {
|
|
106
|
+
case "model":
|
|
107
|
+
return rule.GraphNodePrismaModel, true
|
|
108
|
+
case "relation":
|
|
109
|
+
return rule.GraphNodePrismaRelation, true
|
|
110
|
+
case "column":
|
|
111
|
+
return rule.GraphNodePrismaColumn, true
|
|
112
|
+
}
|
|
113
|
+
return "", false
|
|
114
|
+
case artifactSwagger:
|
|
115
|
+
if unit.Symbol == "operation" {
|
|
116
|
+
return rule.GraphNodeSwaggerOperation, true
|
|
117
|
+
}
|
|
118
|
+
return "", false
|
|
119
|
+
}
|
|
120
|
+
return "", false
|
|
121
|
+
}
|
package/native/loader.go
CHANGED
|
@@ -315,33 +315,50 @@ func (loader *typeScriptLoader) locateInstalledPackage(
|
|
|
315
315
|
return path.Join("node_modules", name), nil
|
|
316
316
|
}
|
|
317
317
|
|
|
318
|
-
// resolveLinkedDirectory returns the directory a path ultimately names
|
|
318
|
+
// resolveLinkedDirectory returns the directory a path ultimately names, and
|
|
319
|
+
// reports whether the chain ended inside the hops this rule follows.
|
|
319
320
|
//
|
|
320
321
|
// `filepath.EvalSymlinks` answers this for a POSIX symlink but not for a
|
|
321
322
|
// Windows junction, which is exactly what pnpm creates for a workspace
|
|
322
323
|
// dependency there: it returns the junction unchanged, so a caller that trusts
|
|
323
324
|
// it walks the link and finds nothing. `os.Readlink` does report a junction's
|
|
324
325
|
// target, and `os.Stat` sees through both, so ask those instead.
|
|
325
|
-
|
|
326
|
+
//
|
|
327
|
+
// The second answer is what separates an exhausted chain from a resolved
|
|
328
|
+
// directory, which are one string without it: a caller comparing paths against
|
|
329
|
+
// a link that still names another link cannot tell that it holds one, and that
|
|
330
|
+
// is how the leaf case of #1269 stayed silent until the caller was told.
|
|
331
|
+
//
|
|
332
|
+
// The bound counts links followed, not answers given. A chain that ends exactly
|
|
333
|
+
// on the last hop this rule follows has landed on its directory with no
|
|
334
|
+
// iteration left to look, and it is a chain the filesystem resolves: Linux and
|
|
335
|
+
// Windows follow further, and Darwin stops at this same number, so the boundary
|
|
336
|
+
// sits where the strictest platform's does rather than inside it. So the answer
|
|
337
|
+
// costs one `os.Lstat` in that case and nothing at all in every other, since
|
|
338
|
+
// every earlier ending is a return from inside the loop. Refusing it instead
|
|
339
|
+
// would turn a root that works into an error at the boundary, which is what
|
|
340
|
+
// this rule exists to keep from happening in the other direction.
|
|
341
|
+
func resolveLinkedDirectory(directory string) (string, bool) {
|
|
326
342
|
current := directory
|
|
327
343
|
for range 32 {
|
|
328
344
|
info, err := os.Lstat(current)
|
|
329
345
|
if err != nil || info.IsDir() {
|
|
330
|
-
return current
|
|
346
|
+
return current, true
|
|
331
347
|
}
|
|
332
348
|
if target, err := os.Stat(current); err != nil || !target.IsDir() {
|
|
333
|
-
return current
|
|
349
|
+
return current, true
|
|
334
350
|
}
|
|
335
351
|
linked, err := os.Readlink(current)
|
|
336
352
|
if err != nil {
|
|
337
|
-
return current
|
|
353
|
+
return current, true
|
|
338
354
|
}
|
|
339
355
|
if !filepath.IsAbs(linked) {
|
|
340
356
|
linked = filepath.Join(filepath.Dir(current), linked)
|
|
341
357
|
}
|
|
342
358
|
current = filepath.ToSlash(linked)
|
|
343
359
|
}
|
|
344
|
-
|
|
360
|
+
info, err := os.Lstat(current)
|
|
361
|
+
return current, err == nil && info.IsDir()
|
|
345
362
|
}
|
|
346
363
|
|
|
347
364
|
// walk lists the project-relative TypeScript files below a directory.
|
|
@@ -357,7 +374,13 @@ func (loader *typeScriptLoader) walk(base string) ([]string, string) {
|
|
|
357
374
|
// nothing, so walking the spelled path finds no files and the reference
|
|
358
375
|
// looks empty rather than unresolvable. Walk what the link points at, and
|
|
359
376
|
// report the files under the spelled path so addresses stay stable.
|
|
360
|
-
|
|
377
|
+
// The bound is not refused here. A package whose chain outruns the resolver
|
|
378
|
+
// walks a link and matches nothing, and the caller already reports that as
|
|
379
|
+
// an empty population rather than passing over it in silence — so what is
|
|
380
|
+
// owed there is a better cause, not a diagnostic that does not exist. A
|
|
381
|
+
// declared base has no such report behind it, which is why
|
|
382
|
+
// `resolvedBaseDirectory` does refuse.
|
|
383
|
+
walked, _ := resolveLinkedDirectory(root)
|
|
361
384
|
found := []string{}
|
|
362
385
|
problem := ""
|
|
363
386
|
err := filepath.WalkDir(walked, func(current string, entry fs.DirEntry, err error) error {
|
package/native/markdown.go
CHANGED
|
@@ -41,39 +41,64 @@ func loadMarkdownBase(
|
|
|
41
41
|
inventories map[string]*artifactInventory,
|
|
42
42
|
) []string {
|
|
43
43
|
problems := []string{}
|
|
44
|
-
if problem :=
|
|
44
|
+
if problem := baseDirectoryProblem(base, artifactMarkdown); problem != "" {
|
|
45
45
|
recordPopulationFailure(inventories, artifactMarkdown, base)
|
|
46
46
|
return []string{problem}
|
|
47
47
|
}
|
|
48
|
-
|
|
48
|
+
from, resolved := resolvedBaseDirectory(base)
|
|
49
|
+
if !resolved {
|
|
50
|
+
recordPopulationFailure(inventories, artifactMarkdown, base)
|
|
51
|
+
return []string{unresolvedBaseProblem(base, artifactMarkdown)}
|
|
52
|
+
}
|
|
53
|
+
err := filepath.WalkDir(from, func(current string, entry fs.DirEntry, walkErr error) error {
|
|
49
54
|
if walkErr != nil {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
55
|
+
// The walk root belongs to its population by construction, so a failure
|
|
56
|
+
// to list it is a failure of the population and is never decided by what
|
|
57
|
+
// the globs happen to select. The relevance test below answers for an
|
|
58
|
+
// entry inside the base, and it answers for the base itself only by
|
|
59
|
+
// accident: its base-relative path is ".", which `couldMatchDescendant`
|
|
60
|
+
// calls true under a pattern opening with `**` and false under one
|
|
61
|
+
// opening with a segment. So the one failure that empties the whole
|
|
62
|
+
// population was reported or discarded by the shape of the globs. The
|
|
63
|
+
// success branch already exempts the base; this is that exemption on the
|
|
64
|
+
// error side.
|
|
65
|
+
//
|
|
66
|
+
// Returning the error ends the walk and carries the failure to the
|
|
67
|
+
// handler below, which is where a population-level finding belongs and
|
|
68
|
+
// where the population is recorded failed rather than healthy and empty.
|
|
69
|
+
if current == from {
|
|
70
|
+
return walkErr
|
|
59
71
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
72
|
+
problem, relevant := unreadableEntryProblem(
|
|
73
|
+
base,
|
|
74
|
+
from,
|
|
75
|
+
"Markdown",
|
|
76
|
+
current,
|
|
77
|
+
walkErr,
|
|
78
|
+
func(relative string) bool {
|
|
79
|
+
return matchesConfiguredMarkdownFile(config, base, relative) ||
|
|
80
|
+
couldContainConfiguredMarkdown(config, base, relative)
|
|
81
|
+
},
|
|
82
|
+
)
|
|
83
|
+
if relevant {
|
|
84
|
+
recordPopulationFailure(inventories, artifactMarkdown, base)
|
|
85
|
+
problems = append(problems, problem)
|
|
64
86
|
}
|
|
65
|
-
|
|
87
|
+
// `WalkDir` passes a nil entry only for its root, which the guard above
|
|
88
|
+
// answers, so this error belongs to a directory whose listing failed and
|
|
89
|
+
// the walk continues with its siblings.
|
|
90
|
+
return filepath.SkipDir
|
|
66
91
|
}
|
|
67
92
|
if entry.IsDir() {
|
|
68
|
-
if current !=
|
|
69
|
-
relative, ok := relativeProjectPath(
|
|
93
|
+
if current != from {
|
|
94
|
+
relative, ok := relativeProjectPath(from, current)
|
|
70
95
|
if !ok || !couldContainConfiguredMarkdown(config, base, relative) {
|
|
71
96
|
return filepath.SkipDir
|
|
72
97
|
}
|
|
73
98
|
}
|
|
74
99
|
return nil
|
|
75
100
|
}
|
|
76
|
-
relative, ok := relativeProjectPath(
|
|
101
|
+
relative, ok := relativeProjectPath(from, current)
|
|
77
102
|
if !ok {
|
|
78
103
|
return nil
|
|
79
104
|
}
|
|
@@ -88,13 +113,13 @@ func loadMarkdownBase(
|
|
|
88
113
|
Type: artifactMarkdown,
|
|
89
114
|
LoadFailed: true,
|
|
90
115
|
}
|
|
91
|
-
problems = append(problems, "Evidence graph could not read Markdown file '"+address.Display+"': "+readErr
|
|
116
|
+
problems = append(problems, "Evidence graph could not read Markdown file '"+address.Display+"': "+causeText(readErr)+". Fix filesystem access or exclude the file from configured globs.")
|
|
92
117
|
return nil
|
|
93
118
|
}
|
|
94
119
|
inventory, _ := scanMarkdownInventory(address, string(content))
|
|
95
120
|
inventories[address.Key] = inventory
|
|
96
121
|
for _, inventoryProblem := range inventory.Problems {
|
|
97
|
-
if
|
|
122
|
+
if selectedByMarkdownPopulation(config, base, relative, inventoryProblem.Symbol) {
|
|
98
123
|
problems = append(problems, inventoryProblem.Message)
|
|
99
124
|
}
|
|
100
125
|
}
|
|
@@ -107,7 +132,7 @@ func loadMarkdownBase(
|
|
|
107
132
|
})
|
|
108
133
|
if err != nil {
|
|
109
134
|
recordPopulationFailure(inventories, artifactMarkdown, base)
|
|
110
|
-
problems = append(problems, "
|
|
135
|
+
problems = append(problems, unlistableBaseProblem(base, "Markdown", err))
|
|
111
136
|
}
|
|
112
137
|
return problems
|
|
113
138
|
}
|
|
@@ -231,14 +256,28 @@ func scanMarkdownInventory(
|
|
|
231
256
|
}
|
|
232
257
|
// A heading that materializes no unit still opens a region, and that
|
|
233
258
|
// region's content belongs to the nearest heading unit enclosing it. An
|
|
234
|
-
// H5, and an H2 whose title yields no anchor, are both such
|
|
259
|
+
// H5 or deeper, and an H2 whose title yields no anchor, are both such
|
|
260
|
+
// headings.
|
|
235
261
|
// Carrying the previous unit forward instead would attribute the region to
|
|
236
262
|
// whatever unit the walk happened to see last, which is a sibling rather
|
|
237
263
|
// than an ancestor when the skipped heading is shallower: editing text
|
|
238
264
|
// under an anchorless H2 would then expire a review of the H3 above it,
|
|
239
265
|
// which does not contain that text.
|
|
240
266
|
currentDigestHostID = fileUnitID
|
|
241
|
-
|
|
267
|
+
// Start at the deepest level the array holds rather than at this
|
|
268
|
+
// heading's own. Only H1 through H4 ever write a slot, so a deeper
|
|
269
|
+
// heading's first candidate ancestor is H4, and reading from its own
|
|
270
|
+
// level indexed past the end: an H6 walked from 5 into a five-slot array
|
|
271
|
+
// and took the whole rule down before it materialized anything. An H5
|
|
272
|
+
// survived that only because it starts at the last valid slot.
|
|
273
|
+
//
|
|
274
|
+
// Clamp rather than widen the array. Widening would also seal it, since
|
|
275
|
+
// `markdownHeading` refuses a level past 6, so this is a choice about
|
|
276
|
+
// what the type says rather than about safety: the array is indexed by
|
|
277
|
+
// heading level and sized to hold every materializable one, so its length
|
|
278
|
+
// is the model. A wider array would carry slots nothing writes and stop
|
|
279
|
+
// saying so. It is not indexed by ordinal, so slot zero is unused.
|
|
280
|
+
for ancestorLevel := min(level-1, len(headingUnitIDs)-1); ancestorLevel >= 1; ancestorLevel-- {
|
|
242
281
|
if headingUnitIDs[ancestorLevel] != "" {
|
|
243
282
|
currentDigestHostID = headingUnitIDs[ancestorLevel]
|
|
244
283
|
break
|
|
@@ -256,8 +295,12 @@ func scanMarkdownInventory(
|
|
|
256
295
|
Message: problems[len(problems)-1],
|
|
257
296
|
})
|
|
258
297
|
} else {
|
|
298
|
+
// Clamped like the digest walk above, though the `level <= 4` around
|
|
299
|
+
// this block already bounds it. Both walks read the same array, so
|
|
300
|
+
// both state the same bound rather than one of them depending on a
|
|
301
|
+
// condition someone could move.
|
|
259
302
|
parentID := fileUnitID
|
|
260
|
-
for ancestorLevel := level -
|
|
303
|
+
for ancestorLevel := min(level-1, len(headingUnitIDs)-1); ancestorLevel >= 1; ancestorLevel-- {
|
|
261
304
|
if headingUnitIDs[ancestorLevel] != "" {
|
|
262
305
|
parentID = headingUnitIDs[ancestorLevel]
|
|
263
306
|
break
|
|
@@ -315,7 +358,6 @@ func scanMarkdownInventory(
|
|
|
315
358
|
}
|
|
316
359
|
for _, review := range parseReviews(comment) {
|
|
317
360
|
inventory.Reviews = append(inventory.Reviews, &evidenceReview{
|
|
318
|
-
HostID: hostIDAtLine[line-1],
|
|
319
361
|
SemanticHostIDs: []string{hostIDAtLine[line-1]},
|
|
320
362
|
Reviews: review.Reviews,
|
|
321
363
|
Type: artifactMarkdown,
|
|
@@ -432,13 +474,34 @@ func couldContainConfiguredMarkdown(
|
|
|
432
474
|
return false
|
|
433
475
|
}
|
|
434
476
|
|
|
435
|
-
|
|
477
|
+
// selectedByMarkdownPopulation reports whether any configured population reads
|
|
478
|
+
// this file for the symbol a scan problem was filed under.
|
|
479
|
+
//
|
|
480
|
+
// Claims are asked as well as references. A scan problem says the file
|
|
481
|
+
// materialized less than it looks like it should, and that is a hole on either
|
|
482
|
+
// side: a reference loses evidence units, while a claim loses the hosts that owe
|
|
483
|
+
// acknowledgements. The claim side was the silent one. A whitespace-named claim
|
|
484
|
+
// file forms no target, so it contributes no host, drops out of the obligation,
|
|
485
|
+
// and said nothing at all; the same file pointed at by a reference reported the
|
|
486
|
+
// path immediately.
|
|
487
|
+
//
|
|
488
|
+
// The claim is matched on its own symbol set for the reason a reference is: a
|
|
489
|
+
// problem about a kind this population does not read is not its problem. The
|
|
490
|
+
// wildcard symbol still reaches both, which is what carries the unaddressable
|
|
491
|
+
// path, since that one is about the file rather than about any heading kind.
|
|
492
|
+
func selectedByMarkdownPopulation(
|
|
436
493
|
config graphConfig,
|
|
437
494
|
base populationBase,
|
|
438
495
|
path string,
|
|
439
496
|
symbol string,
|
|
440
497
|
) bool {
|
|
441
498
|
for _, claim := range config.Claims {
|
|
499
|
+
if claim.Type == artifactMarkdown &&
|
|
500
|
+
claim.Base.Absolute == base.Absolute &&
|
|
501
|
+
claim.Files.matches(path) &&
|
|
502
|
+
(symbol == "*" || claim.Symbols.contains(symbol)) {
|
|
503
|
+
return true
|
|
504
|
+
}
|
|
442
505
|
for _, reference := range claim.References {
|
|
443
506
|
if reference.Type == artifactMarkdown &&
|
|
444
507
|
reference.Base.Absolute == base.Absolute &&
|
|
@@ -561,7 +624,7 @@ func reportUnreadableMarkdownTags(
|
|
|
561
624
|
}
|
|
562
625
|
rendered := false
|
|
563
626
|
for index, rawLine := range lines {
|
|
564
|
-
line := strings.TrimSuffix(rawLine, "
|
|
627
|
+
line := strings.TrimSuffix(rawLine, "\r")
|
|
565
628
|
if opens, closes := renderedCodeEdges(line); opens || closes {
|
|
566
629
|
rendered = opens
|
|
567
630
|
continue
|
package/native/model.go
CHANGED
|
@@ -66,6 +66,11 @@ type claimSpec struct {
|
|
|
66
66
|
// the resolved Base because the two are produced at different times: the
|
|
67
67
|
// spelling decodes from options alone, while the resolution needs a project
|
|
68
68
|
// identity the decoder never sees.
|
|
69
|
+
//
|
|
70
|
+
// `Base.Declared` holds the same spelling once a project identity exists, and
|
|
71
|
+
// the two are not interchangeable. Read this one before resolution, which is
|
|
72
|
+
// what `ProjectInputs` does to publish a topology without touching the
|
|
73
|
+
// filesystem, and read the base's after it, which is what a diagnostic does.
|
|
69
74
|
Root string
|
|
70
75
|
Base populationBase
|
|
71
76
|
Files globSet
|
|
@@ -112,6 +117,31 @@ type referencePolicy struct {
|
|
|
112
117
|
// an `@evidenceReview` whose fingerprint matches the cited scope's current
|
|
113
118
|
// content, so a review expires when the thing it reviewed moves.
|
|
114
119
|
RequireReview bool
|
|
120
|
+
// Checklist moves the obligation from the reference to each selected claim
|
|
121
|
+
// host: every host answers every unit, rather than the population being
|
|
122
|
+
// discharged once by whichever host got there first.
|
|
123
|
+
//
|
|
124
|
+
// It is not a peer of the cardinality options even though it decodes beside
|
|
125
|
+
// them. Those tighten a count inside the existing per-reference obligation;
|
|
126
|
+
// this one gives the obligation a host dimension, which is why the duplicate
|
|
127
|
+
// and conflict keys below it become per host and why `UniqueEvidence` and
|
|
128
|
+
// `SingleEvidencePerSymbol` are refused alongside it rather than composed
|
|
129
|
+
// with it.
|
|
130
|
+
//
|
|
131
|
+
// It also takes the subtree away from a positive citation. A citation answers
|
|
132
|
+
// the item it names and nothing beneath it, and a target naming no item at all
|
|
133
|
+
// is refused outright as an aggregate.
|
|
134
|
+
//
|
|
135
|
+
// Both halves are needed, and only measuring shows why. Refusing the
|
|
136
|
+
// unselected ancestor alone left the default Markdown selector, where the file
|
|
137
|
+
// is itself an item, discharging every heading from one `@evidence
|
|
138
|
+
// docs/rules.md` — the option was a no-op in the first configuration an
|
|
139
|
+
// adopter writes, silently. Any selector admitting an ancestor beside a
|
|
140
|
+
// descendant opens the same hole.
|
|
141
|
+
//
|
|
142
|
+
// `@evidenceExclude` keeps the cascade, because "none of this applies here" is
|
|
143
|
+
// one decision however many items it covers.
|
|
144
|
+
Checklist bool
|
|
115
145
|
}
|
|
116
146
|
|
|
117
147
|
// evidenceReview is one verification statement bound to its host and target.
|
|
@@ -123,15 +153,10 @@ type referencePolicy struct {
|
|
|
123
153
|
// its review spell one address, and resolving twice would let a review answer a
|
|
124
154
|
// scope its citation does not name.
|
|
125
155
|
type evidenceReview struct {
|
|
126
|
-
//
|
|
127
|
-
//
|
|
128
|
-
//
|
|
129
|
-
//
|
|
130
|
-
// `namespace I` for a citation on `interface I` — placement the graph
|
|
131
|
-
// elsewhere calls not worth a diagnostic, and which `evidence/review` accepts.
|
|
132
|
-
HostID string
|
|
133
|
-
// SemanticHostIDs are the selected graph identities this review is written on,
|
|
134
|
-
// which is what a citation is matched by.
|
|
156
|
+
// SemanticHostIDs are the ledger keys this review is written on. They are
|
|
157
|
+
// normally selected graph identities; an unattached Prisma review uses its
|
|
158
|
+
// synthetic file identity so the declaration-side position fallback can meet
|
|
159
|
+
// it. A citation is matched by these keys.
|
|
135
160
|
SemanticHostIDs []string
|
|
136
161
|
// Reviews names which acknowledgement this review answers for. It is part of
|
|
137
162
|
// the match, never inferred: verifying a citation and verifying an exclusion
|
|
@@ -275,8 +300,9 @@ type evidenceDeclaration struct {
|
|
|
275
300
|
ID string
|
|
276
301
|
HostID string
|
|
277
302
|
// SemanticHostIDs names the selected graph identities that physically host
|
|
278
|
-
// this declaration. HostID remains the source-position identity used
|
|
279
|
-
//
|
|
303
|
+
// this declaration. HostID remains the source-position identity used for
|
|
304
|
+
// same-block duplicate detection and as the review lookup fallback for a
|
|
305
|
+
// valid carrier with no semantic host. Policy cardinality must not confuse a
|
|
280
306
|
// declaration position with the public symbol identity it represents.
|
|
281
307
|
SemanticHostIDs []string
|
|
282
308
|
Type artifactKind
|
|
@@ -304,12 +330,19 @@ func (declaration *evidenceDeclaration) valid() bool {
|
|
|
304
330
|
type artifactInventory struct {
|
|
305
331
|
// Address is the population-relative identity used for units and
|
|
306
332
|
// declarations. It differs from Path when a configured root moves the
|
|
307
|
-
// address space while
|
|
333
|
+
// address space while a diagnostic keeps naming the file the way a reader
|
|
334
|
+
// opens it.
|
|
308
335
|
Address string
|
|
309
|
-
// Path is the
|
|
310
|
-
//
|
|
311
|
-
//
|
|
312
|
-
//
|
|
336
|
+
// Path is the source a diagnostic names. For a file it is spelled the way a
|
|
337
|
+
// reader opens it: project-relative, ascending with `..` when the file sits
|
|
338
|
+
// above the project root, and absolute when no relative spelling exists. A
|
|
339
|
+
// Swagger document is its configured source instead, which may be a URL, and
|
|
340
|
+
// the diagnostics for that kind print it through `displaySwaggerSource` rather
|
|
341
|
+
// than from here.
|
|
342
|
+
//
|
|
343
|
+
// It is not the key this inventory is filed under — that key carries the
|
|
344
|
+
// population base as well, because one file reached through two roots owns two
|
|
345
|
+
// sets of targets.
|
|
313
346
|
Path string
|
|
314
347
|
Type artifactKind
|
|
315
348
|
Units []*evidenceUnit
|