@orkestrel/probe 0.0.6 → 0.0.9

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.
@@ -9,7 +9,8 @@ let _src_core = require("../core/index.cjs");
9
9
  let _orkestrel_emitter = require("@orkestrel/emitter");
10
10
  let _orkestrel_queue = require("@orkestrel/queue");
11
11
  let _orkestrel_timeout = require("@orkestrel/timeout");
12
- let node_child_process = require("node:child_process");
12
+ let _orkestrel_lsp = require("@orkestrel/lsp");
13
+ let _orkestrel_lsp_server = require("@orkestrel/lsp/server");
13
14
  let node_stream = require("node:stream");
14
15
  let node_timers_promises = require("node:timers/promises");
15
16
  let _orkestrel_mcp = require("@orkestrel/mcp");
@@ -114,12 +115,18 @@ function resolveWorkspaceFile(workspace, target, mutate = false) {
114
115
  * A containment walk and the write that follows it are separate calls, so a process that swaps the
115
116
  * final component between them decides where the bytes land. `writeFileSync` opens with the default
116
117
  * `w` flag, which follows a symbolic link at that component and creates the file when it is absent,
117
- * so the walk's reading does not bind the write. Opening `O_WRONLY | O_TRUNC | O_NOFOLLOW` moves
118
- * that decision into the call that writes: a link at the final component refuses the open, and the
118
+ * so the walk's reading does not bind the write. Opening `O_WRONLY | O_NOFOLLOW` moves that
119
+ * decision into the call that writes: a link at the final component refuses the open, and the
119
120
  * omitted `O_CREAT` fails `ENOENT` for a target that has gone since the walk saw it. This is the
120
121
  * mutating counterpart of the `wx` flag every create in this package uses. Which code the refusal
121
122
  * carries is the host's; Linux with Node v22.22.2 reported `ELOOP` on 2026-08-24.
122
123
  *
124
+ * Truncation runs through the held descriptor rather than through an `O_TRUNC` flag, so it reaches
125
+ * the file the open bound. A Windows host refuses the numeric `O_TRUNC` without `O_CREAT` outright:
126
+ * measured on 2026-08-26 on Windows 11 with Node v24.19.0 over NTFS, `openSync` reported `EINVAL`
127
+ * for `O_WRONLY | O_TRUNC` and for `O_RDWR | O_TRUNC` while `O_WRONLY` alone opened, and adding
128
+ * `O_CREAT` would trade that refusal for the create this function must never perform.
129
+ *
123
130
  * A directory component stays open, because Node exposes no descriptor-relative call to walk and
124
131
  * write through one set of descriptors. Where a host's Node build defines no `O_NOFOLLOW`, that flag
125
132
  * contributes nothing to the flag set and the final component is open on that host too.
@@ -139,8 +146,9 @@ function resolveWorkspaceFile(workspace, target, mutate = false) {
139
146
  * ```
140
147
  */
141
148
  function overwriteFile(file, text) {
142
- const descriptor = (0, node_fs.openSync)(file, node_fs.constants.O_WRONLY | node_fs.constants.O_TRUNC | node_fs.constants.O_NOFOLLOW);
149
+ const descriptor = (0, node_fs.openSync)(file, node_fs.constants.O_WRONLY | node_fs.constants.O_NOFOLLOW);
143
150
  try {
151
+ (0, node_fs.ftruncateSync)(descriptor, 0);
144
152
  (0, node_fs.writeFileSync)(descriptor, text, "utf8");
145
153
  } finally {
146
154
  (0, node_fs.closeSync)(descriptor);
@@ -515,24 +523,6 @@ function matchesWorkspaceModule(path) {
515
523
  return /\.(?:[cm]?[jt]sx?|vue|json)$/.test(path);
516
524
  }
517
525
  /**
518
- * Reads a JSON-RPC frame's declared byte length.
519
- *
520
- * @param header - The frame header text without its terminating empty line
521
- * @returns The non-negative content length, or `undefined` for an invalid header
522
- *
523
- * @example
524
- * ```ts
525
- * parseContentLength('Content-Length: 128') // 128
526
- * parseContentLength('Content-Type: application/json') // undefined
527
- * ```
528
- */
529
- function parseContentLength(header) {
530
- const text = /(?:^|\r\n)Content-Length:\s*(\d+)(?:\r\n|$)/i.exec(header)?.[1];
531
- if (text === void 0) return void 0;
532
- const length = Number.parseInt(text, 10);
533
- return Number.isSafeInteger(length) && length >= 0 ? length : void 0;
534
- }
535
- /**
536
526
  * Normalizes a caught or foreign error into readable text.
537
527
  *
538
528
  * @param value - The value to describe
@@ -742,6 +732,15 @@ function releaseListeners(emitter, capture) {
742
732
  * is what makes a resident tool re-read a path this overlay holds, so two inspections that supply
743
733
  * different text for one path never share a cached answer.
744
734
  *
735
+ * This overlay matches a lookup key the way the host its stage declares to its tool matches file
736
+ * names. A host that resolves two spellings of one file name to one file leaves the tool holding
737
+ * whichever spelling it met first — the disk one, where the file is on disk — and the tool asks
738
+ * under that, so an overlay matching keys exactly answers nothing and the tool reads the committed
739
+ * file instead. Mint the overlay with the sensitivity the stage declares, and one reading decides
740
+ * both. The recorded spelling is what `paths` reports and what `covers` matches a directory
741
+ * against, so a folded key never reaches the paths a tool is handed and containment stays an exact
742
+ * comparison.
743
+ *
745
744
  * @example
746
745
  * ```ts
747
746
  * const overlay = new Overlay()
@@ -752,22 +751,36 @@ function releaseListeners(emitter, capture) {
752
751
  */
753
752
  var Overlay = class {
754
753
  #revision = (0, node_crypto.randomUUID)();
754
+ #sensitive;
755
755
  #candidates = /* @__PURE__ */ new Map();
756
+ /**
757
+ * Creates an empty candidate set under a fresh identity.
758
+ *
759
+ * @param options - Construction options
760
+ */
761
+ constructor(options = {}) {
762
+ this.#sensitive = options.sensitive ?? true;
763
+ }
756
764
  get revision() {
757
765
  return this.#revision;
758
766
  }
759
767
  get paths() {
760
- return [...this.#candidates.keys()];
768
+ return [...this.#candidates.values()].map(([path]) => path);
761
769
  }
762
770
  /**
763
771
  * Records one candidate's text against the absolute path it stands in for.
764
772
  *
773
+ * @remarks
774
+ * Recording a candidate under a path this overlay's own matching reads as one already recorded
775
+ * replaces that candidate, the way one file holds one text.
776
+ *
765
777
  * @param path - The absolute path the candidate replaces
766
778
  * @param text - The candidate's full contents
767
779
  * @returns Nothing
768
780
  */
769
781
  set(path, text) {
770
- this.#candidates.set(normalizePath(path), text);
782
+ const recorded = normalizePath(path);
783
+ this.#candidates.set(this.#key(recorded), [recorded, text]);
771
784
  }
772
785
  /**
773
786
  * Reads the candidate text recorded for one absolute path.
@@ -776,7 +789,7 @@ var Overlay = class {
776
789
  * @returns The recorded text, or `undefined` when this overlay holds no candidate there
777
790
  */
778
791
  text(path) {
779
- return this.#candidates.get(normalizePath(path));
792
+ return this.#candidates.get(this.#key(normalizePath(path)))?.[1];
780
793
  }
781
794
  /**
782
795
  * Checks whether a candidate sits beneath one directory.
@@ -785,14 +798,16 @@ var Overlay = class {
785
798
  * The answer is derived from the paths the overlay holds rather than stored, so it stops being
786
799
  * true exactly when the inspection that declared those candidates clears them. Both sides pass
787
800
  * through `normalizePath` first, because a tool that normalizes its own paths asks about a
788
- * directory in a spelling the recorded path may not share.
801
+ * directory in a spelling the recorded path may not share. Case is never folded here: this is a
802
+ * containment comparison, and it reads the recorded spelling whatever an overlay minted for a
803
+ * case-folding host matches its lookup keys by.
789
804
  *
790
805
  * @param directory - The absolute directory path to check
791
806
  * @returns True if a candidate path sits beneath the directory; false otherwise
792
807
  */
793
808
  covers(directory) {
794
809
  const base = `${normalizePath(directory).replace(/\/+$/, "")}/`;
795
- for (const path of this.#candidates.keys()) if (path.startsWith(base)) return true;
810
+ for (const [path] of this.#candidates.values()) if (path.startsWith(base)) return true;
796
811
  return false;
797
812
  }
798
813
  /**
@@ -803,10 +818,13 @@ var Overlay = class {
803
818
  clear() {
804
819
  this.#candidates.clear();
805
820
  }
821
+ #key(path) {
822
+ return this.#sensitive ? path : path.toLowerCase();
823
+ }
806
824
  };
807
825
  //#endregion
808
826
  //#region package.json
809
- var version = "0.0.6";
827
+ var version = "0.0.8";
810
828
  var peerDependencies = {
811
829
  "oxlint": "^1.77.0",
812
830
  "typescript": "^6.0.0",
@@ -818,13 +836,22 @@ var peerDependencies = {
818
836
  * Inspects virtual documents through one resident Oxlint language server.
819
837
  *
820
838
  * @remarks
821
- * Construction starts the target workspace's Oxlint binary with its Language Server Protocol
822
- * mode. Each inspection opens the supplied text by URI, waits for published diagnostics, and
823
- * closes the document without writing it to disk.
839
+ * Construction starts the target workspace's Oxlint binary with its Language Server Protocol mode
840
+ * and hands the conversation to `@orkestrel/lsp`: `createStdioTransport` owns the child process and
841
+ * its bytes, and `createLSPClient` owns the framing, the request correlation, the capabilities this
842
+ * stage advertises, and the choice between pulled and pushed diagnostics. This stage owns the
843
+ * workspace, the candidate identity, and the projection from a diagnostic to an `Issue`.
844
+ *
845
+ * Each inspection opens the supplied text by URI, waits for the client's diagnostics, and closes the
846
+ * document without writing it to disk. The caller's own signal is what bounds that wait, so a server
847
+ * that admits a document and publishes nothing refuses the inspection when the caller's bound
848
+ * expires rather than holding it for the life of the process. This stage mints no bound of its own
849
+ * for the wait: a second one would race the caller's, and the answer would depend on scheduling.
824
850
  *
825
851
  * Teardown is bounded at both ends. Warming waits for the server's `initialize` response and the
826
- * ending waits for its `shutdown` reply, and the protocol leaves both answers to the server, so
827
- * `destroy` bounds each exchange and takes the ending back with a signal when the bound passes.
852
+ * ending waits for its `shutdown` reply, and the protocol leaves both answers to the server, so the
853
+ * client's deadline bounds each exchange and the transport's cooperative window takes the ending
854
+ * back with a signal when that window passes.
828
855
  *
829
856
  * The URI is the path the candidate declared, so the candidate receives exactly the rule set the
830
857
  * workspace's own lint gate applies to that path, including an override anchored to one directory,
@@ -837,24 +864,20 @@ var peerDependencies = {
837
864
  * @example
838
865
  * ```ts
839
866
  * const stage = new LintStage('/srv/checkout')
840
- * const check = await stage.inspect(subject)
867
+ * const check = await stage.inspect(subject, { signal: AbortSignal.timeout(30_000) })
841
868
  * await stage.destroy()
842
869
  * ```
843
870
  */
844
871
  var LintStage = class {
845
872
  #workspace;
846
873
  #deadline = 2e3;
874
+ #documents = /* @__PURE__ */ new Set();
847
875
  #warmth;
848
- #responses = /* @__PURE__ */ new Map();
849
- #failures = /* @__PURE__ */ new Map();
850
- #documents = /* @__PURE__ */ new Map();
851
- #publishes = /* @__PURE__ */ new Map();
852
- #refusals = /* @__PURE__ */ new Map();
853
- #child;
854
- #buffer = Buffer.alloc(0);
855
- #sequence = 0;
876
+ #client;
877
+ #ending;
856
878
  #closing;
857
879
  #progress = 0;
880
+ #revision = 0;
858
881
  #destroyed = false;
859
882
  /**
860
883
  * Starts warming the target workspace's Oxlint language server.
@@ -872,8 +895,8 @@ var LintStage = class {
872
895
  get progress() {
873
896
  return this.#progress;
874
897
  }
875
- inspect(subject) {
876
- return guardStage(this.stage, this.#inspect(subject));
898
+ inspect(subject, options) {
899
+ return guardStage(this.stage, this.#inspect(subject, options));
877
900
  }
878
901
  destroy() {
879
902
  if (this.#closing !== void 0) return this.#closing;
@@ -881,13 +904,18 @@ var LintStage = class {
881
904
  this.#closing = guardStage(this.stage, this.#destroy());
882
905
  return this.#closing;
883
906
  }
884
- async #inspect(subject) {
907
+ async #inspect(subject, options) {
885
908
  if (this.#destroyed) throw (0, _src_core.createDestroyedError)("lint stage");
909
+ if (options === void 0) throw new _src_core.ProbeError("The lint stage inspects only under a bound its caller supplies", {
910
+ origin: "claimant",
911
+ code: "refused",
912
+ context: { stage: this.stage }
913
+ });
886
914
  const started = performance.now();
887
- await this.#warmth;
915
+ const client = await this.#warmed();
888
916
  if (this.#destroyed) throw (0, _src_core.createDestroyedError)("lint stage");
889
917
  const issues = [];
890
- for (const draft of [...subject.files, subject.test]) issues.push(...await this.#document(draft));
918
+ for (const draft of [...subject.files, subject.test]) issues.push(...await this.#document(client, draft, options.signal));
891
919
  return {
892
920
  stage: this.stage,
893
921
  elapsed: Math.round(performance.now() - started),
@@ -895,118 +923,86 @@ var LintStage = class {
895
923
  };
896
924
  }
897
925
  async #destroy() {
898
- const child = await this.#warmed();
899
- this.#fail((0, _src_core.createDestroyedError)("lint stage"));
900
- if (child === void 0 || this.#ending !== void 0) return;
901
- const released = new Promise((resolve) => {
902
- child.once("exit", () => resolve());
903
- child.once("close", () => resolve());
926
+ await this.#client?.destroy();
927
+ }
928
+ async #warm() {
929
+ const binary = resolveWorkspaceBinary(this.#workspace, "oxlint");
930
+ const client = (0, _orkestrel_lsp.createLSPClient)({
931
+ transport: (0, _orkestrel_lsp_server.createStdioTransport)({
932
+ server: {
933
+ command: [
934
+ process.execPath,
935
+ binary,
936
+ "--lsp"
937
+ ],
938
+ directory: this.#workspace
939
+ },
940
+ grace: this.#deadline / 2
941
+ }),
942
+ workspace: (0, node_url.pathToFileURL)(this.#workspace).href,
943
+ timeout: this.#deadline
904
944
  });
905
- await this.#release(child, released);
945
+ this.#client = client;
946
+ client.emitter.on("exit", (exit) => this.#retire(exit));
947
+ await client.start();
948
+ return client;
906
949
  }
907
950
  async #warmed() {
908
- const timeout = (0, _orkestrel_timeout.createTimeout)({ ms: this.#deadline });
909
- timeout.start();
910
951
  try {
911
- return await Promise.race([this.#warmth, this.#expiry(timeout)]);
912
- } catch {
913
- return this.#child;
914
- } finally {
915
- timeout.clear();
916
- }
917
- }
918
- async #release(child, released) {
919
- const timeout = (0, _orkestrel_timeout.createTimeout)({ ms: this.#deadline });
920
- timeout.start();
921
- try {
922
- await Promise.race([this.#retire(child).then(() => released), this.#expiry(timeout)]);
923
- } catch {
924
- child.kill("SIGKILL");
925
- await released;
926
- } finally {
927
- timeout.clear();
928
- }
929
- }
930
- async #retire(child) {
931
- try {
932
- await this.#request("shutdown", void 0);
933
- this.#notify("exit", void 0);
934
- } catch {
935
- child.kill("SIGKILL");
952
+ return await this.#warmth;
953
+ } catch (error) {
954
+ throw this.#translate(error);
936
955
  }
937
956
  }
938
- #expiry(timeout) {
939
- return new Promise((_resolve, reject) => {
940
- timeout.signal.addEventListener("abort", () => reject(new _src_core.ProbeError(`The Oxlint language server did not exit within ${timeout.ms} ms`, {
941
- origin: "instrument",
942
- code: "deadline",
943
- context: {
944
- stage: this.stage,
945
- deadline: timeout.ms
946
- }
947
- })), { once: true });
948
- });
949
- }
950
- async #warm() {
951
- const binary = resolveWorkspaceBinary(this.#workspace, "oxlint");
952
- const child = (0, node_child_process.spawn)(process.execPath, [binary, "--lsp"], {
953
- cwd: this.#workspace,
954
- stdio: "pipe"
955
- });
956
- this.#child = child;
957
- child.stderr.resume();
958
- child.stdout.on("data", (chunk) => this.#read(chunk));
959
- child.stdin.on("error", (error) => this.#fail(error));
960
- child.on("error", (error) => this.#fail(error));
961
- child.on("exit", (code, signal) => this.#exit(code, signal));
962
- await this.#request("initialize", {
963
- processId: process.pid,
964
- rootUri: (0, node_url.pathToFileURL)(this.#workspace).href,
965
- capabilities: {},
966
- workspaceFolders: [{
967
- uri: (0, node_url.pathToFileURL)(this.#workspace).href,
968
- name: "workspace"
969
- }]
970
- });
971
- this.#notify("initialized", {});
972
- return child;
957
+ #retire(exit) {
958
+ this.#ending = exit.signal === null ? `code ${exit.code}` : `signal ${exit.signal}`;
973
959
  }
974
- #document(draft) {
960
+ async #document(client, draft, signal) {
975
961
  const uri = (0, node_url.pathToFileURL)(resolveWorkspaceFile(this.#workspace, draft.path)).href;
976
- if (this.#publishes.has(uri)) return Promise.reject(new _src_core.ProbeError(`The lint stage is already inspecting ${draft.path}`, {
962
+ if (this.#documents.has(uri)) throw new _src_core.ProbeError(`The lint stage is already inspecting ${draft.path}`, {
977
963
  origin: "claimant",
978
964
  code: "refused",
979
965
  context: {
980
966
  stage: this.stage,
981
967
  path: draft.path
982
968
  }
983
- }));
984
- const inspected = new Promise((resolve, reject) => {
985
- this.#documents.set(uri, normalizePath(draft.path));
986
- this.#publishes.set(uri, resolve);
987
- this.#refusals.set(uri, reject);
988
- }).finally(() => this.#close(uri));
969
+ });
970
+ this.#documents.add(uri);
971
+ this.#progress += 1;
972
+ this.#revision += 1;
989
973
  try {
990
- this.#notify("textDocument/didOpen", { textDocument: {
974
+ const diagnostics = await client.open({
991
975
  uri,
992
976
  languageId: inferDocumentLanguage(draft.path),
993
- version: this.#sequence,
977
+ version: this.#revision,
994
978
  text: draft.text
995
- } });
996
- this.#progress += 1;
979
+ }, { signal });
980
+ return this.#issues(normalizePath(draft.path), diagnostics);
997
981
  } catch (error) {
998
- this.#refusals.get(uri)?.(this.#fault(describeUnknown(error), error, draft.path));
982
+ if (signal.aborted) throw this.#abandoned(draft.path);
983
+ throw this.#translate(error, draft.path);
984
+ } finally {
985
+ this.#progress -= 1;
986
+ this.#documents.delete(uri);
987
+ await client.close(uri).catch(() => void 0);
999
988
  }
1000
- return inspected;
1001
989
  }
1002
- get #ending() {
1003
- const child = this.#child;
1004
- if (child === void 0) return void 0;
1005
- if (child.exitCode === null && child.signalCode === null) return void 0;
1006
- return this.#describe(child.exitCode, child.signalCode);
990
+ #abandoned(path) {
991
+ return new _src_core.ProbeError("The lint stage inspection stopped at the bound its caller supplied", {
992
+ origin: "claimant",
993
+ code: "deadline",
994
+ context: {
995
+ stage: this.stage,
996
+ path
997
+ }
998
+ });
1007
999
  }
1008
- #describe(code, signal) {
1009
- return signal === null ? `code ${code}` : `signal ${signal}`;
1000
+ #translate(error, path) {
1001
+ if (this.#destroyed) return (0, _src_core.createDestroyedError)("lint stage");
1002
+ const ending = this.#ending;
1003
+ if (ending !== void 0) return this.#fault(`The Oxlint language server exited with ${ending}`, error, path);
1004
+ if (error instanceof Error) return error;
1005
+ return this.#fault(describeUnknown(error), error, path);
1010
1006
  }
1011
1007
  #fault(message, cause, path) {
1012
1008
  return new _src_core.ProbeError(message, {
@@ -1019,153 +1015,22 @@ var LintStage = class {
1019
1015
  ...cause === void 0 ? {} : { cause }
1020
1016
  });
1021
1017
  }
1022
- get #reachable() {
1023
- return this.#ending === void 0 && this.#child?.stdin.writable === true;
1024
- }
1025
- #close(uri) {
1026
- this.#documents.delete(uri);
1027
- this.#publishes.delete(uri);
1028
- this.#refusals.delete(uri);
1029
- if (this.#destroyed || !this.#reachable) return;
1030
- this.#notify("textDocument/didClose", { textDocument: { uri } });
1031
- }
1032
- #request(method, params) {
1033
- this.#sequence += 1;
1034
- const id = this.#sequence;
1035
- const response = new Promise((resolve, reject) => {
1036
- this.#responses.set(id, resolve);
1037
- this.#failures.set(id, reject);
1038
- });
1039
- try {
1040
- this.#send({
1041
- jsonrpc: "2.0",
1042
- id,
1043
- method,
1044
- params
1045
- });
1046
- } catch (error) {
1047
- const reject = this.#failures.get(id);
1048
- this.#responses.delete(id);
1049
- this.#failures.delete(id);
1050
- reject?.(this.#fault(describeUnknown(error), error));
1051
- }
1052
- return response;
1053
- }
1054
- #notify(method, params) {
1055
- this.#sequence += 1;
1056
- this.#send({
1057
- jsonrpc: "2.0",
1058
- method,
1059
- params
1060
- });
1061
- }
1062
- #send(message) {
1063
- const child = this.#child;
1064
- if (this.#ending !== void 0) throw this.#fault(`The Oxlint language server exited with ${this.#ending}`);
1065
- if (child === void 0) throw this.#fault("The Oxlint language server is not running");
1066
- if (!child.stdin.writable) throw this.#fault("The Oxlint language server closed its input");
1067
- const content = JSON.stringify(message);
1068
- const header = `Content-Length: ${Buffer.byteLength(content)}\r\n\r\n`;
1069
- child.stdin.write(header + content);
1070
- }
1071
- #read(chunk) {
1072
- this.#buffer = Buffer.concat([this.#buffer, chunk]);
1073
- while (this.#frame());
1074
- }
1075
- #frame() {
1076
- const boundary = this.#buffer.indexOf("\r\n\r\n");
1077
- if (boundary < 0) return false;
1078
- const length = parseContentLength(this.#buffer.subarray(0, boundary).toString("ascii"));
1079
- if (length === void 0) {
1080
- this.#fail(this.#fault("Oxlint sent an invalid JSON-RPC frame header"));
1081
- return false;
1082
- }
1083
- const start = boundary + 4;
1084
- const end = start + length;
1085
- if (this.#buffer.length < end) return false;
1086
- const content = this.#buffer.subarray(start, end).toString("utf8");
1087
- this.#buffer = this.#buffer.subarray(end);
1088
- try {
1089
- const message = JSON.parse(content);
1090
- this.#receive(message);
1091
- } catch (error) {
1092
- this.#fail(this.#fault(`Oxlint sent invalid JSON: ${describeUnknown(error)}`, error));
1093
- }
1094
- return true;
1095
- }
1096
- #receive(message) {
1097
- if (typeof message !== "object" || message === null) return;
1098
- if ("id" in message && typeof message.id === "number") {
1099
- const id = message.id;
1100
- const resolve = this.#responses.get(id);
1101
- const reject = this.#failures.get(id);
1102
- this.#responses.delete(id);
1103
- this.#failures.delete(id);
1104
- if ("error" in message && message.error !== void 0) reject?.(this.#fault(describeUnknown(message.error)));
1105
- else resolve?.("result" in message ? message.result : void 0);
1106
- return;
1107
- }
1108
- if (!("method" in message) || message.method !== "textDocument/publishDiagnostics") return;
1109
- if (!("params" in message) || typeof message.params !== "object" || message.params === null) return;
1110
- const params = message.params;
1111
- if (!("uri" in params) || typeof params.uri !== "string") return;
1112
- if (!("diagnostics" in params) || !Array.isArray(params.diagnostics)) return;
1113
- const publish = this.#publishes.get(params.uri);
1114
- if (publish === void 0) return;
1115
- publish(this.#issues(params.uri, params.diagnostics));
1116
- }
1117
- #issues(uri, diagnostics) {
1118
- const path = this.#documents.get(uri);
1119
- if (path === void 0) return [];
1120
- const issues = [];
1121
- for (const diagnostic of diagnostics) {
1122
- if (typeof diagnostic !== "object" || diagnostic === null) continue;
1123
- if (!("message" in diagnostic) || typeof diagnostic.message !== "string") continue;
1124
- if (!("range" in diagnostic) || typeof diagnostic.range !== "object" || diagnostic.range === null) {
1125
- issues.push({
1126
- origin: "claimant",
1127
- path,
1128
- message: diagnostic.message
1129
- });
1130
- continue;
1131
- }
1132
- const range = diagnostic.range;
1133
- if (!("start" in range) || typeof range.start !== "object" || range.start === null) {
1134
- issues.push({
1135
- origin: "claimant",
1136
- path,
1137
- message: diagnostic.message
1138
- });
1139
- continue;
1140
- }
1141
- const start = range.start;
1142
- if (!("line" in start) || typeof start.line !== "number") {
1143
- issues.push({
1144
- origin: "claimant",
1145
- path,
1146
- message: diagnostic.message
1147
- });
1148
- continue;
1018
+ #issues(path, diagnostics) {
1019
+ return diagnostics.map((diagnostic) => ({
1020
+ origin: "claimant",
1021
+ path,
1022
+ message: diagnostic.message,
1023
+ range: {
1024
+ start: {
1025
+ line: diagnostic.range.start.line,
1026
+ character: diagnostic.range.start.character
1027
+ },
1028
+ end: {
1029
+ line: diagnostic.range.end.line,
1030
+ character: diagnostic.range.end.character
1031
+ }
1149
1032
  }
1150
- issues.push({
1151
- origin: "claimant",
1152
- path,
1153
- message: diagnostic.message,
1154
- line: start.line + 1
1155
- });
1156
- }
1157
- return issues;
1158
- }
1159
- #fail(error) {
1160
- for (const reject of this.#failures.values()) reject(error);
1161
- for (const reject of this.#refusals.values()) reject(error);
1162
- this.#responses.clear();
1163
- this.#failures.clear();
1164
- this.#publishes.clear();
1165
- this.#refusals.clear();
1166
- }
1167
- #exit(code, signal) {
1168
- this.#fail(this.#fault(`The Oxlint language server exited with ${this.#describe(code, signal)}`));
1033
+ }));
1169
1034
  }
1170
1035
  };
1171
1036
  //#endregion
@@ -1796,11 +1661,19 @@ var RuntimeStage = class {
1796
1661
  path,
1797
1662
  message
1798
1663
  };
1664
+ const character = "column" in stack && typeof stack.column === "number" ? stack.column - 1 : 0;
1665
+ const position = {
1666
+ line: stack.line - 1,
1667
+ character
1668
+ };
1799
1669
  return {
1800
1670
  origin: "claimant",
1801
1671
  path,
1802
1672
  message,
1803
- line: stack.line
1673
+ range: {
1674
+ start: position,
1675
+ end: position
1676
+ }
1804
1677
  };
1805
1678
  }
1806
1679
  return {
@@ -1925,7 +1798,7 @@ var TypeStage = class {
1925
1798
  this.#configure(this.#service(typescript, selection.project), selection.project);
1926
1799
  await this.#unblock();
1927
1800
  }
1928
- const overlay = new Overlay();
1801
+ const overlay = new Overlay({ sensitive: this.#caseSensitive(typescript) });
1929
1802
  this.#overlay = overlay;
1930
1803
  try {
1931
1804
  this.#record(subject.test, overlay);
@@ -2142,7 +2015,10 @@ var TypeStage = class {
2142
2015
  origin: "claimant",
2143
2016
  path,
2144
2017
  message,
2145
- line: diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start).line + 1
2018
+ range: {
2019
+ start: diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start),
2020
+ end: diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start + (diagnostic.length ?? 0))
2021
+ }
2146
2022
  };
2147
2023
  }
2148
2024
  };
@@ -2435,16 +2311,18 @@ var Probe = class {
2435
2311
  const release = await this.#admitType();
2436
2312
  try {
2437
2313
  const stage = this.#type;
2438
- return await this.#inspectStage(stage, stage.progress, stage.inspect(inspection.subject, inspection.claim.project), inspection.claim);
2314
+ return await this.#inspectStage(stage, stage.progress, () => stage.inspect(inspection.subject, inspection.claim.project), inspection.claim);
2439
2315
  } finally {
2440
2316
  release();
2441
2317
  }
2442
2318
  }
2443
2319
  #inspectLint(inspection) {
2444
- return this.#inspectStage(this.#lint, this.#lint.progress, this.#lint.inspect(inspection.subject), inspection.claim);
2320
+ const stage = this.#lint;
2321
+ return this.#inspectStage(stage, stage.progress, (signal) => stage.inspect(inspection.subject, { signal }), inspection.claim);
2445
2322
  }
2446
2323
  #inspectRuntime(inspection) {
2447
- return this.#inspectStage(this.#runtime, this.#runtime.progress, this.#runtime.inspect(inspection.subject), inspection.claim);
2324
+ const stage = this.#runtime;
2325
+ return this.#inspectStage(stage, stage.progress, () => stage.inspect(inspection.subject), inspection.claim);
2448
2326
  }
2449
2327
  async #inspectStage(stage, progress, operation, claim) {
2450
2328
  try {
@@ -2457,12 +2335,14 @@ var Probe = class {
2457
2335
  async #bound(operation, message, stage, progress) {
2458
2336
  const timeout = (0, _orkestrel_timeout.createTimeout)({ ms: this.#deadline });
2459
2337
  timeout.start();
2338
+ const expiry = this.#expiry(timeout, message, stage, progress);
2339
+ const refusal = expiry.catch((error) => error);
2460
2340
  try {
2461
- return await Promise.race([operation, this.#expiry(timeout, message, stage, progress)]);
2341
+ return await Promise.race([operation(timeout.signal), expiry]);
2462
2342
  } catch (error) {
2463
2343
  if (!timeout.expired) throw error;
2464
2344
  await this.#recycle(stage);
2465
- throw error;
2345
+ throw await refusal;
2466
2346
  } finally {
2467
2347
  timeout.clear();
2468
2348
  }
@@ -2503,7 +2383,7 @@ var Probe = class {
2503
2383
  try {
2504
2384
  const stage = this.#type;
2505
2385
  try {
2506
- return await this.#bound(stage.resolve(claim.project), `The type stage project resolution exceeded ${this.#deadline} ms`, stage, stage.progress);
2386
+ return await this.#bound(() => stage.resolve(claim.project), `The type stage project resolution exceeded ${this.#deadline} ms`, stage, stage.progress);
2507
2387
  } catch (error) {
2508
2388
  this.#emitExpiry(stage, claim);
2509
2389
  throw error;
@@ -2544,7 +2424,7 @@ var Probe = class {
2544
2424
  }
2545
2425
  async #destroyStage(stage) {
2546
2426
  try {
2547
- await this.#bound(stage.destroy(), `The ${stage.stage} stage teardown exceeded ${this.#deadline} ms`, stage, stage.progress);
2427
+ await this.#bound(() => stage.destroy(), `The ${stage.stage} stage teardown exceeded ${this.#deadline} ms`, stage, stage.progress);
2548
2428
  } catch (error) {
2549
2429
  if (!(error instanceof _src_core.ProbeError) || !this.#deadlines.has(error)) throw error;
2550
2430
  }
@@ -2753,7 +2633,6 @@ exports.matchesWorkspaceModule = matchesWorkspaceModule;
2753
2633
  exports.normalizePath = normalizePath;
2754
2634
  exports.normalizeValue = normalizeValue;
2755
2635
  exports.overwriteFile = overwriteFile;
2756
- exports.parseContentLength = parseContentLength;
2757
2636
  exports.readWorkspaceManifest = readWorkspaceManifest;
2758
2637
  exports.relativeWorkspaceFile = relativeWorkspaceFile;
2759
2638
  exports.relativeWorkspaceMessage = relativeWorkspaceMessage;