@ttsc/paths 0.11.0-dev.20260518-2 → 0.12.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 +2 -2
- package/driver/paths.go +288 -0
- package/package.json +3 -2
- package/plugin/paths.go +1 -1
- package/src/index.cjs +1 -1
package/README.md
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# `@ttsc/paths`
|
|
2
2
|
|
|
3
|
-

|
|
4
4
|
|
|
5
5
|
[](https://github.com/samchon/ttsc/blob/master/LICENSE)
|
|
6
6
|
[](https://www.npmjs.com/package/@ttsc/paths)
|
|
7
7
|
[](https://www.npmjs.com/package/@ttsc/paths)
|
|
8
8
|
[](https://github.com/samchon/ttsc/actions?query=workflow%3Atest)
|
|
9
|
-
[](https://
|
|
9
|
+
[](https://ttsc.dev/docs)
|
|
10
10
|
[](https://discord.gg/E94XhzrUCZ)
|
|
11
11
|
|
|
12
12
|
`@ttsc/paths` rewrites module specifiers that match `compilerOptions.paths` into relative JavaScript and declaration imports.
|
package/driver/paths.go
ADDED
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
package paths
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"path/filepath"
|
|
5
|
+
"sort"
|
|
6
|
+
"strings"
|
|
7
|
+
|
|
8
|
+
shimast "github.com/microsoft/typescript-go/shim/ast"
|
|
9
|
+
shimcore "github.com/microsoft/typescript-go/shim/core"
|
|
10
|
+
|
|
11
|
+
"github.com/samchon/ttsc/packages/ttsc/driver"
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
func init() {
|
|
15
|
+
driver.RegisterPlugin(plugin{})
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
type plugin struct{}
|
|
19
|
+
|
|
20
|
+
func (plugin) ApplyProgram(prog *driver.Program, _ driver.PluginContext) error {
|
|
21
|
+
rewriter := newRewriter(prog)
|
|
22
|
+
for _, file := range prog.SourceFiles() {
|
|
23
|
+
rewriter.apply(file)
|
|
24
|
+
}
|
|
25
|
+
return nil
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
type rewriter struct {
|
|
29
|
+
basePath string
|
|
30
|
+
outDir string
|
|
31
|
+
patterns []pathPattern
|
|
32
|
+
rootDir string
|
|
33
|
+
sourceFiles map[string]string
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
type pathPattern struct {
|
|
37
|
+
pattern string
|
|
38
|
+
targets []string
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
func newRewriter(prog *driver.Program) *rewriter {
|
|
42
|
+
out := &rewriter{sourceFiles: map[string]string{}}
|
|
43
|
+
if prog == nil || prog.ParsedConfig == nil || prog.ParsedConfig.ParsedConfig == nil || prog.ParsedConfig.ParsedConfig.CompilerOptions == nil {
|
|
44
|
+
return out
|
|
45
|
+
}
|
|
46
|
+
options := prog.ParsedConfig.ParsedConfig.CompilerOptions
|
|
47
|
+
out.basePath = filepath.Clean(options.GetPathsBasePath(prog.Host.GetCurrentDirectory()))
|
|
48
|
+
out.outDir = optionalPath(options.OutDir, prog.Host.GetCurrentDirectory())
|
|
49
|
+
out.rootDir = optionalPath(options.RootDir, prog.Host.GetCurrentDirectory())
|
|
50
|
+
files := prog.SourceFiles()
|
|
51
|
+
if out.rootDir == "" {
|
|
52
|
+
out.rootDir = commonSourceDir(files)
|
|
53
|
+
}
|
|
54
|
+
for _, file := range files {
|
|
55
|
+
name := normalizePath(file.FileName())
|
|
56
|
+
out.sourceFiles[name] = name
|
|
57
|
+
out.sourceFiles[stripKnownSourceExtension(name)] = name
|
|
58
|
+
}
|
|
59
|
+
if options.Paths != nil {
|
|
60
|
+
for key, targets := range options.Paths.Entries() {
|
|
61
|
+
out.patterns = append(out.patterns, pathPattern{
|
|
62
|
+
pattern: key,
|
|
63
|
+
targets: append([]string(nil), targets...),
|
|
64
|
+
})
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
sort.SliceStable(out.patterns, func(i, j int) bool {
|
|
68
|
+
return patternRank(out.patterns[i].pattern) > patternRank(out.patterns[j].pattern)
|
|
69
|
+
})
|
|
70
|
+
return out
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
func (r *rewriter) apply(file *shimast.SourceFile) {
|
|
74
|
+
if r == nil || file == nil || len(r.patterns) == 0 {
|
|
75
|
+
return
|
|
76
|
+
}
|
|
77
|
+
visitModuleSpecifiers(file.AsNode(), func(lit *shimast.Node) {
|
|
78
|
+
if lit == nil || lit.Kind != shimast.KindStringLiteral {
|
|
79
|
+
return
|
|
80
|
+
}
|
|
81
|
+
spec := lit.Text()
|
|
82
|
+
rewritten, ok := r.rewrite(file.FileName(), spec)
|
|
83
|
+
if ok && rewritten != spec {
|
|
84
|
+
lit.AsStringLiteral().Text = rewritten
|
|
85
|
+
lit.Flags |= shimast.NodeFlagsSynthesized
|
|
86
|
+
lit.Loc = shimcore.UndefinedTextRange()
|
|
87
|
+
}
|
|
88
|
+
})
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
func visitModuleSpecifiers(node *shimast.Node, visit func(*shimast.Node)) {
|
|
92
|
+
if node == nil {
|
|
93
|
+
return
|
|
94
|
+
}
|
|
95
|
+
switch node.Kind {
|
|
96
|
+
case shimast.KindImportDeclaration:
|
|
97
|
+
visit(node.AsImportDeclaration().ModuleSpecifier)
|
|
98
|
+
case shimast.KindExportDeclaration:
|
|
99
|
+
visit(node.AsExportDeclaration().ModuleSpecifier)
|
|
100
|
+
case shimast.KindImportEqualsDeclaration:
|
|
101
|
+
ref := node.AsImportEqualsDeclaration().ModuleReference
|
|
102
|
+
if ref != nil && ref.Kind == shimast.KindExternalModuleReference {
|
|
103
|
+
visit(ref.AsExternalModuleReference().Expression)
|
|
104
|
+
}
|
|
105
|
+
case shimast.KindImportType:
|
|
106
|
+
arg := node.AsImportTypeNode().Argument
|
|
107
|
+
if arg != nil && arg.Kind == shimast.KindLiteralType {
|
|
108
|
+
visit(arg.AsLiteralTypeNode().Literal)
|
|
109
|
+
}
|
|
110
|
+
case shimast.KindModuleDeclaration:
|
|
111
|
+
decl := node.AsModuleDeclaration()
|
|
112
|
+
if decl != nil {
|
|
113
|
+
visit(decl.Name())
|
|
114
|
+
}
|
|
115
|
+
case shimast.KindCallExpression:
|
|
116
|
+
call := node.AsCallExpression()
|
|
117
|
+
if isModuleSpecifierCall(call) && call.Arguments != nil && len(call.Arguments.Nodes) > 0 {
|
|
118
|
+
visit(call.Arguments.Nodes[0])
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
node.ForEachChild(func(child *shimast.Node) bool {
|
|
122
|
+
visitModuleSpecifiers(child, visit)
|
|
123
|
+
return false
|
|
124
|
+
})
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
func isModuleSpecifierCall(call *shimast.CallExpression) bool {
|
|
128
|
+
if call == nil || call.Expression == nil {
|
|
129
|
+
return false
|
|
130
|
+
}
|
|
131
|
+
switch call.Expression.Kind {
|
|
132
|
+
case shimast.KindImportKeyword:
|
|
133
|
+
return true
|
|
134
|
+
case shimast.KindIdentifier:
|
|
135
|
+
return call.Expression.Text() == "require"
|
|
136
|
+
default:
|
|
137
|
+
return false
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
func (r *rewriter) rewrite(fromSource string, specifier string) (string, bool) {
|
|
142
|
+
if specifier == "" || strings.HasPrefix(specifier, ".") || strings.HasPrefix(specifier, "/") {
|
|
143
|
+
return specifier, false
|
|
144
|
+
}
|
|
145
|
+
targetSource, ok := r.resolveSource(specifier)
|
|
146
|
+
if !ok {
|
|
147
|
+
return specifier, false
|
|
148
|
+
}
|
|
149
|
+
fromOut := r.outputPathForSource(fromSource)
|
|
150
|
+
targetOut := r.outputPathForSource(targetSource)
|
|
151
|
+
if fromOut == "" || targetOut == "" {
|
|
152
|
+
return specifier, false
|
|
153
|
+
}
|
|
154
|
+
rel, _ := filepath.Rel(filepath.Dir(fromOut), targetOut)
|
|
155
|
+
rel = filepath.ToSlash(rel)
|
|
156
|
+
if !strings.HasPrefix(rel, ".") {
|
|
157
|
+
rel = "./" + rel
|
|
158
|
+
}
|
|
159
|
+
return rel, true
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
func (r *rewriter) resolveSource(specifier string) (string, bool) {
|
|
163
|
+
for _, pattern := range r.patterns {
|
|
164
|
+
star, ok := matchPattern(pattern.pattern, specifier)
|
|
165
|
+
if !ok {
|
|
166
|
+
continue
|
|
167
|
+
}
|
|
168
|
+
for _, target := range pattern.targets {
|
|
169
|
+
candidate := strings.Replace(target, "*", star, 1)
|
|
170
|
+
resolved := normalizePath(filepath.Join(r.basePath, candidate))
|
|
171
|
+
if source, ok := r.lookupSource(resolved); ok {
|
|
172
|
+
return source, true
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return "", false
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
func (r *rewriter) lookupSource(candidate string) (string, bool) {
|
|
180
|
+
if source, ok := r.sourceFiles[normalizePath(candidate)]; ok {
|
|
181
|
+
return source, true
|
|
182
|
+
}
|
|
183
|
+
stem := stripKnownSourceExtension(normalizePath(candidate))
|
|
184
|
+
if source, ok := r.sourceFiles[stem]; ok {
|
|
185
|
+
return source, true
|
|
186
|
+
}
|
|
187
|
+
for _, ext := range []string{".ts", ".tsx", ".mts", ".cts"} {
|
|
188
|
+
if source, ok := r.sourceFiles[stem+ext]; ok {
|
|
189
|
+
return source, true
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
for _, ext := range []string{".ts", ".tsx", ".mts", ".cts"} {
|
|
193
|
+
if source, ok := r.sourceFiles[normalizePath(filepath.Join(stem, "index"+ext))]; ok {
|
|
194
|
+
return source, true
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return "", false
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
func (r *rewriter) outputPathForSource(source string) string {
|
|
201
|
+
if r.outDir == "" || r.rootDir == "" {
|
|
202
|
+
return ""
|
|
203
|
+
}
|
|
204
|
+
rel, err := filepath.Rel(r.rootDir, source)
|
|
205
|
+
if err != nil || isOutsideRelativePath(rel) {
|
|
206
|
+
return ""
|
|
207
|
+
}
|
|
208
|
+
return normalizePath(filepath.Join(r.outDir, replaceSourceExtension(rel, emittedJavaScriptExtension(rel))))
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
func emittedJavaScriptExtension(source string) string {
|
|
212
|
+
switch strings.ToLower(filepath.Ext(source)) {
|
|
213
|
+
case ".mts":
|
|
214
|
+
return ".mjs"
|
|
215
|
+
case ".cts":
|
|
216
|
+
return ".cjs"
|
|
217
|
+
default:
|
|
218
|
+
return ".js"
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
func matchPattern(pattern string, specifier string) (string, bool) {
|
|
223
|
+
if !strings.Contains(pattern, "*") {
|
|
224
|
+
return "", pattern == specifier
|
|
225
|
+
}
|
|
226
|
+
parts := strings.SplitN(pattern, "*", 2)
|
|
227
|
+
if !strings.HasPrefix(specifier, parts[0]) || !strings.HasSuffix(specifier, parts[1]) {
|
|
228
|
+
return "", false
|
|
229
|
+
}
|
|
230
|
+
return specifier[len(parts[0]) : len(specifier)-len(parts[1])], true
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
func patternRank(pattern string) int {
|
|
234
|
+
return len(strings.ReplaceAll(pattern, "*", ""))
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
func optionalPath(value string, cwd string) string {
|
|
238
|
+
if value == "" {
|
|
239
|
+
return ""
|
|
240
|
+
}
|
|
241
|
+
if filepath.IsAbs(value) {
|
|
242
|
+
return normalizePath(value)
|
|
243
|
+
}
|
|
244
|
+
return normalizePath(filepath.Join(cwd, value))
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
func commonSourceDir(files []*shimast.SourceFile) string {
|
|
248
|
+
if len(files) == 0 {
|
|
249
|
+
return ""
|
|
250
|
+
}
|
|
251
|
+
common := normalizePath(filepath.Dir(files[0].FileName()))
|
|
252
|
+
for _, file := range files[1:] {
|
|
253
|
+
dir := normalizePath(filepath.Dir(file.FileName()))
|
|
254
|
+
for common != "" && !strings.HasPrefix(dir+"/", common+"/") {
|
|
255
|
+
next := filepath.Dir(common)
|
|
256
|
+
if next == common {
|
|
257
|
+
return common
|
|
258
|
+
}
|
|
259
|
+
common = normalizePath(next)
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
return common
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
func normalizePath(value string) string {
|
|
266
|
+
if value == "" {
|
|
267
|
+
return ""
|
|
268
|
+
}
|
|
269
|
+
return filepath.ToSlash(filepath.Clean(value))
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
func stripKnownSourceExtension(value string) string {
|
|
273
|
+
lower := strings.ToLower(value)
|
|
274
|
+
for _, ext := range []string{".d.ts", ".d.mts", ".d.cts", ".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"} {
|
|
275
|
+
if strings.HasSuffix(lower, ext) {
|
|
276
|
+
return value[:len(value)-len(ext)]
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
return strings.TrimSuffix(value, filepath.Ext(value))
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
func replaceSourceExtension(value string, ext string) string {
|
|
283
|
+
return stripKnownSourceExtension(filepath.ToSlash(value)) + ext
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
func isOutsideRelativePath(rel string) bool {
|
|
287
|
+
return rel == ".." || strings.HasPrefix(rel, ".."+string(filepath.Separator))
|
|
288
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ttsc/paths",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "First-party ttsc plugin that rewrites emitted module specifiers from tsconfig paths.",
|
|
5
5
|
"main": "src/index.cjs",
|
|
6
6
|
"exports": {
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
},
|
|
21
21
|
"files": [
|
|
22
22
|
"README.md",
|
|
23
|
+
"driver/paths.go",
|
|
23
24
|
"src/index.cjs",
|
|
24
25
|
"go.mod",
|
|
25
26
|
"plugin/main.go",
|
|
@@ -34,5 +35,5 @@
|
|
|
34
35
|
"bugs": {
|
|
35
36
|
"url": "https://github.com/samchon/ttsc/issues"
|
|
36
37
|
},
|
|
37
|
-
"homepage": "https://
|
|
38
|
+
"homepage": "https://ttsc.dev"
|
|
38
39
|
}
|
package/plugin/paths.go
CHANGED
package/src/index.cjs
CHANGED