@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/base.go
ADDED
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
package evidence
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"os"
|
|
5
|
+
"path"
|
|
6
|
+
"path/filepath"
|
|
7
|
+
"sort"
|
|
8
|
+
"strings"
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
// populationBase is the directory one rooted artifact population resolves
|
|
12
|
+
// against.
|
|
13
|
+
//
|
|
14
|
+
// Every population had exactly one base before `root` existed — the ttsc
|
|
15
|
+
// project root — and that is still the default. Declaring a root moves three
|
|
16
|
+
// things together, and keeping them together is what makes the escape coherent:
|
|
17
|
+
// the globs resolve against it, a unit's target is spelled relative to it, and a
|
|
18
|
+
// diagnostic names the file through it. A requirements set shared by two
|
|
19
|
+
// packages therefore answers to the *same* citation text in both, which is the
|
|
20
|
+
// only reason a second project can adopt a document set the first already cites.
|
|
21
|
+
//
|
|
22
|
+
// The alternative — project-relative targets that ascend with `../` — was
|
|
23
|
+
// rejected because it re-couples the citation to the citing project's position
|
|
24
|
+
// in the tree, so the same document owns a different address in every package
|
|
25
|
+
// that reads it. This mirrors what a `package` TypeScript reference already
|
|
26
|
+
// does: moving the base moves the address space with it (`graph.go`,
|
|
27
|
+
// `materializePackageGlobReference`).
|
|
28
|
+
type populationBase struct {
|
|
29
|
+
// Absolute is the canonical directory, and the identity two populations are
|
|
30
|
+
// judged the same base by.
|
|
31
|
+
Absolute string
|
|
32
|
+
// Display is what a diagnostic names: project-relative, ascending with `..`
|
|
33
|
+
// when the base sits above the project, and absolute only when no relative
|
|
34
|
+
// spelling exists. Empty for the default base, whose files are already named
|
|
35
|
+
// by their project-relative path.
|
|
36
|
+
Display string
|
|
37
|
+
// Default marks the base a population takes when it declares no root. Its
|
|
38
|
+
// addresses stay plain project-relative paths, which is what leaves every
|
|
39
|
+
// existing citation, unit identity, and diagnostic byte-identical.
|
|
40
|
+
Default bool
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// artifactAddress is the three spellings one loaded file answers to.
|
|
44
|
+
//
|
|
45
|
+
// They are carried together because each is load-bearing and none can be
|
|
46
|
+
// derived from another once a base is in play. Relative is the citation's
|
|
47
|
+
// target, Display is what a reader has to open, and Key separates two
|
|
48
|
+
// populations that reached the same file through different roots — where the
|
|
49
|
+
// file is one thing and its target is two.
|
|
50
|
+
type artifactAddress struct {
|
|
51
|
+
Base populationBase
|
|
52
|
+
Relative string
|
|
53
|
+
Display string
|
|
54
|
+
Key string
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
func (base populationBase) addressOf(relative string) artifactAddress {
|
|
58
|
+
return artifactAddress{
|
|
59
|
+
Base: base,
|
|
60
|
+
Relative: relative,
|
|
61
|
+
Display: base.display(relative),
|
|
62
|
+
Key: base.address(relative),
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// display spells a base-relative path the way a reader must open it.
|
|
67
|
+
func (base populationBase) display(relative string) string {
|
|
68
|
+
if base.Display == "" {
|
|
69
|
+
return relative
|
|
70
|
+
}
|
|
71
|
+
return base.Display + "/" + relative
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// address composes the key one artifact is filed under.
|
|
75
|
+
//
|
|
76
|
+
// The base is part of the key because one physical file may belong to two
|
|
77
|
+
// populations with different roots, and its target — its path relative to its
|
|
78
|
+
// own base — differs between them. Keying on the file alone would let the second
|
|
79
|
+
// population overwrite the first with addresses the first cannot resolve, and
|
|
80
|
+
// nothing about that failure looks like a failure: the citations simply stop
|
|
81
|
+
// resolving in one of the two claims.
|
|
82
|
+
//
|
|
83
|
+
// The default base returns the bare relative path, so every unit identity, every
|
|
84
|
+
// inventory key, and the whole TypeScript path space are exactly what they were
|
|
85
|
+
// before roots existed.
|
|
86
|
+
func (base populationBase) address(relative string) string {
|
|
87
|
+
if base.Default {
|
|
88
|
+
return relative
|
|
89
|
+
}
|
|
90
|
+
return base.Absolute + "\x00" + relative
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// relativeOf inverts address, recovering the base-relative path an inventory key
|
|
94
|
+
// was composed from and reporting false when that key belongs to another base.
|
|
95
|
+
//
|
|
96
|
+
// Composition and inversion live together on purpose. The key's shape is what
|
|
97
|
+
// separates two populations that reached one file through different roots, and
|
|
98
|
+
// a matcher that re-derived it would be free to drift from the loader that
|
|
99
|
+
// wrote it — silently, since a drifted key matches nothing and reads exactly
|
|
100
|
+
// like a glob that selects nothing.
|
|
101
|
+
func (base populationBase) relativeOf(address string) (string, bool) {
|
|
102
|
+
if base.Default {
|
|
103
|
+
if strings.Contains(address, "\x00") {
|
|
104
|
+
return "", false
|
|
105
|
+
}
|
|
106
|
+
return address, true
|
|
107
|
+
}
|
|
108
|
+
return strings.CutPrefix(address, base.Absolute+"\x00")
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// resolveProjectPath turns a path a diagnostic names back into a location on
|
|
112
|
+
// disk.
|
|
113
|
+
//
|
|
114
|
+
// An absolute display path is used as it stands. Joining it to the project root
|
|
115
|
+
// would produce a path under the project that does not exist, and the failure
|
|
116
|
+
// would arrive as a missing file rather than as the configuration error it is.
|
|
117
|
+
func resolveProjectPath(root string, relative string) string {
|
|
118
|
+
local := filepath.FromSlash(relative)
|
|
119
|
+
if filepath.IsAbs(local) {
|
|
120
|
+
return filepath.Clean(local)
|
|
121
|
+
}
|
|
122
|
+
return filepath.Join(root, local)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// resolvePopulationBase anchors a declared root against the ttsc project root.
|
|
126
|
+
//
|
|
127
|
+
// A declared root that resolves back onto the project root is the default base
|
|
128
|
+
// rather than a second one spelled differently, so `root: "."` and an omitted
|
|
129
|
+
// root produce one population instead of two that address the same files
|
|
130
|
+
// differently.
|
|
131
|
+
func resolvePopulationBase(root string, declared string) populationBase {
|
|
132
|
+
if declared == "" {
|
|
133
|
+
return populationBase{Absolute: root, Default: true}
|
|
134
|
+
}
|
|
135
|
+
absolute := filepath.FromSlash(declared)
|
|
136
|
+
if !filepath.IsAbs(absolute) {
|
|
137
|
+
absolute = filepath.Join(root, absolute)
|
|
138
|
+
}
|
|
139
|
+
absolute = filepath.Clean(absolute)
|
|
140
|
+
if absolute == root {
|
|
141
|
+
return populationBase{Absolute: root, Default: true}
|
|
142
|
+
}
|
|
143
|
+
return populationBase{
|
|
144
|
+
Absolute: absolute,
|
|
145
|
+
Display: projectRelativeDisplay(root, absolute),
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// projectRelativeDisplay spells a directory the way the project sees it.
|
|
150
|
+
//
|
|
151
|
+
// A relative spelling is preferred even when it ascends, because every other
|
|
152
|
+
// path this rule prints is project-relative and a reader compares them against
|
|
153
|
+
// each other. An absolute path appears only when no relative one exists — a
|
|
154
|
+
// different Windows volume — where the alternative would be no spelling at all.
|
|
155
|
+
func projectRelativeDisplay(root string, absolute string) string {
|
|
156
|
+
relative, err := filepath.Rel(root, absolute)
|
|
157
|
+
if err != nil {
|
|
158
|
+
return filepath.ToSlash(absolute)
|
|
159
|
+
}
|
|
160
|
+
slashed := filepath.ToSlash(relative)
|
|
161
|
+
if slashed == "." {
|
|
162
|
+
return ""
|
|
163
|
+
}
|
|
164
|
+
return slashed
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// resolveGraphBases anchors every configured population against the project
|
|
168
|
+
// root, once, before anything is loaded.
|
|
169
|
+
//
|
|
170
|
+
// Decoding cannot do this: the decoder runs from options alone and has no
|
|
171
|
+
// project identity, which is also what lets `ProjectInputs` declare a rooted
|
|
172
|
+
// population's topology without touching the filesystem.
|
|
173
|
+
func resolveGraphBases(root string, config *graphConfig) {
|
|
174
|
+
for claimIndex := range config.Claims {
|
|
175
|
+
claim := &config.Claims[claimIndex]
|
|
176
|
+
claim.Base = resolvePopulationBase(root, claim.Root)
|
|
177
|
+
for referenceIndex := range claim.References {
|
|
178
|
+
reference := &claim.References[referenceIndex]
|
|
179
|
+
reference.Base = resolvePopulationBase(root, reference.Root)
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// configuredBases lists the distinct directories one artifact kind is loaded
|
|
185
|
+
// from, in a stable order.
|
|
186
|
+
//
|
|
187
|
+
// Order is fixed rather than incidental because a loader walks these in
|
|
188
|
+
// sequence and its diagnostics are reported in the order they were produced. A
|
|
189
|
+
// map iteration here would make an unreadable root's message move between runs.
|
|
190
|
+
func configuredBases(config graphConfig, kind artifactKind) []populationBase {
|
|
191
|
+
bases := []populationBase{}
|
|
192
|
+
seen := map[string]bool{}
|
|
193
|
+
add := func(base populationBase) {
|
|
194
|
+
if seen[base.Absolute] {
|
|
195
|
+
return
|
|
196
|
+
}
|
|
197
|
+
seen[base.Absolute] = true
|
|
198
|
+
bases = append(bases, base)
|
|
199
|
+
}
|
|
200
|
+
for _, claim := range config.Claims {
|
|
201
|
+
if claim.Type == kind {
|
|
202
|
+
add(claim.Base)
|
|
203
|
+
}
|
|
204
|
+
for _, reference := range claim.References {
|
|
205
|
+
if reference.Type == kind {
|
|
206
|
+
add(reference.Base)
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
sort.Slice(bases, func(left int, right int) bool {
|
|
211
|
+
return bases[left].Absolute < bases[right].Absolute
|
|
212
|
+
})
|
|
213
|
+
return bases
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// populationRootLabel names a base the way a reader has to think about it: the
|
|
217
|
+
// project-relative spelling when one exists, and the absolute path otherwise.
|
|
218
|
+
func populationRootLabel(base populationBase) string {
|
|
219
|
+
if base.Display == "" {
|
|
220
|
+
return filepath.ToSlash(base.Absolute)
|
|
221
|
+
}
|
|
222
|
+
return base.Display
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// unreadableBaseProblem reports a declared root that is not a readable
|
|
226
|
+
// directory.
|
|
227
|
+
//
|
|
228
|
+
// The default base is excluded because `Check` already validated the project
|
|
229
|
+
// root, and its diagnostic names the ttsc project identity as the repair rather
|
|
230
|
+
// than a configuration property that does not exist there.
|
|
231
|
+
//
|
|
232
|
+
// Both spellings appear. The relative one is the property the author has to
|
|
233
|
+
// edit, and the absolute one is where that property actually landed — which is
|
|
234
|
+
// the whole question the moment a root ascends out of the project.
|
|
235
|
+
func unreadableBaseProblem(base populationBase, kind artifactKind) string {
|
|
236
|
+
if base.Default {
|
|
237
|
+
return ""
|
|
238
|
+
}
|
|
239
|
+
info, err := os.Stat(base.Absolute)
|
|
240
|
+
if err == nil && info.IsDir() {
|
|
241
|
+
return ""
|
|
242
|
+
}
|
|
243
|
+
return "Evidence graph could not read the " + string(kind) + " root '" +
|
|
244
|
+
populationRootLabel(base) + "', which resolves to '" + filepath.ToSlash(base.Absolute) +
|
|
245
|
+
"'. Create the directory, or correct the 'root' property; it resolves against the ttsc project root."
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// normalizeRootPath validates a declared root without resolving it.
|
|
249
|
+
//
|
|
250
|
+
// Two forms stay refused, and both for the reason the `files` grammar refuses
|
|
251
|
+
// them. A glob is not a directory, so accepting one would leave the base itself
|
|
252
|
+
// depending on what the filesystem happens to contain. A Windows drive-relative
|
|
253
|
+
// path such as `C:docs` is not absolute by the path API yet resolves against
|
|
254
|
+
// whatever directory that drive is currently on, so it names a different
|
|
255
|
+
// location on two machines while looking superficially safe — the rationale
|
|
256
|
+
// `glob_honors_public_path_contract_test.go` records for the same rejection in
|
|
257
|
+
// `files`.
|
|
258
|
+
//
|
|
259
|
+
// `..` is accepted here, and that is the whole point of the property. It is
|
|
260
|
+
// unambiguous and portable once resolved against a known root, and the root is
|
|
261
|
+
// the one place where declaring it is visible in the configuration rather than
|
|
262
|
+
// buried in a pattern.
|
|
263
|
+
func normalizeRootPath(value string) (string, string) {
|
|
264
|
+
if value == "" {
|
|
265
|
+
return "", "the root must not be empty."
|
|
266
|
+
}
|
|
267
|
+
if strings.TrimSpace(value) != value {
|
|
268
|
+
return "", "the root must not have leading or trailing whitespace."
|
|
269
|
+
}
|
|
270
|
+
normalized := strings.ReplaceAll(value, "\\", "/")
|
|
271
|
+
if strings.ContainsAny(normalized, "*?") {
|
|
272
|
+
return "", "'" + value + "' is a glob; a root names one directory, and the patterns that select files inside it belong in 'files'."
|
|
273
|
+
}
|
|
274
|
+
if hasWindowsDrivePrefix(normalized) && !strings.HasPrefix(normalized[2:], "/") {
|
|
275
|
+
return "", "'" + 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."
|
|
276
|
+
}
|
|
277
|
+
// A UNC share is cleaned by hand because `path.Clean` collapses its leading
|
|
278
|
+
// `//` into one slash, which turns `//server/share` into an absolute path on
|
|
279
|
+
// the local volume — a different location that still looks like the one that
|
|
280
|
+
// was written.
|
|
281
|
+
if strings.HasPrefix(normalized, "//") {
|
|
282
|
+
normalized = "//" + strings.TrimPrefix(path.Clean(normalized), "/")
|
|
283
|
+
} else {
|
|
284
|
+
normalized = path.Clean(normalized)
|
|
285
|
+
}
|
|
286
|
+
if normalized == "." {
|
|
287
|
+
return "", ""
|
|
288
|
+
}
|
|
289
|
+
// `path.Clean` reads a drive prefix as an ordinary segment and strips the
|
|
290
|
+
// separator behind it, so `C:/` becomes `C:` — which the Windows path API
|
|
291
|
+
// then calls relative and resolves against the project. The separator is
|
|
292
|
+
// restored rather than the clean skipped, because a drive root is the one
|
|
293
|
+
// directory whose separator is part of its identity.
|
|
294
|
+
if len(normalized) == 2 && hasWindowsDrivePrefix(normalized) {
|
|
295
|
+
return normalized + "/", ""
|
|
296
|
+
}
|
|
297
|
+
return normalized, ""
|
|
298
|
+
}
|