@ttsc/evidence 0.29.0 → 0.30.1
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/lib/structures/ITtscEvidenceGraphTypeScriptReference.d.ts +18 -6
- package/lib/typings/TtscEvidenceGraphTypeScriptSymbol.d.ts +11 -12
- package/native/config.go +12 -35
- package/native/declaration.go +50 -1
- package/native/default_exports.go +62 -0
- package/native/export_resolution.go +275 -0
- package/native/exports.go +62 -144
- package/native/file_link.go +370 -0
- package/native/file_link_diagnostics.go +160 -0
- package/native/fingerprint.go +33 -4
- package/native/graph.go +38 -8
- package/native/imports.go +21 -8
- package/native/inputs.go +13 -4
- package/native/loader.go +170 -23
- package/native/model.go +10 -3
- package/native/rooted_typescript.go +116 -0
- package/native/typescript.go +30 -11
- package/native/typescript_cache.go +5 -4
- package/package.json +5 -5
|
@@ -13,13 +13,25 @@ import type { ITtscEvidenceGraphReferenceBase } from "./ITtscEvidenceGraphRefere
|
|
|
13
13
|
* contracts deserve documentary proof.
|
|
14
14
|
*/
|
|
15
15
|
export interface ITtscEvidenceGraphTypeScriptReference extends ITtscEvidenceGraphReferenceBase<"typescript"> {
|
|
16
|
+
/**
|
|
17
|
+
* Explicit directory whose TypeScript exports are read from disk.
|
|
18
|
+
*
|
|
19
|
+
* Resolves relative to the lint project's root, or accepts an absolute path.
|
|
20
|
+
* Files patterns select entry modules relative to this directory. Their
|
|
21
|
+
* re-exports must remain inside it. Active Program sources take precedence
|
|
22
|
+
* over disk snapshots. The directory is watched for changes and new files.
|
|
23
|
+
*
|
|
24
|
+
* Cannot combine with package. Omit both to select only the active Program.
|
|
25
|
+
* File-qualified citation paths remain relative to the citing file.
|
|
26
|
+
*/
|
|
27
|
+
root?: string;
|
|
16
28
|
/**
|
|
17
29
|
* Installed package whose declarations form this population.
|
|
18
30
|
*
|
|
19
|
-
* Omit it to select the active project
|
|
20
|
-
* against the package root instead of the
|
|
21
|
-
* a consumer thinks of the package rather
|
|
22
|
-
* location.
|
|
31
|
+
* Omit it to select the active project or the explicit {@link root}. When
|
|
32
|
+
* present, {@link files} resolves against the package root instead of the
|
|
33
|
+
* project root, so the globs read as a consumer thinks of the package rather
|
|
34
|
+
* than carrying its `node_modules` location.
|
|
23
35
|
*
|
|
24
36
|
* A package population is read from disk rather than from the `ttsc` program.
|
|
25
37
|
* That is the point: a symbol nothing imports is absent from the program by
|
|
@@ -39,8 +51,8 @@ export interface ITtscEvidenceGraphTypeScriptReference extends ITtscEvidenceGrap
|
|
|
39
51
|
* `ttsc` project. A matching file outside the project's `tsconfig` program is
|
|
40
52
|
* not available to the rule and does not count as a match.
|
|
41
53
|
*
|
|
42
|
-
*
|
|
43
|
-
* files
|
|
54
|
+
* With {@link root} or {@link package}, these are relative to that directory or
|
|
55
|
+
* package and entry files can be read from disk outside the Program.
|
|
44
56
|
*
|
|
45
57
|
* These are globs, not regular expressions. `*` matches within one path
|
|
46
58
|
* segment, `**` crosses any number of path segments, and `?` matches one
|
|
@@ -58,8 +58,9 @@
|
|
|
58
58
|
* prepend their namespace, such as `Orders.create`. A namespace itself uses its
|
|
59
59
|
* qualified name, such as `Orders` or `Outer.Inner`. A local declaration
|
|
60
60
|
* exposed as `export { Local as Public }` therefore uses `Public`. A named
|
|
61
|
-
* default declaration
|
|
62
|
-
*
|
|
61
|
+
* default declaration or default alias is publicly addressed as `default`;
|
|
62
|
+
* anonymous default classes and functions also have that stable address. A
|
|
63
|
+
* default-only local binding is not a named export. Members of an ambient
|
|
63
64
|
* namespace are public without their own `export` modifier. A type-only alias
|
|
64
65
|
* exposes a namespace or a class, its public type-space descendants, and
|
|
65
66
|
* everything an object-shaped type alias, or an interface no class merges with,
|
|
@@ -79,17 +80,15 @@
|
|
|
79
80
|
* form. Static class members use `ClassName.member`; instance members use
|
|
80
81
|
* `ClassName.prototype.member`, and a constructor parameter property is an
|
|
81
82
|
* instance member addressed that way. Computed names are not selected, even
|
|
82
|
-
* when their expression is a literal.
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
* qualification, the target is ambiguous.
|
|
83
|
+
* when their expression is a literal. File-qualified accessors use bracketed
|
|
84
|
+
* JSON strings for literal names, including spaces and dots. Existing inline
|
|
85
|
+
* links keep their dotted spelling and report ambiguous literal qualification.
|
|
86
86
|
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
* does not rewrite literal symbol names.
|
|
87
|
+
* A code citation identifies its module through a file path or the citing
|
|
88
|
+
* TypeScript module's imports. A re-export whose declaration lives in another
|
|
89
|
+
* file does not create a second unit in the barrel file. TypeScript target
|
|
90
|
+
* characters are matched exactly; Markdown path-separator normalization does
|
|
91
|
+
* not rewrite literal symbol names.
|
|
93
92
|
*
|
|
94
93
|
* Every supported public declaration described here may carry
|
|
95
94
|
* `@evidenceExclude` when its file belongs to a claim, even when that claim's
|
package/native/config.go
CHANGED
|
@@ -288,6 +288,7 @@ func decodeReference(
|
|
|
288
288
|
Type: kind,
|
|
289
289
|
Policy: policy,
|
|
290
290
|
Root: root,
|
|
291
|
+
Rooted: kind == artifactTypeScript && object["root"] != nil,
|
|
291
292
|
Files: files,
|
|
292
293
|
Source: source,
|
|
293
294
|
Package: packageName,
|
|
@@ -434,24 +435,8 @@ func rejectChecklistConflicts(
|
|
|
434
435
|
return problems
|
|
435
436
|
}
|
|
436
437
|
|
|
437
|
-
//
|
|
438
|
-
//
|
|
439
|
-
//
|
|
440
|
-
// A symbol is cited through an inline link, and that link resolves in the
|
|
441
|
-
// citing module's import scope — which only a TypeScript file has. Every other
|
|
442
|
-
// claim would have to fall back to matching the bare name against one
|
|
443
|
-
// repository-wide table, and that makes symbol-name uniqueness across the whole
|
|
444
|
-
// repository load-bearing: two modules exporting `IPage` make the citation
|
|
445
|
-
// impossible, and the only repair such a diagnostic can offer is renaming
|
|
446
|
-
// production code to suit a lint rule.
|
|
447
|
-
//
|
|
448
|
-
// The check is here rather than at resolution because a configuration error
|
|
449
|
-
// belongs where the configuration is read. Reported once per reference, before
|
|
450
|
-
// any file is opened, instead of once per citation that later fails to resolve.
|
|
451
|
-
//
|
|
452
|
-
// What this gives up is the decision it reverses: documentation can no longer
|
|
453
|
-
// cite code, and the inverse obligation is not the same one. Do not restore the fallback
|
|
454
|
-
// without restoring that record.
|
|
438
|
+
// Code citations require an import scope or the Markdown/TypeScript file-link
|
|
439
|
+
// grammar. Bare repository-wide symbol lookup remains unavailable.
|
|
455
440
|
func rejectForeignTypeScriptReference(
|
|
456
441
|
claimKind artifactKind,
|
|
457
442
|
referenceKind artifactKind,
|
|
@@ -459,13 +444,13 @@ func rejectForeignTypeScriptReference(
|
|
|
459
444
|
) string {
|
|
460
445
|
if referenceKind != artifactTypeScript ||
|
|
461
446
|
claimKind == artifactTypeScript ||
|
|
447
|
+
claimKind == artifactMarkdown ||
|
|
462
448
|
claimKind == "" {
|
|
463
449
|
return ""
|
|
464
450
|
}
|
|
465
451
|
return "Invalid evidence/graph configuration at " + path +
|
|
466
|
-
".type:
|
|
467
|
-
|
|
468
|
-
" comment has none. Invert the obligation so the code cites this artifact, or move the citation into TypeScript."
|
|
452
|
+
".type: TypeScript evidence requires a Markdown or TypeScript claim; a " + string(claimKind) +
|
|
453
|
+
" claim does not support file-qualified code citations. Move the citation to a supported claim."
|
|
469
454
|
}
|
|
470
455
|
|
|
471
456
|
// decodeTypeScriptReference reads the two ways a TypeScript population is
|
|
@@ -482,6 +467,9 @@ func decodeTypeScriptReference(
|
|
|
482
467
|
problems := []string{}
|
|
483
468
|
reference := referenceSpec{}
|
|
484
469
|
if raw, exists := object["package"]; exists {
|
|
470
|
+
if _, rooted := object["root"]; rooted {
|
|
471
|
+
problems = append(problems, "Invalid evidence/graph configuration at "+path+": 'root' and 'package' select different TypeScript populations and cannot be combined. Choose one base and its 'files' patterns.")
|
|
472
|
+
}
|
|
485
473
|
var value string
|
|
486
474
|
if err := json.Unmarshal(raw, &value); err != nil {
|
|
487
475
|
problems = append(problems, "Invalid evidence/graph configuration at "+path+".package: expected an installed package name.")
|
|
@@ -588,15 +576,8 @@ func decodeClaimRoot(
|
|
|
588
576
|
return decodePopulationRoot(raw, configPath)
|
|
589
577
|
}
|
|
590
578
|
|
|
591
|
-
// decodeRoot reads
|
|
592
|
-
//
|
|
593
|
-
//
|
|
594
|
-
// The property is refused on the two artifact kinds that cannot use it, and
|
|
595
|
-
// each refusal names the channel that artifact does have. A TypeScript
|
|
596
|
-
// population is materialized from the ttsc Program, so a directory outside the
|
|
597
|
-
// project contains no file it could ever reach — `package` is the escape there,
|
|
598
|
-
// and it already exists. A Swagger reference names one exact document, so the
|
|
599
|
-
// location belongs in `file`, where it is visible without a second property.
|
|
579
|
+
// decodeRoot reads a population directory. A TypeScript reference uses its
|
|
580
|
+
// presence to opt into disk loading; Swagger still owns one exact file.
|
|
600
581
|
func decodeRoot(
|
|
601
582
|
raw json.RawMessage,
|
|
602
583
|
kind artifactKind,
|
|
@@ -606,11 +587,7 @@ func decodeRoot(
|
|
|
606
587
|
return "", nil
|
|
607
588
|
}
|
|
608
589
|
switch kind {
|
|
609
|
-
case artifactMarkdown, artifactPrisma:
|
|
610
|
-
case artifactTypeScript:
|
|
611
|
-
return "", []string{
|
|
612
|
-
"Invalid evidence/graph configuration at " + configPath + ": a TypeScript reference selects the active ttsc program with 'files', or an installed package with 'package'; only a TypeScript claim accepts 'root'.",
|
|
613
|
-
}
|
|
590
|
+
case artifactMarkdown, artifactPrisma, artifactTypeScript:
|
|
614
591
|
case artifactSwagger:
|
|
615
592
|
return "", []string{
|
|
616
593
|
"Invalid evidence/graph configuration at " + configPath + ": a Swagger reference owns one exact document; write the ancestor-relative or absolute location in 'file' instead.",
|
package/native/declaration.go
CHANGED
|
@@ -35,10 +35,14 @@ func commentHidingTag(comment string) string {
|
|
|
35
35
|
comment = strings.TrimPrefix(comment, "/**")
|
|
36
36
|
comment = strings.TrimPrefix(comment, "/*")
|
|
37
37
|
comment = strings.TrimSuffix(comment, "*/")
|
|
38
|
+
fence := commentFence{}
|
|
38
39
|
for _, rawLine := range strings.Split(comment, "\n") {
|
|
39
40
|
line := strings.TrimSpace(rawLine)
|
|
40
41
|
line = strings.TrimSpace(strings.TrimPrefix(line, "*"))
|
|
41
42
|
line = strings.TrimSpace(strings.TrimPrefix(line, "///"))
|
|
43
|
+
if fence.consume(line) {
|
|
44
|
+
continue
|
|
45
|
+
}
|
|
42
46
|
for _, tag := range hiddenDeclarationTags {
|
|
43
47
|
if line != tag && !strings.HasPrefix(line, tag+" ") {
|
|
44
48
|
continue
|
|
@@ -75,6 +79,7 @@ func parseCommentDeclarations(
|
|
|
75
79
|
lineOffset int
|
|
76
80
|
}
|
|
77
81
|
var pending *pendingDeclaration
|
|
82
|
+
fence := commentFence{}
|
|
78
83
|
parsed := []parsedDeclaration{}
|
|
79
84
|
flush := func() {
|
|
80
85
|
if pending == nil {
|
|
@@ -92,6 +97,12 @@ func parseCommentDeclarations(
|
|
|
92
97
|
for index, rawLine := range lines {
|
|
93
98
|
line := strings.TrimSpace(rawLine)
|
|
94
99
|
line = strings.TrimSpace(strings.TrimPrefix(line, "*"))
|
|
100
|
+
if fence.consume(line) {
|
|
101
|
+
if pending != nil {
|
|
102
|
+
pending.body = append(pending.body, line)
|
|
103
|
+
}
|
|
104
|
+
continue
|
|
105
|
+
}
|
|
95
106
|
tag, body, found := declarationLine(line)
|
|
96
107
|
if found {
|
|
97
108
|
flush()
|
|
@@ -136,6 +147,7 @@ func declarationLine(line string) (tagKind, string, bool) {
|
|
|
136
147
|
}{
|
|
137
148
|
{marker: "@evidenceExclude", tag: tagExclude},
|
|
138
149
|
{marker: "@evidence", tag: tagEvidence},
|
|
150
|
+
{marker: "@link", tag: tagEvidence},
|
|
139
151
|
} {
|
|
140
152
|
if !strings.HasPrefix(line, candidate.marker) {
|
|
141
153
|
continue
|
|
@@ -144,7 +156,11 @@ func declarationLine(line string) (tagKind, string, bool) {
|
|
|
144
156
|
if remainder != "" && remainder[0] != ' ' && remainder[0] != '\t' {
|
|
145
157
|
continue
|
|
146
158
|
}
|
|
147
|
-
|
|
159
|
+
body := strings.TrimSpace(remainder)
|
|
160
|
+
if candidate.marker == "@link" {
|
|
161
|
+
body = fileLinkPrefix + body
|
|
162
|
+
}
|
|
163
|
+
return candidate.tag, body, true
|
|
148
164
|
}
|
|
149
165
|
return "", "", false
|
|
150
166
|
}
|
|
@@ -161,6 +177,9 @@ func splitDeclarationBody(body string) (string, string) {
|
|
|
161
177
|
if body == "" {
|
|
162
178
|
return "", ""
|
|
163
179
|
}
|
|
180
|
+
if target, reason, found := splitFileLinkBody(body); found {
|
|
181
|
+
return target, reason
|
|
182
|
+
}
|
|
164
183
|
if target, reason, found := splitInlineLinkBody(body); found {
|
|
165
184
|
return target, reason
|
|
166
185
|
}
|
|
@@ -221,6 +240,9 @@ func inlineLinkTarget(target string) string {
|
|
|
221
240
|
|
|
222
241
|
// displayTarget renders a target the way its author wrote it.
|
|
223
242
|
func displayTarget(target string) string {
|
|
243
|
+
if isFileLinkTarget(target) {
|
|
244
|
+
return strings.TrimPrefix(target, fileLinkPrefix)
|
|
245
|
+
}
|
|
224
246
|
if isInlineLinkTarget(target) {
|
|
225
247
|
return "{@link " + inlineLinkTarget(target) + "}"
|
|
226
248
|
}
|
|
@@ -315,6 +337,7 @@ func parseReviews(comment string) []parsedReview {
|
|
|
315
337
|
comment = strings.TrimSuffix(comment, "*/")
|
|
316
338
|
reviews := []parsedReview{}
|
|
317
339
|
var pending *parsedReview
|
|
340
|
+
fence := commentFence{}
|
|
318
341
|
var body []string
|
|
319
342
|
flush := func() {
|
|
320
343
|
if pending == nil {
|
|
@@ -333,6 +356,12 @@ func parseReviews(comment string) []parsedReview {
|
|
|
333
356
|
line := strings.TrimSpace(rawLine)
|
|
334
357
|
line = strings.TrimSpace(strings.TrimPrefix(line, "*"))
|
|
335
358
|
line = strings.TrimSpace(strings.TrimPrefix(line, "///"))
|
|
359
|
+
if fence.consume(line) {
|
|
360
|
+
if pending != nil {
|
|
361
|
+
body = append(body, line)
|
|
362
|
+
}
|
|
363
|
+
continue
|
|
364
|
+
}
|
|
336
365
|
if reviews, remainder, opened := reviewLine(line); opened {
|
|
337
366
|
flush()
|
|
338
367
|
pending = &parsedReview{
|
|
@@ -442,6 +471,26 @@ func containsWhitespace(value string) bool {
|
|
|
442
471
|
return false
|
|
443
472
|
}
|
|
444
473
|
|
|
474
|
+
// Fences inside documentation are examples, not declarations. Their text is
|
|
475
|
+
// still retained when it belongs to a pending reason or review description.
|
|
476
|
+
type commentFence struct {
|
|
477
|
+
marker rune
|
|
478
|
+
length int
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
func (fence *commentFence) consume(line string) bool {
|
|
482
|
+
marker, length, remainder, opened := markdownFence(line)
|
|
483
|
+
if opened {
|
|
484
|
+
if fence.marker == 0 {
|
|
485
|
+
fence.marker, fence.length = marker, length
|
|
486
|
+
} else if marker == fence.marker && length >= fence.length && strings.TrimSpace(remainder) == "" {
|
|
487
|
+
fence.marker, fence.length = 0, 0
|
|
488
|
+
}
|
|
489
|
+
return true
|
|
490
|
+
}
|
|
491
|
+
return fence.marker != 0
|
|
492
|
+
}
|
|
493
|
+
|
|
445
494
|
func lineAt(content string, offset int) int {
|
|
446
495
|
if offset < 0 {
|
|
447
496
|
return 1
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
package evidence
|
|
2
|
+
|
|
3
|
+
import shimast "github.com/microsoft/typescript-go/shim/ast"
|
|
4
|
+
|
|
5
|
+
// Default aliases retain the named declaration's identity. Anonymous class
|
|
6
|
+
// and function declarations own the module's default identity directly.
|
|
7
|
+
func typeScriptDeclarationName(node *shimast.Node) string {
|
|
8
|
+
if node == nil {
|
|
9
|
+
return ""
|
|
10
|
+
}
|
|
11
|
+
if name := declarationName(node.Name()); name != "" {
|
|
12
|
+
return name
|
|
13
|
+
}
|
|
14
|
+
if isDefaultExported(node) {
|
|
15
|
+
return "default"
|
|
16
|
+
}
|
|
17
|
+
return ""
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
func collectDefaultExportBindings(statements *shimast.NodeList) map[string]exportedName {
|
|
21
|
+
bindings := map[string]exportedName{}
|
|
22
|
+
if statements == nil {
|
|
23
|
+
return bindings
|
|
24
|
+
}
|
|
25
|
+
for _, statement := range statements.Nodes {
|
|
26
|
+
if statement == nil {
|
|
27
|
+
continue
|
|
28
|
+
}
|
|
29
|
+
if statement.Kind == shimast.KindExportAssignment {
|
|
30
|
+
assignment := statement.AsExportAssignment()
|
|
31
|
+
expression := assignment.Expression
|
|
32
|
+
if !assignment.IsExportEquals && expression != nil && expression.Kind == shimast.KindIdentifier {
|
|
33
|
+
local := declarationName(expression)
|
|
34
|
+
bindings[local] = exportedName{Public: local}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if statement.Kind != shimast.KindExportDeclaration {
|
|
38
|
+
continue
|
|
39
|
+
}
|
|
40
|
+
declaration := statement.AsExportDeclaration()
|
|
41
|
+
if declaration.ModuleSpecifier != nil || declaration.ExportClause == nil || declaration.ExportClause.Kind != shimast.KindNamedExports {
|
|
42
|
+
continue
|
|
43
|
+
}
|
|
44
|
+
for _, element := range declaration.ExportClause.AsNamedExports().Elements.Nodes {
|
|
45
|
+
specifier := element.AsExportSpecifier()
|
|
46
|
+
if declarationName(specifier.Name()) != "default" {
|
|
47
|
+
continue
|
|
48
|
+
}
|
|
49
|
+
local := specifier.PropertyName
|
|
50
|
+
if local == nil {
|
|
51
|
+
local = specifier.Name()
|
|
52
|
+
}
|
|
53
|
+
name := declarationName(local)
|
|
54
|
+
typeOnly := declaration.IsTypeOnly || specifier.IsTypeOnly
|
|
55
|
+
if previous, exists := bindings[name]; exists {
|
|
56
|
+
typeOnly = typeOnly && previous.TypeOnly
|
|
57
|
+
}
|
|
58
|
+
bindings[name] = exportedName{Public: name, TypeOnly: typeOnly}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return bindings
|
|
62
|
+
}
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
package evidence
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"sort"
|
|
5
|
+
"strings"
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
// Export names are finite even when modules re-export each other. Discover
|
|
9
|
+
// that vocabulary before resolving bindings, and expand namespace paths only
|
|
10
|
+
// after a binding is known. A module recursion guard cannot answer whether a
|
|
11
|
+
// particular export exists: the module may be busy resolving another name.
|
|
12
|
+
type typeScriptExportResolver struct {
|
|
13
|
+
loader *typeScriptLoader
|
|
14
|
+
modules map[string]*typeScriptExportModule
|
|
15
|
+
bindings map[scopedTargetKey][]reachedSymbol
|
|
16
|
+
owned *ownedUnitIndex
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
type typeScriptExportModule struct {
|
|
20
|
+
exports []moduleExport
|
|
21
|
+
targets map[string]string
|
|
22
|
+
names map[string]bool
|
|
23
|
+
named map[string][]moduleExport
|
|
24
|
+
stars []moduleExport
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
func newTypeScriptExportResolver(loader *typeScriptLoader, entries []string) *typeScriptExportResolver {
|
|
28
|
+
resolver := &typeScriptExportResolver{loader: loader, modules: map[string]*typeScriptExportModule{}, bindings: map[scopedTargetKey][]reachedSymbol{}}
|
|
29
|
+
resolver.owned = newOwnedUnitIndex(loader)
|
|
30
|
+
for _, entry := range entries {
|
|
31
|
+
resolver.load(entry)
|
|
32
|
+
}
|
|
33
|
+
keys := make([]string, 0, len(resolver.modules))
|
|
34
|
+
for key := range resolver.modules {
|
|
35
|
+
keys = append(keys, key)
|
|
36
|
+
}
|
|
37
|
+
sort.Strings(keys)
|
|
38
|
+
// Only star edges add names. Namespace and named re-exports already have
|
|
39
|
+
// explicit names, so namespace cycles cannot grow this fixed point forever.
|
|
40
|
+
for changed := true; changed; {
|
|
41
|
+
changed = false
|
|
42
|
+
for _, key := range keys {
|
|
43
|
+
module := resolver.modules[key]
|
|
44
|
+
for _, export := range module.stars {
|
|
45
|
+
target := resolver.modules[module.targets[export.Specifier]]
|
|
46
|
+
if target == nil {
|
|
47
|
+
continue
|
|
48
|
+
}
|
|
49
|
+
for name := range target.names {
|
|
50
|
+
if name != "default" && !module.names[name] {
|
|
51
|
+
module.names[name] = true
|
|
52
|
+
changed = true
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if loader.boundary != nil {
|
|
59
|
+
for _, key := range keys {
|
|
60
|
+
module := resolver.modules[key]
|
|
61
|
+
for _, export := range module.exports {
|
|
62
|
+
if export.Specifier == "" || export.Namespace || export.Imported == "" {
|
|
63
|
+
continue
|
|
64
|
+
}
|
|
65
|
+
target := module.targets[export.Specifier]
|
|
66
|
+
if resolver.modules[target] == nil {
|
|
67
|
+
continue
|
|
68
|
+
}
|
|
69
|
+
if len(resolver.resolve(target, export.Imported)) == 0 {
|
|
70
|
+
loader.failures[key+" -> "+export.Specifier+"#"+export.Imported] = "the module has no public export named '" + export.Imported + "'"
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return resolver
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
func (resolver *typeScriptExportResolver) load(entry string) {
|
|
79
|
+
if _, loaded := resolver.modules[entry]; loaded {
|
|
80
|
+
return
|
|
81
|
+
}
|
|
82
|
+
inventory := resolver.loader.inventory(entry)
|
|
83
|
+
if inventory == nil {
|
|
84
|
+
return
|
|
85
|
+
}
|
|
86
|
+
module := &typeScriptExportModule{exports: inventory.Exports, targets: map[string]string{}, names: map[string]bool{}, named: map[string][]moduleExport{}}
|
|
87
|
+
declarations := map[string]bool{}
|
|
88
|
+
if inventory.Source != nil && inventory.Source.Statements != nil {
|
|
89
|
+
for _, statement := range inventory.Source.Statements.Nodes {
|
|
90
|
+
if statement == nil {
|
|
91
|
+
continue
|
|
92
|
+
}
|
|
93
|
+
for _, declaration := range topLevelDeclaredNames(statement) {
|
|
94
|
+
declarations[declaration.Name] = true
|
|
95
|
+
}
|
|
96
|
+
if name := typeScriptDeclarationName(statement); name != "" {
|
|
97
|
+
declarations[name] = true
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
resolver.modules[entry] = module
|
|
102
|
+
for _, export := range module.exports {
|
|
103
|
+
if resolver.loader.boundary != nil && export.Specifier == "" && !declarations[export.Local] {
|
|
104
|
+
resolver.loader.failures[entry+"#"+export.Public] = "the exported binding '" + export.Local + "' has no declaration in this module"
|
|
105
|
+
continue
|
|
106
|
+
}
|
|
107
|
+
if export.Public != "" {
|
|
108
|
+
module.names[export.Public] = true
|
|
109
|
+
module.named[export.Public] = append(module.named[export.Public], export)
|
|
110
|
+
} else if export.Specifier != "" {
|
|
111
|
+
module.stars = append(module.stars, export)
|
|
112
|
+
}
|
|
113
|
+
if export.Specifier == "" {
|
|
114
|
+
continue
|
|
115
|
+
}
|
|
116
|
+
if _, resolved := module.targets[export.Specifier]; resolved {
|
|
117
|
+
continue
|
|
118
|
+
}
|
|
119
|
+
target := resolver.loader.resolve(entry, export.Specifier)
|
|
120
|
+
module.targets[export.Specifier] = target
|
|
121
|
+
if target != "" {
|
|
122
|
+
resolver.load(target)
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// accessor follows only the namespace segments the citation actually names.
|
|
128
|
+
// Each hop consumes at least one query segment, so namespace cycles are finite.
|
|
129
|
+
// Legacy inline spelling can join several query segments into a literal export
|
|
130
|
+
// name; Address records the consumed query segments for the remaining lookup.
|
|
131
|
+
func (resolver *typeScriptExportResolver) accessor(entry string, segments []string, prefix []string, typeOnly bool, legacy bool) []reachedSymbol {
|
|
132
|
+
if len(segments) == 0 {
|
|
133
|
+
return nil
|
|
134
|
+
}
|
|
135
|
+
result := []reachedSymbol{}
|
|
136
|
+
limit := 1
|
|
137
|
+
if legacy {
|
|
138
|
+
limit = len(segments)
|
|
139
|
+
}
|
|
140
|
+
for consumed := 1; consumed <= limit; consumed++ {
|
|
141
|
+
name := strings.Join(segments[:consumed], ".")
|
|
142
|
+
for _, binding := range resolver.resolve(entry, name) {
|
|
143
|
+
binding.Address = append(append([]string{}, prefix...), segments[:consumed]...)
|
|
144
|
+
binding.TypeOnly = binding.TypeOnly || typeOnly
|
|
145
|
+
// Keep namespace prefixes for diagnostics, including when the next name
|
|
146
|
+
// is missing. They never become units in lookup or population traversal.
|
|
147
|
+
result = append(result, binding)
|
|
148
|
+
if binding.Local == "" {
|
|
149
|
+
result = append(result, resolver.accessor(binding.Path, segments[consumed:], binding.Address, binding.TypeOnly, legacy)...)
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return result
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
func (resolver *typeScriptExportResolver) lookup(entry string, segments []string, legacy bool) []*evidenceUnit {
|
|
157
|
+
result := []*evidenceUnit{}
|
|
158
|
+
seen := map[string]bool{}
|
|
159
|
+
for _, binding := range resolver.accessor(entry, segments, nil, false, legacy) {
|
|
160
|
+
if binding.Local == "" {
|
|
161
|
+
continue
|
|
162
|
+
}
|
|
163
|
+
target := append([]string{binding.Local}, segments[len(binding.Address):]...)
|
|
164
|
+
for _, unit := range resolver.owned.of(binding.Path, binding.Local) {
|
|
165
|
+
if binding.TypeOnly && unit.ValueSpace && !unit.TypeSpace {
|
|
166
|
+
continue
|
|
167
|
+
}
|
|
168
|
+
matches := encodeTypeScriptIdentity(unit.Identity) == encodeTypeScriptIdentity(target)
|
|
169
|
+
if legacy {
|
|
170
|
+
matches = strings.Join(unit.Identity, ".") == strings.Join(target, ".")
|
|
171
|
+
}
|
|
172
|
+
if matches && !seen[unit.ID] {
|
|
173
|
+
seen[unit.ID] = true
|
|
174
|
+
result = append(result, unit)
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return result
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
func (resolver *typeScriptExportResolver) resolve(module string, name string) []reachedSymbol {
|
|
182
|
+
key := scopedTargetKey{path: module, target: name}
|
|
183
|
+
if cached, exists := resolver.bindings[key]; exists {
|
|
184
|
+
return cached
|
|
185
|
+
}
|
|
186
|
+
resolved := resolver.resolveFrom(key, map[scopedTargetKey]bool{})
|
|
187
|
+
resolver.bindings[key] = resolved
|
|
188
|
+
return resolved
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
func (resolver *typeScriptExportResolver) resolveFrom(key scopedTargetKey, visited map[scopedTargetKey]bool) []reachedSymbol {
|
|
192
|
+
if visited[key] {
|
|
193
|
+
return nil
|
|
194
|
+
}
|
|
195
|
+
if cached, exists := resolver.bindings[key]; exists {
|
|
196
|
+
return cached
|
|
197
|
+
}
|
|
198
|
+
module := resolver.modules[key.path]
|
|
199
|
+
if module == nil {
|
|
200
|
+
return nil
|
|
201
|
+
}
|
|
202
|
+
visited[key] = true
|
|
203
|
+
defer delete(visited, key)
|
|
204
|
+
result := []reachedSymbol{}
|
|
205
|
+
byBinding := map[scopedTargetKey]int{}
|
|
206
|
+
add := func(binding reachedSymbol, typeOnly bool) {
|
|
207
|
+
binding.TypeOnly = binding.TypeOnly || typeOnly
|
|
208
|
+
identity := scopedTargetKey{path: binding.Path, target: binding.Local}
|
|
209
|
+
if index, exists := byBinding[identity]; exists {
|
|
210
|
+
result[index].TypeOnly = result[index].TypeOnly && binding.TypeOnly
|
|
211
|
+
return
|
|
212
|
+
}
|
|
213
|
+
byBinding[identity] = len(result)
|
|
214
|
+
result = append(result, binding)
|
|
215
|
+
}
|
|
216
|
+
exports := module.named[key.target]
|
|
217
|
+
if len(exports) == 0 && key.target != "default" {
|
|
218
|
+
exports = module.stars
|
|
219
|
+
}
|
|
220
|
+
for _, export := range exports {
|
|
221
|
+
if export.Specifier == "" {
|
|
222
|
+
local := export.Public
|
|
223
|
+
if export.Identity != "" {
|
|
224
|
+
local = export.Identity
|
|
225
|
+
}
|
|
226
|
+
add(reachedSymbol{Path: key.path, Local: local}, export.TypeOnly)
|
|
227
|
+
continue
|
|
228
|
+
}
|
|
229
|
+
target := module.targets[export.Specifier]
|
|
230
|
+
if resolver.modules[target] == nil {
|
|
231
|
+
continue
|
|
232
|
+
}
|
|
233
|
+
if export.Namespace {
|
|
234
|
+
add(reachedSymbol{Path: target}, export.TypeOnly)
|
|
235
|
+
continue
|
|
236
|
+
}
|
|
237
|
+
imported := key.target
|
|
238
|
+
if export.Imported != "" {
|
|
239
|
+
imported = export.Imported
|
|
240
|
+
}
|
|
241
|
+
for _, binding := range resolver.resolveFrom(scopedTargetKey{path: target, target: imported}, visited) {
|
|
242
|
+
add(binding, export.TypeOnly)
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
return result
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
func (resolver *typeScriptExportResolver) traverse(entry string, prefix []string, visited map[string]bool, typeOnly bool) []reachedSymbol {
|
|
249
|
+
if visited[entry] {
|
|
250
|
+
return nil
|
|
251
|
+
}
|
|
252
|
+
module := resolver.modules[entry]
|
|
253
|
+
if module == nil {
|
|
254
|
+
return nil
|
|
255
|
+
}
|
|
256
|
+
visited[entry] = true
|
|
257
|
+
defer delete(visited, entry)
|
|
258
|
+
names := make([]string, 0, len(module.names))
|
|
259
|
+
for name := range module.names {
|
|
260
|
+
names = append(names, name)
|
|
261
|
+
}
|
|
262
|
+
sort.Strings(names)
|
|
263
|
+
reached := []reachedSymbol{}
|
|
264
|
+
for _, name := range names {
|
|
265
|
+
for _, binding := range resolver.resolve(entry, name) {
|
|
266
|
+
binding.Address = append(append([]string{}, prefix...), name)
|
|
267
|
+
binding.TypeOnly = binding.TypeOnly || typeOnly
|
|
268
|
+
reached = append(reached, binding)
|
|
269
|
+
if binding.Local == "" {
|
|
270
|
+
reached = append(reached, resolver.traverse(binding.Path, binding.Address, visited, binding.TypeOnly)...)
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
return reached
|
|
275
|
+
}
|