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