@nestia/core 11.2.1 → 12.0.0-dev.20260521.1
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/MIGRATION.md +169 -0
- package/lib/adaptors/WebSocketAdaptor.js +7 -3
- package/lib/adaptors/WebSocketAdaptor.js.map +1 -1
- package/lib/decorators/DynamicModule.js.map +1 -1
- package/lib/decorators/EncryptedBody.js.map +1 -1
- package/lib/decorators/EncryptedController.js.map +1 -1
- package/lib/decorators/EncryptedModule.js.map +1 -1
- package/lib/decorators/EncryptedRoute.js.map +1 -1
- package/lib/decorators/HumanRoute.js.map +1 -1
- package/lib/decorators/NoTransformConfigurationError.js +5 -2
- package/lib/decorators/NoTransformConfigurationError.js.map +1 -1
- package/lib/decorators/PlainBody.js.map +1 -1
- package/lib/decorators/SwaggerCustomizer.js.map +1 -1
- package/lib/decorators/SwaggerExample.js.map +1 -1
- package/lib/decorators/TypedBody.js.map +1 -1
- package/lib/decorators/TypedException.js.map +1 -1
- package/lib/decorators/TypedFormData.js.map +1 -1
- package/lib/decorators/TypedHeaders.js.map +1 -1
- package/lib/decorators/TypedParam.js +5 -2
- package/lib/decorators/TypedParam.js.map +1 -1
- package/lib/decorators/TypedQuery.js.map +1 -1
- package/lib/decorators/TypedRoute.js.map +1 -1
- package/lib/decorators/WebSocketRoute.js.map +1 -1
- package/lib/decorators/doNotThrowTransformError.js.map +1 -1
- package/lib/decorators/internal/get_path_and_querify.js +5 -2
- package/lib/decorators/internal/get_path_and_querify.js.map +1 -1
- package/lib/decorators/internal/get_path_and_stringify.js +5 -2
- package/lib/decorators/internal/get_path_and_stringify.js.map +1 -1
- package/lib/decorators/internal/get_text_body.js.map +1 -1
- package/lib/decorators/internal/headers_to_object.js.map +1 -1
- package/lib/decorators/internal/is_request_body_undefined.js.map +1 -1
- package/lib/decorators/internal/load_controller.js +48 -16
- package/lib/decorators/internal/load_controller.js.map +1 -1
- package/lib/decorators/internal/route_error.js +3 -3
- package/lib/decorators/internal/route_error.js.map +1 -1
- package/lib/decorators/internal/validate_request_body.js +5 -2
- package/lib/decorators/internal/validate_request_body.js.map +1 -1
- package/lib/decorators/internal/validate_request_form_data.js +5 -2
- package/lib/decorators/internal/validate_request_form_data.js.map +1 -1
- package/lib/decorators/internal/validate_request_headers.js +5 -2
- package/lib/decorators/internal/validate_request_headers.js.map +1 -1
- package/lib/decorators/internal/validate_request_query.js +30 -4
- package/lib/decorators/internal/validate_request_query.js.map +1 -1
- package/lib/index.js.map +1 -1
- package/lib/transform.d.ts +10 -5
- package/lib/transform.js +64 -28
- package/lib/transform.js.map +1 -1
- package/lib/utils/ArrayUtil.js.map +1 -1
- package/lib/utils/ExceptionManager.js.map +1 -1
- package/lib/utils/Singleton.js.map +1 -1
- package/lib/utils/SourceFinder.js.map +1 -1
- package/lib/utils/VersioningStrategy.js.map +1 -1
- package/native/cmd/ttsc-nestia/main.go +11 -0
- package/native/go.mod +32 -0
- package/native/go.sum +54 -0
- package/native/plugin/plan.go +102 -0
- package/native/transform/ast.go +32 -0
- package/native/transform/build.go +413 -0
- package/native/transform/cleanup.go +408 -0
- package/native/transform/cleanup_test.go +76 -0
- package/native/transform/commonjs_import_alias_test.go +49 -0
- package/native/transform/core_dispatch_test.go +127 -0
- package/native/transform/core_querify.go +227 -0
- package/native/transform/core_transform.go +1713 -0
- package/native/transform/core_websocket.go +115 -0
- package/native/transform/exports.go +13 -0
- package/native/transform/path_rewrite.go +285 -0
- package/native/transform/path_rewrite_test.go +243 -0
- package/native/transform/printer.go +244 -0
- package/native/transform/rewrite.go +662 -0
- package/native/transform/rewrite_test.go +118 -0
- package/native/transform/rewrite_unique_base_test.go +48 -0
- package/native/transform/run.go +72 -0
- package/native/transform/transform.go +376 -0
- package/native/transform/typia_fast.go +326 -0
- package/native/transform/typia_replacement.go +24 -0
- package/native/transform.cjs +43 -0
- package/package.json +28 -22
- package/src/decorators/NoTransformConfigurationError.ts +5 -2
- package/src/decorators/internal/load_controller.ts +50 -19
- package/src/decorators/internal/validate_request_query.ts +21 -2
- package/src/transform.ts +101 -35
- package/lib/decorators/internal/NoTransformConfigureError.d.ts +0 -1
- package/lib/decorators/internal/NoTransformConfigureError.js +0 -7
- package/lib/decorators/internal/NoTransformConfigureError.js.map +0 -1
- package/lib/options/INestiaTransformOptions.d.ts +0 -13
- package/lib/options/INestiaTransformOptions.js +0 -3
- package/lib/options/INestiaTransformOptions.js.map +0 -1
- package/lib/options/INestiaTransformProject.d.ts +0 -5
- package/lib/options/INestiaTransformProject.js +0 -3
- package/lib/options/INestiaTransformProject.js.map +0 -1
- package/lib/programmers/PlainBodyProgrammer.d.ts +0 -9
- package/lib/programmers/PlainBodyProgrammer.js +0 -58
- package/lib/programmers/PlainBodyProgrammer.js.map +0 -1
- package/lib/programmers/TypedBodyProgrammer.d.ts +0 -9
- package/lib/programmers/TypedBodyProgrammer.js +0 -94
- package/lib/programmers/TypedBodyProgrammer.js.map +0 -1
- package/lib/programmers/TypedFormDataBodyProgrammer.d.ts +0 -9
- package/lib/programmers/TypedFormDataBodyProgrammer.js +0 -65
- package/lib/programmers/TypedFormDataBodyProgrammer.js.map +0 -1
- package/lib/programmers/TypedHeadersProgrammer.d.ts +0 -9
- package/lib/programmers/TypedHeadersProgrammer.js +0 -38
- package/lib/programmers/TypedHeadersProgrammer.js.map +0 -1
- package/lib/programmers/TypedParamProgrammer.d.ts +0 -10
- package/lib/programmers/TypedParamProgrammer.js +0 -32
- package/lib/programmers/TypedParamProgrammer.js.map +0 -1
- package/lib/programmers/TypedQueryBodyProgrammer.d.ts +0 -9
- package/lib/programmers/TypedQueryBodyProgrammer.js +0 -74
- package/lib/programmers/TypedQueryBodyProgrammer.js.map +0 -1
- package/lib/programmers/TypedQueryProgrammer.d.ts +0 -9
- package/lib/programmers/TypedQueryProgrammer.js +0 -75
- package/lib/programmers/TypedQueryProgrammer.js.map +0 -1
- package/lib/programmers/TypedQueryRouteProgrammer.d.ts +0 -9
- package/lib/programmers/TypedQueryRouteProgrammer.js +0 -75
- package/lib/programmers/TypedQueryRouteProgrammer.js.map +0 -1
- package/lib/programmers/TypedRouteProgrammer.d.ts +0 -9
- package/lib/programmers/TypedRouteProgrammer.js +0 -79
- package/lib/programmers/TypedRouteProgrammer.js.map +0 -1
- package/lib/programmers/http/HttpAssertQuerifyProgrammer.d.ts +0 -9
- package/lib/programmers/http/HttpAssertQuerifyProgrammer.js +0 -39
- package/lib/programmers/http/HttpAssertQuerifyProgrammer.js.map +0 -1
- package/lib/programmers/http/HttpIsQuerifyProgrammer.d.ts +0 -9
- package/lib/programmers/http/HttpIsQuerifyProgrammer.js +0 -36
- package/lib/programmers/http/HttpIsQuerifyProgrammer.js.map +0 -1
- package/lib/programmers/http/HttpQuerifyProgrammer.d.ts +0 -9
- package/lib/programmers/http/HttpQuerifyProgrammer.js +0 -50
- package/lib/programmers/http/HttpQuerifyProgrammer.js.map +0 -1
- package/lib/programmers/http/HttpValidateQuerifyProgrammer.d.ts +0 -9
- package/lib/programmers/http/HttpValidateQuerifyProgrammer.js +0 -40
- package/lib/programmers/http/HttpValidateQuerifyProgrammer.js.map +0 -1
- package/lib/programmers/internal/CoreMetadataUtil.d.ts +0 -5
- package/lib/programmers/internal/CoreMetadataUtil.js +0 -19
- package/lib/programmers/internal/CoreMetadataUtil.js.map +0 -1
- package/lib/transformers/FileTransformer.d.ts +0 -5
- package/lib/transformers/FileTransformer.js +0 -80
- package/lib/transformers/FileTransformer.js.map +0 -1
- package/lib/transformers/MethodTransformer.d.ts +0 -8
- package/lib/transformers/MethodTransformer.js +0 -58
- package/lib/transformers/MethodTransformer.js.map +0 -1
- package/lib/transformers/NodeTransformer.d.ts +0 -8
- package/lib/transformers/NodeTransformer.js +0 -24
- package/lib/transformers/NodeTransformer.js.map +0 -1
- package/lib/transformers/ParameterDecoratorTransformer.d.ts +0 -9
- package/lib/transformers/ParameterDecoratorTransformer.js +0 -104
- package/lib/transformers/ParameterDecoratorTransformer.js.map +0 -1
- package/lib/transformers/ParameterTransformer.d.ts +0 -8
- package/lib/transformers/ParameterTransformer.js +0 -37
- package/lib/transformers/ParameterTransformer.js.map +0 -1
- package/lib/transformers/TypedRouteTransformer.d.ts +0 -9
- package/lib/transformers/TypedRouteTransformer.js +0 -68
- package/lib/transformers/TypedRouteTransformer.js.map +0 -1
- package/lib/transformers/WebSocketRouteTransformer.d.ts +0 -9
- package/lib/transformers/WebSocketRouteTransformer.js +0 -72
- package/lib/transformers/WebSocketRouteTransformer.js.map +0 -1
- package/src/decorators/internal/NoTransformConfigureError.ts +0 -2
- package/src/options/INestiaTransformOptions.ts +0 -34
- package/src/options/INestiaTransformProject.ts +0 -10
- package/src/programmers/PlainBodyProgrammer.ts +0 -72
- package/src/programmers/TypedBodyProgrammer.ts +0 -148
- package/src/programmers/TypedFormDataBodyProgrammer.ts +0 -118
- package/src/programmers/TypedHeadersProgrammer.ts +0 -65
- package/src/programmers/TypedParamProgrammer.ts +0 -33
- package/src/programmers/TypedQueryBodyProgrammer.ts +0 -113
- package/src/programmers/TypedQueryProgrammer.ts +0 -115
- package/src/programmers/TypedQueryRouteProgrammer.ts +0 -107
- package/src/programmers/TypedRouteProgrammer.ts +0 -103
- package/src/programmers/http/HttpAssertQuerifyProgrammer.ts +0 -74
- package/src/programmers/http/HttpIsQuerifyProgrammer.ts +0 -77
- package/src/programmers/http/HttpQuerifyProgrammer.ts +0 -110
- package/src/programmers/http/HttpValidateQuerifyProgrammer.ts +0 -78
- package/src/programmers/internal/CoreMetadataUtil.ts +0 -21
- package/src/transformers/FileTransformer.ts +0 -109
- package/src/transformers/MethodTransformer.ts +0 -103
- package/src/transformers/NodeTransformer.ts +0 -23
- package/src/transformers/ParameterDecoratorTransformer.ts +0 -143
- package/src/transformers/ParameterTransformer.ts +0 -57
- package/src/transformers/TypedRouteTransformer.ts +0 -85
- package/src/transformers/WebSocketRouteTransformer.ts +0 -120
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
package transform
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"fmt"
|
|
5
|
+
"strings"
|
|
6
|
+
|
|
7
|
+
shimast "github.com/microsoft/typescript-go/shim/ast"
|
|
8
|
+
shimscanner "github.com/microsoft/typescript-go/shim/scanner"
|
|
9
|
+
"github.com/samchon/ttsc/packages/ttsc/driver"
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
func validateNestiaCoreWebSocketRoute(
|
|
13
|
+
prog *driver.Program,
|
|
14
|
+
context nestiaCoreFileContext,
|
|
15
|
+
method *shimast.Node,
|
|
16
|
+
call *shimast.CallExpression,
|
|
17
|
+
segments []string,
|
|
18
|
+
) []Diagnostic {
|
|
19
|
+
if call == nil || len(segments) == 0 || segments[len(segments)-1] != "WebSocketRoute" {
|
|
20
|
+
_ = call
|
|
21
|
+
return nil
|
|
22
|
+
}
|
|
23
|
+
diagnostics := []Diagnostic{}
|
|
24
|
+
accepted := false
|
|
25
|
+
methodDecl := method.AsMethodDeclaration()
|
|
26
|
+
if methodDecl == nil || methodDecl.Parameters == nil {
|
|
27
|
+
return []Diagnostic{nestiaCoreWebSocketDiagnostic(context.file, method, "WebSocketRoute", fmt.Sprintf(
|
|
28
|
+
"method %q must have at least one parameter decorated by @WebSocketRoute.Acceptor().",
|
|
29
|
+
NodeName(method),
|
|
30
|
+
))}
|
|
31
|
+
}
|
|
32
|
+
for _, param := range methodDecl.Parameters.Nodes {
|
|
33
|
+
category := nestiaCoreWebSocketParameterCategory(prog, param)
|
|
34
|
+
name := NodeName(param)
|
|
35
|
+
if category == "" {
|
|
36
|
+
diagnostics = append(diagnostics, nestiaCoreWebSocketDiagnostic(context.file, param, "WebSocketRoute", fmt.Sprintf(
|
|
37
|
+
"parameter %q is not decorated with nested function of WebSocketRoute module.",
|
|
38
|
+
name,
|
|
39
|
+
)))
|
|
40
|
+
continue
|
|
41
|
+
}
|
|
42
|
+
switch category {
|
|
43
|
+
case "Acceptor":
|
|
44
|
+
accepted = true
|
|
45
|
+
if strings.HasPrefix(nestiaCoreWebSocketParameterTypeName(param), "WebSocketAcceptor") == false {
|
|
46
|
+
diagnostics = append(diagnostics, nestiaCoreWebSocketDiagnostic(context.file, param, "WebSocketRoute", fmt.Sprintf(
|
|
47
|
+
"parameter %q must have WebSocketAcceptor<Header, Provider, Listener> type.",
|
|
48
|
+
name,
|
|
49
|
+
)))
|
|
50
|
+
}
|
|
51
|
+
case "Driver":
|
|
52
|
+
if strings.HasPrefix(nestiaCoreWebSocketParameterTypeName(param), "Driver") == false {
|
|
53
|
+
diagnostics = append(diagnostics, nestiaCoreWebSocketDiagnostic(context.file, param, "WebSocketRoute", fmt.Sprintf(
|
|
54
|
+
"parameter %q must have Driver<Listener> type.",
|
|
55
|
+
name,
|
|
56
|
+
)))
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if accepted == false {
|
|
61
|
+
diagnostics = append(diagnostics, nestiaCoreWebSocketDiagnostic(context.file, method, "WebSocketRoute", fmt.Sprintf(
|
|
62
|
+
"method %q must have at least one parameter decorated by @WebSocketRoute.Acceptor().",
|
|
63
|
+
NodeName(method),
|
|
64
|
+
)))
|
|
65
|
+
}
|
|
66
|
+
return diagnostics
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
func nestiaCoreWebSocketParameterCategory(prog *driver.Program, param *shimast.Node) string {
|
|
70
|
+
if param == nil || len(param.Decorators()) != 1 {
|
|
71
|
+
return ""
|
|
72
|
+
}
|
|
73
|
+
call, segments, ok := nestiaCoreDecoratorCall(prog, param.Decorators()[0])
|
|
74
|
+
if ok == false || len(segments) < 2 || segments[len(segments)-2] != "WebSocketRoute" {
|
|
75
|
+
_ = call
|
|
76
|
+
return ""
|
|
77
|
+
}
|
|
78
|
+
return segments[len(segments)-1]
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
func nestiaCoreWebSocketParameterTypeName(param *shimast.Node) string {
|
|
82
|
+
if param == nil || param.AsParameterDeclaration() == nil || param.AsParameterDeclaration().Type == nil {
|
|
83
|
+
return ""
|
|
84
|
+
}
|
|
85
|
+
text := NodeText(param.AsParameterDeclaration().Type)
|
|
86
|
+
text = strings.TrimSpace(text)
|
|
87
|
+
if index := strings.Index(text, "<"); index >= 0 {
|
|
88
|
+
text = text[:index]
|
|
89
|
+
}
|
|
90
|
+
if index := strings.LastIndex(text, "."); index >= 0 {
|
|
91
|
+
text = text[index+1:]
|
|
92
|
+
}
|
|
93
|
+
return text
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
func nestiaCoreWebSocketDiagnostic(file *shimast.SourceFile, node *shimast.Node, kind string, message string) Diagnostic {
|
|
97
|
+
filePath := ""
|
|
98
|
+
line, column := 0, 0
|
|
99
|
+
if file != nil {
|
|
100
|
+
filePath = file.FileName()
|
|
101
|
+
if node != nil {
|
|
102
|
+
if pos := node.Pos(); pos >= 0 {
|
|
103
|
+
l, c := shimscanner.GetECMALineAndByteOffsetOfPosition(file, pos)
|
|
104
|
+
line, column = l+1, c+1
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return Diagnostic{
|
|
109
|
+
File: filePath,
|
|
110
|
+
Line: line,
|
|
111
|
+
Column: column,
|
|
112
|
+
Code: "nestia.core." + kind,
|
|
113
|
+
Message: message,
|
|
114
|
+
}
|
|
115
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
package transform
|
|
2
|
+
|
|
3
|
+
// NewSourceRewrite builds a SourceRewrite from another Go module (e.g. the
|
|
4
|
+
// `@nestia/sdk` plugin). SourceRewrite keeps its span fields unexported so
|
|
5
|
+
// that the only way to produce one is this constructor or the in-package
|
|
6
|
+
// collectors — every rewrite therefore carries a validated [start,end) span.
|
|
7
|
+
func NewSourceRewrite(start, end int, replacement string) SourceRewrite {
|
|
8
|
+
return SourceRewrite{
|
|
9
|
+
start: start,
|
|
10
|
+
end: end,
|
|
11
|
+
replacement: replacement,
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +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
|
+
}
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
package transform
|
|
2
|
+
|
|
3
|
+
import "testing"
|
|
4
|
+
|
|
5
|
+
// Verifies emittedJavaScriptExtension maps source-file extensions to the
|
|
6
|
+
// matching JavaScript output extension that tsgo writes.
|
|
7
|
+
//
|
|
8
|
+
// The native rewrite scan asks for the post-emit name of every registered
|
|
9
|
+
// source. A mismatch here means the rewrite searches for `.js` files when
|
|
10
|
+
// the project actually emits `.mjs` (ESM packages) or `.cjs`, and the
|
|
11
|
+
// "could not locate <call>" failure path then fires on legitimate
|
|
12
|
+
// builds. The case-insensitive switch covers Windows-style mixed-case
|
|
13
|
+
// `.MTS` paths some toolchains produce.
|
|
14
|
+
//
|
|
15
|
+
// 1. Cover the three explicit branches (.mts, .cts, default).
|
|
16
|
+
// 2. Cover the case-insensitive variants (.MTS, .CTS).
|
|
17
|
+
// 3. Cover the default fallback for unknown / extensionless inputs.
|
|
18
|
+
func TestEmittedJavaScriptExtensionMapsSourceToOutput(t *testing.T) {
|
|
19
|
+
cases := []struct {
|
|
20
|
+
name string
|
|
21
|
+
source string
|
|
22
|
+
want string
|
|
23
|
+
}{
|
|
24
|
+
{"plain ts", "/repo/src/index.ts", ".js"},
|
|
25
|
+
{"plain tsx", "/repo/src/Component.tsx", ".js"},
|
|
26
|
+
{"esm mts", "/repo/src/index.mts", ".mjs"},
|
|
27
|
+
{"cjs cts", "/repo/src/index.cts", ".cjs"},
|
|
28
|
+
{"uppercase mts", "/repo/src/Index.MTS", ".mjs"},
|
|
29
|
+
{"uppercase cts", "/repo/src/Index.CTS", ".cjs"},
|
|
30
|
+
{"declaration only", "/repo/src/types.d.ts", ".js"},
|
|
31
|
+
{"unknown extension", "/repo/src/data.json", ".js"},
|
|
32
|
+
{"extensionless", "/repo/src/README", ".js"},
|
|
33
|
+
}
|
|
34
|
+
for _, tc := range cases {
|
|
35
|
+
t.Run(tc.name, func(t *testing.T) {
|
|
36
|
+
if got := emittedJavaScriptExtension(tc.source); got != tc.want {
|
|
37
|
+
t.Fatalf("emittedJavaScriptExtension(%q) = %q, want %q", tc.source, got, tc.want)
|
|
38
|
+
}
|
|
39
|
+
})
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Verifies matchPattern strictly matches the literal-pattern branch and
|
|
44
|
+
// returns the wildcard capture for star-bearing patterns.
|
|
45
|
+
//
|
|
46
|
+
// `matchPattern` underpins every `paths` resolver entry — a regression in
|
|
47
|
+
// the prefix / suffix slice arithmetic would silently break tsconfig
|
|
48
|
+
// `paths` aliasing for any project that uses it. The literal branch must
|
|
49
|
+
// reject `pattern !== specifier`; the wildcard branch must demand BOTH
|
|
50
|
+
// the prefix and suffix to anchor before extracting the capture.
|
|
51
|
+
//
|
|
52
|
+
// 1. Literal patterns match only on byte equality.
|
|
53
|
+
// 2. Single-wildcard patterns extract the gap between prefix and suffix.
|
|
54
|
+
// 3. Mismatched prefix or suffix returns no capture.
|
|
55
|
+
func TestMatchPatternLiteralAndWildcardBranches(t *testing.T) {
|
|
56
|
+
cases := []struct {
|
|
57
|
+
name string
|
|
58
|
+
pattern string
|
|
59
|
+
input string
|
|
60
|
+
want string
|
|
61
|
+
matched bool
|
|
62
|
+
}{
|
|
63
|
+
{"literal match", "@api", "@api", "", true},
|
|
64
|
+
{"literal mismatch", "@api", "@api/lib", "", false},
|
|
65
|
+
{"wildcard captures middle", "@api/*", "@api/users", "users", true},
|
|
66
|
+
{"wildcard captures nested", "@api/lib/*", "@api/lib/sub/file", "sub/file", true},
|
|
67
|
+
{"wildcard mismatch prefix", "@api/*", "@other/users", "", false},
|
|
68
|
+
{"wildcard mismatch suffix", "@api/*.ts", "@api/users.tsx", "", false},
|
|
69
|
+
{"wildcard between prefix and suffix", "src/*/index.ts", "src/foo/index.ts", "foo", true},
|
|
70
|
+
{"empty capture allowed", "prefix*suffix", "prefixsuffix", "", true},
|
|
71
|
+
{"second wildcard treated as literal", "@api/*/lib/*", "@api/foo/lib/*", "foo", true},
|
|
72
|
+
{"second wildcard does not expand", "@api/*/lib/*", "@api/foo/lib/bar", "", false},
|
|
73
|
+
}
|
|
74
|
+
for _, tc := range cases {
|
|
75
|
+
t.Run(tc.name, func(t *testing.T) {
|
|
76
|
+
got, ok := matchPattern(tc.pattern, tc.input)
|
|
77
|
+
if ok != tc.matched {
|
|
78
|
+
t.Fatalf("matchPattern(%q, %q) ok = %v, want %v", tc.pattern, tc.input, ok, tc.matched)
|
|
79
|
+
}
|
|
80
|
+
if ok && got != tc.want {
|
|
81
|
+
t.Fatalf("matchPattern(%q, %q) capture = %q, want %q", tc.pattern, tc.input, got, tc.want)
|
|
82
|
+
}
|
|
83
|
+
})
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Verifies patternRank assigns longer literal-character counts higher rank
|
|
88
|
+
// so the longest non-wildcard pattern wins when several match.
|
|
89
|
+
//
|
|
90
|
+
// tsconfig `paths` resolution sorts candidates by `patternRank` descending
|
|
91
|
+
// to break ties — without this ordering the resolver could pick the
|
|
92
|
+
// loosest pattern over a more specific one (e.g. `*` over `@api/*`).
|
|
93
|
+
//
|
|
94
|
+
// 1. A pure-wildcard pattern ranks 0.
|
|
95
|
+
// 2. Each non-wildcard character adds 1 regardless of position.
|
|
96
|
+
// 3. Multiple wildcards do not double-count literal segments.
|
|
97
|
+
func TestPatternRankCountsNonWildcardCharacters(t *testing.T) {
|
|
98
|
+
cases := []struct {
|
|
99
|
+
pattern string
|
|
100
|
+
want int
|
|
101
|
+
}{
|
|
102
|
+
{"*", 0},
|
|
103
|
+
{"@api/*", 5},
|
|
104
|
+
{"@api/lib/*", 9},
|
|
105
|
+
{"src/*/index.ts", 13},
|
|
106
|
+
{"prefix*suffix", 12},
|
|
107
|
+
}
|
|
108
|
+
for _, tc := range cases {
|
|
109
|
+
if got := patternRank(tc.pattern); got != tc.want {
|
|
110
|
+
t.Fatalf("patternRank(%q) = %d, want %d", tc.pattern, got, tc.want)
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Verifies normalizePath collapses redundant POSIX separators and
|
|
116
|
+
// resolves `..` segments via filepath.Clean.
|
|
117
|
+
//
|
|
118
|
+
// The rewrite pipeline keys outputs and sources by their normalized
|
|
119
|
+
// path. A regression here would split a single logical source into
|
|
120
|
+
// multiple cache entries, defeating `nativeRewriteSet`'s deduplication.
|
|
121
|
+
// `filepath.ToSlash` runs after Clean — on Linux it is a no-op for
|
|
122
|
+
// already-POSIX input; backslash handling is delegated to the platform.
|
|
123
|
+
//
|
|
124
|
+
// 1. Empty input returns empty.
|
|
125
|
+
// 2. Adjacent separators collapse.
|
|
126
|
+
// 3. `..` segments are resolved relative to the preceding directory.
|
|
127
|
+
// 4. A leading `./` is stripped (filepath.Clean canonicalization).
|
|
128
|
+
func TestNormalizePathProducesPosixSlashes(t *testing.T) {
|
|
129
|
+
cases := []struct {
|
|
130
|
+
input string
|
|
131
|
+
want string
|
|
132
|
+
}{
|
|
133
|
+
{"", ""},
|
|
134
|
+
{"/repo/src/index.ts", "/repo/src/index.ts"},
|
|
135
|
+
{"/repo//src///index.ts", "/repo/src/index.ts"},
|
|
136
|
+
{"/repo/src/../src/index.ts", "/repo/src/index.ts"},
|
|
137
|
+
{"./relative/path", "relative/path"},
|
|
138
|
+
}
|
|
139
|
+
for _, tc := range cases {
|
|
140
|
+
if got := normalizePath(tc.input); got != tc.want {
|
|
141
|
+
t.Fatalf("normalizePath(%q) = %q, want %q", tc.input, got, tc.want)
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Verifies isOutsideRelativePath flags any relative path that escapes the
|
|
147
|
+
// root via `..` segments.
|
|
148
|
+
//
|
|
149
|
+
// `pathRewriter.outputForSource` refuses to map sources that resolve
|
|
150
|
+
// outside `rootDir`. Without this guard, a controller imported from a
|
|
151
|
+
// monorepo neighbor would emit into the outDir at a relative position
|
|
152
|
+
// that collides with an unrelated file.
|
|
153
|
+
//
|
|
154
|
+
// 1. Bare `..` is outside.
|
|
155
|
+
// 2. `../...` prefix is outside.
|
|
156
|
+
// 3. Same-or-nested paths are inside.
|
|
157
|
+
func TestIsOutsideRelativePathRejectsParentEscapes(t *testing.T) {
|
|
158
|
+
cases := []struct {
|
|
159
|
+
rel string
|
|
160
|
+
want bool
|
|
161
|
+
}{
|
|
162
|
+
{"..", true},
|
|
163
|
+
{"../sibling", true},
|
|
164
|
+
{"../../escape", true},
|
|
165
|
+
{".", false},
|
|
166
|
+
{"nested/file.ts", false},
|
|
167
|
+
{"sub/../same.ts", false},
|
|
168
|
+
}
|
|
169
|
+
for _, tc := range cases {
|
|
170
|
+
if got := isOutsideRelativePath(tc.rel); got != tc.want {
|
|
171
|
+
t.Fatalf("isOutsideRelativePath(%q) = %v, want %v", tc.rel, got, tc.want)
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Verifies stripKnownSourceExtension prefers the longest known suffix and
|
|
177
|
+
// falls back to filepath.Ext for unknown extensions.
|
|
178
|
+
//
|
|
179
|
+
// The declaration-file extensions (`.d.ts`, `.d.mts`, `.d.cts`) must be
|
|
180
|
+
// matched as a whole — falling through to the simpler `.ts` strip would
|
|
181
|
+
// leave the `.d` suffix on the stem and silently produce wrong virtual
|
|
182
|
+
// paths for declaration emitters.
|
|
183
|
+
//
|
|
184
|
+
// 1. `.d.ts` strips the full three-character suffix.
|
|
185
|
+
// 2. Each one-extension form (`.ts`, `.tsx`, `.mts`, …) strips cleanly.
|
|
186
|
+
// 3. Unknown extensions fall back to filepath.Ext.
|
|
187
|
+
// 4. Case-insensitive match on uppercase variants.
|
|
188
|
+
func TestStripKnownSourceExtensionPrefersLongestMatch(t *testing.T) {
|
|
189
|
+
cases := []struct {
|
|
190
|
+
input string
|
|
191
|
+
want string
|
|
192
|
+
}{
|
|
193
|
+
{"types.d.ts", "types"},
|
|
194
|
+
{"types.d.mts", "types"},
|
|
195
|
+
{"types.d.cts", "types"},
|
|
196
|
+
{"index.ts", "index"},
|
|
197
|
+
{"index.tsx", "index"},
|
|
198
|
+
{"index.mts", "index"},
|
|
199
|
+
{"index.cts", "index"},
|
|
200
|
+
{"index.js", "index"},
|
|
201
|
+
{"data.json", "data"},
|
|
202
|
+
{"Index.D.TS", "Index"},
|
|
203
|
+
{"README", "README"},
|
|
204
|
+
}
|
|
205
|
+
for _, tc := range cases {
|
|
206
|
+
if got := stripKnownSourceExtension(tc.input); got != tc.want {
|
|
207
|
+
t.Fatalf("stripKnownSourceExtension(%q) = %q, want %q", tc.input, got, tc.want)
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Verifies replaceSourceExtension swaps the known TypeScript extension
|
|
213
|
+
// while preserving the rest of the path.
|
|
214
|
+
//
|
|
215
|
+
// Combined with `emittedJavaScriptExtension`, this drives the source ↔
|
|
216
|
+
// output filename mapping the rewrite scan depends on. The declaration
|
|
217
|
+
// case is load-bearing: `stripKnownSourceExtension` must prefer the full
|
|
218
|
+
// `.d.ts` suffix over a plain `.ts` strip, otherwise `types.d.ts +
|
|
219
|
+
// .d.ts` would silently become `types..d.ts` and the declaration
|
|
220
|
+
// emitter would write a corrupt path.
|
|
221
|
+
//
|
|
222
|
+
// 1. `.ts` is replaced with the chosen output extension.
|
|
223
|
+
// 2. Declaration suffixes (`.d.ts`) strip wholly before replacement.
|
|
224
|
+
// 3. POSIX separators in the input flow through unchanged.
|
|
225
|
+
// 4. `.d.ts` + `.d.ts` stays `.d.ts` (pins the longest-match prefer rule).
|
|
226
|
+
func TestReplaceSourceExtensionSwapsKnownSuffix(t *testing.T) {
|
|
227
|
+
cases := []struct {
|
|
228
|
+
input string
|
|
229
|
+
ext string
|
|
230
|
+
want string
|
|
231
|
+
}{
|
|
232
|
+
{"src/index.ts", ".js", "src/index.js"},
|
|
233
|
+
{"src/index.mts", ".mjs", "src/index.mjs"},
|
|
234
|
+
{"src/types.d.ts", ".js", "src/types.js"},
|
|
235
|
+
{"src/nested/foo.ts", ".js", "src/nested/foo.js"},
|
|
236
|
+
{"src/types.d.ts", ".d.ts", "src/types.d.ts"},
|
|
237
|
+
}
|
|
238
|
+
for _, tc := range cases {
|
|
239
|
+
if got := replaceSourceExtension(tc.input, tc.ext); got != tc.want {
|
|
240
|
+
t.Fatalf("replaceSourceExtension(%q, %q) = %q, want %q", tc.input, tc.ext, got, tc.want)
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|