@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
package/native/loader.go
CHANGED
|
@@ -22,12 +22,17 @@ import (
|
|
|
22
22
|
// that symbol is precisely the one an obligation needs to name — an evidence
|
|
23
23
|
// graph exists to report the operation the frontend never called.
|
|
24
24
|
type typeScriptLoader struct {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
25
|
+
boundary *populationBase
|
|
26
|
+
root string
|
|
27
|
+
identityRoot string
|
|
28
|
+
program map[string]*artifactInventory
|
|
29
|
+
programIDs map[string]*artifactInventory
|
|
30
|
+
parsed map[string]*artifactInventory
|
|
31
|
+
resolved map[string]string
|
|
32
|
+
failures map[string]string
|
|
33
|
+
parseFailures map[string]string
|
|
34
|
+
identities map[string]typeScriptModuleIdentity
|
|
35
|
+
installs map[string]installedPackageLocation
|
|
31
36
|
}
|
|
32
37
|
|
|
33
38
|
func newTypeScriptLoader(
|
|
@@ -35,23 +40,40 @@ func newTypeScriptLoader(
|
|
|
35
40
|
program map[string]*artifactInventory,
|
|
36
41
|
) *typeScriptLoader {
|
|
37
42
|
loader := &typeScriptLoader{
|
|
38
|
-
root:
|
|
39
|
-
program:
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
43
|
+
root: strings.ReplaceAll(root, "\\", "/"),
|
|
44
|
+
program: map[string]*artifactInventory{},
|
|
45
|
+
programIDs: map[string]*artifactInventory{},
|
|
46
|
+
parsed: map[string]*artifactInventory{},
|
|
47
|
+
resolved: map[string]string{},
|
|
48
|
+
failures: map[string]string{},
|
|
49
|
+
parseFailures: map[string]string{},
|
|
50
|
+
identities: map[string]typeScriptModuleIdentity{},
|
|
51
|
+
installs: map[string]installedPackageLocation{},
|
|
52
|
+
}
|
|
53
|
+
loader.identityRoot, _ = physicalTypeScriptPath(filepath.FromSlash(loader.root))
|
|
54
|
+
keys := make([]string, 0, len(program))
|
|
55
|
+
for key := range program {
|
|
56
|
+
keys = append(keys, key)
|
|
57
|
+
}
|
|
58
|
+
sort.Strings(keys)
|
|
59
|
+
for _, key := range keys {
|
|
60
|
+
inventory := program[key]
|
|
46
61
|
if inventory == nil || inventory.Path == "" {
|
|
47
62
|
continue
|
|
48
63
|
}
|
|
49
64
|
location := loader.projectPath(inventory.Path)
|
|
65
|
+
identity, _ := loader.moduleIdentity(location)
|
|
66
|
+
if inventory.Address != identity && inventory.Source != nil {
|
|
67
|
+
inventory = typeScriptInventories.scan(typeScriptModuleAddress(location, identity), inventory.Source)
|
|
68
|
+
}
|
|
50
69
|
current := loader.program[location]
|
|
51
70
|
if current == nil ||
|
|
52
71
|
current.Address != current.Path && inventory.Address == inventory.Path {
|
|
53
72
|
loader.program[location] = inventory
|
|
54
73
|
}
|
|
74
|
+
if loader.programIDs[identity] == nil || location == identity {
|
|
75
|
+
loader.programIDs[identity] = inventory
|
|
76
|
+
}
|
|
55
77
|
}
|
|
56
78
|
return loader
|
|
57
79
|
}
|
|
@@ -60,24 +82,41 @@ func newTypeScriptLoader(
|
|
|
60
82
|
//
|
|
61
83
|
// The Program's copy wins when it exists so that a file under edit is read as
|
|
62
84
|
// the editor has it, not as the disk last saw it.
|
|
63
|
-
func (loader *typeScriptLoader) inventory(relative string) *artifactInventory {
|
|
85
|
+
func (loader *typeScriptLoader) inventory(relative string) (result *artifactInventory) {
|
|
64
86
|
if relative == "" {
|
|
65
87
|
return nil
|
|
66
88
|
}
|
|
67
89
|
relative = loader.projectPath(relative)
|
|
68
|
-
if
|
|
90
|
+
if !loader.withinBoundary(relative) {
|
|
91
|
+
loader.failures[relative] = "the re-export leaves the explicitly configured root"
|
|
92
|
+
return nil
|
|
93
|
+
}
|
|
94
|
+
defer func() {
|
|
95
|
+
if loader.boundary != nil && result != nil && result.Source != nil && len(result.Source.Diagnostics()) != 0 {
|
|
96
|
+
diagnostic := result.Source.Diagnostics()[0]
|
|
97
|
+
loader.failures[relative] = "TypeScript syntax error TS" + decimal(int(diagnostic.Code())) + " at line " + decimal(lineAt(result.Source.Text(), diagnostic.Pos()))
|
|
98
|
+
result = nil
|
|
99
|
+
}
|
|
100
|
+
}()
|
|
101
|
+
if inventory := loader.programInventory(relative); inventory != nil {
|
|
69
102
|
return inventory
|
|
70
103
|
}
|
|
71
104
|
if inventory, cached := loader.parsed[relative]; cached {
|
|
105
|
+
if inventory == nil && loader.parseFailures[relative] != "" {
|
|
106
|
+
loader.failures[relative] = loader.parseFailures[relative]
|
|
107
|
+
}
|
|
72
108
|
return inventory
|
|
73
109
|
}
|
|
74
110
|
loader.parsed[relative] = loader.parse(relative)
|
|
111
|
+
if loader.parsed[relative] == nil {
|
|
112
|
+
loader.parseFailures[relative] = loader.failures[relative]
|
|
113
|
+
}
|
|
75
114
|
return loader.parsed[relative]
|
|
76
115
|
}
|
|
77
116
|
|
|
78
117
|
func (loader *typeScriptLoader) parse(relative string) *artifactInventory {
|
|
79
118
|
relative = loader.projectPath(relative)
|
|
80
|
-
content, err := os.ReadFile(
|
|
119
|
+
content, err := os.ReadFile(resolveProjectPath(loader.root, relative))
|
|
81
120
|
if err != nil {
|
|
82
121
|
loader.failures[relative] = err.Error()
|
|
83
122
|
return nil
|
|
@@ -88,7 +127,7 @@ func (loader *typeScriptLoader) parse(relative string) *artifactInventory {
|
|
|
88
127
|
}
|
|
89
128
|
file := shimparser.ParseSourceFile(
|
|
90
129
|
shimast.SourceFileParseOptions{
|
|
91
|
-
FileName:
|
|
130
|
+
FileName: filepath.ToSlash(resolveProjectPath(loader.root, relative)),
|
|
92
131
|
},
|
|
93
132
|
string(content),
|
|
94
133
|
kind,
|
|
@@ -97,7 +136,8 @@ func (loader *typeScriptLoader) parse(relative string) *artifactInventory {
|
|
|
97
136
|
loader.failures[relative] = "the TypeScript parser returned no source file"
|
|
98
137
|
return nil
|
|
99
138
|
}
|
|
100
|
-
|
|
139
|
+
identity, _ := loader.moduleIdentity(relative)
|
|
140
|
+
return scanTypeScriptInventoryAt(typeScriptModuleAddress(relative, identity), file)
|
|
101
141
|
}
|
|
102
142
|
|
|
103
143
|
func (loader *typeScriptLoader) failure(relative string) string {
|
|
@@ -111,19 +151,34 @@ func (loader *typeScriptLoader) failure(relative string) string {
|
|
|
111
151
|
// without paying to scan it.
|
|
112
152
|
func (loader *typeScriptLoader) exists(relative string) bool {
|
|
113
153
|
relative = loader.projectPath(relative)
|
|
114
|
-
if loader.
|
|
154
|
+
if loader.programInventory(relative) != nil {
|
|
115
155
|
return true
|
|
116
156
|
}
|
|
117
157
|
return loader.existsOnDisk(relative)
|
|
118
158
|
}
|
|
119
159
|
|
|
160
|
+
// Authored module paths and physical identity keys are separate namespaces.
|
|
161
|
+
// Mixing them can mistake a sibling of a linked root for a different sibling
|
|
162
|
+
// of the physical project. Identity lookup still recovers its editor snapshot.
|
|
163
|
+
func (loader *typeScriptLoader) programInventory(module string) *artifactInventory {
|
|
164
|
+
if inventory := loader.program[module]; inventory != nil {
|
|
165
|
+
return inventory
|
|
166
|
+
}
|
|
167
|
+
if len(loader.programIDs) != 0 {
|
|
168
|
+
if identity, ok := loader.moduleIdentity(module); ok {
|
|
169
|
+
return loader.programIDs[identity]
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return nil
|
|
173
|
+
}
|
|
174
|
+
|
|
120
175
|
// existsOnDisk probes the filesystem for an already-normalized path.
|
|
121
176
|
//
|
|
122
177
|
// The probe is the expensive half of module resolution — one syscall per
|
|
123
178
|
// candidate, and a watch cycle resolves every re-export in the population — so
|
|
124
179
|
// callers that can answer from the Program should do that first.
|
|
125
180
|
func (loader *typeScriptLoader) existsOnDisk(relative string) bool {
|
|
126
|
-
info, err := os.Stat(
|
|
181
|
+
info, err := os.Stat(resolveProjectPath(loader.root, relative))
|
|
127
182
|
return err == nil && !info.IsDir()
|
|
128
183
|
}
|
|
129
184
|
|
|
@@ -135,6 +190,9 @@ func (loader *typeScriptLoader) resolve(from string, specifier string) string {
|
|
|
135
190
|
return cached
|
|
136
191
|
}
|
|
137
192
|
loader.resolved[key] = loader.resolveUncached(from, specifier)
|
|
193
|
+
if loader.boundary != nil && loader.resolved[key] == "" && loader.failures[from+" -> "+specifier] == "" {
|
|
194
|
+
loader.failures[from+" -> "+specifier] = "the exported module cannot be resolved"
|
|
195
|
+
}
|
|
138
196
|
return loader.resolved[key]
|
|
139
197
|
}
|
|
140
198
|
|
|
@@ -142,12 +200,19 @@ func (loader *typeScriptLoader) resolveUncached(
|
|
|
142
200
|
from string,
|
|
143
201
|
specifier string,
|
|
144
202
|
) string {
|
|
203
|
+
failure := from + " -> " + specifier
|
|
145
204
|
if strings.HasPrefix(specifier, "./") || strings.HasPrefix(specifier, "../") {
|
|
205
|
+
if loader.boundary != nil {
|
|
206
|
+
if canonical, ok := loader.moduleIdentity(from); ok {
|
|
207
|
+
from = filepath.ToSlash(resolveProjectPath(loader.identityRoot, canonical))
|
|
208
|
+
}
|
|
209
|
+
}
|
|
146
210
|
base := path.Clean(path.Join(path.Dir(from), specifier))
|
|
147
211
|
candidates := moduleCandidates(base)
|
|
148
212
|
normalized := make([]string, 0, len(candidates))
|
|
149
213
|
for _, candidate := range candidates {
|
|
150
|
-
|
|
214
|
+
module := loader.projectPath(candidate)
|
|
215
|
+
normalized = append(normalized, module)
|
|
151
216
|
}
|
|
152
217
|
// The Program answers first, and not only because it answers without a
|
|
153
218
|
// syscall. A project that emits beside its sources has both `x.js` and
|
|
@@ -155,12 +220,20 @@ func (loader *typeScriptLoader) resolveUncached(
|
|
|
155
220
|
// resolving an import to emitted JavaScript would read a module whose
|
|
156
221
|
// declarations the graph cannot address.
|
|
157
222
|
for _, candidate := range normalized {
|
|
158
|
-
if loader.
|
|
223
|
+
if loader.programInventory(candidate) != nil {
|
|
224
|
+
if !loader.withinBoundary(candidate) {
|
|
225
|
+
loader.failures[failure] = "the re-export leaves the explicitly configured root"
|
|
226
|
+
return ""
|
|
227
|
+
}
|
|
159
228
|
return candidate
|
|
160
229
|
}
|
|
161
230
|
}
|
|
162
231
|
for _, candidate := range normalized {
|
|
163
232
|
if loader.existsOnDisk(candidate) {
|
|
233
|
+
if !loader.withinBoundary(candidate) {
|
|
234
|
+
loader.failures[failure] = "the re-export leaves the explicitly configured root"
|
|
235
|
+
return ""
|
|
236
|
+
}
|
|
164
237
|
return candidate
|
|
165
238
|
}
|
|
166
239
|
}
|
|
@@ -169,9 +242,83 @@ func (loader *typeScriptLoader) resolveUncached(
|
|
|
169
242
|
if strings.HasPrefix(specifier, "/") {
|
|
170
243
|
return ""
|
|
171
244
|
}
|
|
245
|
+
if loader.boundary != nil {
|
|
246
|
+
loader.failures[failure] = "a package re-export is outside the explicitly configured root"
|
|
247
|
+
return ""
|
|
248
|
+
}
|
|
172
249
|
return loader.resolvePackage(specifier)
|
|
173
250
|
}
|
|
174
251
|
|
|
252
|
+
type typeScriptModuleIdentity struct {
|
|
253
|
+
Path string
|
|
254
|
+
Resolved bool
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Module identity follows directory links and file symlinks, while displayed
|
|
258
|
+
// paths and published module addresses retain their authored spellings.
|
|
259
|
+
// Cache once per evaluation so rooted populations share this filesystem work.
|
|
260
|
+
func (loader *typeScriptLoader) moduleIdentity(module string) (string, bool) {
|
|
261
|
+
module = loader.projectPath(module)
|
|
262
|
+
if cached, exists := loader.identities[module]; exists {
|
|
263
|
+
return cached.Path, cached.Resolved
|
|
264
|
+
}
|
|
265
|
+
absolute := resolveProjectPath(loader.root, module)
|
|
266
|
+
resolved, ok := physicalTypeScriptPath(absolute)
|
|
267
|
+
// Resolve both sides before taking a relative identity. Canonicalizing only
|
|
268
|
+
// the file makes a project junction or Windows 8.3 root leak the checkout's
|
|
269
|
+
// absolute location into every unit ID and invalidate unchanged reviews.
|
|
270
|
+
identity := projectRelativeDisplay(loader.identityRoot, resolved)
|
|
271
|
+
loader.identities[module] = typeScriptModuleIdentity{Path: identity, Resolved: ok}
|
|
272
|
+
return identity, ok
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
func physicalTypeScriptPath(absolute string) (string, bool) {
|
|
276
|
+
current := filepath.Clean(absolute)
|
|
277
|
+
// A directory link can introduce another link in an already-walked parent.
|
|
278
|
+
// Settle the complete path, with the same finite bound as directory chains.
|
|
279
|
+
for range 32 {
|
|
280
|
+
resolved, ok := resolveLinkedPath(current)
|
|
281
|
+
if !ok {
|
|
282
|
+
return resolved, false
|
|
283
|
+
}
|
|
284
|
+
resolved = expandTypeScriptPath(resolved)
|
|
285
|
+
if resolved == current {
|
|
286
|
+
return resolved, true
|
|
287
|
+
}
|
|
288
|
+
current = resolved
|
|
289
|
+
}
|
|
290
|
+
return current, false
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// An unsaved or deleted file still has an identity. Expand the deepest existing
|
|
294
|
+
// prefix so Windows short names do not reappear when EvalSymlinks cannot read
|
|
295
|
+
// the complete path, then restore the missing suffix without changing its case.
|
|
296
|
+
func expandTypeScriptPath(absolute string) string {
|
|
297
|
+
probe := absolute
|
|
298
|
+
suffix := []string{}
|
|
299
|
+
for {
|
|
300
|
+
if final, err := filepath.EvalSymlinks(probe); err == nil {
|
|
301
|
+
if !strings.EqualFold(final, probe) {
|
|
302
|
+
probe = final
|
|
303
|
+
}
|
|
304
|
+
for index := len(suffix) - 1; index >= 0; index-- {
|
|
305
|
+
probe = filepath.Join(probe, suffix[index])
|
|
306
|
+
}
|
|
307
|
+
return probe
|
|
308
|
+
}
|
|
309
|
+
parent := filepath.Dir(probe)
|
|
310
|
+
if parent == probe {
|
|
311
|
+
return absolute
|
|
312
|
+
}
|
|
313
|
+
suffix = append(suffix, filepath.Base(probe))
|
|
314
|
+
probe = parent
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
func typeScriptModuleAddress(display string, identity string) artifactAddress {
|
|
319
|
+
return artifactAddress{Base: populationBase{Default: true}, Relative: identity, Display: display, Key: identity}
|
|
320
|
+
}
|
|
321
|
+
|
|
175
322
|
// projectPath gives every Program source and module candidate one identity
|
|
176
323
|
// relative to the ttsc project. A rooted claim may name the same physical file
|
|
177
324
|
// through `../api`, while an import can arrive there through a different
|
package/native/model.go
CHANGED
|
@@ -93,9 +93,12 @@ type referenceSpec struct {
|
|
|
93
93
|
Type artifactKind
|
|
94
94
|
Policy referencePolicy
|
|
95
95
|
Root string
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
96
|
+
// Rooted preserves explicit disk-loading opt-in even when '.' normalizes
|
|
97
|
+
// to the empty spelling of the project root.
|
|
98
|
+
Rooted bool
|
|
99
|
+
Base populationBase
|
|
100
|
+
Files globSet
|
|
101
|
+
Source string
|
|
99
102
|
// Package moves the base that Files resolves against from the project to an
|
|
100
103
|
// installed package. With no globs it also becomes the selection itself: the
|
|
101
104
|
// package's declaration entry defines the population by reachability, while
|
|
@@ -333,6 +336,8 @@ func (declaration *evidenceDeclaration) valid() bool {
|
|
|
333
336
|
}
|
|
334
337
|
|
|
335
338
|
type artifactInventory struct {
|
|
339
|
+
// Source supplies declaration diagnostics and the active editor snapshot.
|
|
340
|
+
Source *shimast.SourceFile
|
|
336
341
|
// Address is the population-relative identity used for units and
|
|
337
342
|
// declarations. It differs from Path when a configured root moves the
|
|
338
343
|
// address space while a diagnostic keeps naming the file the way a reader
|
|
@@ -449,6 +454,8 @@ type claimState struct {
|
|
|
449
454
|
}
|
|
450
455
|
|
|
451
456
|
type referenceState struct {
|
|
457
|
+
// Code resolves finite accessor paths independently of population traversal.
|
|
458
|
+
Code *typeScriptExportResolver
|
|
452
459
|
Spec referenceSpec
|
|
453
460
|
Paths []string
|
|
454
461
|
Units []*evidenceUnit
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
package evidence
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"io/fs"
|
|
5
|
+
"os"
|
|
6
|
+
"path/filepath"
|
|
7
|
+
"sort"
|
|
8
|
+
"strings"
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
// A rooted reference selects entries from disk and follows their exports only
|
|
12
|
+
// inside its declared root. Ordinary imports do not expand the population.
|
|
13
|
+
func materializeRootedTypeScriptReference(claim claimSpec, reference referenceSpec, loader *typeScriptLoader) (referenceState, []string) {
|
|
14
|
+
state := referenceState{Spec: reference, Healthy: false, UnitsByScope: map[string][]*evidenceUnit{}}
|
|
15
|
+
context := claimLabel(claim) + " " + referenceLabel(reference)
|
|
16
|
+
base := reference.Base
|
|
17
|
+
if info, err := os.Stat(base.Absolute); err != nil || !info.IsDir() {
|
|
18
|
+
cause := "the path names a file"
|
|
19
|
+
if err != nil {
|
|
20
|
+
cause = causeText(err)
|
|
21
|
+
}
|
|
22
|
+
return state, []string{context + " could not read TypeScript reference root '" + populationRootLabel(base) + "': " + cause + ". Select an existing readable directory containing the reference files."}
|
|
23
|
+
}
|
|
24
|
+
from, resolved := resolvedBaseDirectory(base)
|
|
25
|
+
if !resolved {
|
|
26
|
+
return state, []string{unresolvedBaseProblem(base, artifactTypeScript)}
|
|
27
|
+
}
|
|
28
|
+
from, resolved = physicalTypeScriptPath(from)
|
|
29
|
+
if !resolved {
|
|
30
|
+
return state, []string{unresolvedBaseProblem(base, artifactTypeScript)}
|
|
31
|
+
}
|
|
32
|
+
entries := map[string]bool{}
|
|
33
|
+
problems := []string{}
|
|
34
|
+
err := filepath.WalkDir(from, func(current string, entry fs.DirEntry, walkErr error) error {
|
|
35
|
+
if walkErr != nil {
|
|
36
|
+
return walkErr
|
|
37
|
+
}
|
|
38
|
+
relative, err := filepath.Rel(from, current)
|
|
39
|
+
if err != nil {
|
|
40
|
+
return err
|
|
41
|
+
}
|
|
42
|
+
relative = filepath.ToSlash(relative)
|
|
43
|
+
if entry.IsDir() {
|
|
44
|
+
if relative != "." && (entry.Name() == "node_modules" || !reference.Files.couldMatchDescendant(relative)) {
|
|
45
|
+
return filepath.SkipDir
|
|
46
|
+
}
|
|
47
|
+
return nil
|
|
48
|
+
}
|
|
49
|
+
if isTypeScriptPath(relative) && reference.Files.matches(relative) {
|
|
50
|
+
entries[loader.projectPath(base.display(relative))] = true
|
|
51
|
+
}
|
|
52
|
+
return nil
|
|
53
|
+
})
|
|
54
|
+
if err != nil {
|
|
55
|
+
problems = append(problems, context+" could not list TypeScript root '"+populationRootLabel(base)+"': "+causeText(err)+". Restore filesystem access before evaluating coverage.")
|
|
56
|
+
}
|
|
57
|
+
// An open Program snapshot can precede its first save. It is authoritative
|
|
58
|
+
// on the same terms as an edited file that already exists on disk.
|
|
59
|
+
for module := range loader.program {
|
|
60
|
+
identity, resolved := loader.moduleIdentity(module)
|
|
61
|
+
if !resolved {
|
|
62
|
+
continue
|
|
63
|
+
}
|
|
64
|
+
relative, ok := relativeProjectPath(from, resolveProjectPath(loader.identityRoot, identity))
|
|
65
|
+
if ok && reference.Files.matches(relative) {
|
|
66
|
+
entries[loader.projectPath(base.display(relative))] = true
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
for entry := range entries {
|
|
70
|
+
state.Paths = append(state.Paths, entry)
|
|
71
|
+
}
|
|
72
|
+
sort.Strings(state.Paths)
|
|
73
|
+
confined := *loader
|
|
74
|
+
confined.boundary = &base
|
|
75
|
+
confined.resolved = map[string]string{}
|
|
76
|
+
confined.failures = map[string]string{}
|
|
77
|
+
population := materializeEntryUnits(&confined, state.Paths, reference.Symbols)
|
|
78
|
+
state.Units, state.Hidden, state.Published = population.Units, population.Hidden, population.Published
|
|
79
|
+
state.Code = population.Code
|
|
80
|
+
applyTraversedScopes(&state, population.Reached)
|
|
81
|
+
failed := []string{}
|
|
82
|
+
for module := range confined.failures {
|
|
83
|
+
failed = append(failed, module)
|
|
84
|
+
}
|
|
85
|
+
sort.Strings(failed)
|
|
86
|
+
for _, module := range failed {
|
|
87
|
+
problems = append(problems, context+" could not load TypeScript module '"+module+"': "+confined.failures[module]+". Repair the export or explicitly select a root containing its implementation.")
|
|
88
|
+
}
|
|
89
|
+
state.Healthy = len(problems) == 0
|
|
90
|
+
if !state.Healthy {
|
|
91
|
+
return state, problems
|
|
92
|
+
}
|
|
93
|
+
if len(state.Paths) == 0 {
|
|
94
|
+
return state, []string{context + " matched no typescript files for " + describePopulation(base, reference.Files) + ". Correct the configured root and files."}
|
|
95
|
+
}
|
|
96
|
+
if len(state.Units) == 0 {
|
|
97
|
+
return state, []string{context + " found no selected evidence units (" + reference.Symbols.names() + "). Select symbol kinds present in its exported declarations."}
|
|
98
|
+
}
|
|
99
|
+
return state, nil
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
func (loader *typeScriptLoader) withinBoundary(module string) bool {
|
|
103
|
+
if loader.boundary == nil {
|
|
104
|
+
return true
|
|
105
|
+
}
|
|
106
|
+
base, baseOK := loader.moduleIdentity(loader.boundary.Absolute)
|
|
107
|
+
source, sourceOK := loader.moduleIdentity(module)
|
|
108
|
+
if !baseOK || !sourceOK {
|
|
109
|
+
return false
|
|
110
|
+
}
|
|
111
|
+
relative, ok := relativeProjectPath(resolveProjectPath(loader.identityRoot, base), resolveProjectPath(loader.identityRoot, source))
|
|
112
|
+
if !ok || strings.Contains("/"+relative+"/", "/node_modules/") {
|
|
113
|
+
return false
|
|
114
|
+
}
|
|
115
|
+
return true
|
|
116
|
+
}
|
package/native/typescript.go
CHANGED
|
@@ -71,11 +71,8 @@ func extendTypeScriptInventories(
|
|
|
71
71
|
// three kinds converge: health alone now decides that the claim stays active,
|
|
72
72
|
// and the population it names is the one the author has to repair.
|
|
73
73
|
//
|
|
74
|
-
//
|
|
75
|
-
//
|
|
76
|
-
// refused a `root` outright, so every reference base of this kind is the default
|
|
77
|
-
// one, which this pass leaves alone: `baseDirectoryProblem` returns on it, and
|
|
78
|
-
// the resolver question below is asked only of a root someone declared.
|
|
74
|
+
// Check passes claim populations here. Rooted references validate their disk
|
|
75
|
+
// directories after claim activation in materializeRootedTypeScriptReference.
|
|
79
76
|
func typeScriptBaseProblems(
|
|
80
77
|
config graphConfig,
|
|
81
78
|
inventories map[string]*artifactInventory,
|
|
@@ -245,6 +242,7 @@ func scanTypeScriptInventoryAt(
|
|
|
245
242
|
file *shimast.SourceFile,
|
|
246
243
|
) *artifactInventory {
|
|
247
244
|
inventory := &artifactInventory{
|
|
245
|
+
Source: file,
|
|
248
246
|
Address: address.Key,
|
|
249
247
|
Path: address.Display,
|
|
250
248
|
Type: artifactTypeScript,
|
|
@@ -528,7 +526,7 @@ func collectTypeScriptStatements(
|
|
|
528
526
|
if typeOnlyProjection {
|
|
529
527
|
continue
|
|
530
528
|
}
|
|
531
|
-
name :=
|
|
529
|
+
name := typeScriptDeclarationName(statement)
|
|
532
530
|
if name == "" {
|
|
533
531
|
continue
|
|
534
532
|
}
|
|
@@ -583,7 +581,7 @@ func collectTypeScriptStatements(
|
|
|
583
581
|
addTypeScriptHost(supportedHosts, statement, symbol)
|
|
584
582
|
}
|
|
585
583
|
case shimast.KindClassDeclaration:
|
|
586
|
-
name :=
|
|
584
|
+
name := typeScriptDeclarationName(statement)
|
|
587
585
|
if name == "" {
|
|
588
586
|
continue
|
|
589
587
|
}
|
|
@@ -1377,7 +1375,7 @@ func collectHiddenDeclarationNames(
|
|
|
1377
1375
|
if statement == nil {
|
|
1378
1376
|
continue
|
|
1379
1377
|
}
|
|
1380
|
-
name :=
|
|
1378
|
+
name := typeScriptDeclarationName(statement)
|
|
1381
1379
|
if name == "" {
|
|
1382
1380
|
continue
|
|
1383
1381
|
}
|
|
@@ -1701,6 +1699,12 @@ func walkTypeScriptNode(node *shimast.Node, visit func(*shimast.Node)) {
|
|
|
1701
1699
|
type exportedName struct {
|
|
1702
1700
|
Public string
|
|
1703
1701
|
TypeOnly bool
|
|
1702
|
+
// IdentityOnly materializes a default's local declaration without
|
|
1703
|
+
// publishing its binding as a named export.
|
|
1704
|
+
IdentityOnly bool
|
|
1705
|
+
// ValueIdentity retains the value side needed by a default alias while
|
|
1706
|
+
// TypeOnly still describes this named export's public restriction.
|
|
1707
|
+
ValueIdentity bool
|
|
1704
1708
|
}
|
|
1705
1709
|
|
|
1706
1710
|
func collectLocalExportNames(
|
|
@@ -1748,6 +1752,21 @@ func collectLocalExportNames(
|
|
|
1748
1752
|
})
|
|
1749
1753
|
}
|
|
1750
1754
|
}
|
|
1755
|
+
for local, binding := range collectDefaultExportBindings(statements) {
|
|
1756
|
+
if len(exports[local]) == 0 {
|
|
1757
|
+
binding.IdentityOnly = true
|
|
1758
|
+
exports[local] = []exportedName{binding}
|
|
1759
|
+
} else if !binding.TypeOnly {
|
|
1760
|
+
selected := 0
|
|
1761
|
+
for index, name := range exports[local] {
|
|
1762
|
+
current := exports[local][selected]
|
|
1763
|
+
if current.TypeOnly && !name.TypeOnly || current.TypeOnly == name.TypeOnly && name.Public < current.Public {
|
|
1764
|
+
selected = index
|
|
1765
|
+
}
|
|
1766
|
+
}
|
|
1767
|
+
exports[local][selected].ValueIdentity = true
|
|
1768
|
+
}
|
|
1769
|
+
}
|
|
1751
1770
|
return exports
|
|
1752
1771
|
}
|
|
1753
1772
|
|
|
@@ -1787,6 +1806,9 @@ func publicTypeScriptExports(
|
|
|
1787
1806
|
names[local] = exportedName{Public: local}
|
|
1788
1807
|
}
|
|
1789
1808
|
for _, exported := range exports[local] {
|
|
1809
|
+
if exported.ValueIdentity {
|
|
1810
|
+
exported.TypeOnly = false
|
|
1811
|
+
}
|
|
1790
1812
|
if exported.TypeOnly && !allowTypeOnly {
|
|
1791
1813
|
continue
|
|
1792
1814
|
}
|
|
@@ -1820,9 +1842,6 @@ func declarationName(node *shimast.Node) string {
|
|
|
1820
1842
|
shimast.KindStringLiteral,
|
|
1821
1843
|
shimast.KindNumericLiteral:
|
|
1822
1844
|
name := node.Text()
|
|
1823
|
-
if containsWhitespace(name) {
|
|
1824
|
-
return ""
|
|
1825
|
-
}
|
|
1826
1845
|
return name
|
|
1827
1846
|
default:
|
|
1828
1847
|
return ""
|
|
@@ -29,11 +29,12 @@ import (
|
|
|
29
29
|
// depend on the hit.
|
|
30
30
|
var typeScriptInventories = newTypeScriptInventoryCache()
|
|
31
31
|
|
|
32
|
-
// typeScriptInventoryKey identifies one scan.
|
|
33
|
-
//
|
|
34
|
-
//
|
|
32
|
+
// typeScriptInventoryKey identifies one scan. Both the unit address and display
|
|
33
|
+
// path belong to its result. Root aliases can share unit IDs while a file has
|
|
34
|
+
// different project-relative paths; reusing the old path can read another file.
|
|
35
35
|
type typeScriptInventoryKey struct {
|
|
36
36
|
address string
|
|
37
|
+
display string
|
|
37
38
|
file *shimast.SourceFile
|
|
38
39
|
}
|
|
39
40
|
|
|
@@ -69,7 +70,7 @@ func (cache *typeScriptInventoryCache) scan(
|
|
|
69
70
|
if cache == nil || file == nil {
|
|
70
71
|
return scanTypeScriptInventoryAt(address, file)
|
|
71
72
|
}
|
|
72
|
-
key := typeScriptInventoryKey{address: address.Key, file: file}
|
|
73
|
+
key := typeScriptInventoryKey{address: address.Key, display: address.Display, file: file}
|
|
73
74
|
cache.mutex.Lock()
|
|
74
75
|
if inventory, hit := cache.live[key]; hit {
|
|
75
76
|
cache.mutex.Unlock()
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ttsc/evidence",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.30.1",
|
|
4
4
|
"description": "Evidence-graph lint contributor for @ttsc/lint: declare provenance with a JSDoc @evidence tag and fail the build when it is missing or dangling.",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"exports": {
|
|
@@ -48,13 +48,13 @@
|
|
|
48
48
|
"@types/node": "^25.3.0",
|
|
49
49
|
"rimraf": "^6.1.2",
|
|
50
50
|
"typescript": "^7.0.2",
|
|
51
|
-
"ttsc": "0.
|
|
52
|
-
"
|
|
51
|
+
"@ttsc/lint": "0.30.1",
|
|
52
|
+
"ttsc": "0.30.1"
|
|
53
53
|
},
|
|
54
54
|
"dependencies": {
|
|
55
55
|
"@prisma/prisma-schema-wasm": "7.10.0-2.d0368e25c88e68caa4d283b5f2d88d1b76ed9356",
|
|
56
|
-
"@typia/interface": "14.0.
|
|
57
|
-
"@typia/utils": "14.0.
|
|
56
|
+
"@typia/interface": "^14.0.6",
|
|
57
|
+
"@typia/utils": "^14.0.6",
|
|
58
58
|
"yaml": "^2.9.0"
|
|
59
59
|
},
|
|
60
60
|
"homepage": "https://ttsc.dev",
|