@ttsc/paths 0.7.1 → 0.7.3
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/package.json +1 -1
- package/plugin/main.go +26 -19
- package/plugin/paths.go +542 -512
package/package.json
CHANGED
package/plugin/main.go
CHANGED
|
@@ -1,31 +1,38 @@
|
|
|
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.
|
|
1
6
|
package main
|
|
2
7
|
|
|
3
8
|
import (
|
|
4
|
-
|
|
5
|
-
|
|
9
|
+
"fmt"
|
|
10
|
+
"os"
|
|
6
11
|
)
|
|
7
12
|
|
|
8
13
|
const version = "0.0.1"
|
|
9
14
|
|
|
10
15
|
func main() {
|
|
11
|
-
|
|
16
|
+
os.Exit(run(os.Args[1:]))
|
|
12
17
|
}
|
|
13
18
|
|
|
14
19
|
func run(args []string) int {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
20
|
+
if len(args) == 0 {
|
|
21
|
+
fmt.Fprintln(os.Stderr, "@ttsc/paths: command required (expected output|version)")
|
|
22
|
+
return 2
|
|
23
|
+
}
|
|
24
|
+
switch args[0] {
|
|
25
|
+
case "-v", "--version", "version":
|
|
26
|
+
fmt.Fprintf(os.Stdout, "@ttsc/paths %s\n", version)
|
|
27
|
+
return 0
|
|
28
|
+
case "check":
|
|
29
|
+
// Path rewriting depends on emitted output, so check has no standalone
|
|
30
|
+
// diagnostics beyond command availability.
|
|
31
|
+
return 0
|
|
32
|
+
case "output":
|
|
33
|
+
return RunOutput(args[1:])
|
|
34
|
+
default:
|
|
35
|
+
fmt.Fprintf(os.Stderr, "@ttsc/paths: unknown command %q\n", args[0])
|
|
36
|
+
return 2
|
|
37
|
+
}
|
|
31
38
|
}
|
package/plugin/paths.go
CHANGED
|
@@ -1,632 +1,662 @@
|
|
|
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.
|
|
1
7
|
package main
|
|
2
8
|
|
|
3
9
|
import (
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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"
|
|
23
29
|
)
|
|
24
30
|
|
|
25
31
|
type pluginEntry struct {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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"`
|
|
29
37
|
}
|
|
30
38
|
|
|
31
39
|
type program struct {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
40
|
+
// parsed owns the compilerOptions used for paths/baseUrl/rootDir/outDir.
|
|
41
|
+
cwd string
|
|
42
|
+
parsed *tsoptions.ParsedCommandLine
|
|
43
|
+
tsProgram *shimcompiler.Program
|
|
35
44
|
}
|
|
36
45
|
|
|
37
46
|
type pathsResolver struct {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
|
43
54
|
}
|
|
44
55
|
|
|
45
56
|
type pathsPattern struct {
|
|
46
|
-
|
|
47
|
-
|
|
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
|
|
48
61
|
}
|
|
49
62
|
|
|
50
63
|
type textEdit struct {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
64
|
+
start int
|
|
65
|
+
end int
|
|
66
|
+
text string
|
|
54
67
|
}
|
|
55
68
|
|
|
56
69
|
func RunOutput(args []string) int {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
|
113
126
|
}
|
|
114
127
|
|
|
115
128
|
func Apply(prog *program, fileName string, text string) (string, error) {
|
|
116
|
-
|
|
117
|
-
|
|
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)
|
|
118
133
|
}
|
|
119
134
|
|
|
120
135
|
func loadProgram(cwd, tsconfigPath string, outDir string) (*program, []*shimast.Diagnostic, error) {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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
|
|
163
180
|
}
|
|
164
181
|
|
|
165
182
|
func (p *program) userSourceFiles() []*shimast.SourceFile {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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
|
|
174
191
|
}
|
|
175
192
|
|
|
176
193
|
func newPathsResolver(prog *program) *pathsResolver {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
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
|
|
209
230
|
}
|
|
210
231
|
|
|
211
232
|
func (r *pathsResolver) apply(fileName string, text string) (string, error) {
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
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
|
|
276
299
|
}
|
|
277
300
|
|
|
278
301
|
func (r *pathsResolver) rewriteSpecifier(outputFile string, specifier string) (string, bool) {
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
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
|
|
309
335
|
}
|
|
310
336
|
|
|
311
337
|
func (r *pathsResolver) resolveTargetSource(target string) (string, bool) {
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
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
|
|
336
364
|
}
|
|
337
365
|
|
|
338
366
|
func (r *pathsResolver) outputPathForSource(source string) string {
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
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))))
|
|
349
377
|
}
|
|
350
378
|
|
|
351
379
|
func requireConfig(pluginsJSON string) error {
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
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")
|
|
365
393
|
}
|
|
366
394
|
|
|
367
395
|
func resolveCwd(override string) (string, error) {
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
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
|
|
380
408
|
}
|
|
381
409
|
|
|
382
410
|
func overrideOutDir(cwd string, parsed *tsoptions.ParsedCommandLine, outDir string) {
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
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))
|
|
391
419
|
}
|
|
392
420
|
|
|
393
421
|
func parseModuleSpecifierFile(fileName string, text string) *shimast.SourceFile {
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
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)
|
|
406
434
|
}
|
|
407
435
|
|
|
408
436
|
func isRequireCall(call *shimast.CallExpression) bool {
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
437
|
+
if call == nil || call.Expression == nil || call.Expression.Kind != shimast.KindIdentifier {
|
|
438
|
+
return false
|
|
439
|
+
}
|
|
440
|
+
return call.Expression.Text() == "require"
|
|
413
441
|
}
|
|
414
442
|
|
|
415
443
|
func isDynamicImportCall(call *shimast.CallExpression) bool {
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
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
|
|
420
448
|
}
|
|
421
449
|
|
|
422
450
|
func stringLiteralRange(text string, node *shimast.Node) (int, int, byte, bool) {
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
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
|
|
439
467
|
}
|
|
440
468
|
|
|
441
469
|
func quoteJSString(quote byte, value string) string {
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
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()
|
|
465
493
|
}
|
|
466
494
|
|
|
467
495
|
const utf8RuneSelf = 0x80
|
|
468
496
|
|
|
469
497
|
func applyTextEdits(text string, edits []textEdit) string {
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
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
|
|
495
525
|
}
|
|
496
526
|
|
|
497
527
|
func matchPathPattern(pattern string, specifier string) (string, bool) {
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
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
|
|
508
538
|
}
|
|
509
539
|
|
|
510
540
|
func commonSourceDir(files []*shimast.SourceFile) string {
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
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
|
|
524
554
|
}
|
|
525
555
|
|
|
526
556
|
func commonPathPrefix(a string, b string) string {
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
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], "/")
|
|
541
571
|
}
|
|
542
572
|
|
|
543
573
|
func normalizeOptionalPath(value string, cwd string) string {
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
574
|
+
if value == "" {
|
|
575
|
+
return ""
|
|
576
|
+
}
|
|
577
|
+
if filepath.IsAbs(value) {
|
|
578
|
+
return normalizePath(value)
|
|
579
|
+
}
|
|
580
|
+
return normalizePath(filepath.Join(cwd, value))
|
|
551
581
|
}
|
|
552
582
|
|
|
553
583
|
func normalizePath(value string) string {
|
|
554
|
-
|
|
584
|
+
return filepath.ToSlash(shimtspath.NormalizePath(value))
|
|
555
585
|
}
|
|
556
586
|
|
|
557
587
|
func stripKnownSourceExtension(value string) string {
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
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
|
|
564
594
|
}
|
|
565
595
|
|
|
566
596
|
func outputExtensionForSource(source string) string {
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
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
|
+
}
|
|
577
607
|
}
|
|
578
608
|
|
|
579
609
|
func changeExtension(value string, ext string) string {
|
|
580
|
-
|
|
610
|
+
return strings.TrimSuffix(value, filepath.Ext(value)) + ext
|
|
581
611
|
}
|
|
582
612
|
|
|
583
613
|
func isExternalModuleNameRelative(specifier string) bool {
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
614
|
+
return strings.HasPrefix(specifier, "./") ||
|
|
615
|
+
strings.HasPrefix(specifier, "../") ||
|
|
616
|
+
specifier == "." ||
|
|
617
|
+
specifier == ".."
|
|
588
618
|
}
|
|
589
619
|
|
|
590
620
|
func isPathsOutput(fileName string) bool {
|
|
591
|
-
|
|
621
|
+
return isJavaScriptOutput(fileName) || isDeclarationOutput(fileName)
|
|
592
622
|
}
|
|
593
623
|
|
|
594
624
|
func isJavaScriptOutput(fileName string) bool {
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
625
|
+
switch strings.ToLower(filepath.Ext(fileName)) {
|
|
626
|
+
case ".js", ".mjs", ".cjs":
|
|
627
|
+
return true
|
|
628
|
+
default:
|
|
629
|
+
return false
|
|
630
|
+
}
|
|
601
631
|
}
|
|
602
632
|
|
|
603
633
|
func isDeclarationOutput(fileName string) bool {
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
634
|
+
lower := strings.ToLower(fileName)
|
|
635
|
+
return strings.HasSuffix(lower, ".d.ts") ||
|
|
636
|
+
strings.HasSuffix(lower, ".d.mts") ||
|
|
637
|
+
strings.HasSuffix(lower, ".d.cts")
|
|
608
638
|
}
|
|
609
639
|
|
|
610
640
|
func knownResolvableExtensions() []string {
|
|
611
|
-
|
|
641
|
+
return []string{".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".json"}
|
|
612
642
|
}
|
|
613
643
|
|
|
614
644
|
func pathsPatternRank(pattern string) int {
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
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)
|
|
622
652
|
}
|
|
623
653
|
|
|
624
654
|
func clamp(value int, min int, max int) int {
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
655
|
+
if value < min {
|
|
656
|
+
return min
|
|
657
|
+
}
|
|
658
|
+
if value > max {
|
|
659
|
+
return max
|
|
660
|
+
}
|
|
661
|
+
return value
|
|
632
662
|
}
|