ttsc 0.31.4 → 0.32.0

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 (140) hide show
  1. package/cmd/ttsc/compiler_argument_cwd_test.go +38 -38
  2. package/cmd/ttscserver/main.go +2 -12
  3. package/cmd/ttscserver/resolve_launch_uses_native_flag_authority_test.go +7 -7
  4. package/driver/lsp.go +4 -43
  5. package/driver/rewrite.go +1 -1
  6. package/internal/e2etrace/completion_hints.go +3 -3
  7. package/internal/e2etrace/completion_hints_test.go +26 -12
  8. package/internal/graph/build.go +5 -6
  9. package/internal/graph/graph.go +3 -3
  10. package/internal/graph/line_model.go +2 -35
  11. package/internal/lspserver/lsp_clean_didopen_refreshes_compiler_state_test.go +13 -36
  12. package/internal/lspserver/lsp_hints_refresh_ignores_a_superseded_generation_test.go +9 -3
  13. package/internal/lspserver/lsp_plugin.go +19 -0
  14. package/internal/lspserver/lsp_proxy.go +9 -129
  15. package/internal/lspserver/lsp_server.go +0 -8
  16. package/internal/lspserver/lsp_watched_file_changes_reach_compiler_caches_test.go +6 -36
  17. package/internal/sourceprocess/windows_target_owns_suspended_admission_test.go +5 -5
  18. package/lib/compiler/internal/build/BuildExecution.js +11 -3
  19. package/lib/compiler/internal/build/BuildExecution.js.map +1 -1
  20. package/lib/compiler/internal/build/TsgoArguments.js.map +1 -1
  21. package/lib/compiler/internal/project/CompilerProjectSelection.d.ts +9 -8
  22. package/lib/compiler/internal/project/CompilerProjectSelection.js +11 -9
  23. package/lib/compiler/internal/project/CompilerProjectSelection.js.map +1 -1
  24. package/lib/compiler/internal/project/resolveOwningProjectConfig.d.ts +20 -8
  25. package/lib/compiler/internal/project/resolveOwningProjectConfig.js +21 -12
  26. package/lib/compiler/internal/project/resolveOwningProjectConfig.js.map +1 -1
  27. package/lib/compiler/internal/runSingleFileEmit.d.ts +5 -4
  28. package/lib/compiler/internal/runSingleFileEmit.js +5 -4
  29. package/lib/compiler/internal/runSingleFileEmit.js.map +1 -1
  30. package/lib/flags/ParseOptions.d.ts +2 -2
  31. package/lib/internal/E2ETrace.d.ts +1 -0
  32. package/lib/internal/E2ETrace.js.map +1 -1
  33. package/lib/internal/OwnedNativeProcess.d.ts +2 -2
  34. package/lib/internal/OwnedNativeProcess.js +2 -2
  35. package/lib/internal/SourceNativeRetirement.d.ts +8 -7
  36. package/lib/internal/SourceNativeRetirement.js +19 -10
  37. package/lib/internal/SourceNativeRetirement.js.map +1 -1
  38. package/lib/internal/TtsxNodeOptions.d.ts +79 -0
  39. package/lib/internal/TtsxNodeOptions.js +121 -0
  40. package/lib/internal/TtsxNodeOptions.js.map +1 -0
  41. package/lib/internal/javascriptRuntimeCapabilities.js +4 -1
  42. package/lib/internal/javascriptRuntimeCapabilities.js.map +1 -1
  43. package/lib/internal/parseWindowsDirectoryCaseSensitivity.d.ts +5 -5
  44. package/lib/internal/parseWindowsDirectoryCaseSensitivity.js +5 -5
  45. package/lib/internal/pathIdentity/createCaseSensitivityObservations.d.ts +35 -0
  46. package/lib/internal/pathIdentity/createCaseSensitivityObservations.js +209 -0
  47. package/lib/internal/pathIdentity/createCaseSensitivityObservations.js.map +1 -0
  48. package/lib/internal/pathIdentity/createFilesystemPathIdentityContext.js +2 -143
  49. package/lib/internal/pathIdentity/createFilesystemPathIdentityContext.js.map +1 -1
  50. package/lib/internal/probeLocalProcess.d.ts +5 -1
  51. package/lib/internal/probeLocalProcess.js +9 -7
  52. package/lib/internal/probeLocalProcess.js.map +1 -1
  53. package/lib/internal/projectInputPathIdentity.d.ts +1 -0
  54. package/lib/internal/projectInputPathIdentity.js +1 -0
  55. package/lib/internal/projectInputPathIdentity.js.map +1 -1
  56. package/lib/launcher/internal/parseTtsxCLI.d.ts +5 -4
  57. package/lib/launcher/internal/parseTtsxCLI.js +5 -4
  58. package/lib/launcher/internal/parseTtsxCLI.js.map +1 -1
  59. package/lib/launcher/internal/prepareExecution.js.map +1 -1
  60. package/lib/launcher/internal/resolveSingleFileOutput.d.ts +8 -9
  61. package/lib/launcher/internal/resolveSingleFileOutput.js +8 -9
  62. package/lib/launcher/internal/resolveSingleFileOutput.js.map +1 -1
  63. package/lib/launcher/internal/runTtsc.js +5 -5
  64. package/lib/launcher/internal/runTtsx.js +5 -2
  65. package/lib/launcher/internal/runTtsx.js.map +1 -1
  66. package/lib/launcher/internal/runtime/ProcessOwnedDirectory.d.ts +2 -0
  67. package/lib/launcher/internal/runtime/ProcessOwnedDirectory.js +2 -1
  68. package/lib/launcher/internal/runtime/ProcessOwnedDirectory.js.map +1 -1
  69. package/lib/launcher/internal/runtime/RuntimeEntrySources.d.ts +40 -0
  70. package/lib/launcher/internal/runtime/RuntimeEntrySources.js +57 -0
  71. package/lib/launcher/internal/runtime/RuntimeEntrySources.js.map +1 -0
  72. package/lib/launcher/internal/runtime/RuntimeProjectOwnership.d.ts +5 -4
  73. package/lib/launcher/internal/runtime/RuntimeProjectOwnership.js +8 -6
  74. package/lib/launcher/internal/runtime/RuntimeProjectOwnership.js.map +1 -1
  75. package/lib/launcher/internal/runtime/commonJsExportNames.d.ts +2 -1
  76. package/lib/launcher/internal/runtime/commonJsExportNames.js +2 -1
  77. package/lib/launcher/internal/runtime/commonJsExportNames.js.map +1 -1
  78. package/lib/launcher/internal/runtime/installRuntimeHooks.js +35 -9
  79. package/lib/launcher/internal/runtime/installRuntimeHooks.js.map +1 -1
  80. package/lib/launcher/internal/runtime/isLocalProcessGone.d.ts +1 -1
  81. package/lib/launcher/internal/runtime/isLocalProcessGone.js +4 -4
  82. package/lib/launcher/internal/runtime/isLocalProcessGone.js.map +1 -1
  83. package/lib/launcher/internal/runtime/resolveRuntimeCleanTargets.js +3 -1
  84. package/lib/launcher/internal/runtime/resolveRuntimeCleanTargets.js.map +1 -1
  85. package/lib/launcher/internal/watch/FseventsStreams.d.ts +2 -0
  86. package/lib/launcher/internal/watch/FseventsStreams.js +19 -0
  87. package/lib/launcher/internal/watch/FseventsStreams.js.map +1 -1
  88. package/lib/launcher/internal/watch/WatchTopology.d.ts +74 -12
  89. package/lib/launcher/internal/watch/WatchTopology.js +301 -92
  90. package/lib/launcher/internal/watch/WatchTopology.js.map +1 -1
  91. package/lib/launcher/internal/watch/projectInputReloadEventShouldNotify.d.ts +4 -1
  92. package/lib/launcher/internal/watch/projectInputReloadEventShouldNotify.js +3 -2
  93. package/lib/launcher/internal/watch/projectInputReloadEventShouldNotify.js.map +1 -1
  94. package/lib/plugin/internal/load/loadProjectPlugins.js +17 -9
  95. package/lib/plugin/internal/load/loadProjectPlugins.js.map +1 -1
  96. package/lib/plugin/internal/load/resolveNativeSource.js +2 -3
  97. package/lib/plugin/internal/load/resolveNativeSource.js.map +1 -1
  98. package/lib/plugin/internal/source/pluginModuleReplaceDirectories.d.ts +4 -1
  99. package/lib/plugin/internal/source/pluginModuleReplaceDirectories.js +9 -2
  100. package/lib/plugin/internal/source/pluginModuleReplaceDirectories.js.map +1 -1
  101. package/lib/structures/internal/TtscCommonOptions.d.ts +2 -2
  102. package/package.json +9 -9
  103. package/src/compiler/internal/build/BuildExecution.ts +15 -6
  104. package/src/compiler/internal/build/TsgoArguments.ts +4 -1
  105. package/src/compiler/internal/project/CompilerProjectSelection.ts +14 -14
  106. package/src/compiler/internal/project/resolveOwningProjectConfig.ts +29 -14
  107. package/src/compiler/internal/runSingleFileEmit.ts +11 -7
  108. package/src/flags/ParseOptions.ts +2 -2
  109. package/src/internal/E2ETrace.ts +5 -1
  110. package/src/internal/OwnedNativeProcess.ts +2 -2
  111. package/src/internal/SourceNativeRetirement.ts +42 -19
  112. package/src/internal/TtsxNodeOptions.ts +124 -0
  113. package/src/internal/javascriptRuntimeCapabilities.ts +6 -1
  114. package/src/internal/parseWindowsDirectoryCaseSensitivity.ts +5 -5
  115. package/src/internal/pathIdentity/createCaseSensitivityObservations.ts +242 -0
  116. package/src/internal/pathIdentity/createFilesystemPathIdentityContext.ts +2 -169
  117. package/src/internal/probeLocalProcess.ts +13 -8
  118. package/src/internal/projectInputPathIdentity.ts +1 -0
  119. package/src/launcher/internal/parseTtsxCLI.ts +5 -4
  120. package/src/launcher/internal/prepareExecution.ts +4 -1
  121. package/src/launcher/internal/resolveSingleFileOutput.ts +8 -9
  122. package/src/launcher/internal/runTtsc.ts +5 -5
  123. package/src/launcher/internal/runTtsx.ts +17 -8
  124. package/src/launcher/internal/runtime/ProcessOwnedDirectory.ts +5 -1
  125. package/src/launcher/internal/runtime/RuntimeEntrySources.ts +57 -0
  126. package/src/launcher/internal/runtime/RuntimeProjectOwnership.ts +23 -10
  127. package/src/launcher/internal/runtime/commonJsExportNames.ts +11 -4
  128. package/src/launcher/internal/runtime/installRuntimeHooks.ts +55 -13
  129. package/src/launcher/internal/runtime/isLocalProcessGone.ts +5 -3
  130. package/src/launcher/internal/runtime/resolveRuntimeCleanTargets.ts +17 -5
  131. package/src/launcher/internal/watch/FseventsStreams.ts +22 -0
  132. package/src/launcher/internal/watch/WatchTopology.ts +387 -99
  133. package/src/launcher/internal/watch/projectInputReloadEventShouldNotify.ts +14 -9
  134. package/src/plugin/internal/load/loadProjectPlugins.ts +17 -3
  135. package/src/plugin/internal/load/resolveNativeSource.ts +10 -4
  136. package/src/plugin/internal/source/pluginModuleReplaceDirectories.ts +10 -1
  137. package/src/structures/internal/TtscCommonOptions.ts +2 -2
  138. package/internal/graphsymbols/position_helpers_follow_ecma_line_terminators_test.go +0 -78
  139. package/internal/graphsymbols/provider.go +0 -650
  140. package/internal/lspserver/lsp_symbols.go +0 -212
@@ -1,6 +1,7 @@
1
1
  import path from "node:path";
2
2
 
3
3
  import { type ProjectInputPathIdentity } from "../../../internal/pathIdentity/ProjectInputPathIdentity";
4
+ import { type ProjectInputPathIdentityContext } from "../../../internal/pathIdentity/ProjectInputPathIdentityContext";
4
5
  import { createProjectInputPathIdentityContext } from "../../../internal/pathIdentity/createProjectInputPathIdentityContext";
5
6
  import { literalGlobRoot } from "./literalGlobRoot";
6
7
 
@@ -12,6 +13,8 @@ import { literalGlobRoot } from "./literalGlobRoot";
12
13
  * lane only after the surrounding change detector admits the event; unchanged
13
14
  * bytes remain quiet before this classifier is observed.
14
15
  *
16
+ * @param identities The decision's identity transaction; absence creates a
17
+ * native one for this classification alone.
15
18
  * @evidence contracts/common.md#principled-implementation Exact selection files and immediate resolution-directory entries choose execution reload; a strictly nested declared glob can explain its own ancestor's membership delta without exempting unrelated selection changes.
16
19
  * @evidence contracts/common.md#clear-and-simple-design Exact-file membership, immediate-entry membership and causal explanations have distinct helpers under one classification operation.
17
20
  * @evidence contracts/common.md#prohibited-implementation-shortcuts A data exemption must explain the same directory's observed delta, not cancel every reload because some unrelated data event occurred.
@@ -22,15 +25,17 @@ import { literalGlobRoot } from "./literalGlobRoot";
22
25
  *
23
26
  * @evidenceExclude contracts/performance.md#bound-retention-and-release-resources Identity caches and predicate collections are local to one decision, without a resident historical cache or native handle.
24
27
  */
25
- export function projectInputReloadEventShouldNotify(input: {
26
- causedBy?: readonly string[];
27
- changed?: string;
28
- changedInputs: readonly string[];
29
- reloadDirectories?: readonly string[];
30
- reloadFiles: readonly string[];
31
- globs?: readonly string[];
32
- }): boolean {
33
- const identities = createProjectInputPathIdentityContext();
28
+ export function projectInputReloadEventShouldNotify(
29
+ input: {
30
+ causedBy?: readonly string[];
31
+ changed?: string;
32
+ changedInputs: readonly string[];
33
+ reloadDirectories?: readonly string[];
34
+ reloadFiles: readonly string[];
35
+ globs?: readonly string[];
36
+ },
37
+ identities: ProjectInputPathIdentityContext = createProjectInputPathIdentityContext(),
38
+ ): boolean {
34
39
  const reloadFiles = new Set(
35
40
  input.reloadFiles.map((location) => identities.resolve(location).key),
36
41
  );
@@ -9,6 +9,7 @@ import { readProjectConfig } from "../../../compiler/internal/project/readProjec
9
9
  import { SidecarEnvironment } from "../../../compiler/internal/sharedHost/SidecarEnvironment";
10
10
  import { E2ETrace } from "../../../internal/E2ETrace";
11
11
  import { OwnedSynchronousProcess } from "../../../internal/OwnedSynchronousProcess";
12
+ import { TtsxNodeOptions } from "../../../internal/TtsxNodeOptions";
12
13
  import { createCanonicalTempDirectory } from "../../../internal/createCanonicalTempDirectory";
13
14
  import { javascriptRuntimeCapabilities } from "../../../internal/javascriptRuntimeCapabilities";
14
15
  import { resolveNodeBinary } from "../../../internal/resolveNodeBinary";
@@ -445,6 +446,7 @@ export function loadProjectPlugins(options: {
445
446
  effectiveEnv,
446
447
  watchReplacements,
447
448
  packageReaders,
449
+ sourceIdentities,
448
450
  ))
449
451
  if (reportsPluginSource(directory)) interests.add(directory);
450
452
  }
@@ -534,6 +536,7 @@ export function loadProjectPlugins(options: {
534
536
  effectiveEnv,
535
537
  watchReplacements,
536
538
  packageReaders,
539
+ sourceIdentities,
537
540
  );
538
541
  // Admission can now narrow the provisional interests without restamping the
539
542
  // retained package/module baselines. Final host errors still keep these roots.
@@ -736,8 +739,10 @@ export function loadProjectPlugins(options: {
736
739
  binary,
737
740
  // This fallback is compiled from our utility-host, whose actual writer
738
741
  // publishes provenance and supports a separate compiler argument cwd.
739
- // A separately selected executable host must opt
740
- // in itself; a linked library cannot certify that foreign host.
742
+ // A separately selected executable host must opt in itself; a linked
743
+ // library cannot certify that foreign host. An undeclared capability
744
+ // only withholds the optional protocol: build execution still runs such
745
+ // a host and observes it from outside, never rejecting it.
741
746
  capabilities:
742
747
  record.stage === "transform" &&
743
748
  record.kind === "linked" &&
@@ -1436,9 +1441,13 @@ class CommonJsDescriptorLoadError extends Error {
1436
1441
  function loadCommonJsDescriptor(
1437
1442
  request: string,
1438
1443
  context: ITtscPluginFactoryContext,
1439
- effectiveEnv: NodeJS.ProcessEnv,
1444
+ loadEnv: NodeJS.ProcessEnv,
1440
1445
  descriptorCache: DescriptorCacheOptions,
1441
1446
  ): IsolatedPluginDescriptor {
1447
+ // The evaluator is ttsc's own helper: inside a ttsx run it gets the user's
1448
+ // NODE_OPTIONS, not ttsx's preloads, which would refuse the descriptor cache
1449
+ // and run in every evaluation (#1740). The runtime probe below does the same.
1450
+ const effectiveEnv = TtsxNodeOptions.forHelpers(loadEnv);
1442
1451
  const runtime = pluginDescriptorRuntimeBinary(effectiveEnv);
1443
1452
  const ttsx = effectiveEnv.TTSC_TTSX_BINARY ?? process.env.TTSC_TTSX_BINARY;
1444
1453
  const runtimeCapabilities = javascriptRuntimeCapabilities(
@@ -2347,6 +2356,7 @@ function pluginBuildDirectories(
2347
2356
  env: NodeJS.ProcessEnv,
2348
2357
  replacements?: Map<string, readonly string[]>,
2349
2358
  readers?: Map<string, SourcePluginWorkspace.GoModReader>,
2359
+ identities?: PluginContentIdentities.Store,
2350
2360
  ): string[] {
2351
2361
  const directories = new Set<string>();
2352
2362
  for (const record of records) {
@@ -2361,6 +2371,7 @@ function pluginBuildDirectories(
2361
2371
  env,
2362
2372
  replacements,
2363
2373
  readers,
2374
+ identities,
2364
2375
  ))
2365
2376
  directories.add(directory);
2366
2377
  }
@@ -2380,17 +2391,20 @@ function pluginReplacementWatchDirectories(
2380
2391
  env: NodeJS.ProcessEnv,
2381
2392
  observed?: Map<string, readonly string[]>,
2382
2393
  readers?: Map<string, SourcePluginWorkspace.GoModReader>,
2394
+ identities?: PluginContentIdentities.Store,
2383
2395
  ): readonly string[] {
2384
2396
  const previous = observed?.get(moduleRoot);
2385
2397
  if (previous !== undefined) return previous;
2386
2398
  let directories: readonly string[];
2387
2399
  try {
2400
+ // The load's store goes with the reader this registers first (#1739).
2388
2401
  directories = pluginModuleReplaceDirectories(
2389
2402
  moduleRoot,
2390
2403
  env,
2391
2404
  undefined,
2392
2405
  undefined,
2393
2406
  readers,
2407
+ identities,
2394
2408
  ).map((replacement) => replacement.directory);
2395
2409
  } catch {
2396
2410
  // An unreadable manifest/tool is not valid metadata. The already observed
@@ -30,13 +30,19 @@ export function resolveNativeSource(
30
30
  plugin: ITtscPlugin,
31
31
  config: ITtscProjectPluginConfig,
32
32
  index: number,
33
- options?: { env?: NodeJS.ProcessEnv; observation?: NativeSourcePackages.Package },
33
+ options?: {
34
+ env?: NodeJS.ProcessEnv;
35
+ observation?: NativeSourcePackages.Package;
36
+ },
34
37
  ): { kind: "executable" | "linked"; moduleRoot: string } {
35
38
  const label = pluginLabel(plugin, config, index);
36
39
  requirePluginSource(source, label);
37
40
  const { moduleRoot } = resolvePluginGoModule(source, label);
38
- const observation = options?.observation ?? NativeSourcePackages.propose([
39
- { source, label, ownModule: plugin.stage === "check" },
40
- ], SidecarEnvironment.merge(options?.env ?? process.env))[0]!.observation;
41
+ const observation =
42
+ options?.observation ??
43
+ NativeSourcePackages.propose(
44
+ [{ source, label, ownModule: plugin.stage === "check" }],
45
+ SidecarEnvironment.merge(options?.env ?? process.env),
46
+ )[0]!.observation;
41
47
  return { kind: NativeSourcePackages.kind(observation, label), moduleRoot };
42
48
  }
@@ -4,6 +4,7 @@ import path from "node:path";
4
4
  import { resolvePhysicalPath } from "../../../internal/pathIdentity/resolvePhysicalPath";
5
5
  import { GoToolResolution } from "./GoToolResolution";
6
6
  import type { IPluginModuleReplaceDirectory } from "./IPluginModuleReplaceDirectory";
7
+ import type { PluginContentIdentities } from "./PluginContentIdentities";
7
8
  import { SourcePluginWorkspace } from "./SourcePluginWorkspace";
8
9
  import { resolveGoCompiler } from "./resolveGoCompiler";
9
10
  import { selectPluginModuleReplaceDirectories } from "./selectPluginModuleReplaceDirectories";
@@ -30,6 +31,8 @@ import { selectPluginModuleReplaceDirectories } from "./selectPluginModuleReplac
30
31
  * @param env The build's effective environment.
31
32
  * @param goBinary The Go tool the build runs, or `undefined` to resolve it as
32
33
  * the build does.
34
+ * @param identities The load's record store, carried by a reader this call
35
+ * registers so the load's later reads reuse recorded syntax.
33
36
  * @returns Each replacement outside the module, sorted by module path: the
34
37
  * replaced module path and version, the target as `go.mod` spells it, and the
35
38
  * target's absolute path.
@@ -52,6 +55,7 @@ export function pluginModuleReplaceDirectories(
52
55
  goBinary?: string,
53
56
  reader?: SourcePluginWorkspace.GoModReader,
54
57
  readers?: Map<string, SourcePluginWorkspace.GoModReader>,
58
+ identities?: PluginContentIdentities.Store,
55
59
  ): IPluginModuleReplaceDirectory[] {
56
60
  const root = path.resolve(moduleRoot);
57
61
  let text: string;
@@ -72,7 +76,12 @@ export function pluginModuleReplaceDirectories(
72
76
  root,
73
77
  );
74
78
  const parsed = (
75
- reader ?? SourcePluginWorkspace.createGoModReader(go, root, env, readers)
79
+ reader ??
80
+ // WARNING (#1739): the first reader registered for a Go binary serves the
81
+ // whole load, so it must carry the load's record store, or every later
82
+ // reader request of the load gets a store-less one and the go.mod syntax
83
+ // records are never read or written.
84
+ SourcePluginWorkspace.createGoModReader(go, root, env, readers, identities)
76
85
  ).read(root);
77
86
  // Parsed syntax may be shared; physical containment is current native state
78
87
  // and must be recomputed even when these manifest bytes are unchanged.
@@ -86,8 +86,8 @@ export interface TtscCommonOptions {
86
86
  }[];
87
87
 
88
88
  /**
89
- * Retained native compiler argument base when project selection redirects
90
- * the Program to a different root. Absent retains the selected project root.
89
+ * Retained native compiler argument base when project selection redirects the
90
+ * Program to a different root. Absent retains the selected project root.
91
91
  * Response frames and relative native options use this base; plugin/rule
92
92
  * context and config-relative paths retain their separate project authority.
93
93
  */
@@ -1,78 +0,0 @@
1
- package graphsymbols
2
-
3
- import (
4
- "testing"
5
- "unicode/utf8"
6
-
7
- "github.com/samchon/ttsc/packages/ttsc/internal/graph"
8
- "github.com/samchon/ttsc/packages/ttsc/internal/lspserver"
9
- )
10
-
11
- // TestPositionHelpersFollowECMALineTerminators verifies graph-backed LSP
12
- // positions use the compiler's LF, CRLF, CR, LS, and PS line boundaries.
13
- //
14
- // An offset-to-position helper that counted only LF while its inverse counted
15
- // CR too would map an editor cursor to a graph offset that returns on a
16
- // different line. Literal starts and ends establish three authored logical
17
- // lines; the test walks their UTF-8 boundaries, including an emoji, and requires
18
- // a round trip. Columns are not compared to literal UTF-16 counts, so symmetric
19
- // column mistakes can pass. No editor/server request is exercised.
20
- //
21
- // 1. Build three lines with each ECMAScript terminator.
22
- // 2. Enumerate every valid cursor boundary before each terminator.
23
- // 3. Convert each offset to UTF-16 LSP position and back to the same offset.
24
- //
25
- // @evidence contracts/testing.md#behavioral-verification Verifies graph-backed LSP positions use the compiler's LF, CRLF, CR, LS, and PS line boundaries.
26
- // @evidence contracts/testing.md#independent-expectations Each case embeds one ECMAScript terminator (LF, CRLF, CR, LS or PS) in a three-line literal, so the expected line count (three) and the line of every offset follow from the language's line-terminator definition. Columns are checked only by an offset -> position -> offset round trip over every UTF-8 rune boundary (including the two-unit emoji), not against literal UTF-16 column numbers, so a column error made symmetrically by both helpers would not be detected.
27
- // @evidence contracts/testing.md#distinguishing-cases Build three lines with each ECMAScript terminator; Enumerate every valid cursor boundary before each terminator; Convert each offset to UTF-16 LSP position and back to the same offset; the five terminator subtests differ only in the terminator, and a position on the line index just past the last line must not resolve (negative case). No empty-text or out-of-range-column input is exercised here.
28
- // @evidence contracts/testing.md#execution-ownership Directly calls the maintained line and position helpers on authored UTF-8 strings in this process. Literal byte boundaries establish enumeration; round-trip column expectations share the two converters and are not a separate UTF-16 oracle. It creates no native fixture, loads no Program, installs no consumer, and starts no product process or editor/server connection.
29
- func TestPositionHelpersFollowECMALineTerminators(t *testing.T) {
30
- cases := []struct {
31
- name string
32
- terminator string
33
- }{
34
- {name: "LF", terminator: "\n"},
35
- {name: "CRLF", terminator: "\r\n"},
36
- {name: "CR", terminator: "\r"},
37
- {name: "LS", terminator: "\u2028"},
38
- {name: "PS", terminator: "\u2029"},
39
- }
40
- for _, tc := range cases {
41
- t.Run(tc.name, func(t *testing.T) {
42
- text := "alpha😀" + tc.terminator + "beta" + tc.terminator + "gamma"
43
- starts := graph.ECMALineStarts(text)
44
- if len(starts) != 3 {
45
- t.Fatalf("line starts = %v, want three lines", starts)
46
- }
47
- expectedStarts := []int{0, 9 + len(tc.terminator), 13 + 2*len(tc.terminator)}
48
- expectedEnds := []int{9, 13 + len(tc.terminator), len(text)}
49
- for line, start := range starts {
50
- end := graph.LineEnd(text, starts, line)
51
- if start != expectedStarts[line] || end != expectedEnds[line] {
52
- t.Fatalf("line %d boundaries = (%d, %d), want (%d, %d)", line, start, end, expectedStarts[line], expectedEnds[line])
53
- }
54
- for offset := start; ; {
55
- position := offsetToPosition(text, offset)
56
- if position.Line != line {
57
- t.Fatalf("offset %d position line = %d, want %d", offset, position.Line, line)
58
- }
59
- restored, ok := lspPositionToOffset(text, position)
60
- if !ok || restored != offset {
61
- t.Fatalf("offset %d -> %+v -> (%d, %t), want itself", offset, position, restored, ok)
62
- }
63
- if offset == end {
64
- break
65
- }
66
- _, size := utf8.DecodeRuneInString(text[offset:])
67
- if size == 0 {
68
- t.Fatal("unexpected zero-width UTF-8 rune")
69
- }
70
- offset += size
71
- }
72
- }
73
- if _, ok := lspPositionToOffset(text, lspserver.LSPPosition{Line: len(starts), Character: 0}); ok {
74
- t.Fatal("position on a nonexistent source line must not resolve")
75
- }
76
- })
77
- }
78
- }