@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
@@ -0,0 +1,1199 @@
1
+ package evidence
2
+
3
+ import (
4
+ "path/filepath"
5
+ "sort"
6
+ "strings"
7
+
8
+ shimast "github.com/microsoft/typescript-go/shim/ast"
9
+ )
10
+
11
+ func loadTypeScriptInventories(
12
+ root string,
13
+ sources []*shimast.SourceFile,
14
+ config graphConfig,
15
+ ) map[string]*artifactInventory {
16
+ inventories := map[string]*artifactInventory{}
17
+ extendTypeScriptInventories(root, sources, config, inventories)
18
+ return inventories
19
+ }
20
+
21
+ // extendTypeScriptInventories adds only populations the caller has not already
22
+ // scanned.
23
+ //
24
+ // Graph activation first needs each TypeScript claim's own population, before
25
+ // it is allowed to inspect that claim's references. Once active claims are
26
+ // known, this second pass adds their reference bases without rescanning the
27
+ // claim bases already materialized.
28
+ func extendTypeScriptInventories(
29
+ root string,
30
+ sources []*shimast.SourceFile,
31
+ config graphConfig,
32
+ inventories map[string]*artifactInventory,
33
+ ) {
34
+ bases := configuredBases(config, artifactTypeScript)
35
+ for _, file := range sources {
36
+ if file == nil || !isTypeScriptPath(file.FileName()) {
37
+ continue
38
+ }
39
+ for _, base := range bases {
40
+ relative, ok := relativeProjectPath(base.Absolute, file.FileName())
41
+ if !ok || !isTypeScriptPath(relative) {
42
+ continue
43
+ }
44
+ address := base.addressOf(relative)
45
+ if inventories[address.Key] != nil {
46
+ continue
47
+ }
48
+ inventories[address.Key] = typeScriptInventories.scan(address, file)
49
+ }
50
+ }
51
+ }
52
+
53
+ // isTypeScriptPath is asked once per source file per configured base on every
54
+ // rebuild, so it compares the suffix in place rather than lowercasing the whole
55
+ // path into a fresh string to answer four fixed questions.
56
+ func isTypeScriptPath(path string) bool {
57
+ for _, suffix := range []string{".ts", ".tsx", ".mts", ".cts"} {
58
+ if len(path) >= len(suffix) &&
59
+ strings.EqualFold(path[len(path)-len(suffix):], suffix) {
60
+ return true
61
+ }
62
+ }
63
+ return false
64
+ }
65
+
66
+ func relativeProjectPath(root string, absolute string) (string, bool) {
67
+ if root == "" || absolute == "" {
68
+ return "", false
69
+ }
70
+ // A source file usually sits below the base that is asking, spelled the same
71
+ // way. Answering that from the two strings keeps the general path machinery
72
+ // off a loop that runs once per file per base on every rebuild.
73
+ if inside, ok := containedProjectPath(root, absolute); ok {
74
+ return inside, true
75
+ }
76
+ relative, err := filepath.Rel(root, absolute)
77
+ if err != nil {
78
+ return "", false
79
+ }
80
+ relative = strings.ReplaceAll(relative, "\\", "/")
81
+ if relative == ".." || strings.HasPrefix(relative, "../") {
82
+ return "", false
83
+ }
84
+ return strings.TrimPrefix(relative, "./"), true
85
+ }
86
+
87
+ // containedProjectPath answers the ordinary case of one path sitting below
88
+ // another: same spelling, one separator between them, and nothing left to
89
+ // normalize. It declines anything else so the general form still decides.
90
+ //
91
+ // The prefix comparison is exact rather than case-insensitive, because the
92
+ // general form is `filepath.Rel`, which is lexical and case-sensitive on every
93
+ // platform. Folding case here would admit a differently-cased sibling that the
94
+ // path this shortcut stands in for rejects.
95
+ func containedProjectPath(root string, absolute string) (string, bool) {
96
+ if len(absolute) <= len(root)+1 ||
97
+ absolute[:len(root)] != root {
98
+ return "", false
99
+ }
100
+ if separator := absolute[len(root)]; separator != '/' && separator != '\\' {
101
+ return "", false
102
+ }
103
+ relative := absolute[len(root)+1:]
104
+ if strings.ContainsRune(relative, '\\') {
105
+ return "", false
106
+ }
107
+ for segment := range strings.SplitSeq(relative, "/") {
108
+ if segment == "" || segment == "." || segment == ".." {
109
+ return "", false
110
+ }
111
+ }
112
+ return relative, true
113
+ }
114
+
115
+ func scanTypeScriptInventory(
116
+ path string,
117
+ file *shimast.SourceFile,
118
+ ) *artifactInventory {
119
+ return scanTypeScriptInventoryAt(artifactAddress{
120
+ Base: populationBase{Default: true},
121
+ Relative: path,
122
+ Display: path,
123
+ Key: path,
124
+ }, file)
125
+ }
126
+
127
+ func scanTypeScriptInventoryAt(
128
+ address artifactAddress,
129
+ file *shimast.SourceFile,
130
+ ) *artifactInventory {
131
+ inventory := &artifactInventory{
132
+ Address: address.Key,
133
+ Path: address.Display,
134
+ Type: artifactTypeScript,
135
+ Imports: collectImportBindings(file),
136
+ Exports: collectModuleExports(file),
137
+ UnitNodes: map[string][]*shimast.Node{},
138
+ }
139
+ supportedHosts := map[*shimast.Node]symbolSet{}
140
+ unitsByID := map[string]*evidenceUnit{}
141
+ collectTypeScriptStatements(
142
+ file,
143
+ file.Statements,
144
+ nil,
145
+ "",
146
+ inventory,
147
+ supportedHosts,
148
+ unitsByID,
149
+ file.IsDeclarationFile,
150
+ false,
151
+ false,
152
+ "",
153
+ )
154
+ collectTypeScriptDeclarations(
155
+ file,
156
+ address.Key,
157
+ address.Display,
158
+ inventory,
159
+ supportedHosts,
160
+ )
161
+ // Policy evaluation needs semantic host IDs, not the AST node associations
162
+ // used to derive them. Declarations now retain those IDs directly, so release
163
+ // the transient index before this inventory enters the immutable graph.
164
+ inventory.UnitNodes = nil
165
+ sort.Slice(inventory.Units, func(left int, right int) bool {
166
+ if inventory.Units[left].Target != inventory.Units[right].Target {
167
+ return inventory.Units[left].Target < inventory.Units[right].Target
168
+ }
169
+ return inventory.Units[left].Line < inventory.Units[right].Line
170
+ })
171
+ return inventory
172
+ }
173
+
174
+ // collectTypeScriptStatements materializes the public units one statement list
175
+ // declares.
176
+ //
177
+ // hidden carries the documentation tag by which an enclosing declaration
178
+ // withdrew itself from the public surface. It is inherited rather than
179
+ // recomputed, which is what makes `@internal` on a namespace reach every member
180
+ // beneath it without each member repeating the tag.
181
+ func collectTypeScriptStatements(
182
+ file *shimast.SourceFile,
183
+ statements *shimast.NodeList,
184
+ prefix []string,
185
+ parentID string,
186
+ inventory *artifactInventory,
187
+ supportedHosts map[*shimast.Node]symbolSet,
188
+ unitsByID map[string]*evidenceUnit,
189
+ ambientContext bool,
190
+ implicitlyExported bool,
191
+ typeOnlyProjection bool,
192
+ hidden string,
193
+ ) {
194
+ if statements == nil {
195
+ return
196
+ }
197
+ exports := collectLocalExportNames(statements)
198
+ hiddenNames := collectHiddenDeclarationNames(file, statements)
199
+ // Built on the first namespace this list holds rather than up front, so a
200
+ // file declaring none pays nothing. Every rebuild scans every configured
201
+ // source, and most of them have no namespace at all.
202
+ var functionNames map[string]bool
203
+ for _, statement := range statements.Nodes {
204
+ if statement == nil {
205
+ continue
206
+ }
207
+ switch statement.Kind {
208
+ case shimast.KindInterfaceDeclaration:
209
+ name := declarationName(statement.Name())
210
+ if name == "" {
211
+ continue
212
+ }
213
+ targets := publicTypeScriptNames(
214
+ statement,
215
+ name,
216
+ exports,
217
+ true,
218
+ implicitlyExported,
219
+ )
220
+ if len(targets) == 0 {
221
+ continue
222
+ }
223
+ memberHidden := hidingTagFor(hidden, hiddenNames, name)
224
+ if memberHidden == "" {
225
+ addTypeScriptHost(supportedHosts, statement, "type")
226
+ }
227
+ for _, name := range targets {
228
+ identity := qualifyTypeScriptName(prefix, name)
229
+ unit := addTypeScriptUnit(
230
+ inventory,
231
+ unitsByID,
232
+ statement,
233
+ "type",
234
+ identity,
235
+ parentID,
236
+ memberHidden,
237
+ )
238
+ collectPropertyMembers(
239
+ file,
240
+ statement.AsInterfaceDeclaration().Members,
241
+ identity,
242
+ unit.ID,
243
+ inventory,
244
+ supportedHosts,
245
+ unitsByID,
246
+ memberHidden,
247
+ )
248
+ }
249
+ case shimast.KindTypeAliasDeclaration:
250
+ name := declarationName(statement.Name())
251
+ if name == "" {
252
+ continue
253
+ }
254
+ targets := publicTypeScriptNames(
255
+ statement,
256
+ name,
257
+ exports,
258
+ true,
259
+ implicitlyExported,
260
+ )
261
+ if len(targets) == 0 {
262
+ continue
263
+ }
264
+ memberHidden := hidingTagFor(hidden, hiddenNames, name)
265
+ if memberHidden == "" {
266
+ addTypeScriptHost(supportedHosts, statement, "type")
267
+ }
268
+ alias := statement.AsTypeAliasDeclaration()
269
+ for _, name := range targets {
270
+ identity := qualifyTypeScriptName(prefix, name)
271
+ unit := addTypeScriptUnit(
272
+ inventory,
273
+ unitsByID,
274
+ statement,
275
+ "type",
276
+ identity,
277
+ parentID,
278
+ memberHidden,
279
+ )
280
+ if alias.Type != nil && alias.Type.Kind == shimast.KindTypeLiteral {
281
+ collectPropertyMembers(
282
+ file,
283
+ alias.Type.AsTypeLiteralNode().Members,
284
+ identity,
285
+ unit.ID,
286
+ inventory,
287
+ supportedHosts,
288
+ unitsByID,
289
+ memberHidden,
290
+ )
291
+ }
292
+ }
293
+ case shimast.KindFunctionDeclaration:
294
+ if typeOnlyProjection {
295
+ continue
296
+ }
297
+ name := declarationName(statement.Name())
298
+ if name == "" {
299
+ continue
300
+ }
301
+ targets := publicTypeScriptNames(
302
+ statement,
303
+ name,
304
+ exports,
305
+ false,
306
+ implicitlyExported,
307
+ )
308
+ if len(targets) == 0 {
309
+ continue
310
+ }
311
+ memberHidden := hidingTagFor(hidden, hiddenNames, name)
312
+ if memberHidden == "" {
313
+ addTypeScriptHost(supportedHosts, statement, "function")
314
+ }
315
+ for _, name := range targets {
316
+ addTypeScriptUnit(
317
+ inventory,
318
+ unitsByID,
319
+ statement,
320
+ "function",
321
+ qualifyTypeScriptName(prefix, name),
322
+ parentID,
323
+ memberHidden,
324
+ )
325
+ }
326
+ case shimast.KindVariableStatement:
327
+ if typeOnlyProjection {
328
+ continue
329
+ }
330
+ memberHidden := typeScriptHidingTag(file, statement, hidden)
331
+ for symbol := range collectTypeScriptVariables(
332
+ statement,
333
+ prefix,
334
+ parentID,
335
+ exports,
336
+ inventory,
337
+ supportedHosts,
338
+ unitsByID,
339
+ implicitlyExported,
340
+ memberHidden,
341
+ ) {
342
+ if memberHidden != "" {
343
+ continue
344
+ }
345
+ // TypeScript attaches the leading JSDoc of
346
+ // a variable declaration to the statement wrapper.
347
+ addTypeScriptHost(supportedHosts, statement, symbol)
348
+ }
349
+ case shimast.KindClassDeclaration:
350
+ if typeOnlyProjection {
351
+ continue
352
+ }
353
+ name := declarationName(statement.Name())
354
+ memberHidden := hidingTagFor(hidden, hiddenNames, name)
355
+ for _, publicName := range publicTypeScriptNames(
356
+ statement,
357
+ name,
358
+ exports,
359
+ false,
360
+ implicitlyExported,
361
+ ) {
362
+ collectClassCallables(
363
+ file,
364
+ statement,
365
+ qualifyTypeScriptName(prefix, publicName),
366
+ parentID,
367
+ inventory,
368
+ supportedHosts,
369
+ unitsByID,
370
+ memberHidden,
371
+ )
372
+ }
373
+ case shimast.KindModuleDeclaration:
374
+ name := declarationName(statement.Name())
375
+ targets := publicTypeScriptExports(
376
+ statement,
377
+ name,
378
+ exports,
379
+ true,
380
+ implicitlyExported,
381
+ )
382
+ if len(targets) == 0 {
383
+ continue
384
+ }
385
+ memberHidden := hidingTagFor(hidden, hiddenNames, name)
386
+ if memberHidden == "" {
387
+ addTypeScriptHost(supportedHosts, statement, "type")
388
+ }
389
+ // A namespace merged with a same-named function is that function's
390
+ // static side, not an independent container. `get.path` is a
391
+ // property of the `get` function value and `get.Output` is the type
392
+ // its own signature spells; neither is authored contract, so the
393
+ // merged namespace contributes its identity and nothing beneath it.
394
+ // Selecting those members is also what promoted the namespace to an
395
+ // addressable aggregate scope, where it collided with the function
396
+ // unit of the same name and left the accessor with no spelling that
397
+ // resolves.
398
+ if functionNames == nil {
399
+ functionNames = collectFunctionDeclarationNames(statements)
400
+ }
401
+ staticSide := functionNames[name]
402
+ for _, target := range targets {
403
+ identity := qualifyTypeScriptName(prefix, target.Public)
404
+ unit := addTypeScriptUnit(
405
+ inventory,
406
+ unitsByID,
407
+ statement,
408
+ "type",
409
+ identity,
410
+ parentID,
411
+ memberHidden,
412
+ )
413
+ if !staticSide {
414
+ collectTypeScriptModule(
415
+ file,
416
+ statement,
417
+ identity,
418
+ unit.ID,
419
+ inventory,
420
+ supportedHosts,
421
+ unitsByID,
422
+ ambientContext,
423
+ typeOnlyProjection || target.TypeOnly,
424
+ memberHidden,
425
+ )
426
+ }
427
+ }
428
+ }
429
+ }
430
+ }
431
+
432
+ // collectFunctionDeclarationNames indexes the local names a statement list
433
+ // declares as functions, which is what a namespace in the same list merges with.
434
+ //
435
+ // A namespace merges only with a function, a class, or an enum in the same
436
+ // scope; a `const` or `let` of the same name is `TS2451`, measured against the
437
+ // pinned compiler. Of the three legal partners only a function also
438
+ // materializes a unit under the merged name — a class registers no unit of its
439
+ // own and the collector has no enum case — so the function form is the one
440
+ // shape where a namespace and the declaration it merges with are the same
441
+ // public entity spelled twice.
442
+ //
443
+ // The name alone decides, without consulting export modifiers, because
444
+ // TypeScript refuses a merged declaration whose halves disagree on export
445
+ // (`TS2395`, measured). The whole list is indexed rather than only the
446
+ // statements already collected, because merging does not depend on which
447
+ // declaration is written first.
448
+ func collectFunctionDeclarationNames(
449
+ statements *shimast.NodeList,
450
+ ) map[string]bool {
451
+ names := map[string]bool{}
452
+ if statements == nil {
453
+ return names
454
+ }
455
+ for _, statement := range statements.Nodes {
456
+ if statement == nil ||
457
+ statement.Kind != shimast.KindFunctionDeclaration {
458
+ continue
459
+ }
460
+ if name := declarationName(statement.Name()); name != "" {
461
+ names[name] = true
462
+ }
463
+ }
464
+ return names
465
+ }
466
+
467
+ func collectTypeScriptVariables(
468
+ statement *shimast.Node,
469
+ prefix []string,
470
+ parentID string,
471
+ exports map[string][]exportedName,
472
+ inventory *artifactInventory,
473
+ supportedHosts map[*shimast.Node]symbolSet,
474
+ unitsByID map[string]*evidenceUnit,
475
+ implicitlyExported bool,
476
+ hidden string,
477
+ ) symbolSet {
478
+ variable := statement.AsVariableStatement()
479
+ if variable.DeclarationList == nil {
480
+ return nil
481
+ }
482
+ list := variable.DeclarationList.AsVariableDeclarationList()
483
+ if list.Declarations == nil {
484
+ return nil
485
+ }
486
+ found := symbolSet{}
487
+ for _, declaration := range list.Declarations.Nodes {
488
+ if declaration == nil {
489
+ continue
490
+ }
491
+ value := declaration.AsVariableDeclaration()
492
+ symbol := "property"
493
+ if !shimast.IsBindingPattern(declaration.Name()) &&
494
+ shimast.IsConst(declaration) &&
495
+ isFunctionValue(value.Initializer) {
496
+ symbol = "function"
497
+ }
498
+ for _, binding := range bindingIdentifierNodes(declaration.Name()) {
499
+ name := declarationName(binding)
500
+ targets := publicTypeScriptNames(
501
+ statement,
502
+ name,
503
+ exports,
504
+ false,
505
+ implicitlyExported,
506
+ )
507
+ if len(targets) == 0 {
508
+ continue
509
+ }
510
+ if hidden == "" {
511
+ addTypeScriptHost(supportedHosts, declaration, symbol)
512
+ }
513
+ for _, name := range targets {
514
+ unit := addTypeScriptUnit(
515
+ inventory,
516
+ unitsByID,
517
+ binding,
518
+ symbol,
519
+ qualifyTypeScriptName(prefix, name),
520
+ parentID,
521
+ hidden,
522
+ )
523
+ // The binding names the unit, but TypeScript attaches a
524
+ // variable's leading JSDoc to the statement wrapper, so that
525
+ // is where a citation for this unit actually lives.
526
+ inventory.recordUnitNode(unit.ID, statement)
527
+ }
528
+ found[symbol] = true
529
+ }
530
+ }
531
+ return found
532
+ }
533
+
534
+ func collectClassCallables(
535
+ file *shimast.SourceFile,
536
+ statement *shimast.Node,
537
+ classIdentity []string,
538
+ parentID string,
539
+ inventory *artifactInventory,
540
+ supportedHosts map[*shimast.Node]symbolSet,
541
+ unitsByID map[string]*evidenceUnit,
542
+ hidden string,
543
+ ) {
544
+ class := statement.AsClassDeclaration()
545
+ if class.Members == nil {
546
+ return
547
+ }
548
+ for _, member := range class.Members.Nodes {
549
+ if member == nil || !isPublicClassMember(member) {
550
+ continue
551
+ }
552
+ callable := false
553
+ switch member.Kind {
554
+ case shimast.KindMethodDeclaration:
555
+ callable = true
556
+ case shimast.KindPropertyDeclaration:
557
+ if member.ModifierFlags()&shimast.ModifierFlagsAccessor != 0 {
558
+ continue
559
+ }
560
+ property := member.AsPropertyDeclaration()
561
+ callable = isFunctionValue(property.Initializer) ||
562
+ isDirectFunctionType(property.Type)
563
+ }
564
+ if !callable {
565
+ continue
566
+ }
567
+ memberName := declarationName(member.Name())
568
+ if memberName == "" {
569
+ continue
570
+ }
571
+ identity := qualifyTypeScriptName(classIdentity, "prototype", memberName)
572
+ if shimast.GetCombinedModifierFlags(member)&shimast.ModifierFlagsStatic != 0 {
573
+ identity = qualifyTypeScriptName(classIdentity, memberName)
574
+ }
575
+ memberHidden := typeScriptHidingTag(file, member, hidden)
576
+ addTypeScriptUnit(
577
+ inventory,
578
+ unitsByID,
579
+ member,
580
+ "function",
581
+ identity,
582
+ parentID,
583
+ memberHidden,
584
+ )
585
+ if memberHidden == "" {
586
+ addTypeScriptHost(supportedHosts, member, "function")
587
+ }
588
+ }
589
+ }
590
+
591
+ func isPublicClassMember(node *shimast.Node) bool {
592
+ flags := shimast.GetCombinedModifierFlags(node)
593
+ return flags&shimast.ModifierFlagsPrivate == 0 &&
594
+ flags&shimast.ModifierFlagsProtected == 0
595
+ }
596
+
597
+ func isFunctionValue(node *shimast.Node) bool {
598
+ for node != nil {
599
+ switch node.Kind {
600
+ case shimast.KindArrowFunction, shimast.KindFunctionExpression:
601
+ return true
602
+ case shimast.KindParenthesizedExpression,
603
+ shimast.KindAsExpression,
604
+ shimast.KindSatisfiesExpression,
605
+ shimast.KindNonNullExpression,
606
+ shimast.KindTypeAssertionExpression:
607
+ node = node.Expression()
608
+ default:
609
+ return false
610
+ }
611
+ }
612
+ return false
613
+ }
614
+
615
+ func isDirectFunctionType(node *shimast.Node) bool {
616
+ for node != nil && node.Kind == shimast.KindParenthesizedType {
617
+ parenthesized := node.AsParenthesizedTypeNode()
618
+ if parenthesized == nil {
619
+ return false
620
+ }
621
+ node = parenthesized.Type
622
+ }
623
+ return node != nil && node.Kind == shimast.KindFunctionType
624
+ }
625
+
626
+ func collectTypeScriptModule(
627
+ file *shimast.SourceFile,
628
+ node *shimast.Node,
629
+ qualified []string,
630
+ parentID string,
631
+ inventory *artifactInventory,
632
+ supportedHosts map[*shimast.Node]symbolSet,
633
+ unitsByID map[string]*evidenceUnit,
634
+ ambientContext bool,
635
+ typeOnlyProjection bool,
636
+ hidden string,
637
+ ) {
638
+ if node == nil || node.Kind != shimast.KindModuleDeclaration {
639
+ return
640
+ }
641
+ module := node.AsModuleDeclaration()
642
+ if module.Body == nil {
643
+ return
644
+ }
645
+ switch module.Body.Kind {
646
+ case shimast.KindModuleBlock:
647
+ moduleAmbient := ambientContext ||
648
+ shimast.GetCombinedModifierFlags(node)&shimast.ModifierFlagsAmbient != 0
649
+ collectTypeScriptStatements(
650
+ file,
651
+ module.Body.AsModuleBlock().Statements,
652
+ qualified,
653
+ parentID,
654
+ inventory,
655
+ supportedHosts,
656
+ unitsByID,
657
+ moduleAmbient,
658
+ moduleAmbient,
659
+ typeOnlyProjection,
660
+ hidden,
661
+ )
662
+ case shimast.KindModuleDeclaration:
663
+ // `export namespace Outer.Inner {}` is represented as nested module
664
+ // declarations; the inner declaration inherits the outer export.
665
+ name := declarationName(module.Body.Name())
666
+ if name != "" {
667
+ identity := qualifyTypeScriptName(qualified, name)
668
+ innerHidden := typeScriptHidingTag(file, module.Body, hidden)
669
+ if innerHidden == "" {
670
+ addTypeScriptHost(supportedHosts, module.Body, "type")
671
+ }
672
+ unit := addTypeScriptUnit(
673
+ inventory,
674
+ unitsByID,
675
+ module.Body,
676
+ "type",
677
+ identity,
678
+ parentID,
679
+ innerHidden,
680
+ )
681
+ collectTypeScriptModule(
682
+ file,
683
+ module.Body,
684
+ identity,
685
+ unit.ID,
686
+ inventory,
687
+ supportedHosts,
688
+ unitsByID,
689
+ ambientContext ||
690
+ shimast.GetCombinedModifierFlags(node)&shimast.ModifierFlagsAmbient != 0,
691
+ typeOnlyProjection,
692
+ innerHidden,
693
+ )
694
+ }
695
+ }
696
+ }
697
+
698
+ func collectPropertyMembers(
699
+ file *shimast.SourceFile,
700
+ members *shimast.NodeList,
701
+ owner []string,
702
+ parentID string,
703
+ inventory *artifactInventory,
704
+ supportedHosts map[*shimast.Node]symbolSet,
705
+ unitsByID map[string]*evidenceUnit,
706
+ hidden string,
707
+ ) {
708
+ if members == nil {
709
+ return
710
+ }
711
+ for _, member := range members.Nodes {
712
+ if member == nil || member.Kind != shimast.KindPropertySignature {
713
+ continue
714
+ }
715
+ name := declarationName(member.Name())
716
+ if name == "" {
717
+ continue
718
+ }
719
+ identity := qualifyTypeScriptName(owner, name)
720
+ memberHidden := typeScriptHidingTag(file, member, hidden)
721
+ addTypeScriptUnit(
722
+ inventory,
723
+ unitsByID,
724
+ member,
725
+ "property",
726
+ identity,
727
+ parentID,
728
+ memberHidden,
729
+ )
730
+ if memberHidden == "" {
731
+ addTypeScriptHost(supportedHosts, member, "property")
732
+ }
733
+ }
734
+ }
735
+
736
+ // collectHiddenDeclarationNames indexes the local names a statement list
737
+ // withdraws from the public surface, by the tag that withdrew each.
738
+ //
739
+ // The index is over names rather than over nodes because declaration merging
740
+ // makes one name several declarations. `interface I` beside `namespace I` is
741
+ // one public identity and one unit, so a tag on either half withdraws the
742
+ // identity — and which half carries it is a matter of where the author wrote
743
+ // the comment. Reading only the node in hand would leave the identity
744
+ // withdrawn while its members stayed selected, depending on source order.
745
+ func collectHiddenDeclarationNames(
746
+ file *shimast.SourceFile,
747
+ statements *shimast.NodeList,
748
+ ) map[string]string {
749
+ if file == nil || statements == nil {
750
+ return nil
751
+ }
752
+ var names map[string]string
753
+ for _, statement := range statements.Nodes {
754
+ if statement == nil {
755
+ continue
756
+ }
757
+ name := declarationName(statement.Name())
758
+ if name == "" {
759
+ continue
760
+ }
761
+ tag := typeScriptHidingTag(file, statement, "")
762
+ if tag == "" {
763
+ continue
764
+ }
765
+ if names == nil {
766
+ names = map[string]string{}
767
+ }
768
+ if names[name] == "" {
769
+ names[name] = tag
770
+ }
771
+ }
772
+ return names
773
+ }
774
+
775
+ // hidingTagFor answers for one local name, preferring an inherited tag.
776
+ func hidingTagFor(
777
+ inherited string,
778
+ names map[string]string,
779
+ local string,
780
+ ) string {
781
+ if inherited != "" {
782
+ return inherited
783
+ }
784
+ return names[local]
785
+ }
786
+
787
+ // typeScriptHidingTag reports the documentation tag that withdraws a
788
+ // declaration from the public surface, inheriting an enclosing one.
789
+ //
790
+ // An inherited tag wins outright and the node's own blocks are not consulted:
791
+ // once an ancestor is out of the surface, nothing beneath it can opt back in,
792
+ // and the cause an author has to be told about is the outermost tag.
793
+ func typeScriptHidingTag(
794
+ file *shimast.SourceFile,
795
+ node *shimast.Node,
796
+ inherited string,
797
+ ) string {
798
+ if inherited != "" {
799
+ return inherited
800
+ }
801
+ if file == nil || node == nil {
802
+ return ""
803
+ }
804
+ content := file.Text()
805
+ for _, doc := range node.JSDoc(file) {
806
+ if doc == nil ||
807
+ doc.Pos() < 0 ||
808
+ doc.End() > len(content) ||
809
+ doc.Pos() >= doc.End() {
810
+ continue
811
+ }
812
+ if tag := commentHidingTag(content[doc.Pos():doc.End()]); tag != "" {
813
+ return tag
814
+ }
815
+ }
816
+ return ""
817
+ }
818
+
819
+ func addTypeScriptUnit(
820
+ inventory *artifactInventory,
821
+ unitsByID map[string]*evidenceUnit,
822
+ node *shimast.Node,
823
+ symbol string,
824
+ identity []string,
825
+ parentID string,
826
+ hidden string,
827
+ ) *evidenceUnit {
828
+ target := strings.Join(identity, ".")
829
+ address := inventory.Address
830
+ if address == "" {
831
+ address = inventory.Path
832
+ }
833
+ id := "typescript:" + address + ":" + symbol + ":" + encodeTypeScriptIdentity(identity)
834
+ // Recorded before the dedupe below, so a merged identity keeps every
835
+ // declaration that spells it. `interface I` beside `namespace I` is one
836
+ // unit and two nodes, and a rule asking where that unit's JSDoc may live
837
+ // has to see both.
838
+ inventory.recordUnitNode(id, node)
839
+ if unit := unitsByID[id]; unit != nil {
840
+ // A merged identity is one unit, so one declaration marking itself
841
+ // internal marks the identity. Both halves of `interface I` beside
842
+ // `namespace I` name the same public thing, and honoring only the tagged
843
+ // half would leave the identity half in and half out of the surface.
844
+ if hidden != "" && unit.Hidden == "" {
845
+ unit.Hidden = hidden
846
+ }
847
+ return unit
848
+ }
849
+ unit := &evidenceUnit{
850
+ ID: id,
851
+ ParentID: parentID,
852
+ Target: target,
853
+ Identity: append([]string{}, identity...),
854
+ Type: artifactTypeScript,
855
+ Symbol: symbol,
856
+ Path: inventory.Path,
857
+ Line: lineAtNode(inventory.Path, node),
858
+ Readable: "TypeScript " + symbol + " '" + target + "'",
859
+ Hidden: hidden,
860
+ }
861
+ unitsByID[id] = unit
862
+ inventory.Units = append(inventory.Units, unit)
863
+ return unit
864
+ }
865
+
866
+ func addTypeScriptHost(
867
+ hosts map[*shimast.Node]symbolSet,
868
+ node *shimast.Node,
869
+ symbol string,
870
+ ) {
871
+ if node == nil {
872
+ return
873
+ }
874
+ if hosts[node] == nil {
875
+ hosts[node] = symbolSet{}
876
+ }
877
+ hosts[node][symbol] = true
878
+ }
879
+
880
+ // lineAtNode stores a byte offset until declarations are scanned against the
881
+ // complete source text. A position inside the name is on the declaration
882
+ // itself, while both the parent and name full starts may include leading trivia.
883
+ func lineAtNode(_ string, node *shimast.Node) int {
884
+ if node == nil {
885
+ return 0
886
+ }
887
+ if name := node.Name(); name != nil && name.End() > 0 {
888
+ return name.End() - 1
889
+ }
890
+ return node.Pos()
891
+ }
892
+
893
+ func collectTypeScriptDeclarations(
894
+ file *shimast.SourceFile,
895
+ address string,
896
+ location string,
897
+ inventory *artifactInventory,
898
+ supportedHosts map[*shimast.Node]symbolSet,
899
+ ) {
900
+ type docHost struct {
901
+ node *shimast.Node
902
+ hosts symbolSet
903
+ hostIDs map[string]bool
904
+ semanticHostIDs map[string]bool
905
+ }
906
+ semanticHostsByNode := map[*shimast.Node]map[string]bool{}
907
+ for unitID, nodes := range inventory.UnitNodes {
908
+ for _, node := range nodes {
909
+ if node == nil {
910
+ continue
911
+ }
912
+ if semanticHostsByNode[node] == nil {
913
+ semanticHostsByNode[node] = map[string]bool{}
914
+ }
915
+ semanticHostsByNode[node][unitID] = true
916
+ }
917
+ }
918
+ docs := map[string]docHost{}
919
+ walkTypeScriptNode(file.AsNode(), func(node *shimast.Node) {
920
+ for _, doc := range node.JSDoc(file) {
921
+ if doc == nil {
922
+ continue
923
+ }
924
+ key := decimal(doc.Pos()) + ":" + decimal(doc.End())
925
+ candidate := docHost{
926
+ node: doc,
927
+ hosts: supportedHosts[node],
928
+ semanticHostIDs: semanticHostsByNode[node],
929
+ }
930
+ if len(candidate.hosts) != 0 {
931
+ candidate.hostIDs = map[string]bool{
932
+ address + ":" + decimal(node.Pos()) + ":" + decimal(node.End()): true,
933
+ }
934
+ }
935
+ current, exists := docs[key]
936
+ if !exists {
937
+ docs[key] = candidate
938
+ continue
939
+ }
940
+ for symbol := range candidate.hosts {
941
+ if current.hosts == nil {
942
+ current.hosts = symbolSet{}
943
+ }
944
+ current.hosts[symbol] = true
945
+ }
946
+ for hostID := range candidate.hostIDs {
947
+ if current.hostIDs == nil {
948
+ current.hostIDs = map[string]bool{}
949
+ }
950
+ current.hostIDs[hostID] = true
951
+ }
952
+ for semanticHostID := range candidate.semanticHostIDs {
953
+ if current.semanticHostIDs == nil {
954
+ current.semanticHostIDs = map[string]bool{}
955
+ }
956
+ current.semanticHostIDs[semanticHostID] = true
957
+ }
958
+ docs[key] = current
959
+ }
960
+ })
961
+ keys := make([]string, 0, len(docs))
962
+ for key := range docs {
963
+ keys = append(keys, key)
964
+ }
965
+ sort.Slice(keys, func(left int, right int) bool {
966
+ leftNode := docs[keys[left]].node
967
+ rightNode := docs[keys[right]].node
968
+ if leftNode.Pos() != rightNode.Pos() {
969
+ return leftNode.Pos() < rightNode.Pos()
970
+ }
971
+ return leftNode.End() < rightNode.End()
972
+ })
973
+ content := file.Text()
974
+ sequence := 0
975
+ for _, key := range keys {
976
+ entry := docs[key]
977
+ if entry.node.Pos() < 0 || entry.node.End() > len(content) || entry.node.Pos() >= entry.node.End() {
978
+ continue
979
+ }
980
+ baseLine := lineAt(content, entry.node.Pos())
981
+ hostIDs := make([]string, 0, len(entry.hostIDs))
982
+ for hostID := range entry.hostIDs {
983
+ hostIDs = append(hostIDs, hostID)
984
+ }
985
+ sort.Strings(hostIDs)
986
+ hostID := strings.Join(hostIDs, "|")
987
+ semanticHostIDs := make([]string, 0, len(entry.semanticHostIDs))
988
+ for semanticHostID := range entry.semanticHostIDs {
989
+ semanticHostIDs = append(semanticHostIDs, semanticHostID)
990
+ }
991
+ sort.Strings(semanticHostIDs)
992
+ for _, parsed := range parseDeclarations(content[entry.node.Pos():entry.node.End()]) {
993
+ sequence++
994
+ inventory.Declarations = append(inventory.Declarations, &evidenceDeclaration{
995
+ ID: "typescript:" + address + ":" + decimal(baseLine+parsed.LineOffset) + ":" + decimal(sequence),
996
+ HostID: hostID,
997
+ SemanticHostIDs: semanticHostIDs,
998
+ Type: artifactTypeScript,
999
+ Tag: parsed.Tag,
1000
+ Target: parsed.Target,
1001
+ Reason: parsed.Reason,
1002
+ Hosts: entry.hosts,
1003
+ ExclusionCarrier: len(entry.hosts) != 0,
1004
+ Path: location,
1005
+ Line: baseLine + parsed.LineOffset,
1006
+ Sequence: sequence,
1007
+ })
1008
+ }
1009
+ }
1010
+ for _, unit := range inventory.Units {
1011
+ // TypeScript AST positions are byte offsets; translate them only after
1012
+ // the complete source text is available.
1013
+ unit.Line = lineAt(content, unit.Line)
1014
+ }
1015
+ }
1016
+
1017
+ func walkTypeScriptNode(node *shimast.Node, visit func(*shimast.Node)) {
1018
+ if node == nil {
1019
+ return
1020
+ }
1021
+ visit(node)
1022
+ node.ForEachChild(func(child *shimast.Node) bool {
1023
+ walkTypeScriptNode(child, visit)
1024
+ return false
1025
+ })
1026
+ }
1027
+
1028
+ type exportedName struct {
1029
+ Public string
1030
+ TypeOnly bool
1031
+ }
1032
+
1033
+ func collectLocalExportNames(
1034
+ statements *shimast.NodeList,
1035
+ ) map[string][]exportedName {
1036
+ exports := map[string][]exportedName{}
1037
+ if statements == nil {
1038
+ return exports
1039
+ }
1040
+ for _, statement := range statements.Nodes {
1041
+ if statement == nil || statement.Kind != shimast.KindExportDeclaration {
1042
+ continue
1043
+ }
1044
+ declaration := statement.AsExportDeclaration()
1045
+ if declaration == nil ||
1046
+ declaration.ModuleSpecifier != nil ||
1047
+ declaration.ExportClause == nil ||
1048
+ declaration.ExportClause.Kind != shimast.KindNamedExports {
1049
+ continue
1050
+ }
1051
+ named := declaration.ExportClause.AsNamedExports()
1052
+ if named == nil || named.Elements == nil {
1053
+ continue
1054
+ }
1055
+ for _, element := range named.Elements.Nodes {
1056
+ if element == nil || element.Kind != shimast.KindExportSpecifier {
1057
+ continue
1058
+ }
1059
+ specifier := element.AsExportSpecifier()
1060
+ if specifier == nil {
1061
+ continue
1062
+ }
1063
+ localNode := specifier.PropertyName
1064
+ if localNode == nil {
1065
+ localNode = specifier.Name()
1066
+ }
1067
+ local := declarationName(localNode)
1068
+ public := declarationName(specifier.Name())
1069
+ if local == "" || public == "" || public == "default" {
1070
+ continue
1071
+ }
1072
+ exports[local] = append(exports[local], exportedName{
1073
+ Public: public,
1074
+ TypeOnly: declaration.IsTypeOnly || specifier.IsTypeOnly,
1075
+ })
1076
+ }
1077
+ }
1078
+ return exports
1079
+ }
1080
+
1081
+ func publicTypeScriptNames(
1082
+ node *shimast.Node,
1083
+ local string,
1084
+ exports map[string][]exportedName,
1085
+ allowTypeOnly bool,
1086
+ implicitlyExported bool,
1087
+ ) []string {
1088
+ projected := publicTypeScriptExports(
1089
+ node,
1090
+ local,
1091
+ exports,
1092
+ allowTypeOnly,
1093
+ implicitlyExported,
1094
+ )
1095
+ result := make([]string, 0, len(projected))
1096
+ for _, exported := range projected {
1097
+ result = append(result, exported.Public)
1098
+ }
1099
+ return result
1100
+ }
1101
+
1102
+ func publicTypeScriptExports(
1103
+ node *shimast.Node,
1104
+ local string,
1105
+ exports map[string][]exportedName,
1106
+ allowTypeOnly bool,
1107
+ implicitlyExported bool,
1108
+ ) []exportedName {
1109
+ if local == "" {
1110
+ return nil
1111
+ }
1112
+ names := map[string]exportedName{}
1113
+ if implicitlyExported || isSyntacticallyExported(node) {
1114
+ names[local] = exportedName{Public: local}
1115
+ }
1116
+ for _, exported := range exports[local] {
1117
+ if exported.TypeOnly && !allowTypeOnly {
1118
+ continue
1119
+ }
1120
+ current, exists := names[exported.Public]
1121
+ if !exists || current.TypeOnly && !exported.TypeOnly {
1122
+ names[exported.Public] = exported
1123
+ }
1124
+ }
1125
+ result := make([]string, 0, len(names))
1126
+ for name := range names {
1127
+ result = append(result, name)
1128
+ }
1129
+ sort.Strings(result)
1130
+ projected := make([]exportedName, 0, len(result))
1131
+ for _, name := range result {
1132
+ projected = append(projected, names[name])
1133
+ }
1134
+ return projected
1135
+ }
1136
+
1137
+ func isSyntacticallyExported(node *shimast.Node) bool {
1138
+ return node != nil && shimast.GetCombinedModifierFlags(node)&shimast.ModifierFlagsExport != 0
1139
+ }
1140
+
1141
+ func declarationName(node *shimast.Node) string {
1142
+ if node == nil {
1143
+ return ""
1144
+ }
1145
+ switch node.Kind {
1146
+ case shimast.KindIdentifier,
1147
+ shimast.KindStringLiteral,
1148
+ shimast.KindNumericLiteral:
1149
+ name := node.Text()
1150
+ if containsWhitespace(name) {
1151
+ return ""
1152
+ }
1153
+ return name
1154
+ default:
1155
+ return ""
1156
+ }
1157
+ }
1158
+
1159
+ func bindingIdentifierNodes(node *shimast.Node) []*shimast.Node {
1160
+ if node == nil {
1161
+ return nil
1162
+ }
1163
+ if declarationName(node) != "" {
1164
+ return []*shimast.Node{node}
1165
+ }
1166
+ if !shimast.IsBindingPattern(node) {
1167
+ return nil
1168
+ }
1169
+ pattern := node.AsBindingPattern()
1170
+ if pattern == nil || pattern.Elements == nil {
1171
+ return nil
1172
+ }
1173
+ nodes := []*shimast.Node{}
1174
+ for _, element := range pattern.Elements.Nodes {
1175
+ if element == nil || element.Kind != shimast.KindBindingElement {
1176
+ continue
1177
+ }
1178
+ nodes = append(nodes, bindingIdentifierNodes(element.Name())...)
1179
+ }
1180
+ return nodes
1181
+ }
1182
+
1183
+ func qualifyTypeScriptName(prefix []string, names ...string) []string {
1184
+ qualified := make([]string, 0, len(prefix)+len(names))
1185
+ qualified = append(qualified, prefix...)
1186
+ qualified = append(qualified, names...)
1187
+ return qualified
1188
+ }
1189
+
1190
+ func encodeTypeScriptIdentity(identity []string) string {
1191
+ var builder strings.Builder
1192
+ for _, segment := range identity {
1193
+ builder.WriteString(decimal(len(segment)))
1194
+ builder.WriteByte(':')
1195
+ builder.WriteString(segment)
1196
+ builder.WriteByte(';')
1197
+ }
1198
+ return builder.String()
1199
+ }