@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.
Files changed (74) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +279 -0
  3. package/lib/index.d.ts +65 -0
  4. package/lib/index.js +87 -0
  5. package/lib/internal/loadPrismaModels.d.ts +1 -0
  6. package/lib/internal/loadPrismaModels.js +197 -0
  7. package/lib/internal/loadSwaggerOperations.d.ts +1 -0
  8. package/lib/internal/loadSwaggerOperations.js +156 -0
  9. package/lib/internal/normalizeSwaggerDocument.d.ts +1 -0
  10. package/lib/internal/normalizeSwaggerDocument.js +7 -0
  11. package/lib/structures/ITtscEvidenceDocumentedConfig.d.ts +32 -0
  12. package/lib/structures/ITtscEvidenceDocumentedConfig.js +2 -0
  13. package/lib/structures/ITtscEvidenceGraphClaim.d.ts +14 -0
  14. package/lib/structures/ITtscEvidenceGraphClaim.js +2 -0
  15. package/lib/structures/ITtscEvidenceGraphClaimBase.d.ts +102 -0
  16. package/lib/structures/ITtscEvidenceGraphClaimBase.js +2 -0
  17. package/lib/structures/ITtscEvidenceGraphConfig.d.ts +19 -0
  18. package/lib/structures/ITtscEvidenceGraphConfig.js +2 -0
  19. package/lib/structures/ITtscEvidenceGraphMarkdownClaim.d.ts +40 -0
  20. package/lib/structures/ITtscEvidenceGraphMarkdownClaim.js +2 -0
  21. package/lib/structures/ITtscEvidenceGraphMarkdownReference.d.ts +76 -0
  22. package/lib/structures/ITtscEvidenceGraphMarkdownReference.js +2 -0
  23. package/lib/structures/ITtscEvidenceGraphPrismaClaim.d.ts +65 -0
  24. package/lib/structures/ITtscEvidenceGraphPrismaClaim.js +2 -0
  25. package/lib/structures/ITtscEvidenceGraphPrismaReference.d.ts +75 -0
  26. package/lib/structures/ITtscEvidenceGraphPrismaReference.js +2 -0
  27. package/lib/structures/ITtscEvidenceGraphReference.d.ts +14 -0
  28. package/lib/structures/ITtscEvidenceGraphReference.js +2 -0
  29. package/lib/structures/ITtscEvidenceGraphReferenceBase.d.ts +73 -0
  30. package/lib/structures/ITtscEvidenceGraphReferenceBase.js +2 -0
  31. package/lib/structures/ITtscEvidenceGraphSwaggerReference.d.ts +39 -0
  32. package/lib/structures/ITtscEvidenceGraphSwaggerReference.js +2 -0
  33. package/lib/structures/ITtscEvidenceGraphTypeScriptClaim.d.ts +54 -0
  34. package/lib/structures/ITtscEvidenceGraphTypeScriptClaim.js +2 -0
  35. package/lib/structures/ITtscEvidenceGraphTypeScriptReference.d.ts +83 -0
  36. package/lib/structures/ITtscEvidenceGraphTypeScriptReference.js +2 -0
  37. package/lib/structures/index.d.ts +13 -0
  38. package/lib/structures/index.js +29 -0
  39. package/lib/typings/ITtscLintContributorRules.d.ts +34 -0
  40. package/lib/typings/ITtscLintContributorRules.js +2 -0
  41. package/lib/typings/ITtscLintRuleOptionsMap.d.ts +20 -0
  42. package/lib/typings/ITtscLintRuleOptionsMap.js +2 -0
  43. package/lib/typings/TtscEvidenceGraphMarkdownSymbol.d.ts +32 -0
  44. package/lib/typings/TtscEvidenceGraphMarkdownSymbol.js +2 -0
  45. package/lib/typings/TtscEvidenceGraphPrismaSymbol.d.ts +38 -0
  46. package/lib/typings/TtscEvidenceGraphPrismaSymbol.js +2 -0
  47. package/lib/typings/TtscEvidenceGraphTypeScriptSymbol.d.ts +64 -0
  48. package/lib/typings/TtscEvidenceGraphTypeScriptSymbol.js +2 -0
  49. package/lib/typings/index.d.ts +5 -0
  50. package/lib/typings/index.js +21 -0
  51. package/native/base.go +298 -0
  52. package/native/config.go +798 -0
  53. package/native/declaration.go +240 -0
  54. package/native/documented.go +353 -0
  55. package/native/exports.go +484 -0
  56. package/native/glob.go +207 -0
  57. package/native/graph.go +1409 -0
  58. package/native/health.go +45 -0
  59. package/native/hints.go +261 -0
  60. package/native/imports.go +373 -0
  61. package/native/inputs.go +125 -0
  62. package/native/loader.go +424 -0
  63. package/native/markdown.go +423 -0
  64. package/native/model.go +296 -0
  65. package/native/prisma.go +719 -0
  66. package/native/prisma_cache.go +213 -0
  67. package/native/prisma_locator.go +417 -0
  68. package/native/singular.go +369 -0
  69. package/native/swagger.go +331 -0
  70. package/native/swagger_cache.go +274 -0
  71. package/native/todo.go +138 -0
  72. package/native/typescript.go +1199 -0
  73. package/native/typescript_cache.go +96 -0
  74. 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
+ }