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