@ttsc/evidence 0.30.4 → 0.31.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -6
- package/lib/index.d.ts +2 -3
- package/lib/index.js +2 -3
- package/lib/internal/canonicalDigest.js +12 -6
- package/lib/internal/loadPrismaModels.js +52 -7
- package/lib/internal/loadSwaggerOperations.js +253 -59
- package/lib/internal/normalizeSwaggerDocument.js +320 -2
- package/lib/structures/ITtscEvidenceDocumentedConfig.d.ts +16 -8
- package/lib/structures/ITtscEvidenceGraphClaim.d.ts +6 -0
- package/lib/structures/ITtscEvidenceGraphClaimBase.d.ts +8 -2
- package/lib/structures/ITtscEvidenceGraphConfig.d.ts +6 -0
- package/lib/structures/ITtscEvidenceGraphMarkdownClaim.d.ts +7 -1
- package/lib/structures/ITtscEvidenceGraphMarkdownReference.d.ts +7 -1
- package/lib/structures/ITtscEvidenceGraphPrismaClaim.d.ts +7 -1
- package/lib/structures/ITtscEvidenceGraphPrismaReference.d.ts +7 -1
- package/lib/structures/ITtscEvidenceGraphReference.d.ts +10 -4
- package/lib/structures/ITtscEvidenceGraphReferenceBase.d.ts +6 -0
- package/lib/structures/ITtscEvidenceGraphSwaggerReference.d.ts +6 -0
- package/lib/structures/ITtscEvidenceGraphTypeScriptClaim.d.ts +7 -1
- package/lib/structures/ITtscEvidenceGraphTypeScriptReference.d.ts +11 -3
- package/lib/structures/ITtscEvidenceRules.d.ts +93 -0
- package/lib/structures/TtscEvidenceGraphMarkdownSymbol.d.ts +39 -0
- package/lib/{typings → structures}/TtscEvidenceGraphPrismaSymbol.d.ts +6 -0
- package/lib/{typings → structures}/TtscEvidenceGraphTypeScriptSymbol.d.ts +6 -0
- package/lib/structures/index.d.ts +4 -0
- package/lib/structures/index.js +4 -0
- package/native/base.go +25 -23
- package/native/bridge_trace.go +348 -0
- package/native/comments.go +1 -1
- package/native/config.go +6 -3
- package/native/declaration.go +17 -43
- package/native/documentation_regions.go +214 -0
- package/native/documented.go +5 -4
- package/native/exports.go +1 -1
- package/native/fingerprint.go +2 -8
- package/native/glob.go +4 -1
- package/native/graph.go +59 -23
- package/native/graph_nodes.go +2 -1
- package/native/hints.go +22 -18
- package/native/imports.go +7 -6
- package/native/input_reader.go +83 -0
- package/native/inputs.go +13 -12
- package/native/loader.go +36 -24
- package/native/markdown.go +587 -177
- package/native/markdown_capture.go +223 -0
- package/native/model.go +2 -1
- package/native/prisma.go +110 -25
- package/native/prisma_cache.go +12 -8
- package/native/prisma_locator.go +70 -33
- package/native/review.go +8 -7
- package/native/rooted_typescript.go +3 -4
- package/native/swagger.go +123 -15
- package/native/swagger_cache.go +16 -12
- package/native/todo.go +12 -0
- package/native/typescript.go +129 -25
- package/package.json +9 -7
- package/lib/typings/ITtscLintContributorRules.d.ts +0 -57
- package/lib/typings/ITtscLintRuleOptionsMap.d.ts +0 -20
- package/lib/typings/ITtscLintRuleOptionsMap.js +0 -2
- package/lib/typings/TtscEvidenceGraphMarkdownSymbol.d.ts +0 -32
- package/lib/typings/index.d.ts +0 -5
- package/lib/typings/index.js +0 -21
- /package/lib/{typings/ITtscLintContributorRules.js → structures/ITtscEvidenceRules.js} +0 -0
- /package/lib/{typings → structures}/TtscEvidenceGraphMarkdownSymbol.js +0 -0
- /package/lib/{typings → structures}/TtscEvidenceGraphPrismaSymbol.js +0 -0
- /package/lib/{typings → structures}/TtscEvidenceGraphTypeScriptSymbol.js +0 -0
package/native/hints.go
CHANGED
|
@@ -10,17 +10,21 @@ import (
|
|
|
10
10
|
// graphCorpus is what a passing Check publishes for Hints to project.
|
|
11
11
|
//
|
|
12
12
|
// It carries the inventories Check already built rather than the paths to
|
|
13
|
-
// rebuild them from. The graph loads Markdown and Swagger to do its own
|
|
14
|
-
// handing that result forward costs a build nothing; reloading inside
|
|
15
|
-
// would read every source a second time to answer a question the rule had
|
|
16
|
-
// finished answering.
|
|
13
|
+
// rebuild them from. The graph loads Markdown, Prisma and Swagger to do its own
|
|
14
|
+
// job, so handing that result forward costs a build nothing; reloading inside
|
|
15
|
+
// Hints would read every source a second time to answer a question the rule had
|
|
16
|
+
// just finished answering.
|
|
17
17
|
//
|
|
18
18
|
// What the editor path does pay is Check itself. A hints request runs a fresh
|
|
19
|
-
// project cycle (`linthost
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
//
|
|
23
|
-
// is reported by Check, which fails the rule, which withdraws the corpus.
|
|
19
|
+
// project cycle (`linthost.collectProjectHints` over `runProjectCycle`), so
|
|
20
|
+
// every source the graph reads is read again there. Local Swagger documents are
|
|
21
|
+
// validated by content, and successful HTTP(S) Swagger documents are reused for
|
|
22
|
+
// the process lifetime while failed fetches are retried. A source that fails to
|
|
23
|
+
// load is reported by Check, which fails the rule, which withdraws the corpus.
|
|
24
|
+
// Markdown activation and evaluation share the first observation of each
|
|
25
|
+
// addressed document within that Check. Hints reflects that captured content;
|
|
26
|
+
// an edit after consumption is observed by a later Check, not by projecting
|
|
27
|
+
// this corpus. Reusable downstream delivery still validates consumed proofs.
|
|
24
28
|
type graphCorpus struct {
|
|
25
29
|
Config graphConfig
|
|
26
30
|
Markdown map[string]*artifactInventory
|
|
@@ -70,14 +74,13 @@ func (gate *programDiagnosticGate) first(key string) bool {
|
|
|
70
74
|
// here can be computed per keystroke.
|
|
71
75
|
//
|
|
72
76
|
// The corpus is available only while the graph passes, and that is a host gate
|
|
73
|
-
// rather than a choice made here. `linthost
|
|
74
|
-
// snapshot is not `ProjectRulePassed` or whose state is nil, and
|
|
75
|
-
// `projectReporter.
|
|
76
|
-
//
|
|
77
|
-
//
|
|
78
|
-
//
|
|
79
|
-
//
|
|
80
|
-
// reported against.
|
|
77
|
+
// rather than a choice made here. `linthost.collectProjectHints` skips a rule
|
|
78
|
+
// whose snapshot is not `ProjectRulePassed` or whose state is nil, and
|
|
79
|
+
// `projectReporter.ReportSeverity` marks the rule failed on every finding that
|
|
80
|
+
// is not severity off. So the cycle that reports an unmet obligation is the
|
|
81
|
+
// cycle that withdraws the completions, which is the cycle an author is most
|
|
82
|
+
// likely to be writing a citation in. Nothing in this package can widen that; it
|
|
83
|
+
// widens upstream, by letting a project rule publish state it reported against.
|
|
81
84
|
func (graphRule) Hints(ctx *rule.HintContext) []rule.Hint {
|
|
82
85
|
if ctx == nil {
|
|
83
86
|
return nil
|
|
@@ -175,7 +178,8 @@ func selectsTypeScriptReference(config graphConfig, exclusion bool) bool {
|
|
|
175
178
|
// author cannot supply from memory. A heading's generated anchor is neither
|
|
176
179
|
// visible in the project tree nor guessable from its text, so selected headings
|
|
177
180
|
// come first; a Markdown file path is typed as easily as it is read, so file
|
|
178
|
-
// targets follow; Swagger operations come
|
|
181
|
+
// targets follow; Swagger operations come next, being both few and mechanical,
|
|
182
|
+
// and Prisma models and their fields come last.
|
|
179
183
|
//
|
|
180
184
|
// Order therefore serves the author who is still browsing, and the filter
|
|
181
185
|
// serves the one who is not. No hint sets Label, so the editor lists and
|
package/native/imports.go
CHANGED
|
@@ -2,7 +2,6 @@ package evidence
|
|
|
2
2
|
|
|
3
3
|
import (
|
|
4
4
|
"encoding/json"
|
|
5
|
-
"os"
|
|
6
5
|
"path"
|
|
7
6
|
"strings"
|
|
8
7
|
|
|
@@ -109,7 +108,8 @@ func stringLiteralText(node *shimast.Node) string {
|
|
|
109
108
|
return node.Text()
|
|
110
109
|
}
|
|
111
110
|
|
|
112
|
-
// typeScriptModuleExtensions are
|
|
111
|
+
// typeScriptModuleExtensions are the suffixes an extensionless specifier may
|
|
112
|
+
// take, a source file before the declaration file that would describe it.
|
|
113
113
|
var typeScriptModuleExtensions = []string{
|
|
114
114
|
".ts",
|
|
115
115
|
".tsx",
|
|
@@ -120,8 +120,6 @@ var typeScriptModuleExtensions = []string{
|
|
|
120
120
|
".d.cts",
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
-
// splitPackageSpecifier separates a package name from a deep-import subpath,
|
|
124
|
-
// keeping the leading segment of a scoped name attached to its scope.
|
|
125
123
|
// moduleCandidates lists the files a specifier may denote, most specific first.
|
|
126
124
|
//
|
|
127
125
|
// The `.js` rewrite is not a convenience: under `nodenext` a TypeScript source
|
|
@@ -154,6 +152,8 @@ func moduleCandidates(base string) []string {
|
|
|
154
152
|
return candidates
|
|
155
153
|
}
|
|
156
154
|
|
|
155
|
+
// splitPackageSpecifier separates a package name from a deep-import subpath,
|
|
156
|
+
// keeping the leading segment of a scoped name attached to its scope.
|
|
157
157
|
func splitPackageSpecifier(specifier string) (string, string) {
|
|
158
158
|
segments := strings.Split(specifier, "/")
|
|
159
159
|
if len(segments) == 0 || segments[0] == "" {
|
|
@@ -174,8 +174,9 @@ func splitPackageSpecifier(specifier string) (string, string) {
|
|
|
174
174
|
return name, subpath
|
|
175
175
|
}
|
|
176
176
|
|
|
177
|
-
func readPackageManifest(root string, relative string) map[string]json.RawMessage {
|
|
178
|
-
|
|
177
|
+
func readPackageManifest(root string, relative string, readers ...evidenceInputReader) map[string]json.RawMessage {
|
|
178
|
+
inputs := inputReader(readers)
|
|
179
|
+
content, err := inputs.ReadFile(path.Join(strings.ReplaceAll(root, "\\", "/"), relative))
|
|
179
180
|
if err != nil {
|
|
180
181
|
return nil
|
|
181
182
|
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
package evidence
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"io/fs"
|
|
5
|
+
"os"
|
|
6
|
+
"path/filepath"
|
|
7
|
+
|
|
8
|
+
"github.com/samchon/ttsc/packages/lint/rule"
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
// evidenceInputReader delegates only the operations an inventory or locator
|
|
12
|
+
// actually performs. A nil host reader preserves the original OS operations
|
|
13
|
+
// for manual contexts and native unit callers. It never preloads a population.
|
|
14
|
+
type evidenceInputReader struct{ host rule.ProjectInputReader }
|
|
15
|
+
|
|
16
|
+
// ReadFile routes actual raw consumption through the generation reader when
|
|
17
|
+
// supplied. Nil manual contexts keep os.ReadFile's bytes and error semantics.
|
|
18
|
+
func (r evidenceInputReader) ReadFile(name string) ([]byte, error) {
|
|
19
|
+
if r.host != nil {
|
|
20
|
+
return r.host.ReadFile(name)
|
|
21
|
+
}
|
|
22
|
+
return os.ReadFile(name)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Stat delegates target metadata without changing native link following.
|
|
26
|
+
func (r evidenceInputReader) Stat(name string) (os.FileInfo, error) {
|
|
27
|
+
if r.host != nil {
|
|
28
|
+
return r.host.Stat(name)
|
|
29
|
+
}
|
|
30
|
+
return os.Stat(name)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Lstat keeps entry metadata distinct from the followed target; an observing
|
|
34
|
+
// host owns withdrawal when its protocol cannot represent that native entry.
|
|
35
|
+
func (r evidenceInputReader) Lstat(name string) (os.FileInfo, error) {
|
|
36
|
+
if r.host != nil {
|
|
37
|
+
return r.host.Lstat(name)
|
|
38
|
+
}
|
|
39
|
+
return os.Lstat(name)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Readlink preserves native target text and errors instead of resolving it.
|
|
43
|
+
func (r evidenceInputReader) Readlink(name string) (string, error) {
|
|
44
|
+
if r.host != nil {
|
|
45
|
+
return r.host.Readlink(name)
|
|
46
|
+
}
|
|
47
|
+
return os.Readlink(name)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// WalkDir delegates callback ordering and SkipDir/SkipAll behavior. Neither
|
|
51
|
+
// branch preloads unvisited populations or retains a separate walk cache.
|
|
52
|
+
func (r evidenceInputReader) WalkDir(name string, visit fs.WalkDirFunc) error {
|
|
53
|
+
if r.host != nil {
|
|
54
|
+
return r.host.WalkDir(name, visit)
|
|
55
|
+
}
|
|
56
|
+
return filepath.WalkDir(name, visit)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Unavailable propagates unsupported external consumption to the observing
|
|
60
|
+
// host; a nil manual context has no reusable input authority to withdraw.
|
|
61
|
+
func (r evidenceInputReader) Unavailable() {
|
|
62
|
+
if r.host != nil {
|
|
63
|
+
r.host.Unavailable()
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// inputReader preserves optional variadic-call compatibility: only the first
|
|
68
|
+
// supplied reader belongs to this operation, otherwise native calls remain.
|
|
69
|
+
func inputReader(readers []evidenceInputReader) evidenceInputReader {
|
|
70
|
+
if len(readers) != 0 {
|
|
71
|
+
return readers[0]
|
|
72
|
+
}
|
|
73
|
+
return evidenceInputReader{}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// EvalSymlinks preserves native resolution and returned spelling; observation
|
|
77
|
+
// coordinates and unresolved-proof refusal belong to the supplied host.
|
|
78
|
+
func (r evidenceInputReader) EvalSymlinks(name string) (string, error) {
|
|
79
|
+
if r.host != nil {
|
|
80
|
+
return r.host.EvalSymlinks(name)
|
|
81
|
+
}
|
|
82
|
+
return filepath.EvalSymlinks(name)
|
|
83
|
+
}
|
package/native/inputs.go
CHANGED
|
@@ -9,12 +9,11 @@ import (
|
|
|
9
9
|
// ProjectInputs declares the files this rule reads from outside the Program.
|
|
10
10
|
//
|
|
11
11
|
// Markdown, Prisma, and Swagger evidence never enters the TypeScript Program,
|
|
12
|
-
// so
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
// schema saw the citation that had just gone stale keep reporting green.
|
|
12
|
+
// so without this declaration the host cannot learn that the graph depends on
|
|
13
|
+
// them. Editing code drives a project cycle that reloads the documents along
|
|
14
|
+
// with it, but editing only a spec section or a schema would raise no event the
|
|
15
|
+
// host associates with this rule, and the citation that had just gone stale
|
|
16
|
+
// would keep reporting green.
|
|
18
17
|
//
|
|
19
18
|
// The host calls this after resolving options and project identity but before
|
|
20
19
|
// loading a Program, so nothing here may read `ctx.Sources`, and nothing here
|
|
@@ -84,8 +83,8 @@ func graphProjectInputs(config graphConfig) []rule.ProjectInput {
|
|
|
84
83
|
//
|
|
85
84
|
// The root is prefixed as the author wrote it rather than resolved here. The
|
|
86
85
|
// host anchors a relative pattern against the same physical project root this
|
|
87
|
-
// rule uses (`linthost/project_inputs.go
|
|
88
|
-
// ascends or is absolute — so `../../docs/**/*.md` and `C:/shared/docs/**/*.md`
|
|
86
|
+
// rule uses (`normalizeProjectInput` in `linthost/project_inputs.go`), and it
|
|
87
|
+
// accepts one that ascends or is absolute — so `../../docs/**/*.md` and `C:/shared/docs/**/*.md`
|
|
89
88
|
// both arrive at the directory `Check` will read. Resolving it here would
|
|
90
89
|
// duplicate that arithmetic and make this contract depend on a project identity
|
|
91
90
|
// it is supposed to be able to answer without.
|
|
@@ -110,14 +109,16 @@ func globInputs(root string, globs globSet) []rule.ProjectInput {
|
|
|
110
109
|
// localSwaggerInputs publishes a Swagger reference only when it names a file.
|
|
111
110
|
//
|
|
112
111
|
// Withholding the URL form is not a shortcut. The host rejects a remote pattern
|
|
113
|
-
// outright (`linthost/project_inputs.go
|
|
114
|
-
// discards the whole snapshot for every project rule in the run
|
|
115
|
-
// (`
|
|
112
|
+
// outright (`normalizeProjectInput` in `linthost/project_inputs.go`), and one
|
|
113
|
+
// rejected input discards the whole snapshot for every project rule in the run
|
|
114
|
+
// (`collectProjectInputs` returns the joined error and an empty snapshot) — so declaring a URL here would take
|
|
116
115
|
// this graph's Markdown globs down with it and restore the exact blindness the
|
|
117
116
|
// contract removes.
|
|
118
117
|
//
|
|
119
118
|
// The boundary is also real rather than imposed: a URL has no filesystem event
|
|
120
|
-
// to observe
|
|
119
|
+
// to observe. The loader reuses a successful fetch until its process ends and
|
|
120
|
+
// retries failed fetches on later evaluations; restarting the session refreshes
|
|
121
|
+
// a remote document that changed while the process remained alive.
|
|
121
122
|
//
|
|
122
123
|
// A local path is published exactly as it normalized, including one that
|
|
123
124
|
// ascends out of the project or names an absolute location. The host resolves
|
package/native/loader.go
CHANGED
|
@@ -3,7 +3,6 @@ package evidence
|
|
|
3
3
|
import (
|
|
4
4
|
"encoding/json"
|
|
5
5
|
"io/fs"
|
|
6
|
-
"os"
|
|
7
6
|
"path"
|
|
8
7
|
"path/filepath"
|
|
9
8
|
"sort"
|
|
@@ -22,6 +21,7 @@ import (
|
|
|
22
21
|
// that symbol is precisely the one an obligation needs to name — an evidence
|
|
23
22
|
// graph exists to report the operation the frontend never called.
|
|
24
23
|
type typeScriptLoader struct {
|
|
24
|
+
inputs evidenceInputReader
|
|
25
25
|
boundary *populationBase
|
|
26
26
|
root string
|
|
27
27
|
identityRoot string
|
|
@@ -38,8 +38,10 @@ type typeScriptLoader struct {
|
|
|
38
38
|
func newTypeScriptLoader(
|
|
39
39
|
root string,
|
|
40
40
|
program map[string]*artifactInventory,
|
|
41
|
+
readers ...evidenceInputReader,
|
|
41
42
|
) *typeScriptLoader {
|
|
42
43
|
loader := &typeScriptLoader{
|
|
44
|
+
inputs: inputReader(readers),
|
|
43
45
|
root: strings.ReplaceAll(root, "\\", "/"),
|
|
44
46
|
program: map[string]*artifactInventory{},
|
|
45
47
|
programIDs: map[string]*artifactInventory{},
|
|
@@ -50,7 +52,7 @@ func newTypeScriptLoader(
|
|
|
50
52
|
identities: map[string]typeScriptModuleIdentity{},
|
|
51
53
|
installs: map[string]installedPackageLocation{},
|
|
52
54
|
}
|
|
53
|
-
loader.identityRoot, _ = physicalTypeScriptPath(filepath.FromSlash(loader.root))
|
|
55
|
+
loader.identityRoot, _ = physicalTypeScriptPath(filepath.FromSlash(loader.root), loader.inputs)
|
|
54
56
|
keys := make([]string, 0, len(program))
|
|
55
57
|
for key := range program {
|
|
56
58
|
keys = append(keys, key)
|
|
@@ -116,7 +118,7 @@ func (loader *typeScriptLoader) inventory(relative string) (result *artifactInve
|
|
|
116
118
|
|
|
117
119
|
func (loader *typeScriptLoader) parse(relative string) *artifactInventory {
|
|
118
120
|
relative = loader.projectPath(relative)
|
|
119
|
-
content, err :=
|
|
121
|
+
content, err := loader.inputs.ReadFile(resolveProjectPath(loader.root, relative))
|
|
120
122
|
if err != nil {
|
|
121
123
|
loader.failures[relative] = err.Error()
|
|
122
124
|
return nil
|
|
@@ -178,7 +180,7 @@ func (loader *typeScriptLoader) programInventory(module string) *artifactInvento
|
|
|
178
180
|
// candidate, and a watch cycle resolves every re-export in the population — so
|
|
179
181
|
// callers that can answer from the Program should do that first.
|
|
180
182
|
func (loader *typeScriptLoader) existsOnDisk(relative string) bool {
|
|
181
|
-
info, err :=
|
|
183
|
+
info, err := loader.inputs.Stat(resolveProjectPath(loader.root, relative))
|
|
182
184
|
return err == nil && !info.IsDir()
|
|
183
185
|
}
|
|
184
186
|
|
|
@@ -215,10 +217,10 @@ func (loader *typeScriptLoader) resolveUncached(
|
|
|
215
217
|
normalized = append(normalized, module)
|
|
216
218
|
}
|
|
217
219
|
// The Program answers first, and not only because it answers without a
|
|
218
|
-
// syscall. A
|
|
219
|
-
//
|
|
220
|
-
//
|
|
221
|
-
//
|
|
220
|
+
// syscall. A module under edit has to be read as the editor has it rather
|
|
221
|
+
// than as the disk last saw it, which is the rule `inventory` states for a
|
|
222
|
+
// file already named. The candidates never include emitted JavaScript, so
|
|
223
|
+
// an emit beside its sources cannot win here.
|
|
222
224
|
for _, candidate := range normalized {
|
|
223
225
|
if loader.programInventory(candidate) != nil {
|
|
224
226
|
if !loader.withinBoundary(candidate) {
|
|
@@ -263,7 +265,7 @@ func (loader *typeScriptLoader) moduleIdentity(module string) (string, bool) {
|
|
|
263
265
|
return cached.Path, cached.Resolved
|
|
264
266
|
}
|
|
265
267
|
absolute := resolveProjectPath(loader.root, module)
|
|
266
|
-
resolved, ok := physicalTypeScriptPath(absolute)
|
|
268
|
+
resolved, ok := physicalTypeScriptPath(absolute, loader.inputs)
|
|
267
269
|
// Resolve both sides before taking a relative identity. Canonicalizing only
|
|
268
270
|
// the file makes a project junction or Windows 8.3 root leak the checkout's
|
|
269
271
|
// absolute location into every unit ID and invalidate unchanged reviews.
|
|
@@ -272,16 +274,17 @@ func (loader *typeScriptLoader) moduleIdentity(module string) (string, bool) {
|
|
|
272
274
|
return identity, ok
|
|
273
275
|
}
|
|
274
276
|
|
|
275
|
-
func physicalTypeScriptPath(absolute string) (string, bool) {
|
|
277
|
+
func physicalTypeScriptPath(absolute string, readers ...evidenceInputReader) (string, bool) {
|
|
278
|
+
inputs := inputReader(readers)
|
|
276
279
|
current := filepath.Clean(absolute)
|
|
277
280
|
// A directory link can introduce another link in an already-walked parent.
|
|
278
281
|
// Settle the complete path, with the same finite bound as directory chains.
|
|
279
282
|
for range 32 {
|
|
280
|
-
resolved, ok := resolveLinkedPath(current)
|
|
283
|
+
resolved, ok := resolveLinkedPath(current, inputs)
|
|
281
284
|
if !ok {
|
|
282
285
|
return resolved, false
|
|
283
286
|
}
|
|
284
|
-
resolved = expandTypeScriptPath(resolved)
|
|
287
|
+
resolved = expandTypeScriptPath(resolved, inputs)
|
|
285
288
|
if resolved == current {
|
|
286
289
|
return resolved, true
|
|
287
290
|
}
|
|
@@ -293,11 +296,12 @@ func physicalTypeScriptPath(absolute string) (string, bool) {
|
|
|
293
296
|
// An unsaved or deleted file still has an identity. Expand the deepest existing
|
|
294
297
|
// prefix so Windows short names do not reappear when EvalSymlinks cannot read
|
|
295
298
|
// the complete path, then restore the missing suffix without changing its case.
|
|
296
|
-
func expandTypeScriptPath(absolute string) string {
|
|
299
|
+
func expandTypeScriptPath(absolute string, readers ...evidenceInputReader) string {
|
|
300
|
+
inputs := inputReader(readers)
|
|
297
301
|
probe := absolute
|
|
298
302
|
suffix := []string{}
|
|
299
303
|
for {
|
|
300
|
-
if final, err :=
|
|
304
|
+
if final, err := inputs.EvalSymlinks(probe); err == nil {
|
|
301
305
|
if !strings.EqualFold(final, probe) {
|
|
302
306
|
probe = final
|
|
303
307
|
}
|
|
@@ -352,7 +356,7 @@ func (loader *typeScriptLoader) projectPath(relative string) string {
|
|
|
352
356
|
|
|
353
357
|
// isCleanProjectRelativePath reports whether a path is already the identity
|
|
354
358
|
// `projectPath` would produce: forward slashes, no drive or leading separator,
|
|
355
|
-
// and no
|
|
359
|
+
// and no empty, `.`, or `..` segment to collapse.
|
|
356
360
|
func isCleanProjectRelativePath(value string) bool {
|
|
357
361
|
if value == "" || strings.ContainsRune(value, '\\') {
|
|
358
362
|
return false
|
|
@@ -447,16 +451,23 @@ type installedPackageLocation struct {
|
|
|
447
451
|
func (loader *typeScriptLoader) locateInstalledPackage(
|
|
448
452
|
name string,
|
|
449
453
|
) (string, map[string]json.RawMessage) {
|
|
454
|
+
current := filepath.Clean(filepath.FromSlash(loader.root))
|
|
450
455
|
prefix := ""
|
|
451
|
-
for
|
|
456
|
+
for {
|
|
452
457
|
directory := path.Join(prefix, "node_modules", name)
|
|
453
458
|
manifest := readPackageManifest(
|
|
454
459
|
loader.root,
|
|
455
460
|
path.Join(directory, "package.json"),
|
|
461
|
+
loader.inputs,
|
|
456
462
|
)
|
|
457
463
|
if manifest != nil {
|
|
458
464
|
return directory, manifest
|
|
459
465
|
}
|
|
466
|
+
parent := filepath.Dir(current)
|
|
467
|
+
if parent == current {
|
|
468
|
+
break
|
|
469
|
+
}
|
|
470
|
+
current = parent
|
|
460
471
|
prefix = path.Join(prefix, "..")
|
|
461
472
|
}
|
|
462
473
|
return path.Join("node_modules", name), nil
|
|
@@ -474,7 +485,7 @@ func (loader *typeScriptLoader) locateInstalledPackage(
|
|
|
474
485
|
// The second answer is what separates an exhausted chain from a resolved
|
|
475
486
|
// directory, which are one string without it: a caller comparing paths against
|
|
476
487
|
// a link that still names another link cannot tell that it holds one, and that
|
|
477
|
-
// is how
|
|
488
|
+
// is how a leaf link stays silent unless the caller is told.
|
|
478
489
|
//
|
|
479
490
|
// The bound counts links followed, not answers given. A chain that ends exactly
|
|
480
491
|
// on the last hop this rule follows has landed on its directory with no
|
|
@@ -485,17 +496,18 @@ func (loader *typeScriptLoader) locateInstalledPackage(
|
|
|
485
496
|
// every earlier ending is a return from inside the loop. Refusing it instead
|
|
486
497
|
// would turn a root that works into an error at the boundary, which is what
|
|
487
498
|
// this rule exists to keep from happening in the other direction.
|
|
488
|
-
func resolveLinkedDirectory(directory string) (string, bool) {
|
|
499
|
+
func resolveLinkedDirectory(directory string, readers ...evidenceInputReader) (string, bool) {
|
|
500
|
+
inputs := inputReader(readers)
|
|
489
501
|
current := directory
|
|
490
502
|
for range 32 {
|
|
491
|
-
info, err :=
|
|
503
|
+
info, err := inputs.Lstat(current)
|
|
492
504
|
if err != nil || info.IsDir() {
|
|
493
505
|
return current, true
|
|
494
506
|
}
|
|
495
|
-
if target, err :=
|
|
507
|
+
if target, err := inputs.Stat(current); err != nil || !target.IsDir() {
|
|
496
508
|
return current, true
|
|
497
509
|
}
|
|
498
|
-
linked, err :=
|
|
510
|
+
linked, err := inputs.Readlink(current)
|
|
499
511
|
if err != nil {
|
|
500
512
|
return current, true
|
|
501
513
|
}
|
|
@@ -504,7 +516,7 @@ func resolveLinkedDirectory(directory string) (string, bool) {
|
|
|
504
516
|
}
|
|
505
517
|
current = filepath.ToSlash(linked)
|
|
506
518
|
}
|
|
507
|
-
info, err :=
|
|
519
|
+
info, err := inputs.Lstat(current)
|
|
508
520
|
return current, err == nil && info.IsDir()
|
|
509
521
|
}
|
|
510
522
|
|
|
@@ -527,10 +539,10 @@ func (loader *typeScriptLoader) walk(base string) ([]string, string) {
|
|
|
527
539
|
// owed there is a better cause, not a diagnostic that does not exist. A
|
|
528
540
|
// declared base has no such report behind it, which is why
|
|
529
541
|
// `resolvedBaseDirectory` does refuse.
|
|
530
|
-
walked, _ := resolveLinkedDirectory(root)
|
|
542
|
+
walked, _ := resolveLinkedDirectory(root, loader.inputs)
|
|
531
543
|
found := []string{}
|
|
532
544
|
problem := ""
|
|
533
|
-
err :=
|
|
545
|
+
err := loader.inputs.WalkDir(walked, func(current string, entry fs.DirEntry, err error) error {
|
|
534
546
|
if walked != root {
|
|
535
547
|
if inside, ok := containedProjectPath(walked, filepath.ToSlash(current)); ok {
|
|
536
548
|
current = path.Join(root, inside)
|