@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
|
@@ -0,0 +1,484 @@
|
|
|
1
|
+
package evidence
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"sort"
|
|
5
|
+
"strings"
|
|
6
|
+
|
|
7
|
+
shimast "github.com/microsoft/typescript-go/shim/ast"
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
// moduleExport is one name a module offers to whoever imports it.
|
|
11
|
+
//
|
|
12
|
+
// A local export names a declaration in this file; a re-export names one in
|
|
13
|
+
// another. Keeping the two apart is what lets identity stay with the declaring
|
|
14
|
+
// file while reachability is decided by whoever re-exports it.
|
|
15
|
+
type moduleExport struct {
|
|
16
|
+
// Public is the name an importer sees. Empty for `export * from`, which
|
|
17
|
+
// contributes its target's names rather than a name of its own.
|
|
18
|
+
Public string
|
|
19
|
+
// Local is the declaration's own name in this file, empty for a re-export.
|
|
20
|
+
Local string
|
|
21
|
+
// Specifier is the module a re-export pulls from, empty for a local export.
|
|
22
|
+
Specifier string
|
|
23
|
+
// Imported is the name taken from that module, empty for a star re-export.
|
|
24
|
+
Imported string
|
|
25
|
+
// Namespace marks `export * as ns from`, which nests the target's whole
|
|
26
|
+
// surface one segment deeper instead of flattening it.
|
|
27
|
+
Namespace bool
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// collectModuleExports reads a file's public surface as importers see it.
|
|
31
|
+
//
|
|
32
|
+
// The materializer deliberately skips re-exports when building units, because a
|
|
33
|
+
// re-export declares nothing and must not create a second unit. That rule is
|
|
34
|
+
// untouched here: this table records reachability, and identity is resolved
|
|
35
|
+
// separately by following the specifier to the file that declares the symbol.
|
|
36
|
+
func collectModuleExports(file *shimast.SourceFile) []moduleExport {
|
|
37
|
+
exports := []moduleExport{}
|
|
38
|
+
if file == nil || file.Statements == nil {
|
|
39
|
+
return exports
|
|
40
|
+
}
|
|
41
|
+
locals := collectLocalExportNames(file.Statements)
|
|
42
|
+
for _, statement := range file.Statements.Nodes {
|
|
43
|
+
if statement == nil {
|
|
44
|
+
continue
|
|
45
|
+
}
|
|
46
|
+
if statement.Kind == shimast.KindExportDeclaration {
|
|
47
|
+
exports = append(exports, exportDeclarationEntries(statement, locals)...)
|
|
48
|
+
continue
|
|
49
|
+
}
|
|
50
|
+
if !isSyntacticallyExported(statement) {
|
|
51
|
+
continue
|
|
52
|
+
}
|
|
53
|
+
for _, declared := range topLevelDeclaredNames(statement) {
|
|
54
|
+
exports = append(exports, moduleExport{
|
|
55
|
+
Public: declared.Name,
|
|
56
|
+
Local: declared.Name,
|
|
57
|
+
})
|
|
58
|
+
for _, exported := range locals[declared.Name] {
|
|
59
|
+
if exported.Public != declared.Name {
|
|
60
|
+
exports = append(exports, moduleExport{
|
|
61
|
+
Public: exported.Public,
|
|
62
|
+
Local: declared.Name,
|
|
63
|
+
})
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
for local, names := range locals {
|
|
69
|
+
for _, exported := range names {
|
|
70
|
+
if exported.Public == "" {
|
|
71
|
+
continue
|
|
72
|
+
}
|
|
73
|
+
exports = append(exports, moduleExport{
|
|
74
|
+
Public: exported.Public,
|
|
75
|
+
Local: local,
|
|
76
|
+
})
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return dedupeModuleExports(exports)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
func exportDeclarationEntries(
|
|
83
|
+
statement *shimast.Node,
|
|
84
|
+
locals map[string][]exportedName,
|
|
85
|
+
) []moduleExport {
|
|
86
|
+
declaration := statement.AsExportDeclaration()
|
|
87
|
+
if declaration == nil {
|
|
88
|
+
return nil
|
|
89
|
+
}
|
|
90
|
+
specifier := stringLiteralText(declaration.ModuleSpecifier)
|
|
91
|
+
if specifier == "" {
|
|
92
|
+
// A local export list is already covered by the locals table, which the
|
|
93
|
+
// caller folds in; recording it twice would double every aliased name.
|
|
94
|
+
return nil
|
|
95
|
+
}
|
|
96
|
+
if declaration.ExportClause == nil {
|
|
97
|
+
return []moduleExport{{Specifier: specifier}}
|
|
98
|
+
}
|
|
99
|
+
switch declaration.ExportClause.Kind {
|
|
100
|
+
case shimast.KindNamespaceExport:
|
|
101
|
+
name := declarationName(declaration.ExportClause.Name())
|
|
102
|
+
if name == "" {
|
|
103
|
+
return nil
|
|
104
|
+
}
|
|
105
|
+
return []moduleExport{{
|
|
106
|
+
Public: name,
|
|
107
|
+
Specifier: specifier,
|
|
108
|
+
Namespace: true,
|
|
109
|
+
}}
|
|
110
|
+
case shimast.KindNamedExports:
|
|
111
|
+
named := declaration.ExportClause.AsNamedExports()
|
|
112
|
+
if named == nil || named.Elements == nil {
|
|
113
|
+
return nil
|
|
114
|
+
}
|
|
115
|
+
entries := []moduleExport{}
|
|
116
|
+
for _, element := range named.Elements.Nodes {
|
|
117
|
+
if element == nil || element.Kind != shimast.KindExportSpecifier {
|
|
118
|
+
continue
|
|
119
|
+
}
|
|
120
|
+
exportSpecifier := element.AsExportSpecifier()
|
|
121
|
+
if exportSpecifier == nil {
|
|
122
|
+
continue
|
|
123
|
+
}
|
|
124
|
+
public := declarationName(element.Name())
|
|
125
|
+
imported := public
|
|
126
|
+
if exportSpecifier.PropertyName != nil {
|
|
127
|
+
imported = declarationName(exportSpecifier.PropertyName)
|
|
128
|
+
}
|
|
129
|
+
if public == "" || imported == "" {
|
|
130
|
+
continue
|
|
131
|
+
}
|
|
132
|
+
entries = append(entries, moduleExport{
|
|
133
|
+
Public: public,
|
|
134
|
+
Specifier: specifier,
|
|
135
|
+
Imported: imported,
|
|
136
|
+
})
|
|
137
|
+
}
|
|
138
|
+
return entries
|
|
139
|
+
}
|
|
140
|
+
return nil
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
func dedupeModuleExports(exports []moduleExport) []moduleExport {
|
|
144
|
+
seen := map[moduleExport]bool{}
|
|
145
|
+
unique := make([]moduleExport, 0, len(exports))
|
|
146
|
+
for _, export := range exports {
|
|
147
|
+
if seen[export] {
|
|
148
|
+
continue
|
|
149
|
+
}
|
|
150
|
+
seen[export] = true
|
|
151
|
+
unique = append(unique, export)
|
|
152
|
+
}
|
|
153
|
+
sort.SliceStable(unique, func(left int, right int) bool {
|
|
154
|
+
return unique[left].Public < unique[right].Public
|
|
155
|
+
})
|
|
156
|
+
return unique
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// reachedSymbol is one public address an entry traversal arrived at.
|
|
160
|
+
// traversedPopulation is what selecting modules and walking their exports
|
|
161
|
+
// yields: the obligations, the scopes above them, and the addresses that reach
|
|
162
|
+
// them.
|
|
163
|
+
type traversedPopulation struct {
|
|
164
|
+
Units []*evidenceUnit
|
|
165
|
+
Reached []*evidenceUnit
|
|
166
|
+
// Hidden are the reached declarations that withdrew themselves from the
|
|
167
|
+
// public surface with an `@internal`, `@hidden`, or `@ignore` documentation
|
|
168
|
+
// tag. They are addressed like any other reached unit and selected as none,
|
|
169
|
+
// so a citation of one is answered with its cause.
|
|
170
|
+
Hidden []*evidenceUnit
|
|
171
|
+
Published []publishedAddress
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// publishedAddress is one module publishing one unit under one address.
|
|
175
|
+
//
|
|
176
|
+
// A symbol is citable from every module that exposes it, under the name that
|
|
177
|
+
// module gives it. Recording the pair keeps import-scope resolution honest when
|
|
178
|
+
// several selected modules publish the same declaration: each address is legal
|
|
179
|
+
// exactly where it is written, and none of them is legal everywhere.
|
|
180
|
+
type publishedAddress struct {
|
|
181
|
+
Module string
|
|
182
|
+
Address string
|
|
183
|
+
Unit *evidenceUnit
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
type reachedSymbol struct {
|
|
187
|
+
// Address is the accessor path from the entry, segment by segment.
|
|
188
|
+
Address []string
|
|
189
|
+
// Path is the file that declares the symbol, which owns its identity.
|
|
190
|
+
Path string
|
|
191
|
+
// Local is the name that file's inventory gives the declaration, which is
|
|
192
|
+
// the name it exposes rather than the binding it wrote. `export { a as b }`
|
|
193
|
+
// declares one unit called `b`, so matching on `a` would find nothing.
|
|
194
|
+
Local string
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// traverseEntryExports walks a module's export graph and reports every public
|
|
198
|
+
// symbol it reaches, with the address the entry gives it.
|
|
199
|
+
//
|
|
200
|
+
// Membership comes from reachability and identity comes from the declaring
|
|
201
|
+
// file, so a symbol re-exported through two barrels is reached twice and still
|
|
202
|
+
// resolves to one unit. Cycles are bounded by the visited set: a barrel that
|
|
203
|
+
// re-exports itself is a real shape, not a reason to hang the build.
|
|
204
|
+
func traverseEntryExports(
|
|
205
|
+
loader *typeScriptLoader,
|
|
206
|
+
entry string,
|
|
207
|
+
prefix []string,
|
|
208
|
+
visited map[string]bool,
|
|
209
|
+
) []reachedSymbol {
|
|
210
|
+
if visited[entry] {
|
|
211
|
+
return nil
|
|
212
|
+
}
|
|
213
|
+
visited[entry] = true
|
|
214
|
+
defer delete(visited, entry)
|
|
215
|
+
|
|
216
|
+
inventory := loader.inventory(entry)
|
|
217
|
+
if inventory == nil {
|
|
218
|
+
return nil
|
|
219
|
+
}
|
|
220
|
+
reached := []reachedSymbol{}
|
|
221
|
+
// Named re-exports are grouped by the module they come from and that module
|
|
222
|
+
// is walked once. Walking it per specifier would re-traverse the whole
|
|
223
|
+
// subtree for every name a barrel forwards, which is quadratic on exactly
|
|
224
|
+
// the wide generated barrels this selection exists to describe.
|
|
225
|
+
surfaces := map[string]map[string]reachedSymbol{}
|
|
226
|
+
surfaceOf := func(target string) map[string]reachedSymbol {
|
|
227
|
+
if surface, built := surfaces[target]; built {
|
|
228
|
+
return surface
|
|
229
|
+
}
|
|
230
|
+
surface := map[string]reachedSymbol{}
|
|
231
|
+
for _, nested := range traverseEntryExports(loader, target, nil, visited) {
|
|
232
|
+
if len(nested.Address) != 1 {
|
|
233
|
+
continue
|
|
234
|
+
}
|
|
235
|
+
if _, taken := surface[nested.Address[0]]; !taken {
|
|
236
|
+
surface[nested.Address[0]] = nested
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
surfaces[target] = surface
|
|
240
|
+
return surface
|
|
241
|
+
}
|
|
242
|
+
for _, export := range inventory.Exports {
|
|
243
|
+
if export.Specifier == "" {
|
|
244
|
+
// A declaration this module exposes is inventoried under the name it
|
|
245
|
+
// exposes it as. `export { local as renamed }` is one unit named
|
|
246
|
+
// `renamed`, so the local binding never identifies it.
|
|
247
|
+
reached = append(reached, reachedSymbol{
|
|
248
|
+
Address: append(append([]string{}, prefix...), export.Public),
|
|
249
|
+
Path: entry,
|
|
250
|
+
Local: export.Public,
|
|
251
|
+
})
|
|
252
|
+
continue
|
|
253
|
+
}
|
|
254
|
+
target := loader.resolve(entry, export.Specifier)
|
|
255
|
+
if target == "" {
|
|
256
|
+
continue
|
|
257
|
+
}
|
|
258
|
+
switch {
|
|
259
|
+
case export.Namespace:
|
|
260
|
+
reached = append(reached, traverseEntryExports(
|
|
261
|
+
loader,
|
|
262
|
+
target,
|
|
263
|
+
append(append([]string{}, prefix...), export.Public),
|
|
264
|
+
visited,
|
|
265
|
+
)...)
|
|
266
|
+
case export.Imported == "":
|
|
267
|
+
reached = append(reached, traverseEntryExports(
|
|
268
|
+
loader,
|
|
269
|
+
target,
|
|
270
|
+
prefix,
|
|
271
|
+
visited,
|
|
272
|
+
)...)
|
|
273
|
+
default:
|
|
274
|
+
nested, found := surfaceOf(target)[export.Imported]
|
|
275
|
+
if !found {
|
|
276
|
+
continue
|
|
277
|
+
}
|
|
278
|
+
reached = append(reached, reachedSymbol{
|
|
279
|
+
Address: append(append([]string{}, prefix...), export.Public),
|
|
280
|
+
Path: nested.Path,
|
|
281
|
+
Local: nested.Local,
|
|
282
|
+
})
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
return reached
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// materializeEntryUnits turns reached symbols into the units an obligation
|
|
289
|
+
// counts, giving each its entry-relative address.
|
|
290
|
+
//
|
|
291
|
+
// A declaration's descendants travel with it: reaching `ISale` also reaches
|
|
292
|
+
// `ISale.price`, because the property is addressable exactly when its owner is.
|
|
293
|
+
// Addresses are rebuilt from identity segments rather than by rewriting the
|
|
294
|
+
// joined target, so a literal dot inside a name cannot collapse into
|
|
295
|
+
// qualification.
|
|
296
|
+
//
|
|
297
|
+
// Several entries union into one population. A glob selects modules, not
|
|
298
|
+
// symbols, so a barrel it matched carries in everything that barrel re-exports;
|
|
299
|
+
// a declaration two entries both reach stays one unit answering to both
|
|
300
|
+
// addresses.
|
|
301
|
+
//
|
|
302
|
+
// Units are the selected obligation set, Reached is everything the traversal
|
|
303
|
+
// saw — which keeps an unselected ancestor addressable as the aggregate scope
|
|
304
|
+
// of the units below it — and Published records where each address is legal.
|
|
305
|
+
func materializeEntryUnits(
|
|
306
|
+
loader *typeScriptLoader,
|
|
307
|
+
entries []string,
|
|
308
|
+
symbols symbolSet,
|
|
309
|
+
) traversedPopulation {
|
|
310
|
+
byID := map[string]*evidenceUnit{}
|
|
311
|
+
order := []string{}
|
|
312
|
+
published := []publishedAddress{}
|
|
313
|
+
candidates := newOwnedUnitIndex(loader)
|
|
314
|
+
for _, entry := range entries {
|
|
315
|
+
for _, symbol := range traverseEntryExports(loader, entry, nil, map[string]bool{}) {
|
|
316
|
+
if symbol.Local == "" {
|
|
317
|
+
continue
|
|
318
|
+
}
|
|
319
|
+
for _, unit := range candidates.of(symbol.Path, symbol.Local) {
|
|
320
|
+
suffix, owned := identitySuffix(unit.Identity, symbol.Local)
|
|
321
|
+
if !owned {
|
|
322
|
+
continue
|
|
323
|
+
}
|
|
324
|
+
address := append(append([]string{}, symbol.Address...), suffix...)
|
|
325
|
+
target := strings.Join(address, ".")
|
|
326
|
+
current := byID[unit.ID]
|
|
327
|
+
if current == nil {
|
|
328
|
+
clone := *unit
|
|
329
|
+
clone.Target = target
|
|
330
|
+
clone.Identity = address
|
|
331
|
+
clone.Aliases = nil
|
|
332
|
+
current = &clone
|
|
333
|
+
byID[unit.ID] = current
|
|
334
|
+
order = append(order, unit.ID)
|
|
335
|
+
} else if current.Target != target &&
|
|
336
|
+
!containsString(current.Aliases, target) {
|
|
337
|
+
// Two addresses for one declaration. The shorter one reads as
|
|
338
|
+
// the canonical path, and the rest resolve to the same unit so
|
|
339
|
+
// the obligation is still counted once.
|
|
340
|
+
if len(address) < len(current.Identity) {
|
|
341
|
+
current.Aliases = append(current.Aliases, current.Target)
|
|
342
|
+
current.Target = target
|
|
343
|
+
current.Identity = address
|
|
344
|
+
} else {
|
|
345
|
+
current.Aliases = append(current.Aliases, target)
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
published = append(published, publishedAddress{
|
|
349
|
+
Module: entry,
|
|
350
|
+
Address: target,
|
|
351
|
+
Unit: current,
|
|
352
|
+
})
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
population := traversedPopulation{Published: published}
|
|
357
|
+
for _, id := range order {
|
|
358
|
+
unit := byID[id]
|
|
359
|
+
sort.Strings(unit.Aliases)
|
|
360
|
+
// A declaration withdrawn from the surface by its own documentation tag
|
|
361
|
+
// is neither an obligation nor an aggregate scope. It is kept apart
|
|
362
|
+
// rather than dropped so a citation naming it can be told why, which a
|
|
363
|
+
// bare unresolved target could not say.
|
|
364
|
+
if unit.Hidden != "" {
|
|
365
|
+
population.Hidden = append(population.Hidden, unit)
|
|
366
|
+
continue
|
|
367
|
+
}
|
|
368
|
+
// Every reached declaration stays available as an aggregate scope, even
|
|
369
|
+
// when its own kind is unselected. The selector is the obligation
|
|
370
|
+
// denominator, not the list of targets an author may write.
|
|
371
|
+
population.Reached = append(population.Reached, unit)
|
|
372
|
+
if symbols.contains(unit.Symbol) {
|
|
373
|
+
population.Units = append(population.Units, unit)
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
return population
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// narrowTraversedPopulation keeps the entry's addresses and the narrowing's
|
|
380
|
+
// membership.
|
|
381
|
+
//
|
|
382
|
+
// Both arguments describe the same declarations; they differ only in which
|
|
383
|
+
// module each was reached from, and therefore in the address each unit answers
|
|
384
|
+
// to. Identity is what joins them, because a re-export decides reachability and
|
|
385
|
+
// never identity — the same declaration carries the same unit ID however it was
|
|
386
|
+
// reached.
|
|
387
|
+
//
|
|
388
|
+
// Reached and Published stay whole. Reached is read only to walk the parent
|
|
389
|
+
// chain above a selected unit, and an ancestor is addressable because it is an
|
|
390
|
+
// ancestor rather than because it was selected, so narrowing it would be work
|
|
391
|
+
// with nothing to change. Published is what a citation resolves against, and it
|
|
392
|
+
// is the entry's addresses that a consumer can write.
|
|
393
|
+
func narrowTraversedPopulation(
|
|
394
|
+
published traversedPopulation,
|
|
395
|
+
membership traversedPopulation,
|
|
396
|
+
) traversedPopulation {
|
|
397
|
+
selected := make(map[string]bool, len(membership.Units))
|
|
398
|
+
for _, unit := range membership.Units {
|
|
399
|
+
selected[unit.ID] = true
|
|
400
|
+
}
|
|
401
|
+
withdrawn := make(map[string]bool, len(membership.Hidden))
|
|
402
|
+
for _, unit := range membership.Hidden {
|
|
403
|
+
withdrawn[unit.ID] = true
|
|
404
|
+
}
|
|
405
|
+
narrowed := traversedPopulation{
|
|
406
|
+
Reached: published.Reached,
|
|
407
|
+
Published: published.Published,
|
|
408
|
+
}
|
|
409
|
+
for _, unit := range published.Units {
|
|
410
|
+
if selected[unit.ID] {
|
|
411
|
+
narrowed.Units = append(narrowed.Units, unit)
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
for _, unit := range published.Hidden {
|
|
415
|
+
if withdrawn[unit.ID] {
|
|
416
|
+
narrowed.Hidden = append(narrowed.Hidden, unit)
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
return narrowed
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// ownedUnitIndex narrows a file's units to the ones a reached name can own.
|
|
423
|
+
//
|
|
424
|
+
// Membership is decided by identity prefix, so only units whose first segment
|
|
425
|
+
// equals the reached name are candidates. Scanning the whole file instead costs
|
|
426
|
+
// one pass per reached name, which is quadratic in a module's own width — and a
|
|
427
|
+
// generated declaration barrel is exactly where that width is largest.
|
|
428
|
+
type ownedUnitIndex struct {
|
|
429
|
+
loader *typeScriptLoader
|
|
430
|
+
byPath map[string]map[string][]*evidenceUnit
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
func newOwnedUnitIndex(loader *typeScriptLoader) *ownedUnitIndex {
|
|
434
|
+
return &ownedUnitIndex{
|
|
435
|
+
loader: loader,
|
|
436
|
+
byPath: map[string]map[string][]*evidenceUnit{},
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// of lists the units of one file that may be owned by one reached name.
|
|
441
|
+
func (index *ownedUnitIndex) of(path string, local string) []*evidenceUnit {
|
|
442
|
+
roots, built := index.byPath[path]
|
|
443
|
+
if !built {
|
|
444
|
+
roots = map[string][]*evidenceUnit{}
|
|
445
|
+
if inventory := index.loader.inventory(path); inventory != nil {
|
|
446
|
+
for _, unit := range inventory.Units {
|
|
447
|
+
if len(unit.Identity) == 0 {
|
|
448
|
+
continue
|
|
449
|
+
}
|
|
450
|
+
roots[unit.Identity[0]] = append(roots[unit.Identity[0]], unit)
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
index.byPath[path] = roots
|
|
454
|
+
}
|
|
455
|
+
root := local
|
|
456
|
+
if cut := strings.Index(local, "."); cut != -1 {
|
|
457
|
+
root = local[:cut]
|
|
458
|
+
}
|
|
459
|
+
return roots[root]
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// identitySuffix reports the segments below a reached declaration, and whether
|
|
463
|
+
// the unit belongs to it at all.
|
|
464
|
+
func identitySuffix(identity []string, local string) ([]string, bool) {
|
|
465
|
+
owner := strings.Split(local, ".")
|
|
466
|
+
if len(identity) < len(owner) {
|
|
467
|
+
return nil, false
|
|
468
|
+
}
|
|
469
|
+
for index, segment := range owner {
|
|
470
|
+
if identity[index] != segment {
|
|
471
|
+
return nil, false
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
return identity[len(owner):], true
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
func containsString(values []string, wanted string) bool {
|
|
478
|
+
for _, value := range values {
|
|
479
|
+
if value == wanted {
|
|
480
|
+
return true
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
return false
|
|
484
|
+
}
|
package/native/glob.go
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
package evidence
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"errors"
|
|
5
|
+
"path/filepath"
|
|
6
|
+
"strings"
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
type globSet struct {
|
|
10
|
+
Patterns []globPattern
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
type globPattern struct {
|
|
14
|
+
Raw string
|
|
15
|
+
Segments []string
|
|
16
|
+
Exclude bool
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
func newGlobSet(patterns []string) (globSet, error) {
|
|
20
|
+
set := globSet{Patterns: make([]globPattern, 0, len(patterns))}
|
|
21
|
+
positive := false
|
|
22
|
+
for _, raw := range patterns {
|
|
23
|
+
pattern, err := compileGlob(raw)
|
|
24
|
+
if err != nil {
|
|
25
|
+
return globSet{}, err
|
|
26
|
+
}
|
|
27
|
+
if !pattern.Exclude {
|
|
28
|
+
positive = true
|
|
29
|
+
}
|
|
30
|
+
set.Patterns = append(set.Patterns, pattern)
|
|
31
|
+
}
|
|
32
|
+
if !positive {
|
|
33
|
+
return globSet{}, errors.New("the files array must contain at least one positive glob")
|
|
34
|
+
}
|
|
35
|
+
return set, nil
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
func compileGlob(raw string) (globPattern, error) {
|
|
39
|
+
if strings.TrimSpace(raw) == "" {
|
|
40
|
+
return globPattern{}, errors.New("glob strings must not be empty")
|
|
41
|
+
}
|
|
42
|
+
exclude := strings.HasPrefix(raw, "!")
|
|
43
|
+
value := raw
|
|
44
|
+
if exclude {
|
|
45
|
+
value = strings.TrimPrefix(value, "!")
|
|
46
|
+
}
|
|
47
|
+
value = strings.ReplaceAll(value, "\\", "/")
|
|
48
|
+
for strings.HasPrefix(value, "./") {
|
|
49
|
+
value = strings.TrimPrefix(value, "./")
|
|
50
|
+
}
|
|
51
|
+
if value == "" {
|
|
52
|
+
return globPattern{}, errors.New("the exclusion marker '!' must be followed by a glob")
|
|
53
|
+
}
|
|
54
|
+
if filepath.IsAbs(value) ||
|
|
55
|
+
hasWindowsDrivePrefix(value) ||
|
|
56
|
+
strings.HasPrefix(value, "/") {
|
|
57
|
+
return globPattern{}, errors.New("glob '" + raw + "' is absolute; every files pattern must be project-relative")
|
|
58
|
+
}
|
|
59
|
+
value = strings.TrimSuffix(value, "/")
|
|
60
|
+
segments := strings.Split(value, "/")
|
|
61
|
+
for _, segment := range segments {
|
|
62
|
+
if segment == "" {
|
|
63
|
+
return globPattern{}, errors.New("glob '" + raw + "' contains an empty path segment")
|
|
64
|
+
}
|
|
65
|
+
if segment == ".." {
|
|
66
|
+
return globPattern{}, errors.New("glob '" + raw + "' escapes the project root through '..'")
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return globPattern{Raw: raw, Segments: segments, Exclude: exclude}, nil
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
func hasWindowsDrivePrefix(value string) bool {
|
|
73
|
+
if len(value) < 2 || value[1] != ':' {
|
|
74
|
+
return false
|
|
75
|
+
}
|
|
76
|
+
letter := value[0]
|
|
77
|
+
return (letter >= 'A' && letter <= 'Z') ||
|
|
78
|
+
(letter >= 'a' && letter <= 'z')
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
func (set globSet) matches(path string) bool {
|
|
82
|
+
segments := splitProjectPath(path)
|
|
83
|
+
included := false
|
|
84
|
+
for _, pattern := range set.Patterns {
|
|
85
|
+
if !matchGlobSegments(pattern.Segments, segments) {
|
|
86
|
+
continue
|
|
87
|
+
}
|
|
88
|
+
if pattern.Exclude {
|
|
89
|
+
included = false
|
|
90
|
+
} else {
|
|
91
|
+
included = true
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return included
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
func (set globSet) couldMatchDescendant(directory string) bool {
|
|
98
|
+
prefix := splitProjectPath(directory)
|
|
99
|
+
for _, pattern := range set.Patterns {
|
|
100
|
+
if pattern.Exclude || !couldMatchGlobBelow(pattern.Segments, prefix) {
|
|
101
|
+
continue
|
|
102
|
+
}
|
|
103
|
+
return true
|
|
104
|
+
}
|
|
105
|
+
return false
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
func couldMatchGlobBelow(pattern []string, prefix []string) bool {
|
|
109
|
+
type state struct{ pattern, prefix int }
|
|
110
|
+
memo := map[state]bool{}
|
|
111
|
+
known := map[state]bool{}
|
|
112
|
+
var match func(int, int) bool
|
|
113
|
+
match = func(patternIndex int, prefixIndex int) bool {
|
|
114
|
+
key := state{pattern: patternIndex, prefix: prefixIndex}
|
|
115
|
+
if known[key] {
|
|
116
|
+
return memo[key]
|
|
117
|
+
}
|
|
118
|
+
known[key] = true
|
|
119
|
+
result := false
|
|
120
|
+
switch {
|
|
121
|
+
case prefixIndex == len(prefix):
|
|
122
|
+
result = patternIndex < len(pattern)
|
|
123
|
+
case patternIndex == len(pattern):
|
|
124
|
+
result = false
|
|
125
|
+
case pattern[patternIndex] == "**":
|
|
126
|
+
result = match(patternIndex+1, prefixIndex) ||
|
|
127
|
+
match(patternIndex, prefixIndex+1)
|
|
128
|
+
case matchGlobSegment(pattern[patternIndex], prefix[prefixIndex]):
|
|
129
|
+
result = match(patternIndex+1, prefixIndex+1)
|
|
130
|
+
}
|
|
131
|
+
memo[key] = result
|
|
132
|
+
return result
|
|
133
|
+
}
|
|
134
|
+
return match(0, 0)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
func splitProjectPath(value string) []string {
|
|
138
|
+
value = strings.ReplaceAll(value, "\\", "/")
|
|
139
|
+
for strings.HasPrefix(value, "./") {
|
|
140
|
+
value = strings.TrimPrefix(value, "./")
|
|
141
|
+
}
|
|
142
|
+
value = strings.Trim(value, "/")
|
|
143
|
+
if value == "" {
|
|
144
|
+
return nil
|
|
145
|
+
}
|
|
146
|
+
return strings.Split(value, "/")
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
func matchGlobSegments(pattern []string, path []string) bool {
|
|
150
|
+
type state struct{ pattern, path int }
|
|
151
|
+
memo := map[state]bool{}
|
|
152
|
+
known := map[state]bool{}
|
|
153
|
+
var match func(int, int) bool
|
|
154
|
+
match = func(patternIndex int, pathIndex int) bool {
|
|
155
|
+
key := state{pattern: patternIndex, path: pathIndex}
|
|
156
|
+
if known[key] {
|
|
157
|
+
return memo[key]
|
|
158
|
+
}
|
|
159
|
+
known[key] = true
|
|
160
|
+
result := false
|
|
161
|
+
switch {
|
|
162
|
+
case patternIndex == len(pattern):
|
|
163
|
+
result = pathIndex == len(path)
|
|
164
|
+
case pattern[patternIndex] == "**":
|
|
165
|
+
result = match(patternIndex+1, pathIndex)
|
|
166
|
+
if !result && pathIndex < len(path) {
|
|
167
|
+
result = match(patternIndex, pathIndex+1)
|
|
168
|
+
}
|
|
169
|
+
case pathIndex < len(path) && matchGlobSegment(pattern[patternIndex], path[pathIndex]):
|
|
170
|
+
result = match(patternIndex+1, pathIndex+1)
|
|
171
|
+
}
|
|
172
|
+
memo[key] = result
|
|
173
|
+
return result
|
|
174
|
+
}
|
|
175
|
+
return match(0, 0)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
func matchGlobSegment(pattern string, value string) bool {
|
|
179
|
+
patternRunes := []rune(pattern)
|
|
180
|
+
valueRunes := []rune(value)
|
|
181
|
+
patternIndex := 0
|
|
182
|
+
valueIndex := 0
|
|
183
|
+
starPattern := -1
|
|
184
|
+
starValue := 0
|
|
185
|
+
for valueIndex < len(valueRunes) {
|
|
186
|
+
switch {
|
|
187
|
+
case patternIndex < len(patternRunes) && patternRunes[patternIndex] == '*':
|
|
188
|
+
starPattern = patternIndex
|
|
189
|
+
starValue = valueIndex
|
|
190
|
+
patternIndex++
|
|
191
|
+
case patternIndex < len(patternRunes) &&
|
|
192
|
+
(patternRunes[patternIndex] == '?' || patternRunes[patternIndex] == valueRunes[valueIndex]):
|
|
193
|
+
patternIndex++
|
|
194
|
+
valueIndex++
|
|
195
|
+
case starPattern >= 0:
|
|
196
|
+
patternIndex = starPattern + 1
|
|
197
|
+
starValue++
|
|
198
|
+
valueIndex = starValue
|
|
199
|
+
default:
|
|
200
|
+
return false
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
for patternIndex < len(patternRunes) && patternRunes[patternIndex] == '*' {
|
|
204
|
+
patternIndex++
|
|
205
|
+
}
|
|
206
|
+
return patternIndex == len(patternRunes)
|
|
207
|
+
}
|