ttsc 0.18.2 → 0.18.4

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 (51) hide show
  1. package/cmd/ttscgraph/main.go +18 -12
  2. package/cmd/ttscgraph/run_help_version_and_bad_flags_exit_codes_test.go +23 -5
  3. package/cmd/ttscgraph/serve.go +863 -0
  4. package/cmd/ttscgraph/serve_protocol_rejects_invalid_request_line_test.go +43 -0
  5. package/cmd/ttscgraph/serve_protocol_retries_session_creation_test.go +83 -0
  6. package/cmd/ttscgraph/serve_protocol_reuses_session_test.go +35 -0
  7. package/cmd/ttscgraph/serve_session_applies_source_edit_incrementally_test.go +55 -0
  8. package/cmd/ttscgraph/serve_session_fails_closed_on_deleted_config_test.go +68 -0
  9. package/cmd/ttscgraph/serve_session_fails_closed_on_invalid_config_test.go +50 -0
  10. package/cmd/ttscgraph/serve_session_keeps_missing_literal_root_unchanged_test.go +46 -0
  11. package/cmd/ttscgraph/serve_session_rebuilds_import_graph_change_test.go +60 -0
  12. package/cmd/ttscgraph/serve_session_reloads_created_literal_root_test.go +44 -0
  13. package/cmd/ttscgraph/serve_session_reloads_extended_config_change_test.go +45 -0
  14. package/cmd/ttscgraph/serve_session_reloads_newly_resolved_conditional_export_test.go +60 -0
  15. package/cmd/ttscgraph/serve_session_reloads_newly_resolved_config_types_test.go +44 -0
  16. package/cmd/ttscgraph/serve_session_reloads_newly_resolved_excluded_import_test.go +62 -0
  17. package/cmd/ttscgraph/serve_session_reloads_newly_resolved_package_export_test.go +46 -0
  18. package/cmd/ttscgraph/serve_session_reloads_newly_resolved_package_import_test.go +46 -0
  19. package/cmd/ttscgraph/serve_session_reloads_newly_resolved_path_alias_test.go +47 -0
  20. package/cmd/ttscgraph/serve_session_reloads_newly_resolved_reference_path_test.go +42 -0
  21. package/cmd/ttscgraph/serve_session_reloads_newly_resolved_root_dirs_target_test.go +46 -0
  22. package/cmd/ttscgraph/serve_session_reloads_newly_resolved_type_reference_test.go +42 -0
  23. package/cmd/ttscgraph/serve_session_reloads_root_file_set_test.go +52 -0
  24. package/cmd/ttscgraph/serve_session_reloads_source_deleted_during_load_test.go +60 -0
  25. package/cmd/ttscgraph/serve_session_reloads_transitive_project_reference_test.go +55 -0
  26. package/cmd/ttscgraph/serve_session_reloads_when_program_plugin_linked_test.go +53 -0
  27. package/cmd/ttscgraph/serve_session_reuses_unchanged_preamble_source_test.go +45 -0
  28. package/cmd/ttscgraph/serve_session_reuses_unchanged_snapshot_test.go +52 -0
  29. package/cmd/ttscgraph/serve_session_revisits_source_edited_during_load_test.go +61 -0
  30. package/cmd/ttscgraph/serve_session_tolerates_os_invalid_resolution_candidate_test.go +56 -0
  31. package/cmd/ttscgraph/serve_session_tracks_all_module_specifier_forms_test.go +41 -0
  32. package/cmd/ttscgraph/serve_session_tracks_diamond_project_references_once_test.go +61 -0
  33. package/driver/emit_plugin.go +27 -12
  34. package/driver/plugins.go +21 -5
  35. package/driver/program.go +43 -14
  36. package/driver/session.go +11 -3
  37. package/lib/TtscCompiler.d.ts +3 -2
  38. package/lib/TtscCompiler.js +6 -14
  39. package/lib/TtscCompiler.js.map +1 -1
  40. package/lib/compiler/internal/compileProjectInMemory.js +14 -1
  41. package/lib/compiler/internal/compileProjectInMemory.js.map +1 -1
  42. package/lib/compiler/internal/runBuild.d.ts +7 -0
  43. package/lib/compiler/internal/runBuild.js +176 -16
  44. package/lib/compiler/internal/runBuild.js.map +1 -1
  45. package/lib/structures/ITtscCompilerResult.d.ts +8 -5
  46. package/package.json +8 -8
  47. package/shim/printer/shim.go +22 -4
  48. package/src/TtscCompiler.ts +4 -16
  49. package/src/compiler/internal/compileProjectInMemory.ts +17 -1
  50. package/src/compiler/internal/runBuild.ts +237 -23
  51. package/src/structures/ITtscCompilerResult.ts +8 -5
@@ -0,0 +1,43 @@
1
+ package main
2
+
3
+ import (
4
+ "bytes"
5
+ "encoding/json"
6
+ "strings"
7
+ "testing"
8
+ )
9
+
10
+ // TestServeProtocolRejectsInvalidRequestLine verifies a malformed NDJSON line
11
+ // answers with a protocol error and never poisons the following requests.
12
+ //
13
+ // The launcher only ever writes well-formed requests, so a garbage line means
14
+ // a corrupted pipe or a foreign writer; the server must report it (with the
15
+ // zero ID, since none could be parsed) and keep serving instead of exiting.
16
+ //
17
+ // 1. Send a non-JSON line followed by a valid request.
18
+ // 2. Assert the first response carries an error and no dump.
19
+ // 3. Assert the second response is the normal initial snapshot.
20
+ func TestServeProtocolRejectsInvalidRequestLine(t *testing.T) {
21
+ root := graphSessionFixture(t)
22
+ input := strings.NewReader("not-json\n{\"id\":1}\n")
23
+ var output bytes.Buffer
24
+
25
+ if code := serveSnapshots(input, &output, root, "tsconfig.json"); code != 0 {
26
+ t.Fatalf("serveSnapshots exited %d", code)
27
+ }
28
+ decoder := json.NewDecoder(&output)
29
+ var invalid serveResponse
30
+ var initial serveResponse
31
+ if err := decoder.Decode(&invalid); err != nil {
32
+ t.Fatal(err)
33
+ }
34
+ if err := decoder.Decode(&initial); err != nil {
35
+ t.Fatal(err)
36
+ }
37
+ if invalid.ID != 0 || invalid.Error == "" || !strings.Contains(invalid.Error, "invalid request") || invalid.Dump != nil || invalid.Changed {
38
+ t.Fatalf("invalid line response: %#v", invalid)
39
+ }
40
+ if initial.ID != 1 || initial.Mode != "initial" || !initial.Changed || initial.Dump == nil || initial.Error != "" {
41
+ t.Fatalf("follow-up response: %#v", initial)
42
+ }
43
+ }
@@ -0,0 +1,83 @@
1
+ package main
2
+
3
+ import (
4
+ "bytes"
5
+ "encoding/json"
6
+ "io"
7
+ "os"
8
+ "path/filepath"
9
+ "testing"
10
+ )
11
+
12
+ // stepReader hands the scanner one scripted chunk per Read call, running each
13
+ // step's side effect at the moment the server asks for more input — after the
14
+ // previous request has been fully answered.
15
+ type stepReader struct {
16
+ steps []func() []byte
17
+ buf []byte
18
+ }
19
+
20
+ func (r *stepReader) Read(p []byte) (int, error) {
21
+ if len(r.buf) == 0 {
22
+ if len(r.steps) == 0 {
23
+ return 0, io.EOF
24
+ }
25
+ r.buf = r.steps[0]()
26
+ r.steps = r.steps[1:]
27
+ }
28
+ n := copy(p, r.buf)
29
+ r.buf = r.buf[n:]
30
+ return n, nil
31
+ }
32
+
33
+ // TestServeProtocolRetriesSessionCreation verifies a project that is invalid
34
+ // at startup answers with an error and recovers on a later request.
35
+ //
36
+ // The MCP server starts the native process lazily, so the first request may
37
+ // hit a broken tsconfig. Failing session creation must stay per-request: the
38
+ // server keeps running, and once the config is fixed the same process builds
39
+ // the session and serves the initial dump.
40
+ //
41
+ // 1. Request a snapshot while tsconfig.json is invalid; assert an error.
42
+ // 2. Fix the config between requests, request again.
43
+ // 3. Assert the second response is a normal initial snapshot.
44
+ func TestServeProtocolRetriesSessionCreation(t *testing.T) {
45
+ root := t.TempDir()
46
+ config := filepath.Join(root, "tsconfig.json")
47
+ writeGraphFile(t, config, "{ invalid")
48
+ writeGraphFile(t, filepath.Join(root, "src", "index.ts"), "export class Recovered {}\n")
49
+
50
+ valid := `{"compilerOptions":{"target":"ES2022","module":"commonjs","strict":true},"include":["src"]}`
51
+ input := &stepReader{steps: []func() []byte{
52
+ func() []byte { return []byte("{\"id\":1}\n") },
53
+ func() []byte {
54
+ if err := os.WriteFile(config, []byte(valid), 0o644); err != nil {
55
+ t.Fatal(err)
56
+ }
57
+ return []byte("{\"id\":2}\n")
58
+ },
59
+ }}
60
+ var output bytes.Buffer
61
+
62
+ if code := serveSnapshots(input, &output, root, "tsconfig.json"); code != 0 {
63
+ t.Fatalf("serveSnapshots exited %d", code)
64
+ }
65
+ decoder := json.NewDecoder(&output)
66
+ var failed serveResponse
67
+ var recovered serveResponse
68
+ if err := decoder.Decode(&failed); err != nil {
69
+ t.Fatal(err)
70
+ }
71
+ if err := decoder.Decode(&recovered); err != nil {
72
+ t.Fatal(err)
73
+ }
74
+ if failed.ID != 1 || failed.Error == "" || failed.Dump != nil || failed.Changed {
75
+ t.Fatalf("invalid project response: %#v", failed)
76
+ }
77
+ if recovered.ID != 2 || recovered.Mode != "initial" || !recovered.Changed || recovered.Dump == nil || recovered.Error != "" {
78
+ t.Fatalf("recovered response: %#v", recovered)
79
+ }
80
+ if !hasDumpNode(*recovered.Dump, "Recovered") {
81
+ t.Fatalf("recovered dump omitted the declaration: %#v", recovered.Dump.Nodes)
82
+ }
83
+ }
@@ -0,0 +1,35 @@
1
+ package main
2
+
3
+ import (
4
+ "bytes"
5
+ "encoding/json"
6
+ "strings"
7
+ "testing"
8
+ )
9
+
10
+ // TestServeProtocolReusesSession verifies multiple NDJSON requests share one
11
+ // compiler session and unchanged responses omit the full dump.
12
+ func TestServeProtocolReusesSession(t *testing.T) {
13
+ root := graphSessionFixture(t)
14
+ input := strings.NewReader("{\"id\":1}\n{\"id\":2}\n")
15
+ var output bytes.Buffer
16
+
17
+ if code := serveSnapshots(input, &output, root, "tsconfig.json"); code != 0 {
18
+ t.Fatalf("serveSnapshots exited %d", code)
19
+ }
20
+ decoder := json.NewDecoder(&output)
21
+ var initial serveResponse
22
+ var unchanged serveResponse
23
+ if err := decoder.Decode(&initial); err != nil {
24
+ t.Fatal(err)
25
+ }
26
+ if err := decoder.Decode(&unchanged); err != nil {
27
+ t.Fatal(err)
28
+ }
29
+ if initial.ID != 1 || initial.Mode != "initial" || !initial.Changed || initial.Dump == nil {
30
+ t.Fatalf("initial response: %#v", initial)
31
+ }
32
+ if unchanged.ID != 2 || unchanged.Mode != "unchanged" || unchanged.Changed || unchanged.Dump != nil {
33
+ t.Fatalf("unchanged response: %#v", unchanged)
34
+ }
35
+ }
@@ -0,0 +1,55 @@
1
+ package main
2
+
3
+ import (
4
+ "os"
5
+ "path/filepath"
6
+ "testing"
7
+
8
+ "github.com/samchon/ttsc/packages/ttsc/internal/graph"
9
+ )
10
+
11
+ // TestServeSessionAppliesSourceEditIncrementally verifies a content-only source
12
+ // edit updates the graph through the resident tsgo Program.
13
+ //
14
+ // A declaration rename changes graph nodes but not imports, so UpdateProgram can
15
+ // reuse every unchanged AST. The new dump must expose the replacement symbol
16
+ // and remove the old one without reopening the project.
17
+ //
18
+ // 1. Build the initial graph containing `BeforeEdit`.
19
+ // 2. Replace it with `AfterEdit` in the same source file.
20
+ // 3. Assert incremental mode and an exact post-edit node set.
21
+ func TestServeSessionAppliesSourceEditIncrementally(t *testing.T) {
22
+ root := graphSessionFixture(t)
23
+ session, err := newGraphSession(root, "tsconfig.json")
24
+ if err != nil {
25
+ t.Fatal(err)
26
+ }
27
+ defer session.Close()
28
+ if _, _, _, err := session.Snapshot(); err != nil {
29
+ t.Fatal(err)
30
+ }
31
+
32
+ file := filepath.Join(root, "src", "index.ts")
33
+ if err := os.WriteFile(file, []byte("export class AfterEdit {}\n"), 0o644); err != nil {
34
+ t.Fatal(err)
35
+ }
36
+ dump, mode, changed, err := session.Snapshot()
37
+ if err != nil {
38
+ t.Fatal(err)
39
+ }
40
+ if dump == nil || mode != "incremental" || !changed {
41
+ t.Fatalf("edited snapshot = dump:%v mode:%q changed:%v", dump != nil, mode, changed)
42
+ }
43
+ if !hasDumpNode(*dump, "AfterEdit") || hasDumpNode(*dump, "BeforeEdit") {
44
+ t.Fatalf("incremental dump kept stale nodes: %#v", dump.Nodes)
45
+ }
46
+ }
47
+
48
+ func hasDumpNode(dump graph.Dump, name string) bool {
49
+ for _, node := range dump.Nodes {
50
+ if node.Name == name {
51
+ return true
52
+ }
53
+ }
54
+ return false
55
+ }
@@ -0,0 +1,68 @@
1
+ package main
2
+
3
+ import (
4
+ "os"
5
+ "path/filepath"
6
+ "testing"
7
+ )
8
+
9
+ // TestServeSessionFailsClosedOnDeletedConfig verifies removing the project
10
+ // config fails the snapshot without poisoning the resident session.
11
+ //
12
+ // The boundary twin of the invalid-config case: deletion takes the
13
+ // hashesChanged ErrNotExist branch and its forced reload fails at session
14
+ // construction rather than config parsing. A byte-identical restore must then
15
+ // report unchanged — the resident graph still matches the disk exactly — and
16
+ // the compiler session must keep serving incremental edits, proving the
17
+ // failed reload episode left no partial state behind.
18
+ //
19
+ // 1. Build a valid initial graph, then delete tsconfig.json.
20
+ // 2. Assert the snapshot fails with no dump.
21
+ // 3. Restore the identical config and assert the snapshot is unchanged.
22
+ // 4. Edit the source and assert an incremental refresh still works.
23
+ func TestServeSessionFailsClosedOnDeletedConfig(t *testing.T) {
24
+ root := graphSessionFixture(t)
25
+ session, err := newGraphSession(root, "tsconfig.json")
26
+ if err != nil {
27
+ t.Fatal(err)
28
+ }
29
+ defer session.Close()
30
+ if _, _, _, err := session.Snapshot(); err != nil {
31
+ t.Fatal(err)
32
+ }
33
+
34
+ config := filepath.Join(root, "tsconfig.json")
35
+ saved, err := os.ReadFile(config)
36
+ if err != nil {
37
+ t.Fatal(err)
38
+ }
39
+ if err := os.Remove(config); err != nil {
40
+ t.Fatal(err)
41
+ }
42
+ dump, _, changed, err := session.Snapshot()
43
+ if err == nil || dump != nil || changed {
44
+ t.Fatalf("deleted config must fail closed: dump:%v changed:%v err:%v", dump != nil, changed, err)
45
+ }
46
+
47
+ if err := os.WriteFile(config, saved, 0o644); err != nil {
48
+ t.Fatal(err)
49
+ }
50
+ dump, mode, changed, err := session.Snapshot()
51
+ if err != nil {
52
+ t.Fatal(err)
53
+ }
54
+ if dump != nil || mode != "unchanged" || changed {
55
+ t.Fatalf("identical restored config must be unchanged: dump:%v mode:%q changed:%v", dump != nil, mode, changed)
56
+ }
57
+
58
+ if err := os.WriteFile(filepath.Join(root, "src", "index.ts"), []byte("export class AfterEdit {}\n"), 0o644); err != nil {
59
+ t.Fatal(err)
60
+ }
61
+ dump, mode, changed, err = session.Snapshot()
62
+ if err != nil {
63
+ t.Fatal(err)
64
+ }
65
+ if dump == nil || mode != "incremental" || !changed || !hasDumpNode(*dump, "AfterEdit") {
66
+ t.Fatalf("session did not survive the failed reload: dump:%v mode:%q changed:%v", dump != nil, mode, changed)
67
+ }
68
+ }
@@ -0,0 +1,50 @@
1
+ package main
2
+
3
+ import (
4
+ "os"
5
+ "path/filepath"
6
+ "testing"
7
+ )
8
+
9
+ // TestServeSessionFailsClosedOnInvalidConfig verifies a broken project config
10
+ // never falls back to the last valid graph and recovers after the config is fixed.
11
+ //
12
+ // Returning stale compiler facts is worse than returning an MCP error. A config
13
+ // parse failure must therefore suppress the dump while preserving enough
14
+ // session state to retry the reload on the next request.
15
+ //
16
+ // 1. Build a valid initial graph, then replace tsconfig.json with invalid JSON.
17
+ // 2. Assert snapshot fails with no dump instead of serving the cached graph.
18
+ // 3. Restore a valid config and assert the next request reloads successfully.
19
+ func TestServeSessionFailsClosedOnInvalidConfig(t *testing.T) {
20
+ root := graphSessionFixture(t)
21
+ session, err := newGraphSession(root, "tsconfig.json")
22
+ if err != nil {
23
+ t.Fatal(err)
24
+ }
25
+ defer session.Close()
26
+ if _, _, _, err := session.Snapshot(); err != nil {
27
+ t.Fatal(err)
28
+ }
29
+
30
+ config := filepath.Join(root, "tsconfig.json")
31
+ if err := os.WriteFile(config, []byte("{ invalid"), 0o644); err != nil {
32
+ t.Fatal(err)
33
+ }
34
+ dump, _, changed, err := session.Snapshot()
35
+ if err == nil || dump != nil || changed {
36
+ t.Fatalf("invalid config must fail closed: dump:%v changed:%v err:%v", dump != nil, changed, err)
37
+ }
38
+
39
+ valid := `{"compilerOptions":{"target":"ES2022","module":"commonjs","strict":true},"include":["src"]}`
40
+ if err := os.WriteFile(config, []byte(valid), 0o644); err != nil {
41
+ t.Fatal(err)
42
+ }
43
+ dump, mode, changed, err := session.Snapshot()
44
+ if err != nil {
45
+ t.Fatal(err)
46
+ }
47
+ if dump == nil || mode != "reload" || !changed || !hasDumpNode(*dump, "BeforeEdit") {
48
+ t.Fatalf("fixed config did not recover: dump:%v mode:%q changed:%v", dump != nil, mode, changed)
49
+ }
50
+ }
@@ -0,0 +1,46 @@
1
+ package main
2
+
3
+ import (
4
+ "path/filepath"
5
+ "testing"
6
+ )
7
+
8
+ // TestServeSessionKeepsMissingLiteralRootUnchanged verifies a permanently
9
+ // absent `files` entry does not churn the session.
10
+ //
11
+ // The negative twin of the created-literal-root reload: tracking a missing
12
+ // root as a freshness input must record a stable absent state. If the stored
13
+ // parse-time root set and the reloaded one disagreed about missing literal
14
+ // entries, or the absent state flapped, every snapshot would degrade into a
15
+ // full reload and silently erase the resident session's entire benefit.
16
+ //
17
+ // 1. Open a session whose tsconfig lists absent `src/generated.ts` in files.
18
+ // 2. Take repeated snapshots without touching the project.
19
+ // 3. Assert each one reports unchanged with no replacement dump.
20
+ func TestServeSessionKeepsMissingLiteralRootUnchanged(t *testing.T) {
21
+ root := t.TempDir()
22
+ writeGraphFile(t, filepath.Join(root, "tsconfig.json"), `{
23
+ "compilerOptions": { "target": "ES2022", "module": "commonjs" },
24
+ "files": ["src/index.ts", "src/generated.ts"]
25
+ }`)
26
+ writeGraphFile(t, filepath.Join(root, "src", "index.ts"), "export class Present {}\n")
27
+
28
+ session, err := newGraphSession(root, "tsconfig.json")
29
+ if err != nil {
30
+ t.Fatal(err)
31
+ }
32
+ defer session.Close()
33
+ if _, _, _, err := session.Snapshot(); err != nil {
34
+ t.Fatal(err)
35
+ }
36
+
37
+ for i := 0; i < 2; i++ {
38
+ dump, mode, changed, err := session.Snapshot()
39
+ if err != nil {
40
+ t.Fatal(err)
41
+ }
42
+ if dump != nil || mode != "unchanged" || changed {
43
+ t.Fatalf("iteration %d: missing literal root churned = dump:%v mode:%q changed:%v", i, dump != nil, mode, changed)
44
+ }
45
+ }
46
+ }
@@ -0,0 +1,60 @@
1
+ package main
2
+
3
+ import (
4
+ "os"
5
+ "path/filepath"
6
+ "testing"
7
+ )
8
+
9
+ // TestServeSessionRebuildsImportGraphChange verifies an edited import set uses
10
+ // tsgo's safe rebuild path instead of claiming structural reuse.
11
+ //
12
+ // UpdateProgram can replace one AST only while its module references are
13
+ // unchanged. Adding an import must rebuild the Program, refresh the Checker, and
14
+ // emit the newly-resolved call edge in the same resident native process.
15
+ //
16
+ // 1. Start with two exported functions and no edge between them.
17
+ // 2. Edit the first file to import and call the second.
18
+ // 3. Assert rebuild mode and a calls edge from `main` to `helper`.
19
+ func TestServeSessionRebuildsImportGraphChange(t *testing.T) {
20
+ root := graphSessionFixture(t)
21
+ helper := filepath.Join(root, "src", "helper.ts")
22
+ if err := os.WriteFile(helper, []byte("export function helper(): void {}\n"), 0o644); err != nil {
23
+ t.Fatal(err)
24
+ }
25
+ session, err := newGraphSession(root, "tsconfig.json")
26
+ if err != nil {
27
+ t.Fatal(err)
28
+ }
29
+ defer session.Close()
30
+ if _, _, _, err := session.Snapshot(); err != nil {
31
+ t.Fatal(err)
32
+ }
33
+
34
+ index := filepath.Join(root, "src", "index.ts")
35
+ content := "import { helper } from './helper';\nexport function main(): void { helper(); }\n"
36
+ if err := os.WriteFile(index, []byte(content), 0o644); err != nil {
37
+ t.Fatal(err)
38
+ }
39
+ dump, mode, changed, err := session.Snapshot()
40
+ if err != nil {
41
+ t.Fatal(err)
42
+ }
43
+ if dump == nil || mode != "rebuild" || !changed {
44
+ t.Fatalf("import edit = dump:%v mode:%q changed:%v", dump != nil, mode, changed)
45
+ }
46
+ mainID, helperID := "", ""
47
+ for _, node := range dump.Nodes {
48
+ if node.Name == "main" {
49
+ mainID = node.ID
50
+ } else if node.Name == "helper" {
51
+ helperID = node.ID
52
+ }
53
+ }
54
+ for _, edge := range dump.Edges {
55
+ if edge.From == mainID && edge.To == helperID && edge.Kind == "calls" {
56
+ return
57
+ }
58
+ }
59
+ t.Fatalf("rebuilt graph omitted main -> helper call: nodes=%#v edges=%#v", dump.Nodes, dump.Edges)
60
+ }
@@ -0,0 +1,44 @@
1
+ package main
2
+
3
+ import (
4
+ "path/filepath"
5
+ "testing"
6
+ )
7
+
8
+ // TestServeSessionReloadsCreatedLiteralRoot verifies a `files`-listed root
9
+ // that does not exist yet joins the graph once it is created.
10
+ //
11
+ // A literal `files` entry survives a root-set reload whether or not the file
12
+ // exists, so the root comparison never flips for it, and a file absent from
13
+ // the program has no source hash and no unresolved-import candidate. The
14
+ // session must track missing literal roots as freshness inputs of their own.
15
+ //
16
+ // 1. Open a session whose tsconfig lists absent `src/generated.ts` in files.
17
+ // 2. Create that file without touching any other project input.
18
+ // 3. Assert the next snapshot reloads and exposes the new declaration.
19
+ func TestServeSessionReloadsCreatedLiteralRoot(t *testing.T) {
20
+ root := t.TempDir()
21
+ writeGraphFile(t, filepath.Join(root, "tsconfig.json"), `{
22
+ "compilerOptions": { "target": "ES2022", "module": "commonjs" },
23
+ "files": ["src/index.ts", "src/generated.ts"]
24
+ }`)
25
+ writeGraphFile(t, filepath.Join(root, "src", "index.ts"), "export class Present {}\n")
26
+
27
+ session, err := newGraphSession(root, "tsconfig.json")
28
+ if err != nil {
29
+ t.Fatal(err)
30
+ }
31
+ defer session.Close()
32
+ if _, _, _, err := session.Snapshot(); err != nil {
33
+ t.Fatal(err)
34
+ }
35
+
36
+ writeGraphFile(t, filepath.Join(root, "src", "generated.ts"), "export class Generated {}\n")
37
+ dump, mode, changed, err := session.Snapshot()
38
+ if err != nil {
39
+ t.Fatal(err)
40
+ }
41
+ if dump == nil || mode != "reload" || !changed || !hasDumpNode(*dump, "Generated") {
42
+ t.Fatalf("created files-listed root was not reloaded: dump:%v mode:%q changed:%v", dump != nil, mode, changed)
43
+ }
44
+ }
@@ -0,0 +1,45 @@
1
+ package main
2
+
3
+ import (
4
+ "os"
5
+ "path/filepath"
6
+ "testing"
7
+ )
8
+
9
+ // TestServeSessionReloadsExtendedConfigChange verifies every config in an
10
+ // extends chain participates in snapshot freshness.
11
+ //
12
+ // Watching only the requested tsconfig misses compiler-option changes inherited
13
+ // from a base file. The session records all parsed extended source files and
14
+ // must full-reload before answering after any one changes.
15
+ //
16
+ // 1. Create a project whose tsconfig extends `base.json`.
17
+ // 2. Build once, then change a compiler option only in the base config.
18
+ // 3. Assert the next snapshot reports a full reload.
19
+ func TestServeSessionReloadsExtendedConfigChange(t *testing.T) {
20
+ root := t.TempDir()
21
+ writeGraphFile(t, filepath.Join(root, "base.json"), `{"compilerOptions":{"strict":true,"target":"ES2022"}}`)
22
+ writeGraphFile(t, filepath.Join(root, "tsconfig.json"), `{"extends":"./base.json","include":["src"]}`)
23
+ writeGraphFile(t, filepath.Join(root, "src", "index.ts"), "export class Value {}\n")
24
+
25
+ session, err := newGraphSession(root, "tsconfig.json")
26
+ if err != nil {
27
+ t.Fatal(err)
28
+ }
29
+ defer session.Close()
30
+ if _, _, _, err := session.Snapshot(); err != nil {
31
+ t.Fatal(err)
32
+ }
33
+
34
+ base := filepath.Join(root, "base.json")
35
+ if err := os.WriteFile(base, []byte(`{"compilerOptions":{"strict":false,"target":"ES2022"}}`), 0o644); err != nil {
36
+ t.Fatal(err)
37
+ }
38
+ dump, mode, changed, err := session.Snapshot()
39
+ if err != nil {
40
+ t.Fatal(err)
41
+ }
42
+ if dump == nil || mode != "reload" || !changed {
43
+ t.Fatalf("extended config edit = dump:%v mode:%q changed:%v", dump != nil, mode, changed)
44
+ }
45
+ }
@@ -0,0 +1,60 @@
1
+ package main
2
+
3
+ import (
4
+ "os"
5
+ "path/filepath"
6
+ "testing"
7
+ )
8
+
9
+ // TestServeSessionReloadsNewlyResolvedConditionalExport verifies conditional
10
+ // exports objects and array fallbacks contribute resolution candidates.
11
+ //
12
+ // Real packages rarely map a subpath straight to one string: `exports`
13
+ // commonly nests condition objects (`types`, `default`) and array fallbacks.
14
+ // Every leaf target must be tracked, or a package shipping its compiled
15
+ // output later stays invisible to the resident graph.
16
+ //
17
+ // 1. Import a package subpath whose exports leaf targets do not exist yet.
18
+ // 2. Create the `types` condition's declaration target.
19
+ // 3. Assert the next snapshot reports a full reload.
20
+ func TestServeSessionReloadsNewlyResolvedConditionalExport(t *testing.T) {
21
+ root := t.TempDir()
22
+ writeGraphFile(t, filepath.Join(root, "tsconfig.json"), `{
23
+ "compilerOptions": { "target": "ES2022", "module": "commonjs" },
24
+ "files": ["src/index.ts"]
25
+ }`)
26
+ writeGraphFile(t, filepath.Join(root, "src", "index.ts"), "import { feature } from 'fixture-package/feature';\nexport function main(): void { feature(); }\n")
27
+ writeGraphFile(t, filepath.Join(root, "node_modules", "fixture-package", "package.json"), `{
28
+ "name": "fixture-package",
29
+ "exports": {
30
+ "./feature": {
31
+ "types": "./dist/feature.d.ts",
32
+ "default": ["./dist/feature.js"]
33
+ }
34
+ }
35
+ }`)
36
+
37
+ session, err := newGraphSession(root, "tsconfig.json")
38
+ if err != nil {
39
+ t.Fatal(err)
40
+ }
41
+ defer session.Close()
42
+ if _, _, _, err := session.Snapshot(); err != nil {
43
+ t.Fatal(err)
44
+ }
45
+
46
+ target := filepath.Join(root, "node_modules", "fixture-package", "dist", "feature.d.ts")
47
+ if err := os.MkdirAll(filepath.Dir(target), 0o755); err != nil {
48
+ t.Fatal(err)
49
+ }
50
+ if err := os.WriteFile(target, []byte("export declare function feature(): void;\n"), 0o644); err != nil {
51
+ t.Fatal(err)
52
+ }
53
+ dump, mode, changed, err := session.Snapshot()
54
+ if err != nil {
55
+ t.Fatal(err)
56
+ }
57
+ if dump == nil || mode != "reload" || !changed {
58
+ t.Fatalf("new conditional export target = dump:%v mode:%q changed:%v", dump != nil, mode, changed)
59
+ }
60
+ }
@@ -0,0 +1,44 @@
1
+ package main
2
+
3
+ import (
4
+ "path/filepath"
5
+ "testing"
6
+ )
7
+
8
+ // TestServeSessionReloadsNewlyResolvedConfigTypes verifies compilerOptions
9
+ // `types` entries participate in freshness without any source-level syntax.
10
+ //
11
+ // A `types` package is requested by the config alone, so no triple-slash
12
+ // directive or import exists to contribute resolution candidates. Generated
13
+ // typeRoots packages appear without touching package.json or the lockfile,
14
+ // which would leave the graph permanently blind to them.
15
+ //
16
+ // 1. Open a session whose config lists absent types package `fixture-types`.
17
+ // 2. Create the package under the configured typeRoots directory.
18
+ // 3. Assert the next snapshot reports a full reload.
19
+ func TestServeSessionReloadsNewlyResolvedConfigTypes(t *testing.T) {
20
+ root := t.TempDir()
21
+ writeGraphFile(t, filepath.Join(root, "tsconfig.json"), `{
22
+ "compilerOptions": { "target": "ES2022", "typeRoots": ["./types"], "types": ["fixture-types"] },
23
+ "files": ["src/index.ts"]
24
+ }`)
25
+ writeGraphFile(t, filepath.Join(root, "src", "index.ts"), "export const value = 1;\n")
26
+
27
+ session, err := newGraphSession(root, "tsconfig.json")
28
+ if err != nil {
29
+ t.Fatal(err)
30
+ }
31
+ defer session.Close()
32
+ if _, _, _, err := session.Snapshot(); err != nil {
33
+ t.Fatal(err)
34
+ }
35
+
36
+ writeGraphFile(t, filepath.Join(root, "types", "fixture-types", "index.d.ts"), "interface FixtureType { id: number }\n")
37
+ dump, mode, changed, err := session.Snapshot()
38
+ if err != nil {
39
+ t.Fatal(err)
40
+ }
41
+ if dump == nil || mode != "reload" || !changed {
42
+ t.Fatalf("new config types package = dump:%v mode:%q changed:%v", dump != nil, mode, changed)
43
+ }
44
+ }