@nestia/core 12.0.0-rc.2 → 12.0.0-rc.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.
Files changed (80) hide show
  1. package/LICENSE +21 -21
  2. package/MIGRATION.md +138 -169
  3. package/lib/decorators/NoTransformConfigurationError.js +5 -5
  4. package/lib/decorators/NoTransformConfigurationError.js.map +1 -1
  5. package/native/cmd/ttsc-nestia/main.go +11 -11
  6. package/native/go.mod +32 -32
  7. package/native/go.sum +54 -54
  8. package/native/plugin/plan.go +102 -102
  9. package/native/transform/ast.go +32 -32
  10. package/native/transform/build.go +388 -380
  11. package/native/transform/cleanup.go +408 -408
  12. package/native/transform/contributor.go +97 -97
  13. package/native/transform/core_querify.go +231 -231
  14. package/native/transform/core_transform.go +1996 -1996
  15. package/native/transform/core_websocket.go +115 -115
  16. package/native/transform/exports.go +13 -13
  17. package/native/transform/mcp_transform.go +414 -414
  18. package/native/transform/node_transform.go +357 -357
  19. package/native/transform/path_rewrite.go +285 -285
  20. package/native/transform/printer.go +244 -244
  21. package/native/transform/rewrite.go +668 -668
  22. package/native/transform/run.go +73 -73
  23. package/native/transform/transform.go +336 -336
  24. package/native/transform/typia_fast.go +375 -352
  25. package/native/transform/typia_replacement.go +24 -24
  26. package/native/transform.cjs +43 -43
  27. package/package.json +9 -9
  28. package/src/adaptors/McpAdaptor.ts +276 -276
  29. package/src/adaptors/WebSocketAdaptor.ts +429 -429
  30. package/src/decorators/DynamicModule.ts +44 -44
  31. package/src/decorators/EncryptedBody.ts +97 -97
  32. package/src/decorators/EncryptedController.ts +40 -40
  33. package/src/decorators/EncryptedModule.ts +98 -98
  34. package/src/decorators/EncryptedRoute.ts +213 -213
  35. package/src/decorators/HumanRoute.ts +21 -21
  36. package/src/decorators/McpRoute.ts +154 -154
  37. package/src/decorators/NoTransformConfigurationError.ts +40 -40
  38. package/src/decorators/PlainBody.ts +76 -76
  39. package/src/decorators/SwaggerCustomizer.ts +97 -97
  40. package/src/decorators/SwaggerExample.ts +180 -180
  41. package/src/decorators/TypedBody.ts +57 -57
  42. package/src/decorators/TypedException.ts +147 -147
  43. package/src/decorators/TypedFormData.ts +187 -187
  44. package/src/decorators/TypedHeaders.ts +66 -66
  45. package/src/decorators/TypedParam.ts +77 -77
  46. package/src/decorators/TypedQuery.ts +234 -234
  47. package/src/decorators/TypedRoute.ts +198 -198
  48. package/src/decorators/WebSocketRoute.ts +242 -242
  49. package/src/decorators/doNotThrowTransformError.ts +5 -5
  50. package/src/decorators/internal/EncryptedConstant.ts +2 -2
  51. package/src/decorators/internal/IMcpRouteReflect.ts +40 -40
  52. package/src/decorators/internal/IWebSocketRouteReflect.ts +23 -23
  53. package/src/decorators/internal/get_path_and_querify.ts +94 -94
  54. package/src/decorators/internal/get_path_and_stringify.ts +110 -110
  55. package/src/decorators/internal/get_text_body.ts +16 -16
  56. package/src/decorators/internal/headers_to_object.ts +11 -11
  57. package/src/decorators/internal/is_request_body_undefined.ts +12 -12
  58. package/src/decorators/internal/load_controller.ts +94 -94
  59. package/src/decorators/internal/route_error.ts +43 -43
  60. package/src/decorators/internal/validate_request_body.ts +64 -64
  61. package/src/decorators/internal/validate_request_form_data.ts +67 -67
  62. package/src/decorators/internal/validate_request_headers.ts +76 -76
  63. package/src/decorators/internal/validate_request_query.ts +83 -83
  64. package/src/index.ts +5 -5
  65. package/src/module.ts +25 -25
  66. package/src/options/IRequestBodyValidator.ts +20 -20
  67. package/src/options/IRequestFormDataProps.ts +27 -27
  68. package/src/options/IRequestHeadersValidator.ts +22 -22
  69. package/src/options/IRequestQueryValidator.ts +20 -20
  70. package/src/options/IResponseBodyQuerifier.ts +25 -25
  71. package/src/options/IResponseBodyStringifier.ts +30 -30
  72. package/src/transform.ts +26 -26
  73. package/src/typings/Creator.ts +3 -3
  74. package/src/typings/get-function-location.d.ts +7 -7
  75. package/src/utils/ArrayUtil.ts +7 -7
  76. package/src/utils/ExceptionManager.ts +115 -115
  77. package/src/utils/Singleton.ts +16 -16
  78. package/src/utils/SourceFinder.ts +54 -54
  79. package/src/utils/VersioningStrategy.ts +27 -27
  80. package/README.md +0 -93
@@ -1,285 +1,285 @@
1
- package transform
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
- "github.com/samchon/ttsc/packages/ttsc/driver"
11
- )
12
-
13
- type pathsRewriter struct {
14
- basePath string
15
- outDir string
16
- patterns []pathsPattern
17
- rootDir string
18
- sourceFiles map[string]string
19
- }
20
-
21
- type pathsPattern struct {
22
- pattern string
23
- targets []string
24
- }
25
-
26
- func newPathsRewriter(prog *driver.Program) *pathsRewriter {
27
- out := &pathsRewriter{sourceFiles: map[string]string{}}
28
- if prog == nil || prog.ParsedConfig == nil || prog.ParsedConfig.ParsedConfig == nil || prog.ParsedConfig.ParsedConfig.CompilerOptions == nil {
29
- return out
30
- }
31
- options := prog.ParsedConfig.ParsedConfig.CompilerOptions
32
- out.basePath = filepath.Clean(options.GetPathsBasePath(prog.Host.GetCurrentDirectory()))
33
- out.outDir = optionalPath(options.OutDir, prog.Host.GetCurrentDirectory())
34
- out.rootDir = optionalPath(options.RootDir, prog.Host.GetCurrentDirectory())
35
- files := prog.SourceFiles()
36
- if out.rootDir == "" {
37
- out.rootDir = commonSourceDir(files)
38
- }
39
- for _, file := range files {
40
- name := normalizePath(file.FileName())
41
- out.sourceFiles[name] = name
42
- out.sourceFiles[stripKnownSourceExtension(name)] = name
43
- }
44
- if options.Paths != nil {
45
- for pattern, targets := range options.Paths.Entries() {
46
- out.patterns = append(out.patterns, pathsPattern{
47
- pattern: pattern,
48
- targets: append([]string(nil), targets...),
49
- })
50
- }
51
- }
52
- sort.SliceStable(out.patterns, func(i, j int) bool {
53
- return patternRank(out.patterns[i].pattern) > patternRank(out.patterns[j].pattern)
54
- })
55
- return out
56
- }
57
-
58
- func (r *pathsRewriter) applyAll(files []*shimast.SourceFile) {
59
- if r == nil || len(r.patterns) == 0 {
60
- return
61
- }
62
- for _, file := range files {
63
- r.apply(file)
64
- }
65
- }
66
-
67
- func (r *pathsRewriter) apply(file *shimast.SourceFile) {
68
- if r == nil || file == nil || len(r.patterns) == 0 {
69
- return
70
- }
71
- visitModuleSpecifiers(file.AsNode(), func(lit *shimast.Node) {
72
- if lit == nil || lit.Kind != shimast.KindStringLiteral {
73
- return
74
- }
75
- spec := lit.Text()
76
- rewritten, ok := r.rewrite(file.FileName(), spec)
77
- if ok && rewritten != spec {
78
- lit.AsStringLiteral().Text = rewritten
79
- lit.Flags |= shimast.NodeFlagsSynthesized
80
- lit.Loc = shimcore.UndefinedTextRange()
81
- }
82
- })
83
- }
84
-
85
- func visitModuleSpecifiers(node *shimast.Node, visit func(*shimast.Node)) {
86
- if node == nil {
87
- return
88
- }
89
- switch node.Kind {
90
- case shimast.KindImportDeclaration:
91
- visit(node.AsImportDeclaration().ModuleSpecifier)
92
- case shimast.KindExportDeclaration:
93
- visit(node.AsExportDeclaration().ModuleSpecifier)
94
- case shimast.KindImportEqualsDeclaration:
95
- ref := node.AsImportEqualsDeclaration().ModuleReference
96
- if ref != nil && ref.Kind == shimast.KindExternalModuleReference {
97
- visit(ref.AsExternalModuleReference().Expression)
98
- }
99
- case shimast.KindImportType:
100
- arg := node.AsImportTypeNode().Argument
101
- if arg != nil && arg.Kind == shimast.KindLiteralType {
102
- visit(arg.AsLiteralTypeNode().Literal)
103
- }
104
- case shimast.KindModuleDeclaration:
105
- decl := node.AsModuleDeclaration()
106
- if decl != nil {
107
- visit(decl.Name())
108
- }
109
- case shimast.KindCallExpression:
110
- call := node.AsCallExpression()
111
- if isModuleSpecifierCall(call) && call.Arguments != nil && len(call.Arguments.Nodes) > 0 {
112
- visit(call.Arguments.Nodes[0])
113
- }
114
- }
115
- node.ForEachChild(func(child *shimast.Node) bool {
116
- visitModuleSpecifiers(child, visit)
117
- return false
118
- })
119
- }
120
-
121
- func isModuleSpecifierCall(call *shimast.CallExpression) bool {
122
- if call == nil || call.Expression == nil {
123
- return false
124
- }
125
- switch call.Expression.Kind {
126
- case shimast.KindImportKeyword:
127
- return true
128
- case shimast.KindIdentifier:
129
- return call.Expression.Text() == "require"
130
- default:
131
- return false
132
- }
133
- }
134
-
135
- func (r *pathsRewriter) rewrite(fromSource string, specifier string) (string, bool) {
136
- if specifier == "" || strings.HasPrefix(specifier, ".") || strings.HasPrefix(specifier, "/") {
137
- return specifier, false
138
- }
139
- targetSource, ok := r.resolveSource(specifier)
140
- if !ok {
141
- return specifier, false
142
- }
143
- fromOut := r.outputPathForSource(fromSource)
144
- targetOut := r.outputPathForSource(targetSource)
145
- if fromOut == "" || targetOut == "" {
146
- return specifier, false
147
- }
148
- rel, err := filepath.Rel(filepath.Dir(fromOut), targetOut)
149
- if err != nil {
150
- return specifier, false
151
- }
152
- rel = filepath.ToSlash(rel)
153
- if !strings.HasPrefix(rel, ".") {
154
- rel = "./" + rel
155
- }
156
- return rel, true
157
- }
158
-
159
- func (r *pathsRewriter) resolveSource(specifier string) (string, bool) {
160
- for _, pattern := range r.patterns {
161
- star, ok := matchPattern(pattern.pattern, specifier)
162
- if !ok {
163
- continue
164
- }
165
- for _, target := range pattern.targets {
166
- candidate := strings.Replace(target, "*", star, 1)
167
- resolved := normalizePath(filepath.Join(r.basePath, candidate))
168
- if source, ok := r.lookupSource(resolved); ok {
169
- return source, true
170
- }
171
- }
172
- }
173
- return "", false
174
- }
175
-
176
- func (r *pathsRewriter) lookupSource(candidate string) (string, bool) {
177
- if source, ok := r.sourceFiles[normalizePath(candidate)]; ok {
178
- return source, true
179
- }
180
- stem := stripKnownSourceExtension(normalizePath(candidate))
181
- if source, ok := r.sourceFiles[stem]; ok {
182
- return source, true
183
- }
184
- for _, ext := range []string{".ts", ".tsx", ".mts", ".cts"} {
185
- if source, ok := r.sourceFiles[stem+ext]; ok {
186
- return source, true
187
- }
188
- }
189
- for _, ext := range []string{".ts", ".tsx", ".mts", ".cts"} {
190
- if source, ok := r.sourceFiles[normalizePath(filepath.Join(stem, "index"+ext))]; ok {
191
- return source, true
192
- }
193
- }
194
- return "", false
195
- }
196
-
197
- func (r *pathsRewriter) outputPathForSource(source string) string {
198
- if r.outDir == "" || r.rootDir == "" {
199
- return ""
200
- }
201
- rel, err := filepath.Rel(r.rootDir, source)
202
- if err != nil || isOutsideRelativePath(rel) {
203
- return ""
204
- }
205
- return normalizePath(filepath.Join(r.outDir, replaceSourceExtension(rel, emittedJavaScriptExtension(rel))))
206
- }
207
-
208
- func emittedJavaScriptExtension(source string) string {
209
- switch strings.ToLower(filepath.Ext(source)) {
210
- case ".mts":
211
- return ".mjs"
212
- case ".cts":
213
- return ".cjs"
214
- default:
215
- return ".js"
216
- }
217
- }
218
-
219
- func matchPattern(pattern string, specifier string) (string, bool) {
220
- if !strings.Contains(pattern, "*") {
221
- return "", pattern == specifier
222
- }
223
- parts := strings.SplitN(pattern, "*", 2)
224
- if !strings.HasPrefix(specifier, parts[0]) || !strings.HasSuffix(specifier, parts[1]) {
225
- return "", false
226
- }
227
- return specifier[len(parts[0]) : len(specifier)-len(parts[1])], true
228
- }
229
-
230
- func patternRank(pattern string) int {
231
- return len(strings.ReplaceAll(pattern, "*", ""))
232
- }
233
-
234
- func optionalPath(value string, cwd string) string {
235
- if value == "" {
236
- return ""
237
- }
238
- if filepath.IsAbs(value) {
239
- return normalizePath(value)
240
- }
241
- return normalizePath(filepath.Join(cwd, value))
242
- }
243
-
244
- func commonSourceDir(files []*shimast.SourceFile) string {
245
- if len(files) == 0 {
246
- return ""
247
- }
248
- common := normalizePath(filepath.Dir(files[0].FileName()))
249
- for _, file := range files[1:] {
250
- dir := normalizePath(filepath.Dir(file.FileName()))
251
- for common != "" && !strings.HasPrefix(dir+"/", common+"/") {
252
- next := filepath.Dir(common)
253
- if next == common {
254
- return common
255
- }
256
- common = normalizePath(next)
257
- }
258
- }
259
- return common
260
- }
261
-
262
- func normalizePath(value string) string {
263
- if value == "" {
264
- return ""
265
- }
266
- return filepath.ToSlash(filepath.Clean(value))
267
- }
268
-
269
- func isOutsideRelativePath(rel string) bool {
270
- return rel == ".." || strings.HasPrefix(filepath.ToSlash(rel), "../")
271
- }
272
-
273
- func stripKnownSourceExtension(value string) string {
274
- lower := strings.ToLower(value)
275
- for _, ext := range []string{".d.ts", ".d.mts", ".d.cts", ".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"} {
276
- if strings.HasSuffix(lower, ext) {
277
- return value[:len(value)-len(ext)]
278
- }
279
- }
280
- return strings.TrimSuffix(value, filepath.Ext(value))
281
- }
282
-
283
- func replaceSourceExtension(value string, ext string) string {
284
- return stripKnownSourceExtension(filepath.ToSlash(value)) + ext
285
- }
1
+ package transform
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
+ "github.com/samchon/ttsc/packages/ttsc/driver"
11
+ )
12
+
13
+ type pathsRewriter struct {
14
+ basePath string
15
+ outDir string
16
+ patterns []pathsPattern
17
+ rootDir string
18
+ sourceFiles map[string]string
19
+ }
20
+
21
+ type pathsPattern struct {
22
+ pattern string
23
+ targets []string
24
+ }
25
+
26
+ func newPathsRewriter(prog *driver.Program) *pathsRewriter {
27
+ out := &pathsRewriter{sourceFiles: map[string]string{}}
28
+ if prog == nil || prog.ParsedConfig == nil || prog.ParsedConfig.ParsedConfig == nil || prog.ParsedConfig.ParsedConfig.CompilerOptions == nil {
29
+ return out
30
+ }
31
+ options := prog.ParsedConfig.ParsedConfig.CompilerOptions
32
+ out.basePath = filepath.Clean(options.GetPathsBasePath(prog.Host.GetCurrentDirectory()))
33
+ out.outDir = optionalPath(options.OutDir, prog.Host.GetCurrentDirectory())
34
+ out.rootDir = optionalPath(options.RootDir, prog.Host.GetCurrentDirectory())
35
+ files := prog.SourceFiles()
36
+ if out.rootDir == "" {
37
+ out.rootDir = commonSourceDir(files)
38
+ }
39
+ for _, file := range files {
40
+ name := normalizePath(file.FileName())
41
+ out.sourceFiles[name] = name
42
+ out.sourceFiles[stripKnownSourceExtension(name)] = name
43
+ }
44
+ if options.Paths != nil {
45
+ for pattern, targets := range options.Paths.Entries() {
46
+ out.patterns = append(out.patterns, pathsPattern{
47
+ pattern: pattern,
48
+ targets: append([]string(nil), targets...),
49
+ })
50
+ }
51
+ }
52
+ sort.SliceStable(out.patterns, func(i, j int) bool {
53
+ return patternRank(out.patterns[i].pattern) > patternRank(out.patterns[j].pattern)
54
+ })
55
+ return out
56
+ }
57
+
58
+ func (r *pathsRewriter) applyAll(files []*shimast.SourceFile) {
59
+ if r == nil || len(r.patterns) == 0 {
60
+ return
61
+ }
62
+ for _, file := range files {
63
+ r.apply(file)
64
+ }
65
+ }
66
+
67
+ func (r *pathsRewriter) apply(file *shimast.SourceFile) {
68
+ if r == nil || file == nil || len(r.patterns) == 0 {
69
+ return
70
+ }
71
+ visitModuleSpecifiers(file.AsNode(), func(lit *shimast.Node) {
72
+ if lit == nil || lit.Kind != shimast.KindStringLiteral {
73
+ return
74
+ }
75
+ spec := lit.Text()
76
+ rewritten, ok := r.rewrite(file.FileName(), spec)
77
+ if ok && rewritten != spec {
78
+ lit.AsStringLiteral().Text = rewritten
79
+ lit.Flags |= shimast.NodeFlagsSynthesized
80
+ lit.Loc = shimcore.UndefinedTextRange()
81
+ }
82
+ })
83
+ }
84
+
85
+ func visitModuleSpecifiers(node *shimast.Node, visit func(*shimast.Node)) {
86
+ if node == nil {
87
+ return
88
+ }
89
+ switch node.Kind {
90
+ case shimast.KindImportDeclaration:
91
+ visit(node.AsImportDeclaration().ModuleSpecifier)
92
+ case shimast.KindExportDeclaration:
93
+ visit(node.AsExportDeclaration().ModuleSpecifier)
94
+ case shimast.KindImportEqualsDeclaration:
95
+ ref := node.AsImportEqualsDeclaration().ModuleReference
96
+ if ref != nil && ref.Kind == shimast.KindExternalModuleReference {
97
+ visit(ref.AsExternalModuleReference().Expression)
98
+ }
99
+ case shimast.KindImportType:
100
+ arg := node.AsImportTypeNode().Argument
101
+ if arg != nil && arg.Kind == shimast.KindLiteralType {
102
+ visit(arg.AsLiteralTypeNode().Literal)
103
+ }
104
+ case shimast.KindModuleDeclaration:
105
+ decl := node.AsModuleDeclaration()
106
+ if decl != nil {
107
+ visit(decl.Name())
108
+ }
109
+ case shimast.KindCallExpression:
110
+ call := node.AsCallExpression()
111
+ if isModuleSpecifierCall(call) && call.Arguments != nil && len(call.Arguments.Nodes) > 0 {
112
+ visit(call.Arguments.Nodes[0])
113
+ }
114
+ }
115
+ node.ForEachChild(func(child *shimast.Node) bool {
116
+ visitModuleSpecifiers(child, visit)
117
+ return false
118
+ })
119
+ }
120
+
121
+ func isModuleSpecifierCall(call *shimast.CallExpression) bool {
122
+ if call == nil || call.Expression == nil {
123
+ return false
124
+ }
125
+ switch call.Expression.Kind {
126
+ case shimast.KindImportKeyword:
127
+ return true
128
+ case shimast.KindIdentifier:
129
+ return call.Expression.Text() == "require"
130
+ default:
131
+ return false
132
+ }
133
+ }
134
+
135
+ func (r *pathsRewriter) rewrite(fromSource string, specifier string) (string, bool) {
136
+ if specifier == "" || strings.HasPrefix(specifier, ".") || strings.HasPrefix(specifier, "/") {
137
+ return specifier, false
138
+ }
139
+ targetSource, ok := r.resolveSource(specifier)
140
+ if !ok {
141
+ return specifier, false
142
+ }
143
+ fromOut := r.outputPathForSource(fromSource)
144
+ targetOut := r.outputPathForSource(targetSource)
145
+ if fromOut == "" || targetOut == "" {
146
+ return specifier, false
147
+ }
148
+ rel, err := filepath.Rel(filepath.Dir(fromOut), targetOut)
149
+ if err != nil {
150
+ return specifier, false
151
+ }
152
+ rel = filepath.ToSlash(rel)
153
+ if !strings.HasPrefix(rel, ".") {
154
+ rel = "./" + rel
155
+ }
156
+ return rel, true
157
+ }
158
+
159
+ func (r *pathsRewriter) resolveSource(specifier string) (string, bool) {
160
+ for _, pattern := range r.patterns {
161
+ star, ok := matchPattern(pattern.pattern, specifier)
162
+ if !ok {
163
+ continue
164
+ }
165
+ for _, target := range pattern.targets {
166
+ candidate := strings.Replace(target, "*", star, 1)
167
+ resolved := normalizePath(filepath.Join(r.basePath, candidate))
168
+ if source, ok := r.lookupSource(resolved); ok {
169
+ return source, true
170
+ }
171
+ }
172
+ }
173
+ return "", false
174
+ }
175
+
176
+ func (r *pathsRewriter) lookupSource(candidate string) (string, bool) {
177
+ if source, ok := r.sourceFiles[normalizePath(candidate)]; ok {
178
+ return source, true
179
+ }
180
+ stem := stripKnownSourceExtension(normalizePath(candidate))
181
+ if source, ok := r.sourceFiles[stem]; ok {
182
+ return source, true
183
+ }
184
+ for _, ext := range []string{".ts", ".tsx", ".mts", ".cts"} {
185
+ if source, ok := r.sourceFiles[stem+ext]; ok {
186
+ return source, true
187
+ }
188
+ }
189
+ for _, ext := range []string{".ts", ".tsx", ".mts", ".cts"} {
190
+ if source, ok := r.sourceFiles[normalizePath(filepath.Join(stem, "index"+ext))]; ok {
191
+ return source, true
192
+ }
193
+ }
194
+ return "", false
195
+ }
196
+
197
+ func (r *pathsRewriter) outputPathForSource(source string) string {
198
+ if r.outDir == "" || r.rootDir == "" {
199
+ return ""
200
+ }
201
+ rel, err := filepath.Rel(r.rootDir, source)
202
+ if err != nil || isOutsideRelativePath(rel) {
203
+ return ""
204
+ }
205
+ return normalizePath(filepath.Join(r.outDir, replaceSourceExtension(rel, emittedJavaScriptExtension(rel))))
206
+ }
207
+
208
+ func emittedJavaScriptExtension(source string) string {
209
+ switch strings.ToLower(filepath.Ext(source)) {
210
+ case ".mts":
211
+ return ".mjs"
212
+ case ".cts":
213
+ return ".cjs"
214
+ default:
215
+ return ".js"
216
+ }
217
+ }
218
+
219
+ func matchPattern(pattern string, specifier string) (string, bool) {
220
+ if !strings.Contains(pattern, "*") {
221
+ return "", pattern == specifier
222
+ }
223
+ parts := strings.SplitN(pattern, "*", 2)
224
+ if !strings.HasPrefix(specifier, parts[0]) || !strings.HasSuffix(specifier, parts[1]) {
225
+ return "", false
226
+ }
227
+ return specifier[len(parts[0]) : len(specifier)-len(parts[1])], true
228
+ }
229
+
230
+ func patternRank(pattern string) int {
231
+ return len(strings.ReplaceAll(pattern, "*", ""))
232
+ }
233
+
234
+ func optionalPath(value string, cwd string) string {
235
+ if value == "" {
236
+ return ""
237
+ }
238
+ if filepath.IsAbs(value) {
239
+ return normalizePath(value)
240
+ }
241
+ return normalizePath(filepath.Join(cwd, value))
242
+ }
243
+
244
+ func commonSourceDir(files []*shimast.SourceFile) string {
245
+ if len(files) == 0 {
246
+ return ""
247
+ }
248
+ common := normalizePath(filepath.Dir(files[0].FileName()))
249
+ for _, file := range files[1:] {
250
+ dir := normalizePath(filepath.Dir(file.FileName()))
251
+ for common != "" && !strings.HasPrefix(dir+"/", common+"/") {
252
+ next := filepath.Dir(common)
253
+ if next == common {
254
+ return common
255
+ }
256
+ common = normalizePath(next)
257
+ }
258
+ }
259
+ return common
260
+ }
261
+
262
+ func normalizePath(value string) string {
263
+ if value == "" {
264
+ return ""
265
+ }
266
+ return filepath.ToSlash(filepath.Clean(value))
267
+ }
268
+
269
+ func isOutsideRelativePath(rel string) bool {
270
+ return rel == ".." || strings.HasPrefix(filepath.ToSlash(rel), "../")
271
+ }
272
+
273
+ func stripKnownSourceExtension(value string) string {
274
+ lower := strings.ToLower(value)
275
+ for _, ext := range []string{".d.ts", ".d.mts", ".d.cts", ".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"} {
276
+ if strings.HasSuffix(lower, ext) {
277
+ return value[:len(value)-len(ext)]
278
+ }
279
+ }
280
+ return strings.TrimSuffix(value, filepath.Ext(value))
281
+ }
282
+
283
+ func replaceSourceExtension(value string, ext string) string {
284
+ return stripKnownSourceExtension(filepath.ToSlash(value)) + ext
285
+ }