@ttsc/paths 0.7.2 → 0.8.0-dev.20260505

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 CHANGED
@@ -9,7 +9,7 @@
9
9
  [![Guide Documents](https://img.shields.io/badge/Guide-Documents-forestgreen)](https://github.com/samchon/ttsc/tree/master/docs)
10
10
  [![Discord Badge](https://img.shields.io/badge/discord-samchon-d91965?style=flat&labelColor=5866f2&logo=discord&logoColor=white&link=https://discord.gg/E94XhzrUCZ)](https://discord.gg/E94XhzrUCZ)
11
11
 
12
- `@ttsc/paths` rewrites emitted module specifiers that match `compilerOptions.paths` into relative JavaScript paths.
12
+ `@ttsc/paths` rewrites source and declaration module specifiers that match `compilerOptions.paths` into relative JavaScript paths.
13
13
 
14
14
  ## Setup
15
15
 
@@ -43,7 +43,7 @@ Run your normal `ttsc` command:
43
43
  npx ttsc
44
44
  ```
45
45
 
46
- An emitted import such as `import { value } from "@lib/value"` becomes a relative JavaScript import such as `import { value } from "./modules/value.js"`.
46
+ An import such as `import { value } from "@lib/value"` becomes a relative JavaScript import such as `import { value } from "./modules/value.js"`. Declaration imports are rewritten with the same path rule.
47
47
 
48
48
  ## Notes
49
49
 
@@ -61,8 +61,8 @@ No separate plugin options are required. `@ttsc/paths` reads the same `compilerO
61
61
  // Keep lint first.
62
62
  { "transform": "@ttsc/lint", "config": { "no-var": "error" } },
63
63
 
64
- // Output plugins run after emit, in order.
65
- { "transform": "@ttsc/banner", "banner": "/*! @license MIT */" },
64
+ // First-party utilities use their documented source/emit hook order.
65
+ { "transform": "@ttsc/banner", "banner": "License MIT" },
66
66
  { "transform": "@ttsc/paths" },
67
67
  { "transform": "@ttsc/strip", "calls": ["console.log"] }
68
68
  ]
package/go.mod CHANGED
@@ -2,18 +2,7 @@ module github.com/samchon/ttsc/packages/paths
2
2
 
3
3
  go 1.26
4
4
 
5
- require (
6
- github.com/microsoft/typescript-go/shim/ast v0.0.0
7
- github.com/microsoft/typescript-go/shim/bundled v0.0.0
8
- github.com/microsoft/typescript-go/shim/compiler v0.0.0
9
- github.com/microsoft/typescript-go/shim/core v0.0.0
10
- github.com/microsoft/typescript-go/shim/diagnosticwriter v0.0.0
11
- github.com/microsoft/typescript-go/shim/parser v0.0.0
12
- github.com/microsoft/typescript-go/shim/tsoptions v0.0.0
13
- github.com/microsoft/typescript-go/shim/tspath v0.0.0
14
- github.com/microsoft/typescript-go/shim/vfs/cachedvfs v0.0.0
15
- github.com/microsoft/typescript-go/shim/vfs/osvfs v0.0.0
16
- )
5
+ require github.com/samchon/ttsc/packages/ttsc v0.0.0
17
6
 
18
7
  require (
19
8
  github.com/go-json-experiment/json v0.0.0-20260214004413-d219187c3433 // indirect
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ttsc/paths",
3
- "version": "0.7.2",
3
+ "version": "0.8.0-dev.20260505",
4
4
  "description": "First-party ttsc plugin that rewrites emitted module specifiers from tsconfig paths.",
5
5
  "main": "src/index.cjs",
6
6
  "exports": {
package/plugin/main.go CHANGED
@@ -1,31 +1,36 @@
1
+ // Native sidecar entrypoint for `@ttsc/paths`.
1
2
  package main
2
3
 
3
4
  import (
4
- "fmt"
5
- "os"
5
+ "fmt"
6
+ "os"
7
+
8
+ "github.com/samchon/ttsc/packages/ttsc/utility"
6
9
  )
7
10
 
8
11
  const version = "0.0.1"
9
12
 
10
13
  func main() {
11
- os.Exit(run(os.Args[1:]))
14
+ os.Exit(run(os.Args[1:]))
12
15
  }
13
16
 
14
17
  func run(args []string) int {
15
- if len(args) == 0 {
16
- fmt.Fprintln(os.Stderr, "@ttsc/paths: command required (expected output|version)")
17
- return 2
18
- }
19
- switch args[0] {
20
- case "-v", "--version", "version":
21
- fmt.Fprintf(os.Stdout, "@ttsc/paths %s\n", version)
22
- return 0
23
- case "check":
24
- return 0
25
- case "output":
26
- return RunOutput(args[1:])
27
- default:
28
- fmt.Fprintf(os.Stderr, "@ttsc/paths: unknown command %q\n", args[0])
29
- return 2
30
- }
18
+ if len(args) == 0 {
19
+ fmt.Fprintln(os.Stderr, "@ttsc/paths: command required (expected build|transform|check|version)")
20
+ return 2
21
+ }
22
+ switch args[0] {
23
+ case "-v", "--version", "version":
24
+ fmt.Fprintf(os.Stdout, "@ttsc/paths %s\n", version)
25
+ return 0
26
+ case "build":
27
+ return utility.RunBuild(args[1:])
28
+ case "transform":
29
+ return utility.RunTransform(args[1:])
30
+ case "check":
31
+ return 0
32
+ default:
33
+ fmt.Fprintf(os.Stderr, "@ttsc/paths: unknown command %q\n", args[0])
34
+ return 2
35
+ }
31
36
  }
package/plugin/paths.go CHANGED
@@ -1,632 +1,3 @@
1
1
  package main
2
2
 
3
- import (
4
- "encoding/json"
5
- "errors"
6
- "flag"
7
- "fmt"
8
- "os"
9
- "path/filepath"
10
- "sort"
11
- "strings"
12
-
13
- shimast "github.com/microsoft/typescript-go/shim/ast"
14
- "github.com/microsoft/typescript-go/shim/bundled"
15
- shimcompiler "github.com/microsoft/typescript-go/shim/compiler"
16
- shimcore "github.com/microsoft/typescript-go/shim/core"
17
- shimdw "github.com/microsoft/typescript-go/shim/diagnosticwriter"
18
- shimparser "github.com/microsoft/typescript-go/shim/parser"
19
- "github.com/microsoft/typescript-go/shim/tsoptions"
20
- shimtspath "github.com/microsoft/typescript-go/shim/tspath"
21
- "github.com/microsoft/typescript-go/shim/vfs/cachedvfs"
22
- "github.com/microsoft/typescript-go/shim/vfs/osvfs"
23
- )
24
-
25
- type pluginEntry struct {
26
- Config map[string]any `json:"config"`
27
- Name string `json:"name"`
28
- Stage string `json:"stage"`
29
- }
30
-
31
- type program struct {
32
- cwd string
33
- parsed *tsoptions.ParsedCommandLine
34
- tsProgram *shimcompiler.Program
35
- }
36
-
37
- type pathsResolver struct {
38
- basePath string
39
- outDir string
40
- patterns []pathsPattern
41
- rootDir string
42
- sourceFiles map[string]string
43
- }
44
-
45
- type pathsPattern struct {
46
- pattern string
47
- targets []string
48
- }
49
-
50
- type textEdit struct {
51
- start int
52
- end int
53
- text string
54
- }
55
-
56
- func RunOutput(args []string) int {
57
- fs := flag.NewFlagSet("output", flag.ContinueOnError)
58
- fs.SetOutput(os.Stderr)
59
- file := fs.String("file", "", "emitted file to transform")
60
- out := fs.String("out", "", "write transformed text to this file instead of updating --file")
61
- cwd := fs.String("cwd", "", "project directory")
62
- outDir := fs.String("outDir", "", "emit directory override")
63
- pluginsJSON := fs.String("plugins-json", "", "ttsc plugin manifest JSON")
64
- tsconfig := fs.String("tsconfig", "tsconfig.json", "project tsconfig")
65
- if err := fs.Parse(args); err != nil {
66
- return 2
67
- }
68
- if *file == "" {
69
- fmt.Fprintln(os.Stderr, "@ttsc/paths: output requires --file")
70
- return 2
71
- }
72
- if err := requireConfig(*pluginsJSON); err != nil {
73
- fmt.Fprintln(os.Stderr, err)
74
- return 2
75
- }
76
- resolvedCwd, err := resolveCwd(*cwd)
77
- if err != nil {
78
- fmt.Fprintln(os.Stderr, err)
79
- return 2
80
- }
81
- prog, parseDiags, err := loadProgram(resolvedCwd, *tsconfig, *outDir)
82
- if err != nil {
83
- fmt.Fprintf(os.Stderr, "@ttsc/paths: %v\n", err)
84
- return 2
85
- }
86
- if len(parseDiags) > 0 {
87
- shimdw.FormatASTDiagnosticsWithColorAndContext(os.Stderr, parseDiags, resolvedCwd)
88
- return 2
89
- }
90
- text, err := os.ReadFile(*file)
91
- if err != nil {
92
- fmt.Fprintf(os.Stderr, "@ttsc/paths: read %s: %v\n", *file, err)
93
- return 2
94
- }
95
- patched, err := Apply(prog, *file, string(text))
96
- if err != nil {
97
- fmt.Fprintln(os.Stderr, err)
98
- return 2
99
- }
100
- target := *file
101
- if *out != "" {
102
- target = *out
103
- }
104
- if err := os.MkdirAll(filepath.Dir(target), 0o755); err != nil {
105
- fmt.Fprintf(os.Stderr, "@ttsc/paths: mkdir: %v\n", err)
106
- return 2
107
- }
108
- if err := os.WriteFile(target, []byte(patched), 0o644); err != nil {
109
- fmt.Fprintf(os.Stderr, "@ttsc/paths: write %s: %v\n", target, err)
110
- return 2
111
- }
112
- return 0
113
- }
114
-
115
- func Apply(prog *program, fileName string, text string) (string, error) {
116
- resolver := newPathsResolver(prog)
117
- return resolver.apply(fileName, text)
118
- }
119
-
120
- func loadProgram(cwd, tsconfigPath string, outDir string) (*program, []*shimast.Diagnostic, error) {
121
- if !filepath.IsAbs(cwd) {
122
- abs, err := filepath.Abs(cwd)
123
- if err != nil {
124
- return nil, nil, fmt.Errorf("cwd: %w", err)
125
- }
126
- cwd = abs
127
- }
128
- resolved := tsconfigPath
129
- if !filepath.IsAbs(resolved) {
130
- resolved = filepath.Join(cwd, resolved)
131
- }
132
- fs := bundled.WrapFS(cachedvfs.From(osvfs.FS()))
133
- host := shimcompiler.NewCompilerHost(cwd, fs, bundled.LibPath(), nil, nil)
134
- parsed, parseDiags := tsoptions.GetParsedCommandLineOfConfigFile(
135
- resolved,
136
- &shimcore.CompilerOptions{},
137
- nil,
138
- host,
139
- nil,
140
- )
141
- if parsed == nil {
142
- return nil, nil, fmt.Errorf("tsoptions: parsed command line was nil for %s", resolved)
143
- }
144
- if len(parseDiags) > 0 {
145
- return nil, parseDiags, nil
146
- }
147
- if len(parsed.Errors) > 0 {
148
- return nil, parsed.Errors, nil
149
- }
150
- if outDir != "" {
151
- overrideOutDir(cwd, parsed, outDir)
152
- }
153
- tsProgram := shimcompiler.NewProgram(shimcompiler.ProgramOptions{
154
- Config: parsed,
155
- SingleThreaded: shimcore.TSTrue,
156
- Host: host,
157
- UseSourceOfProjectReference: true,
158
- })
159
- if tsProgram == nil {
160
- return nil, nil, errors.New("compiler.NewProgram returned nil")
161
- }
162
- return &program{cwd: cwd, parsed: parsed, tsProgram: tsProgram}, nil, nil
163
- }
164
-
165
- func (p *program) userSourceFiles() []*shimast.SourceFile {
166
- out := make([]*shimast.SourceFile, 0)
167
- for _, f := range p.tsProgram.SourceFiles() {
168
- if f == nil || f.IsDeclarationFile {
169
- continue
170
- }
171
- out = append(out, f)
172
- }
173
- return out
174
- }
175
-
176
- func newPathsResolver(prog *program) *pathsResolver {
177
- resolver := &pathsResolver{sourceFiles: map[string]string{}}
178
- if prog == nil || prog.parsed == nil || prog.parsed.ParsedConfig == nil || prog.parsed.ParsedConfig.CompilerOptions == nil {
179
- return resolver
180
- }
181
- options := prog.parsed.ParsedConfig.CompilerOptions
182
- resolver.basePath = options.GetPathsBasePath(prog.cwd)
183
- resolver.outDir = normalizeOptionalPath(options.OutDir, prog.cwd)
184
- resolver.rootDir = normalizeOptionalPath(options.RootDir, prog.cwd)
185
- files := prog.userSourceFiles()
186
- if resolver.rootDir == "" {
187
- resolver.rootDir = commonSourceDir(files)
188
- }
189
- for _, file := range files {
190
- if file == nil {
191
- continue
192
- }
193
- name := normalizePath(file.FileName())
194
- resolver.sourceFiles[name] = name
195
- resolver.sourceFiles[stripKnownSourceExtension(name)] = name
196
- }
197
- if options.Paths != nil {
198
- for pattern, targets := range options.Paths.Entries() {
199
- resolver.patterns = append(resolver.patterns, pathsPattern{
200
- pattern: pattern,
201
- targets: append([]string(nil), targets...),
202
- })
203
- }
204
- }
205
- sort.SliceStable(resolver.patterns, func(i, j int) bool {
206
- return pathsPatternRank(resolver.patterns[i].pattern) > pathsPatternRank(resolver.patterns[j].pattern)
207
- })
208
- return resolver
209
- }
210
-
211
- func (r *pathsResolver) apply(fileName string, text string) (string, error) {
212
- if r == nil || len(r.patterns) == 0 || !isPathsOutput(fileName) {
213
- return text, nil
214
- }
215
- file := parseModuleSpecifierFile(fileName, text)
216
- if file == nil {
217
- return text, nil
218
- }
219
- edits := make([]textEdit, 0)
220
- addEdit := func(lit *shimast.Node) {
221
- if lit == nil || lit.Kind != shimast.KindStringLiteral {
222
- return
223
- }
224
- specifier := lit.Text()
225
- rewritten, ok := r.rewriteSpecifier(fileName, specifier)
226
- if !ok || rewritten == specifier {
227
- return
228
- }
229
- start, end, quote, ok := stringLiteralRange(text, lit)
230
- if !ok {
231
- return
232
- }
233
- edits = append(edits, textEdit{
234
- start: start,
235
- end: end,
236
- text: quoteJSString(quote, rewritten),
237
- })
238
- }
239
- var walk func(*shimast.Node)
240
- walk = func(node *shimast.Node) {
241
- if node == nil {
242
- return
243
- }
244
- switch node.Kind {
245
- case shimast.KindImportDeclaration:
246
- addEdit(node.AsImportDeclaration().ModuleSpecifier)
247
- case shimast.KindExportDeclaration:
248
- addEdit(node.AsExportDeclaration().ModuleSpecifier)
249
- case shimast.KindImportEqualsDeclaration:
250
- ref := node.AsImportEqualsDeclaration().ModuleReference
251
- if ref != nil && ref.Kind == shimast.KindExternalModuleReference {
252
- addEdit(ref.AsExternalModuleReference().Expression)
253
- }
254
- case shimast.KindImportType:
255
- arg := node.AsImportTypeNode().Argument
256
- if arg != nil && arg.Kind == shimast.KindLiteralType {
257
- addEdit(arg.AsLiteralTypeNode().Literal)
258
- }
259
- case shimast.KindModuleDeclaration:
260
- addEdit(node.AsModuleDeclaration().Name())
261
- case shimast.KindCallExpression:
262
- call := node.AsCallExpression()
263
- if call != nil && (isRequireCall(call) || isDynamicImportCall(call)) && call.Arguments != nil && len(call.Arguments.Nodes) > 0 {
264
- addEdit(call.Arguments.Nodes[0])
265
- }
266
- }
267
- node.ForEachChild(func(child *shimast.Node) bool {
268
- walk(child)
269
- return false
270
- })
271
- }
272
- for _, stmt := range file.Statements.Nodes {
273
- walk(stmt)
274
- }
275
- return applyTextEdits(text, edits), nil
276
- }
277
-
278
- func (r *pathsResolver) rewriteSpecifier(outputFile string, specifier string) (string, bool) {
279
- if isExternalModuleNameRelative(specifier) || strings.HasPrefix(specifier, "/") {
280
- return specifier, false
281
- }
282
- for _, pattern := range r.patterns {
283
- capture, ok := matchPathPattern(pattern.pattern, specifier)
284
- if !ok || len(pattern.targets) == 0 {
285
- continue
286
- }
287
- for _, targetPattern := range pattern.targets {
288
- target := strings.ReplaceAll(targetPattern, "*", capture)
289
- source, ok := r.resolveTargetSource(target)
290
- if !ok {
291
- continue
292
- }
293
- targetOutput := r.outputPathForSource(source)
294
- relative, err := filepath.Rel(filepath.Dir(normalizePath(outputFile)), targetOutput)
295
- if err != nil {
296
- return specifier, false
297
- }
298
- relative = filepath.ToSlash(relative)
299
- if relative == "." {
300
- relative = "./" + filepath.Base(targetOutput)
301
- }
302
- if !strings.HasPrefix(relative, ".") {
303
- relative = "./" + relative
304
- }
305
- return relative, true
306
- }
307
- }
308
- return specifier, false
309
- }
310
-
311
- func (r *pathsResolver) resolveTargetSource(target string) (string, bool) {
312
- base := r.basePath
313
- if base == "" {
314
- base = "."
315
- }
316
- raw := normalizePath(filepath.Join(base, target))
317
- candidates := []string{raw}
318
- if stripKnownSourceExtension(raw) == raw {
319
- for _, ext := range knownResolvableExtensions() {
320
- candidates = append(candidates, raw+ext)
321
- }
322
- for _, ext := range knownResolvableExtensions() {
323
- candidates = append(candidates, filepath.ToSlash(filepath.Join(raw, "index"+ext)))
324
- }
325
- }
326
- for _, candidate := range candidates {
327
- normalized := normalizePath(candidate)
328
- if source, ok := r.sourceFiles[normalized]; ok {
329
- return source, true
330
- }
331
- if source, ok := r.sourceFiles[stripKnownSourceExtension(normalized)]; ok {
332
- return source, true
333
- }
334
- }
335
- return "", false
336
- }
337
-
338
- func (r *pathsResolver) outputPathForSource(source string) string {
339
- outputExt := outputExtensionForSource(source)
340
- if r.outDir == "" {
341
- return changeExtension(source, outputExt)
342
- }
343
- if r.rootDir != "" {
344
- if rel, err := filepath.Rel(r.rootDir, source); err == nil && !strings.HasPrefix(rel, "..") && !filepath.IsAbs(rel) {
345
- return normalizePath(filepath.Join(r.outDir, changeExtension(rel, outputExt)))
346
- }
347
- }
348
- return normalizePath(filepath.Join(r.outDir, filepath.Base(changeExtension(source, outputExt))))
349
- }
350
-
351
- func requireConfig(pluginsJSON string) error {
352
- if strings.TrimSpace(pluginsJSON) == "" {
353
- return fmt.Errorf("@ttsc/paths: missing --plugins-json")
354
- }
355
- var entries []pluginEntry
356
- if err := json.Unmarshal([]byte(pluginsJSON), &entries); err != nil {
357
- return fmt.Errorf("@ttsc/paths: invalid --plugins-json: %w", err)
358
- }
359
- for _, entry := range entries {
360
- if entry.Name == "@ttsc/paths" {
361
- return nil
362
- }
363
- }
364
- return fmt.Errorf("@ttsc/paths: plugin entry not found")
365
- }
366
-
367
- func resolveCwd(override string) (string, error) {
368
- if override != "" {
369
- abs, err := filepath.Abs(override)
370
- if err != nil {
371
- return "", fmt.Errorf("@ttsc/paths: --cwd: %w", err)
372
- }
373
- return abs, nil
374
- }
375
- wd, err := os.Getwd()
376
- if err != nil {
377
- return "", fmt.Errorf("@ttsc/paths: cwd: %w", err)
378
- }
379
- return wd, nil
380
- }
381
-
382
- func overrideOutDir(cwd string, parsed *tsoptions.ParsedCommandLine, outDir string) {
383
- if parsed == nil || parsed.ParsedConfig == nil || parsed.ParsedConfig.CompilerOptions == nil {
384
- return
385
- }
386
- if filepath.IsAbs(outDir) {
387
- parsed.ParsedConfig.CompilerOptions.OutDir = filepath.ToSlash(outDir)
388
- return
389
- }
390
- parsed.ParsedConfig.CompilerOptions.OutDir = filepath.ToSlash(filepath.Join(cwd, outDir))
391
- }
392
-
393
- func parseModuleSpecifierFile(fileName string, text string) *shimast.SourceFile {
394
- normalized := normalizePath(fileName)
395
- if !filepath.IsAbs(normalized) {
396
- if abs, err := filepath.Abs(normalized); err == nil {
397
- normalized = normalizePath(abs)
398
- }
399
- }
400
- opts := shimast.SourceFileParseOptions{FileName: normalized}
401
- kind := shimcore.ScriptKindJS
402
- if isDeclarationOutput(fileName) {
403
- kind = shimcore.ScriptKindTS
404
- }
405
- return shimparser.ParseSourceFile(opts, text, kind)
406
- }
407
-
408
- func isRequireCall(call *shimast.CallExpression) bool {
409
- if call == nil || call.Expression == nil || call.Expression.Kind != shimast.KindIdentifier {
410
- return false
411
- }
412
- return call.Expression.Text() == "require"
413
- }
414
-
415
- func isDynamicImportCall(call *shimast.CallExpression) bool {
416
- if call == nil || call.Expression == nil || call.Expression.Kind != shimast.KindImportKeyword {
417
- return false
418
- }
419
- return call.Arguments != nil && len(call.Arguments.Nodes) == 1
420
- }
421
-
422
- func stringLiteralRange(text string, node *shimast.Node) (int, int, byte, bool) {
423
- start := clamp(node.Pos(), 0, len(text))
424
- end := clamp(node.End(), start, len(text))
425
- for start < end && text[start] != '"' && text[start] != '\'' {
426
- start++
427
- }
428
- if start >= end {
429
- return 0, 0, 0, false
430
- }
431
- quote := text[start]
432
- for end > start+1 && text[end-1] != quote {
433
- end--
434
- }
435
- if end <= start+1 {
436
- return 0, 0, 0, false
437
- }
438
- return start, end, quote, true
439
- }
440
-
441
- func quoteJSString(quote byte, value string) string {
442
- var b strings.Builder
443
- b.WriteByte(quote)
444
- for _, r := range value {
445
- switch r {
446
- case '\\':
447
- b.WriteString(`\\`)
448
- case '\n':
449
- b.WriteString(`\n`)
450
- case '\r':
451
- b.WriteString(`\r`)
452
- case '\t':
453
- b.WriteString(`\t`)
454
- default:
455
- if byte(r) == quote && r < utf8RuneSelf {
456
- b.WriteByte('\\')
457
- b.WriteByte(byte(r))
458
- } else {
459
- b.WriteRune(r)
460
- }
461
- }
462
- }
463
- b.WriteByte(quote)
464
- return b.String()
465
- }
466
-
467
- const utf8RuneSelf = 0x80
468
-
469
- func applyTextEdits(text string, edits []textEdit) string {
470
- if len(edits) == 0 {
471
- return text
472
- }
473
- sort.SliceStable(edits, func(i, j int) bool {
474
- if edits[i].start == edits[j].start {
475
- return edits[i].end > edits[j].end
476
- }
477
- return edits[i].start > edits[j].start
478
- })
479
- out := text
480
- lastStart := len(text) + 1
481
- for _, edit := range edits {
482
- if edit.start < 0 || edit.end < edit.start || edit.start > len(out) {
483
- continue
484
- }
485
- if edit.end > lastStart {
486
- edit.end = lastStart
487
- }
488
- if edit.end > len(out) {
489
- edit.end = len(out)
490
- }
491
- out = out[:edit.start] + edit.text + out[edit.end:]
492
- lastStart = edit.start
493
- }
494
- return out
495
- }
496
-
497
- func matchPathPattern(pattern string, specifier string) (string, bool) {
498
- star := strings.Index(pattern, "*")
499
- if star < 0 {
500
- return "", pattern == specifier
501
- }
502
- prefix := pattern[:star]
503
- suffix := pattern[star+1:]
504
- if !strings.HasPrefix(specifier, prefix) || !strings.HasSuffix(specifier, suffix) {
505
- return "", false
506
- }
507
- return specifier[len(prefix) : len(specifier)-len(suffix)], true
508
- }
509
-
510
- func commonSourceDir(files []*shimast.SourceFile) string {
511
- var common string
512
- for _, file := range files {
513
- if file == nil {
514
- continue
515
- }
516
- dir := filepath.Dir(normalizePath(file.FileName()))
517
- if common == "" {
518
- common = dir
519
- continue
520
- }
521
- common = commonPathPrefix(common, dir)
522
- }
523
- return common
524
- }
525
-
526
- func commonPathPrefix(a string, b string) string {
527
- aParts := strings.Split(normalizePath(a), "/")
528
- bParts := strings.Split(normalizePath(b), "/")
529
- n := len(aParts)
530
- if len(bParts) < n {
531
- n = len(bParts)
532
- }
533
- i := 0
534
- for i < n && aParts[i] == bParts[i] {
535
- i++
536
- }
537
- if i == 0 {
538
- return ""
539
- }
540
- return strings.Join(aParts[:i], "/")
541
- }
542
-
543
- func normalizeOptionalPath(value string, cwd string) string {
544
- if value == "" {
545
- return ""
546
- }
547
- if filepath.IsAbs(value) {
548
- return normalizePath(value)
549
- }
550
- return normalizePath(filepath.Join(cwd, value))
551
- }
552
-
553
- func normalizePath(value string) string {
554
- return filepath.ToSlash(shimtspath.NormalizePath(value))
555
- }
556
-
557
- func stripKnownSourceExtension(value string) string {
558
- for _, ext := range []string{".d.ts", ".d.mts", ".d.cts", ".tsx", ".ts", ".mts", ".cts", ".jsx", ".js", ".json"} {
559
- if strings.HasSuffix(value, ext) {
560
- return strings.TrimSuffix(value, ext)
561
- }
562
- }
563
- return value
564
- }
565
-
566
- func outputExtensionForSource(source string) string {
567
- switch strings.ToLower(filepath.Ext(source)) {
568
- case ".mts":
569
- return ".mjs"
570
- case ".cts":
571
- return ".cjs"
572
- case ".json":
573
- return ".json"
574
- default:
575
- return ".js"
576
- }
577
- }
578
-
579
- func changeExtension(value string, ext string) string {
580
- return strings.TrimSuffix(value, filepath.Ext(value)) + ext
581
- }
582
-
583
- func isExternalModuleNameRelative(specifier string) bool {
584
- return strings.HasPrefix(specifier, "./") ||
585
- strings.HasPrefix(specifier, "../") ||
586
- specifier == "." ||
587
- specifier == ".."
588
- }
589
-
590
- func isPathsOutput(fileName string) bool {
591
- return isJavaScriptOutput(fileName) || isDeclarationOutput(fileName)
592
- }
593
-
594
- func isJavaScriptOutput(fileName string) bool {
595
- switch strings.ToLower(filepath.Ext(fileName)) {
596
- case ".js", ".mjs", ".cjs":
597
- return true
598
- default:
599
- return false
600
- }
601
- }
602
-
603
- func isDeclarationOutput(fileName string) bool {
604
- lower := strings.ToLower(fileName)
605
- return strings.HasSuffix(lower, ".d.ts") ||
606
- strings.HasSuffix(lower, ".d.mts") ||
607
- strings.HasSuffix(lower, ".d.cts")
608
- }
609
-
610
- func knownResolvableExtensions() []string {
611
- return []string{".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".json"}
612
- }
613
-
614
- func pathsPatternRank(pattern string) int {
615
- star := strings.Index(pattern, "*")
616
- if star < 0 {
617
- return 1_000_000 + len(pattern)
618
- }
619
- prefix := len(pattern[:star])
620
- suffix := len(pattern[star+1:])
621
- return prefix*1_000 + suffix*10 + len(pattern)
622
- }
623
-
624
- func clamp(value int, min int, max int) int {
625
- if value < min {
626
- return min
627
- }
628
- if value > max {
629
- return max
630
- }
631
- return value
632
- }
3
+ // Paths transform logic lives in github.com/samchon/ttsc/packages/ttsc/utility.
package/src/index.cjs CHANGED
@@ -7,6 +7,10 @@ module.exports = function createTtscPaths() {
7
7
  return {
8
8
  name: "@ttsc/paths",
9
9
  source: path.resolve(__dirname, "..", "plugin"),
10
- stage: "output",
10
+ stage: "transform",
11
+ hooks: {
12
+ source: true,
13
+ declaration: true,
14
+ },
11
15
  };
12
16
  };