ttsc 0.27.0 → 0.28.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.
Files changed (73) hide show
  1. package/cmd/graphbench/main.go +26 -16
  2. package/cmd/graphdump/main.go +37 -12
  3. package/cmd/graphdump/run_declares_the_capability_its_output_carries_test.go +137 -0
  4. package/cmd/graphdump/run_reports_an_unknown_flag_without_exiting_the_process_test.go +56 -0
  5. package/cmd/graphdump/run_reports_an_unloadable_project_without_printing_a_dump_test.go +46 -0
  6. package/cmd/ttsc/api_transform.go +17 -6
  7. package/cmd/ttscgraph/dump.go +43 -11
  8. package/cmd/ttscgraph/run_dump_claims_artifacts_only_when_it_asked_test.go +135 -0
  9. package/cmd/ttscgraph/run_dump_declares_the_capability_its_output_carries_test.go +96 -0
  10. package/cmd/ttscgraph/serve.go +253 -25
  11. package/cmd/ttscgraph/serve_adopts_a_republished_artifact_set_test.go +348 -0
  12. package/cmd/ttscgraph/serve_projects_an_artifact_no_source_owns_test.go +90 -0
  13. package/cmd/ttscgraph/serve_shards.go +31 -9
  14. package/driver/config_discovery.go +121 -0
  15. package/driver/emit_plugin.go +57 -8
  16. package/driver/plugins.go +125 -14
  17. package/driver/transform_dependencies.go +283 -0
  18. package/driver/transform_dependencies_declares_every_file_without_a_contributor_test.go +28 -0
  19. package/driver/transform_dependencies_drops_a_self_reported_dependency_test.go +21 -0
  20. package/driver/transform_dependencies_keys_a_reported_path_like_the_envelope_test.go +46 -0
  21. package/driver/transform_dependencies_requires_every_contributor_to_declare_test.go +38 -0
  22. package/driver/transform_dependencies_stays_silent_for_an_undeclared_contributor_test.go +23 -0
  23. package/driver/transform_dependencies_withdraws_completeness_after_an_unusable_dependency_test.go +37 -0
  24. package/internal/graph/artifacts.go +230 -0
  25. package/internal/graph/artifacts_index_what_a_plugin_published_test.go +154 -0
  26. package/internal/graph/artifacts_survive_a_partial_rebuild_test.go +89 -0
  27. package/internal/graph/build.go +35 -0
  28. package/internal/graph/build_releases_the_scratch_it_no_longer_reads_test.go +118 -0
  29. package/internal/graph/doc_refs_are_the_use_the_checker_already_counts_test.go +93 -0
  30. package/internal/graph/doc_refs_resolve_documentation_links_test.go +335 -0
  31. package/internal/graph/doc_tags.go +416 -0
  32. package/internal/graph/doc_tags_are_claimed_before_they_are_read_test.go +131 -0
  33. package/internal/graph/doc_tags_capture_every_unrecognized_tag_test.go +165 -0
  34. package/internal/graph/doc_tags_read_only_what_the_parser_attached_test.go +97 -0
  35. package/internal/graph/doc_tags_stop_at_the_external_boundary_test.go +59 -0
  36. package/internal/graph/doc_tags_survive_line_endings_and_repeated_blocks_test.go +146 -0
  37. package/internal/graph/dump.go +60 -13
  38. package/internal/graph/dump_checkout_paths_are_stable_test.go +2 -2
  39. package/internal/graph/dump_paths.go +2 -2
  40. package/internal/graph/dump_paths_are_portable_and_injective_test.go +1 -1
  41. package/internal/graph/edge_kinds_names_every_declared_kind_test.go +49 -0
  42. package/internal/graph/edges.go +1 -0
  43. package/internal/graph/graph.go +79 -4
  44. package/internal/graph/provenance.go +29 -2
  45. package/lib/compiler/internal/project/createNativeProjectContextArgs.d.ts +9 -0
  46. package/lib/compiler/internal/project/createNativeProjectContextArgs.js +16 -4
  47. package/lib/compiler/internal/project/createNativeProjectContextArgs.js.map +1 -1
  48. package/lib/index.d.ts +1 -0
  49. package/lib/index.js +1 -0
  50. package/lib/index.js.map +1 -1
  51. package/lib/launcher/internal/runtimeHooks.d.ts +10 -2
  52. package/lib/launcher/internal/runtimeHooks.js +93 -2
  53. package/lib/launcher/internal/runtimeHooks.js.map +1 -1
  54. package/lib/plugin/internal/capabilityResolutionCache.d.ts +76 -0
  55. package/lib/plugin/internal/capabilityResolutionCache.js +224 -0
  56. package/lib/plugin/internal/capabilityResolutionCache.js.map +1 -0
  57. package/lib/plugin/resolveCapabilityPlugins.d.ts +55 -0
  58. package/lib/plugin/resolveCapabilityPlugins.js +118 -0
  59. package/lib/plugin/resolveCapabilityPlugins.js.map +1 -0
  60. package/lib/structures/ITtscCompilerTransformation.d.ts +7 -0
  61. package/package.json +8 -8
  62. package/shim/ast/parent.go +16 -3
  63. package/shim/checker/enums_gen.go +11 -0
  64. package/shim/checker/shim.go +3 -0
  65. package/shim/compiler/emithook.go +10 -0
  66. package/src/compiler/internal/project/createNativeProjectContextArgs.ts +19 -4
  67. package/src/index.ts +1 -0
  68. package/src/launcher/internal/runtimeHooks.ts +111 -2
  69. package/src/plugin/internal/capabilityResolutionCache.ts +305 -0
  70. package/src/plugin/resolveCapabilityPlugins.ts +171 -0
  71. package/src/structures/ITtscCompilerTransformation.ts +7 -0
  72. package/tools/shim_audit/baseline.json +0 -1
  73. package/utility/host.go +15 -6
@@ -0,0 +1,49 @@
1
+ package graph
2
+
3
+ import (
4
+ "os"
5
+ "regexp"
6
+ "slices"
7
+ "testing"
8
+ )
9
+
10
+ // TestEdgeKindsNamesEveryDeclaredKind verifies that EdgeKinds returns exactly the
11
+ // EdgeKind constants this package declares.
12
+ //
13
+ // EdgeKinds exists so a consumer reporting per-kind numbers can seed every family
14
+ // rather than only the ones a particular project contains. That guarantee is only
15
+ // worth having if the list cannot fall behind the constants: a kind added to the
16
+ // block and not to the list is reported as absent rather than as zero, which is
17
+ // the defect the list was introduced to end. Go has no reflection over a const
18
+ // block, so the declarations are read from the source that makes them.
19
+ //
20
+ // 1. Read the EdgeKind constant block from graph.go.
21
+ // 2. Collect every declared wire value.
22
+ // 3. Assert EdgeKinds returns that set, with no extra and no missing member.
23
+ func TestEdgeKindsNamesEveryDeclaredKind(t *testing.T) {
24
+ source, err := os.ReadFile("graph.go")
25
+ if err != nil {
26
+ t.Fatal(err)
27
+ }
28
+ matches := regexp.MustCompile(`(?m)^\s*Edge\w+ EdgeKind = "([a-z-]+)"`).
29
+ FindAllStringSubmatch(string(source), -1)
30
+ if len(matches) == 0 {
31
+ t.Fatal("no EdgeKind constant declarations were found in graph.go")
32
+ }
33
+
34
+ declared := make([]string, 0, len(matches))
35
+ for _, match := range matches {
36
+ declared = append(declared, match[1])
37
+ }
38
+ returned := make([]string, 0, len(EdgeKinds()))
39
+ for _, kind := range EdgeKinds() {
40
+ returned = append(returned, string(kind))
41
+ }
42
+
43
+ slices.Sort(declared)
44
+ slices.Sort(returned)
45
+ if !slices.Equal(declared, returned) {
46
+ t.Fatalf("EdgeKinds returns %v, but graph.go declares %v; a kind missing "+
47
+ "from the list is reported as absent rather than as zero", returned, declared)
48
+ }
49
+ }
@@ -31,6 +31,7 @@ func (g *Graph) addEdges(prog *driver.Program, selected map[string]bool, partial
31
31
  g.collectHeritage(checker, file)
32
32
  g.collectCalls(checker, file)
33
33
  g.collectTypeRefs(checker, file)
34
+ g.collectDocRefs(checker, file)
34
35
  g.collectLiterals(checker, file)
35
36
  if file.Statements != nil {
36
37
  g.collectDecorators(file.FileName(), file.Statements.Nodes)
@@ -24,6 +24,18 @@ const (
24
24
  // imports from. A barrel declares nothing, so this is the only node it has,
25
25
  // and it is the node a package.json entry path resolves to.
26
26
  NodeModule NodeKind = "module"
27
+ // The artifact kinds. These are not declarations and they are not this
28
+ // Program's facts: a plugin materialized them from a Markdown document, a
29
+ // Prisma schema, or an API document, and the graph indexes what it was handed
30
+ // without interpreting any of it. Their ids are the address a citation names
31
+ // rather than the `path#name:kind` grammar, which is why a consumer parsing an
32
+ // id gates on the kind first.
33
+ NodeMarkdownDocument NodeKind = "markdown_document"
34
+ NodeMarkdownSection NodeKind = "markdown_section"
35
+ NodePrismaModel NodeKind = "prisma_model"
36
+ NodePrismaColumn NodeKind = "prisma_column"
37
+ NodePrismaRelation NodeKind = "prisma_relation"
38
+ NodeSwaggerOperation NodeKind = "swagger_operation"
27
39
  )
28
40
 
29
41
  // Node is one declared symbol. Its ID is position-invariant, built from the file
@@ -39,10 +51,22 @@ type Node struct {
39
51
  // (`"a.b"` → Name `C.a.b`), so the simple/qualified boundary cannot be
40
52
  // recovered from Name by splitting on a dot. Recording it here keeps the dump
41
53
  // split exact instead of guessing.
42
- Simple string
43
- Kind NodeKind
44
- File string
45
- External bool
54
+ Simple string
55
+ Kind NodeKind
56
+ File string
57
+ // ArtifactLine is the 1-based line a published artifact starts on, and is 0
58
+ // for every declaration — a declaration carries a real span, resolved from the
59
+ // compiler, and does not need this. It exists because an artifact has no AST
60
+ // position to take a span from: the plugin that parsed the document reports
61
+ // where the heading is, and that single number is the whole span an index
62
+ // carries. The section's content is read from the file when someone needs it,
63
+ // exactly as a function body is.
64
+ ArtifactLine int
65
+ // ArtifactParent is the id of the artifact containing this one — a section's
66
+ // document or enclosing section, a column's model. Empty for every
67
+ // declaration, whose containment the TypeScript memory layer synthesizes.
68
+ ArtifactParent string
69
+ External bool
46
70
  // Exported marks a node that is part of its module's export surface, resolved
47
71
  // through the checker's export table so a re-export (`export { Foo } from`) or
48
72
  // a barrel (`export *`) counts, not only an inline `export` modifier. It is
@@ -163,6 +187,19 @@ const (
163
187
  // type it mentions (a parameter, return, property, or alias type). It is not a
164
188
  // runtime call, so an impact query can filter value edges from type edges.
165
189
  EdgeTypeRef EdgeKind = "type-ref"
190
+ // EdgeDocRef is a reference from a declaration to a symbol its own
191
+ // documentation names through an inline link. The checker resolves the name
192
+ // and counts it as a use — an import that exists only to support one survives
193
+ // `noUnusedLocals` — so the relationship is a compiler fact like every other
194
+ // edge here, and it was the one class of resolved reference the graph held no
195
+ // edge for.
196
+ //
197
+ // It is its own kind rather than a type-ref because it is not a type
198
+ // position: a link may name a function, and a consumer filtering type edges
199
+ // from value edges would be told a documentation mention is one or the other
200
+ // when it is neither. The tag around the link decides nothing; a link under
201
+ // `@evidence`, under `@see`, or in ordinary prose is one relation.
202
+ EdgeDocRef EdgeKind = "doc-ref"
166
203
  // EdgeExports runs from a module to a declaration its export table resolves
167
204
  // to, through re-exports and barrels. It records which surface a symbol is
168
205
  // public on, which the Exported flag cannot: a package's front door and its
@@ -170,6 +207,24 @@ const (
170
207
  EdgeExports EdgeKind = "exports"
171
208
  )
172
209
 
210
+ // EdgeKinds returns every kind Build can put on an edge, in declaration order.
211
+ //
212
+ // A consumer that reports per-kind numbers needs the whole vocabulary rather
213
+ // than the subset a particular project happens to contain: counting only what
214
+ // was seen makes the reported shape depend on the project measured, and a
215
+ // family that dropped to zero disappears instead of reading zero.
216
+ func EdgeKinds() []EdgeKind {
217
+ return []EdgeKind{
218
+ EdgeHeritage,
219
+ EdgeMemberRelation,
220
+ EdgeValueCall,
221
+ EdgeValueAccess,
222
+ EdgeTypeRef,
223
+ EdgeDocRef,
224
+ EdgeExports,
225
+ }
226
+ }
227
+
173
228
  // Edge is a directed, checker-resolved relationship from one node to another,
174
229
  // both referenced by Node.ID. File, Pos, and End bound the source expression
175
230
  // that produced the edge. File is empty when the From node names it and is set
@@ -204,6 +259,26 @@ type Graph struct {
204
259
  // re-parsing source. It is dump-only metadata, separate from Edges so the
205
260
  // existing checker-resolved relationships are untouched.
206
261
  Decorators []*Decorator
262
+ // DocTags holds the documentation tags TypeScript does not recognize, written
263
+ // on the workspace's declarations and captured verbatim so the JSON dump can
264
+ // attach them to each target node. A convention that attaches a declaration to
265
+ // something outside the type system — a specification section, an API
266
+ // operation, a reference document — writes it here and nowhere the graph could
267
+ // otherwise see. Dump-only metadata, separate from Edges for the same reason
268
+ // Decorators is.
269
+ DocTags []*DocTag
270
+ // docTagPositions deduplicates DocTags by where each tag is written, so a
271
+ // declaration presented to putDeclaredNode more than once contributes its tags
272
+ // once. Build-only, like bodyNodes.
273
+ docTagPositions map[docTagKey]struct{}
274
+ // docHosts are the declarations the node pass found documentation on, paired
275
+ // with the node it attributed them to and keyed by their file. The edge pass
276
+ // resolves documentation links from this set rather than from a walk of its
277
+ // own, so both halves of a citation cover the same declarations, and it asks
278
+ // per file so a project's whole documented population is never rescanned for
279
+ // each one. Build-only.
280
+ docHosts map[string][]docHost
281
+ docHostPositions map[docTagKey]struct{}
207
282
  // bodyNodes tracks whether a callable node's display span is the overload
208
283
  // implementation rather than an overload signature. It is build-only metadata
209
284
  // and intentionally stays out of JSON dumps.
@@ -15,7 +15,7 @@ import (
15
15
  // field is added, removed, or given a new meaning, independently of the serve
16
16
  // envelope's protocol version: a one-shot `ttscgraph dump` written to a file has
17
17
  // a schema but never rode the protocol.
18
- const DumpSchemaVersion = 6
18
+ const DumpSchemaVersion = 8
19
19
 
20
20
  // The capabilities a snapshot can declare. Each names one class of evidence a
21
21
  // consumer may rely on when, and only when, the snapshot lists it.
@@ -26,6 +26,13 @@ const (
26
26
  // CapabilitySourceDigests means Sources covers every file the program
27
27
  // loaded, each with the digest of the text the checker read.
28
28
  CapabilitySourceDigests = "sourceDigests"
29
+ // CapabilityArtifactNodes means the producer asked the project's configured
30
+ // plugins for the artifacts a citation can name, so a dump carrying none is a
31
+ // project that publishes none rather than a producer that never looked. The
32
+ // two states are otherwise the same absent nodes, and a consumer honouring the
33
+ // difference is the whole reason a capability is declared rather than
34
+ // inferred.
35
+ CapabilityArtifactNodes = "artifactNodes"
29
36
  // CapabilityDiskDigests means Sources also carries each file's on-disk
30
37
  // digest, so an empty one genuinely means the file could not be read.
31
38
  //
@@ -37,6 +44,15 @@ const (
37
44
  // CapabilityDiagnostics means Diagnostics is the compiler's complete
38
45
  // findings for this generation, as opposed to not having been collected.
39
46
  CapabilityDiagnostics = "diagnostics"
47
+ // CapabilityDocTags means every node carries the documentation tags
48
+ // TypeScript does not recognize, so an absent `docTags` genuinely means the
49
+ // declaration carries none.
50
+ //
51
+ // It is a separate claim rather than an inference from emptiness for the
52
+ // reason CapabilityDiskDigests is: a producer built before the field existed
53
+ // emits nothing, and a consumer reading that as "this declaration cites
54
+ // nothing" would answer a citation question with a confident, wrong "no".
55
+ CapabilityDocTags = "docTags"
40
56
  )
41
57
 
42
58
  // Provenance is the snapshot's evidence about the program that produced it.
@@ -67,6 +83,17 @@ type Provenance struct {
67
83
  // Producer identifies the binary and the checker behind the facts.
68
84
  Producer Producer `json:"producer"`
69
85
 
86
+ // ArtifactProducer identifies the second producer behind the artifact nodes,
87
+ // and is absent when the dump carries none.
88
+ //
89
+ // Every other fact here came from one Program. These did not: a plugin parsed
90
+ // documents this Program never read, in a process of its own, and saying so is
91
+ // what keeps the dump's one-generation contract honest instead of letting an
92
+ // overlay ride the same claim as the compiler's facts. A consumer that must
93
+ // not mix the two reads this field; one that only answers "what does this
94
+ // address name" does not care.
95
+ ArtifactProducer *Producer `json:"artifactProducer,omitempty"`
96
+
70
97
  // Universe fingerprints the inputs that decide which files are in the
71
98
  // program at all, as opposed to what is inside them.
72
99
  Universe Universe `json:"universe"`
@@ -219,7 +246,7 @@ func TypescriptVersion() string { return shimcore.Version() }
219
246
 
220
247
  // NewProvenance assembles the evidence for a snapshot while retaining the
221
248
  // compiler's physical paths. NewDump projects those paths together with every
222
- // node, edge, span, and diagnostic through its one cached schema-v6 mapper.
249
+ // node, edge, span, and diagnostic through its one cached path mapper.
223
250
  // texts maps a source file's path to the text the checker read (as SourceTexts
224
251
  // returns it); disk maps that path to the hex digest of its on-disk bytes, and
225
252
  // a path absent from it is reported with an empty Disk. configs and roots come
@@ -1,3 +1,12 @@
1
1
  import type { ITtscParsedProjectConfig } from "../../../structures/internal/ITtscParsedProjectConfig";
2
2
  /** Serialize the retained project identity for a native sidecar invocation. */
3
3
  export declare function createNativeProjectContextArgs(project: ITtscParsedProjectConfig, pluginConfigOrigin?: string): string[];
4
+ /**
5
+ * The identity payload itself, for a caller that spawns a sidecar without going
6
+ * through the compiler's own argument list.
7
+ *
8
+ * Split from the flag so there is still exactly one place that decides what a
9
+ * sidecar is told about the project. A second caller assembling the same JSON,
10
+ * or slicing it back out of the flag, is how the two drift.
11
+ */
12
+ export declare function createNativeProjectContextJson(project: ITtscParsedProjectConfig, pluginConfigOrigin?: string): string;
@@ -1,14 +1,26 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.createNativeProjectContextArgs = createNativeProjectContextArgs;
4
+ exports.createNativeProjectContextJson = createNativeProjectContextJson;
4
5
  /** Serialize the retained project identity for a native sidecar invocation. */
5
6
  function createNativeProjectContextArgs(project, pluginConfigOrigin) {
6
7
  return [
7
8
  "--project-context-json=" +
8
- JSON.stringify({
9
- ...project.identity,
10
- ...(pluginConfigOrigin === undefined ? {} : { pluginConfigOrigin }),
11
- }),
9
+ createNativeProjectContextJson(project, pluginConfigOrigin),
12
10
  ];
13
11
  }
12
+ /**
13
+ * The identity payload itself, for a caller that spawns a sidecar without going
14
+ * through the compiler's own argument list.
15
+ *
16
+ * Split from the flag so there is still exactly one place that decides what a
17
+ * sidecar is told about the project. A second caller assembling the same JSON,
18
+ * or slicing it back out of the flag, is how the two drift.
19
+ */
20
+ function createNativeProjectContextJson(project, pluginConfigOrigin) {
21
+ return JSON.stringify({
22
+ ...project.identity,
23
+ ...(pluginConfigOrigin === undefined ? {} : { pluginConfigOrigin }),
24
+ });
25
+ }
14
26
  //# sourceMappingURL=createNativeProjectContextArgs.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"createNativeProjectContextArgs.js","sourceRoot":"","sources":["../../../../src/compiler/internal/project/createNativeProjectContextArgs.ts"],"names":[],"mappings":";;;AAEA,+EAA+E;AAC/E,wCACE,OAAiC,EACjC,kBAA2B;IAE3B,OAAO;QACL,yBAAyB;YACvB,IAAI,CAAC,SAAS,CAAC;gBACb,GAAG,OAAO,CAAC,QAAQ;gBACnB,GAAG,CAAC,kBAAkB,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,kBAAkB,EAAE,CAAC;aACpE,CAAC;KACL,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"createNativeProjectContextArgs.js","sourceRoot":"","sources":["../../../../src/compiler/internal/project/createNativeProjectContextArgs.ts"],"names":[],"mappings":";;;;AAEA,+EAA+E;AAC/E,wCACE,OAAiC,EACjC,kBAA2B;IAE3B,OAAO;QACL,yBAAyB;YACvB,8BAA8B,CAAC,OAAO,EAAE,kBAAkB,CAAC;KAC9D,CAAC;AACJ,CAAC;AAED;;;;;;;GAOG;AACH,wCACE,OAAiC,EACjC,kBAA2B;IAE3B,OAAO,IAAI,CAAC,SAAS,CAAC;QACpB,GAAG,OAAO,CAAC,QAAQ;QACnB,GAAG,CAAC,kBAAkB,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,kBAAkB,EAAE,CAAC;KACpE,CAAC,CAAC;AACL,CAAC"}
package/lib/index.d.ts CHANGED
@@ -6,6 +6,7 @@
6
6
  * project parsing helpers, and native build helpers stay internal so the public
7
7
  * package surface remains small and stable.
8
8
  */
9
+ export * from "./plugin/resolveCapabilityPlugins";
9
10
  export * from "./TtscCompiler";
10
11
  export * from "./TtscService";
11
12
  export * from "./structures/index";
package/lib/index.js CHANGED
@@ -22,6 +22,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
22
22
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
23
23
  };
24
24
  Object.defineProperty(exports, "__esModule", { value: true });
25
+ __exportStar(require("./plugin/resolveCapabilityPlugins"), exports);
25
26
  __exportStar(require("./TtscCompiler"), exports);
26
27
  __exportStar(require("./TtscService"), exports);
27
28
  __exportStar(require("./structures/index"), exports);
package/lib/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA;;;;;;;GAOG;;;;;;;;;;;;;;;;AAEH,iDAA+B;AAC/B,gDAA8B;AAC9B,qDAAmC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA;;;;;;;GAOG;;;;;;;;;;;;;;;;AAEH,oEAAkD;AAClD,iDAA+B;AAC/B,gDAA8B;AAC9B,qDAAmC"}
@@ -75,8 +75,16 @@ export interface RuntimeHookOptions {
75
75
  * `require()` made from inside a CommonJS module that was itself reached
76
76
  * through an ESM `import` (the interop translator loads it on the raw CJS
77
77
  * path). The ESM graph goes through `registerHooks`; the CommonJS `require`
78
- * graph goes through `Module._extensions` — the canonical loader extension
79
- * point `ts-node`/`tsx` use for the same reason.
78
+ * graph goes through `Module._extensions` and `Module._resolveFilename` — the
79
+ * canonical loader extension points `ts-node`/`tsx` use for the same reason.
80
+ *
81
+ * Both halves of that second graph are required, and they answer different
82
+ * questions. `_extensions` decides how a file that was _found_ is compiled, and
83
+ * Node reuses its keys when it probes an extensionless request, so
84
+ * `require("./x")` reaches `x.ts` for free. Nothing there resolves a request
85
+ * that already carries an extension: `require("./x.js")` backed only by `x.ts`
86
+ * is a resolution failure before any compiler is consulted, which is why the
87
+ * rescue is installed on `_resolveFilename` as well (samchon/ttsc#1280).
80
88
  */
81
89
  export declare function installRuntimeHooks(options?: RuntimeHookOptions): void;
82
90
  /**
@@ -182,8 +182,16 @@ let prepareRuntimeEntry;
182
182
  * `require()` made from inside a CommonJS module that was itself reached
183
183
  * through an ESM `import` (the interop translator loads it on the raw CJS
184
184
  * path). The ESM graph goes through `registerHooks`; the CommonJS `require`
185
- * graph goes through `Module._extensions` — the canonical loader extension
186
- * point `ts-node`/`tsx` use for the same reason.
185
+ * graph goes through `Module._extensions` and `Module._resolveFilename` — the
186
+ * canonical loader extension points `ts-node`/`tsx` use for the same reason.
187
+ *
188
+ * Both halves of that second graph are required, and they answer different
189
+ * questions. `_extensions` decides how a file that was _found_ is compiled, and
190
+ * Node reuses its keys when it probes an extensionless request, so
191
+ * `require("./x")` reaches `x.ts` for free. Nothing there resolves a request
192
+ * that already carries an extension: `require("./x.js")` backed only by `x.ts`
193
+ * is a resolution failure before any compiler is consulted, which is why the
194
+ * rescue is installed on `_resolveFilename` as well (samchon/ttsc#1280).
187
195
  */
188
196
  function installRuntimeHooks(options = {}) {
189
197
  if (options.prepareEntry !== undefined) {
@@ -203,6 +211,7 @@ function installRuntimeHooks(options = {}) {
203
211
  }
204
212
  (0, node_module_1.registerHooks)({ load, resolve });
205
213
  installCommonJsHook();
214
+ installCommonJsResolveHook();
206
215
  }
207
216
  /**
208
217
  * Register a CommonJS `require` handler for each TypeScript source extension so
@@ -219,6 +228,88 @@ function installCommonJsHook() {
219
228
  extensions[extension] = compile;
220
229
  }
221
230
  }
231
+ /**
232
+ * Map a JavaScript spelling back to its TypeScript source on the CommonJS
233
+ * resolution path, so `require("./x.js")` finds `x.ts` when nothing emitted
234
+ * `x.js`.
235
+ *
236
+ * TypeScript asks authors to write the emitted `.js` extension in a relative
237
+ * specifier, so this spelling is the documented one rather than a mistake, and
238
+ * the ESM `resolve` hook has always rescued it. The CommonJS graph could not,
239
+ * and on Node 22 that graph is reached by every `require()` inside a CommonJS
240
+ * module the ESM loader evaluated — `module.registerHooks` sees the `import()`
241
+ * of that module and nothing within it. Node 24 routes the same `require`
242
+ * through the hooks, which is why the gap was only ever visible on the oldest
243
+ * runtime ttsx supports (`engines.node` is `>=22.15.0`).
244
+ *
245
+ * The wrapper only ever answers a request Node's own resolver already refused,
246
+ * so a resolution that succeeds is never perturbed, and a request no candidate
247
+ * satisfies rethrows Node's original error with its `MODULE_NOT_FOUND` code and
248
+ * `requireStack` intact. `require.resolve` shares this entry point and is
249
+ * rescued with it, except in its `{ paths }` form, which resolves against the
250
+ * caller's list rather than the parent this reads.
251
+ */
252
+ function installCommonJsResolveHook() {
253
+ const internals = node_module_1.Module;
254
+ const original = internals._resolveFilename;
255
+ internals._resolveFilename = function resolveFilename(request, parent, isMain, options) {
256
+ try {
257
+ return original.call(this, request, parent, isMain, options);
258
+ }
259
+ catch (error) {
260
+ const rescued = rescueCommonJsRequest(request, parent, options);
261
+ if (rescued === null) {
262
+ throw error;
263
+ }
264
+ return rescued;
265
+ }
266
+ };
267
+ }
268
+ /**
269
+ * The TypeScript source a refused CommonJS request names, or `null`.
270
+ *
271
+ * Only a relative or absolute request can be rescued: a bare specifier is a
272
+ * package lookup, whose own resolver already probed every extension the
273
+ * installed `_extensions` keys declare.
274
+ *
275
+ * Two shapes decline before that. `_resolveFilename` is an internal entry point
276
+ * anything may call, so a non-string request reaches here as readily as a
277
+ * specifier does, and reading it would replace Node's own argument error with a
278
+ * `TypeError` from this file. And `require.resolve(request, { paths })`
279
+ * resolves against the caller's list, which this rescue does not consult, so
280
+ * answering it from the parent's directory would be a different question's
281
+ * answer.
282
+ */
283
+ function rescueCommonJsRequest(request, parent, options) {
284
+ if (typeof request !== "string") {
285
+ return null;
286
+ }
287
+ if (typeof options === "object" &&
288
+ options !== null &&
289
+ Array.isArray(options.paths)) {
290
+ return null;
291
+ }
292
+ let base;
293
+ if (node_path_1.default.isAbsolute(request)) {
294
+ base = request;
295
+ }
296
+ else if (isRelativeSpecifier(request)) {
297
+ const from = parent?.filename;
298
+ if (typeof from !== "string") {
299
+ return null;
300
+ }
301
+ base = node_path_1.default.resolve(node_path_1.default.dirname(from), request);
302
+ }
303
+ else {
304
+ return null;
305
+ }
306
+ for (const candidate of typescriptSourcesForJavaScriptSpecifier(base)) {
307
+ if (isFile(candidate)) {
308
+ return candidate;
309
+ }
310
+ }
311
+ return null;
312
+ }
222
313
  /**
223
314
  * The module format of the entry source file, derived from the entry project's
224
315
  * compiler options (via the runtime manifest) the same way the served files are