@ttsc/paths 0.16.7 → 0.16.9
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 +1 -1
- package/driver/paths.go +149 -62
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -42,7 +42,7 @@ Configure those fields under `compilerOptions`:
|
|
|
42
42
|
|
|
43
43
|
An import such as `import { value } from "@lib/value"` becomes a relative JavaScript import such as `import { value } from "./modules/value.js"`. Declaration output follows the same source rewrite.
|
|
44
44
|
|
|
45
|
-
`outDir` must be set for the rewrite to run. Without it, `@ttsc/paths` cannot map a source path to its emitted location and makes no changes. `rootDir` is optional: when omitted it
|
|
45
|
+
`outDir` must be set for the rewrite to run. Without it, `@ttsc/paths` cannot map a source path to its emitted location and makes no changes. `rootDir` is optional: when omitted it defaults to the tsconfig's directory, matching where TypeScript-Go anchors emitted output.
|
|
46
46
|
|
|
47
47
|
## Sponsors
|
|
48
48
|
|
package/driver/paths.go
CHANGED
|
@@ -7,6 +7,7 @@ import (
|
|
|
7
7
|
|
|
8
8
|
shimast "github.com/microsoft/typescript-go/shim/ast"
|
|
9
9
|
shimcore "github.com/microsoft/typescript-go/shim/core"
|
|
10
|
+
shimtspath "github.com/microsoft/typescript-go/shim/tspath"
|
|
10
11
|
|
|
11
12
|
"github.com/samchon/ttsc/packages/ttsc/driver"
|
|
12
13
|
)
|
|
@@ -60,16 +61,20 @@ func newRewriter(prog *driver.Program) *rewriter {
|
|
|
60
61
|
return out
|
|
61
62
|
}
|
|
62
63
|
options := prog.ParsedConfig.ParsedConfig.CompilerOptions
|
|
63
|
-
|
|
64
|
+
cwd := prog.Host.GetCurrentDirectory()
|
|
65
|
+
out.basePath = filepath.Clean(options.GetPathsBasePath(cwd))
|
|
64
66
|
out.jsxPreserve = options.Jsx == shimcore.JsxEmitPreserve
|
|
65
|
-
out.outDir = optionalPath(options.OutDir,
|
|
66
|
-
out.rootDir = optionalPath(options.RootDir,
|
|
67
|
+
out.outDir = optionalPath(options.OutDir, cwd)
|
|
68
|
+
out.rootDir = optionalPath(options.RootDir, cwd)
|
|
67
69
|
files := prog.SourceFiles()
|
|
70
|
+
fileNames := make([]string, 0, len(files))
|
|
71
|
+
for _, file := range files {
|
|
72
|
+
fileNames = append(fileNames, normalizePath(file.FileName()))
|
|
73
|
+
}
|
|
68
74
|
if out.rootDir == "" {
|
|
69
|
-
out.rootDir =
|
|
75
|
+
out.rootDir = inferredRootDir(options.ConfigFilePath, fileNames, cwd, useCaseSensitiveFileNames(prog))
|
|
70
76
|
}
|
|
71
|
-
for _,
|
|
72
|
-
name := normalizePath(file.FileName())
|
|
77
|
+
for _, name := range fileNames {
|
|
73
78
|
out.sourceFiles[name] = name
|
|
74
79
|
}
|
|
75
80
|
if options.Paths != nil {
|
|
@@ -80,12 +85,20 @@ func newRewriter(prog *driver.Program) *rewriter {
|
|
|
80
85
|
})
|
|
81
86
|
}
|
|
82
87
|
}
|
|
83
|
-
|
|
84
|
-
return patternRank(out.patterns[i].pattern) > patternRank(out.patterns[j].pattern)
|
|
85
|
-
})
|
|
88
|
+
orderPatterns(out.patterns)
|
|
86
89
|
return out
|
|
87
90
|
}
|
|
88
91
|
|
|
92
|
+
// useCaseSensitiveFileNames reports the host filesystem's case sensitivity,
|
|
93
|
+
// defaulting to case-sensitive when the program carries no filesystem (bare
|
|
94
|
+
// rewriters built by unit tests).
|
|
95
|
+
func useCaseSensitiveFileNames(prog *driver.Program) bool {
|
|
96
|
+
if prog == nil || prog.FS == nil {
|
|
97
|
+
return true
|
|
98
|
+
}
|
|
99
|
+
return prog.FS.UseCaseSensitiveFileNames()
|
|
100
|
+
}
|
|
101
|
+
|
|
89
102
|
// apply rewrites all module specifiers in file that match a tsconfig paths pattern.
|
|
90
103
|
func (r *rewriter) apply(file *shimast.SourceFile) {
|
|
91
104
|
if r == nil || file == nil || len(r.patterns) == 0 {
|
|
@@ -109,40 +122,49 @@ func (r *rewriter) apply(file *shimast.SourceFile) {
|
|
|
109
122
|
// visit for every string-literal module specifier it finds. Covered nodes
|
|
110
123
|
// include import/export declarations, require() calls, dynamic import()
|
|
111
124
|
// expressions, import-equals declarations, and import-type nodes.
|
|
125
|
+
//
|
|
126
|
+
// The recursion runs through one closure created per walk, not one per node:
|
|
127
|
+
// handing ForEachChild a fresh closure at every node would allocate once per
|
|
128
|
+
// AST node across the whole program on every apply pass.
|
|
112
129
|
func visitModuleSpecifiers(node *shimast.Node, visit func(*shimast.Node)) {
|
|
113
130
|
if node == nil {
|
|
114
131
|
return
|
|
115
132
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
visit(node.AsExportDeclaration().ModuleSpecifier)
|
|
121
|
-
case shimast.KindImportEqualsDeclaration:
|
|
122
|
-
ref := node.AsImportEqualsDeclaration().ModuleReference
|
|
123
|
-
if ref != nil && ref.Kind == shimast.KindExternalModuleReference {
|
|
124
|
-
visit(ref.AsExternalModuleReference().Expression)
|
|
125
|
-
}
|
|
126
|
-
case shimast.KindImportType:
|
|
127
|
-
arg := node.AsImportTypeNode().Argument
|
|
128
|
-
if arg != nil && arg.Kind == shimast.KindLiteralType {
|
|
129
|
-
visit(arg.AsLiteralTypeNode().Literal)
|
|
133
|
+
var walk func(node *shimast.Node) bool
|
|
134
|
+
walk = func(node *shimast.Node) bool {
|
|
135
|
+
if node == nil {
|
|
136
|
+
return false
|
|
130
137
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
138
|
+
switch node.Kind {
|
|
139
|
+
case shimast.KindImportDeclaration:
|
|
140
|
+
visit(node.AsImportDeclaration().ModuleSpecifier)
|
|
141
|
+
case shimast.KindExportDeclaration:
|
|
142
|
+
visit(node.AsExportDeclaration().ModuleSpecifier)
|
|
143
|
+
case shimast.KindImportEqualsDeclaration:
|
|
144
|
+
ref := node.AsImportEqualsDeclaration().ModuleReference
|
|
145
|
+
if ref != nil && ref.Kind == shimast.KindExternalModuleReference {
|
|
146
|
+
visit(ref.AsExternalModuleReference().Expression)
|
|
147
|
+
}
|
|
148
|
+
case shimast.KindImportType:
|
|
149
|
+
arg := node.AsImportTypeNode().Argument
|
|
150
|
+
if arg != nil && arg.Kind == shimast.KindLiteralType {
|
|
151
|
+
visit(arg.AsLiteralTypeNode().Literal)
|
|
152
|
+
}
|
|
153
|
+
case shimast.KindModuleDeclaration:
|
|
154
|
+
decl := node.AsModuleDeclaration()
|
|
155
|
+
if decl != nil {
|
|
156
|
+
visit(decl.Name())
|
|
157
|
+
}
|
|
158
|
+
case shimast.KindCallExpression:
|
|
159
|
+
call := node.AsCallExpression()
|
|
160
|
+
if isModuleSpecifierCall(call) && call.Arguments != nil && len(call.Arguments.Nodes) > 0 {
|
|
161
|
+
visit(call.Arguments.Nodes[0])
|
|
162
|
+
}
|
|
140
163
|
}
|
|
141
|
-
|
|
142
|
-
node.ForEachChild(func(child *shimast.Node) bool {
|
|
143
|
-
visitModuleSpecifiers(child, visit)
|
|
164
|
+
node.ForEachChild(walk)
|
|
144
165
|
return false
|
|
145
|
-
}
|
|
166
|
+
}
|
|
167
|
+
walk(node)
|
|
146
168
|
}
|
|
147
169
|
|
|
148
170
|
// isModuleSpecifierCall reports whether call is a dynamic import() or a
|
|
@@ -185,9 +207,13 @@ func (r *rewriter) rewrite(fromSource string, specifier string) (string, bool) {
|
|
|
185
207
|
return rel, true
|
|
186
208
|
}
|
|
187
209
|
|
|
188
|
-
// resolveSource finds the source file that a tsconfig paths specifier
|
|
189
|
-
//
|
|
190
|
-
//
|
|
210
|
+
// resolveSource finds the source file that a tsconfig paths specifier
|
|
211
|
+
// resolves to. Patterns are pre-sorted into tsc's precedence order, and like
|
|
212
|
+
// tsc's tryLoadModuleUsingPaths the resolution commits to the first (best)
|
|
213
|
+
// matching pattern: only that pattern's substitution targets are tried, in
|
|
214
|
+
// order, with extension and index fallbacks. When none of them names a
|
|
215
|
+
// program source the specifier stays unrewritten — falling through to a
|
|
216
|
+
// weaker pattern would rewrite at a module the type checker never resolved.
|
|
191
217
|
func (r *rewriter) resolveSource(specifier string) (string, bool) {
|
|
192
218
|
for _, pattern := range r.patterns {
|
|
193
219
|
star, ok := matchPattern(pattern.pattern, specifier)
|
|
@@ -201,6 +227,7 @@ func (r *rewriter) resolveSource(specifier string) (string, bool) {
|
|
|
201
227
|
return source, true
|
|
202
228
|
}
|
|
203
229
|
}
|
|
230
|
+
return "", false
|
|
204
231
|
}
|
|
205
232
|
return "", false
|
|
206
233
|
}
|
|
@@ -261,23 +288,53 @@ func emittedJavaScriptExtension(source string, jsxPreserve bool) string {
|
|
|
261
288
|
// matchPattern matches specifier against a tsconfig paths pattern (which may
|
|
262
289
|
// contain at most one "*" wildcard). Returns the captured wildcard segment and
|
|
263
290
|
// true on a match, or ("", false) otherwise. Exact patterns are matched with
|
|
264
|
-
// simple equality.
|
|
291
|
+
// simple equality. The length guard mirrors tsc's isPatternMatch: a specifier
|
|
292
|
+
// shorter than the pattern's literal halves combined can still satisfy both
|
|
293
|
+
// the prefix and suffix probes ("@lib/x" against "@lib/x*x"), and slicing the
|
|
294
|
+
// star capture out of it would panic on inverted bounds.
|
|
265
295
|
func matchPattern(pattern string, specifier string) (string, bool) {
|
|
266
296
|
if !strings.Contains(pattern, "*") {
|
|
267
297
|
return "", pattern == specifier
|
|
268
298
|
}
|
|
269
299
|
parts := strings.SplitN(pattern, "*", 2)
|
|
270
|
-
if
|
|
300
|
+
if strings.Contains(parts[1], "*") {
|
|
301
|
+
// More than one wildcard is not a pattern at all in tsc
|
|
302
|
+
// (TryParsePattern discards it), so it must never match here either.
|
|
303
|
+
return "", false
|
|
304
|
+
}
|
|
305
|
+
if len(specifier) < len(parts[0])+len(parts[1]) ||
|
|
306
|
+
!strings.HasPrefix(specifier, parts[0]) ||
|
|
307
|
+
!strings.HasSuffix(specifier, parts[1]) {
|
|
271
308
|
return "", false
|
|
272
309
|
}
|
|
273
310
|
return specifier[len(parts[0]) : len(specifier)-len(parts[1])], true
|
|
274
311
|
}
|
|
275
312
|
|
|
276
|
-
//
|
|
277
|
-
//
|
|
278
|
-
//
|
|
279
|
-
|
|
280
|
-
|
|
313
|
+
// orderPatterns sorts patterns in place into tsc's matchPatternOrExact
|
|
314
|
+
// precedence: exact patterns (no wildcard) first, then wildcard patterns by
|
|
315
|
+
// decreasing literal-prefix length. Ranking by total literal length instead
|
|
316
|
+
// would steer a specifier at a long-suffix pattern ("*-styles") even though
|
|
317
|
+
// tsc resolves it through the longer prefix ("@app/*"), making the rewriter
|
|
318
|
+
// disagree with the type checker's own module resolution. Ties keep the
|
|
319
|
+
// tsconfig's declaration order, matching tsc's first-longest-prefix-wins scan.
|
|
320
|
+
func orderPatterns(patterns []pathPattern) {
|
|
321
|
+
sort.SliceStable(patterns, func(i, j int) bool {
|
|
322
|
+
a, b := patterns[i].pattern, patterns[j].pattern
|
|
323
|
+
aExact, bExact := !strings.Contains(a, "*"), !strings.Contains(b, "*")
|
|
324
|
+
if aExact != bExact {
|
|
325
|
+
return aExact
|
|
326
|
+
}
|
|
327
|
+
return patternPrefixLength(a) > patternPrefixLength(b)
|
|
328
|
+
})
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// patternPrefixLength returns the length of the literal text before the "*"
|
|
332
|
+
// wildcard, or the whole pattern length for exact patterns.
|
|
333
|
+
func patternPrefixLength(pattern string) int {
|
|
334
|
+
if i := strings.IndexByte(pattern, '*'); i >= 0 {
|
|
335
|
+
return i
|
|
336
|
+
}
|
|
337
|
+
return len(pattern)
|
|
281
338
|
}
|
|
282
339
|
|
|
283
340
|
// optionalPath resolves value as a path relative to cwd when it is non-empty
|
|
@@ -292,25 +349,55 @@ func optionalPath(value string, cwd string) string {
|
|
|
292
349
|
return normalizePath(filepath.Join(cwd, value))
|
|
293
350
|
}
|
|
294
351
|
|
|
295
|
-
//
|
|
296
|
-
//
|
|
297
|
-
//
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
352
|
+
// inferredRootDir mirrors TypeScript-Go's GetCommonSourceDirectory fallback
|
|
353
|
+
// chain for a project without an explicit rootDir: the tsconfig's directory
|
|
354
|
+
// when the program was loaded from one, else the deepest directory shared by
|
|
355
|
+
// every input file. The rewriter must anchor output paths exactly where tsgo
|
|
356
|
+
// anchors its own emit, or the rewritten specifiers drift from the real
|
|
357
|
+
// output layout.
|
|
358
|
+
func inferredRootDir(configFilePath string, fileNames []string, currentDirectory string, useCaseSensitiveFileNames bool) string {
|
|
359
|
+
if configFilePath != "" {
|
|
360
|
+
return normalizePath(filepath.Dir(configFilePath))
|
|
301
361
|
}
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
362
|
+
return commonSourceDir(fileNames, currentDirectory, useCaseSensitiveFileNames)
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// commonSourceDir mirrors TypeScript-Go's
|
|
366
|
+
// computeCommonSourceDirectoryOfFilenames: the deepest directory shared by
|
|
367
|
+
// every file, intersected per normalized path component under the host's case
|
|
368
|
+
// sensitivity. Returns "" when the files share no root at all — on Windows, a
|
|
369
|
+
// `files` list spanning two volumes — so the caller skips output mapping
|
|
370
|
+
// instead of guessing. The previous byte-oriented walk hung there (#310):
|
|
371
|
+
// once the shared prefix shrank to a volume root, filepath.Dir handed back
|
|
372
|
+
// the backslash form ("C:\") while the termination guard compared it against
|
|
373
|
+
// the slash-normalized cursor ("C:/"), re-normalizing the same directory
|
|
374
|
+
// forever.
|
|
375
|
+
func commonSourceDir(fileNames []string, currentDirectory string, useCaseSensitiveFileNames bool) string {
|
|
376
|
+
var common []string
|
|
377
|
+
for _, fileName := range fileNames {
|
|
378
|
+
components := shimtspath.GetNormalizedPathComponents(fileName, currentDirectory)
|
|
379
|
+
// The base file name is not part of the common directory path.
|
|
380
|
+
components = components[:len(components)-1]
|
|
381
|
+
if common == nil {
|
|
382
|
+
common = components
|
|
383
|
+
continue
|
|
311
384
|
}
|
|
385
|
+
shared := 0
|
|
386
|
+
limit := min(len(common), len(components))
|
|
387
|
+
for shared < limit &&
|
|
388
|
+
shimtspath.GetCanonicalFileName(common[shared], useCaseSensitiveFileNames) ==
|
|
389
|
+
shimtspath.GetCanonicalFileName(components[shared], useCaseSensitiveFileNames) {
|
|
390
|
+
shared++
|
|
391
|
+
}
|
|
392
|
+
if shared == 0 {
|
|
393
|
+
return ""
|
|
394
|
+
}
|
|
395
|
+
common = common[:shared]
|
|
396
|
+
}
|
|
397
|
+
if len(common) == 0 {
|
|
398
|
+
return ""
|
|
312
399
|
}
|
|
313
|
-
return common
|
|
400
|
+
return shimtspath.GetPathFromPathComponents(common)
|
|
314
401
|
}
|
|
315
402
|
|
|
316
403
|
// normalizePath cleans and converts a file path to forward-slash form.
|