@tsrx/oxc 0.0.0-trusted-publishing-bootstrap → 0.8.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.
- package/LICENSE +21 -0
- package/README.md +141 -0
- package/THIRD_PARTY_NOTICES.md +49 -0
- package/bin/oxc-tsrx +2 -0
- package/bin/oxc-tsrx-fmt +2 -0
- package/bin/oxc-tsrx-lint +2 -0
- package/bin/oxc-tsrx-lsp +2 -0
- package/bin/oxfmt +2 -0
- package/bin/oxlint +2 -0
- package/dist/bin/oxc-tsrx-fmt.js +13 -0
- package/dist/bin/oxc-tsrx-lint.js +13 -0
- package/dist/bin/oxc-tsrx-lsp.js +13 -0
- package/dist/bin/oxc-tsrx.js +115 -0
- package/dist/bin/oxfmt.js +24 -0
- package/dist/bin/oxlint.js +33 -0
- package/dist/canonical-command.d.ts +50 -0
- package/dist/canonical-command.js +196 -0
- package/dist/compat.d.ts +149 -0
- package/dist/compat.js +1615 -0
- package/dist/editor-resolution.js +508 -0
- package/dist/format-cli.js +276 -0
- package/dist/format-invocation.js +97 -0
- package/dist/format.d.ts +1 -0
- package/dist/format.js +56 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +16 -0
- package/dist/lint-cli.js +487 -0
- package/dist/lint-invocation.js +192 -0
- package/dist/lint-js-plugins.js +819 -0
- package/dist/lint-plugins-dev.d.ts +1 -0
- package/dist/lint-plugins-dev.js +2 -0
- package/dist/lint-prestart.js +16 -0
- package/dist/lint.d.ts +1 -0
- package/dist/lint.js +2 -0
- package/dist/native-targets.js +76 -0
- package/dist/oxlint-lsp-multiplexer.js +622 -0
- package/dist/package-binary.js +29 -0
- package/dist/parser.d.ts +216 -0
- package/dist/parser.js +557 -0
- package/dist/process.js +88 -0
- package/dist/provider-resolve.d.ts +160 -0
- package/dist/provider-resolve.js +471 -0
- package/dist/providers-report.js +49 -0
- package/dist/runtime.js +323 -0
- package/dist/spawn-command.d.ts +20 -0
- package/dist/spawn-command.js +87 -0
- package/dist/tsrx-core-compat/facade.js +1184 -0
- package/dist/tsrx-core-compat/index.d.ts +6 -0
- package/dist/tsrx-core-compat/index.js +9 -0
- package/dist/tsrx-core-compat/style.js +525 -0
- package/dist/tsrx-core-compat/types/estree.d.ts +20 -0
- package/dist/tsrx-core-compat/types/index.d.ts +50 -0
- package/dist/tsrx-transfer.js +352 -0
- package/package.json +144 -5
|
@@ -0,0 +1,819 @@
|
|
|
1
|
+
import { resolvePackageBinary } from "./package-binary.js";
|
|
2
|
+
import { runCaptured } from "./process.js";
|
|
3
|
+
import { resolveNativeCommand } from "./runtime.js";
|
|
4
|
+
import { createRequire } from "node:module";
|
|
5
|
+
import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
6
|
+
import { dirname, isAbsolute, join, parse, relative, resolve, sep } from "node:path";
|
|
7
|
+
import { pathToFileURL } from "node:url";
|
|
8
|
+
import { tmpdir } from "node:os";
|
|
9
|
+
import { existsSync, realpathSync } from "node:fs";
|
|
10
|
+
//#region src/lint-js-plugins.ts
|
|
11
|
+
const OXLINT_JS_PLUGIN_LANE_MINIMUM = "1.74.0";
|
|
12
|
+
const OXLINT_JS_PLUGIN_LANE_BELOW = "2.0.0";
|
|
13
|
+
const CONFIG_FILE_NAMES = [".oxlintrc.json", ".oxlintrc.jsonc"];
|
|
14
|
+
const BUILTIN_CATEGORIES = [
|
|
15
|
+
"correctness",
|
|
16
|
+
"nursery",
|
|
17
|
+
"pedantic",
|
|
18
|
+
"perf",
|
|
19
|
+
"restriction",
|
|
20
|
+
"style",
|
|
21
|
+
"suspicious"
|
|
22
|
+
];
|
|
23
|
+
function versionParts(version) {
|
|
24
|
+
return String(version).split(/[-+]/u, 1)[0].split(".").map((part) => Number.parseInt(part, 10));
|
|
25
|
+
}
|
|
26
|
+
function compareVersions(left, right) {
|
|
27
|
+
const a = versionParts(left);
|
|
28
|
+
const b = versionParts(right);
|
|
29
|
+
for (let index = 0; index < 3; index += 1) {
|
|
30
|
+
const first = Number.isInteger(a[index]) ? a[index] : 0;
|
|
31
|
+
const second = Number.isInteger(b[index]) ? b[index] : 0;
|
|
32
|
+
if (first !== second) return first < second ? -1 : 1;
|
|
33
|
+
}
|
|
34
|
+
return 0;
|
|
35
|
+
}
|
|
36
|
+
function laneSupportsOxlintVersion(version) {
|
|
37
|
+
if (typeof version !== "string" || !/^\d+\.\d+\.\d+/u.test(version)) return false;
|
|
38
|
+
return compareVersions(version, "1.74.0") >= 0 && compareVersions(version, "2.0.0") < 0;
|
|
39
|
+
}
|
|
40
|
+
function oxlintVersionRefusal(version) {
|
|
41
|
+
return `oxlint (oxc-tsrx): JS plugins on .tsrx require oxlint >=${OXLINT_JS_PLUGIN_LANE_MINIMUM} <${OXLINT_JS_PLUGIN_LANE_BELOW}; found ${version}. Refusing rather than silently skipping your rules.`;
|
|
42
|
+
}
|
|
43
|
+
/** The pinned Oxlint's own version, read through its public `./package.json` export. */
|
|
44
|
+
function installedOxlintVersion(fromUrl = import.meta.url) {
|
|
45
|
+
const manifest = createRequire(fromUrl)("oxlint-current/package.json");
|
|
46
|
+
return typeof manifest.version === "string" ? manifest.version : "unknown";
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* The one line this lane prints before the report.
|
|
50
|
+
*
|
|
51
|
+
* The extra parse is real and the user is told about it every time, on stderr,
|
|
52
|
+
* with the exact key that turns it off. `--silent` suppresses it along with
|
|
53
|
+
* everything else the command would have printed.
|
|
54
|
+
*/
|
|
55
|
+
function jsPluginDisclosure(fileCount) {
|
|
56
|
+
return `oxlint (oxc-tsrx): running JS plugins on ${fileCount} .tsrx file(s) by linting the TSX projection; this parses each of those files once more. Disable with "settings": { "oxcTsrx": { "jsPluginsOnTsrx": false } }.`;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* The line that reports plugin diagnostics this lane could not place.
|
|
60
|
+
*
|
|
61
|
+
* A diagnostic whose labels land on text the projection inserted has no
|
|
62
|
+
* position in the file the developer wrote, so it is dropped rather than
|
|
63
|
+
* reported somewhere they can see no such code. Dropping it quietly is the same
|
|
64
|
+
* silence this lane exists to remove, one level down: the rule looks like it
|
|
65
|
+
* simply found nothing. So the count reaches stderr, and
|
|
66
|
+
* `oxcTsrx.jsPluginProjection.unmapped` carries it in `--format=json` too.
|
|
67
|
+
*/
|
|
68
|
+
function jsPluginUnmappedNote(count) {
|
|
69
|
+
return `oxlint (oxc-tsrx): ${count} JS plugin diagnostic(s) on .tsrx had no position in the source you wrote (they landed on text the TSX projection inserted) and were dropped. See oxcTsrx.jsPluginProjection.unmapped in --format=json.`;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Read a `.oxlintrc.json` or `.oxlintrc.jsonc`.
|
|
73
|
+
*
|
|
74
|
+
* Comments and trailing commas are stripped rather than parsed, because this
|
|
75
|
+
* file only ever re-emits plain JSON. Anything it does not understand is copied
|
|
76
|
+
* through untouched, so Oxlint keeps deciding what the configuration means.
|
|
77
|
+
*/
|
|
78
|
+
function parseOxlintConfigText(text) {
|
|
79
|
+
let stripped = "";
|
|
80
|
+
let index = 0;
|
|
81
|
+
while (index < text.length) {
|
|
82
|
+
const character = text[index];
|
|
83
|
+
if (character === "\"") {
|
|
84
|
+
const start = index;
|
|
85
|
+
index += 1;
|
|
86
|
+
while (index < text.length) {
|
|
87
|
+
if (text[index] === "\\") {
|
|
88
|
+
index += 2;
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
if (text[index] === "\"") {
|
|
92
|
+
index += 1;
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
index += 1;
|
|
96
|
+
}
|
|
97
|
+
stripped += text.slice(start, index);
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (character === "/" && text[index + 1] === "/") {
|
|
101
|
+
while (index < text.length && text[index] !== "\n") index += 1;
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
if (character === "/" && text[index + 1] === "*") {
|
|
105
|
+
const end = text.indexOf("*/", index + 2);
|
|
106
|
+
index = end === -1 ? text.length : end + 2;
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
stripped += character;
|
|
110
|
+
index += 1;
|
|
111
|
+
}
|
|
112
|
+
return JSON.parse(stripped.replace(/,(\s*[}\]])/gu, "$1"));
|
|
113
|
+
}
|
|
114
|
+
async function readOxlintConfig(path) {
|
|
115
|
+
try {
|
|
116
|
+
const parsed = parseOxlintConfigText(await readFile(path, "utf8"));
|
|
117
|
+
return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
118
|
+
} catch {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
/** The nearest Oxlint config at or above `directory`, the way Oxlint looks for one. */
|
|
123
|
+
function findOxlintConfig(directory) {
|
|
124
|
+
let current = resolve(directory);
|
|
125
|
+
const root = parse(current).root;
|
|
126
|
+
for (;;) {
|
|
127
|
+
for (const name of CONFIG_FILE_NAMES) {
|
|
128
|
+
const candidate = join(current, name);
|
|
129
|
+
if (existsSync(candidate)) return candidate;
|
|
130
|
+
}
|
|
131
|
+
if (current === root) return null;
|
|
132
|
+
current = dirname(current);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Every entry of one config's `jsPlugins`, normalized.
|
|
137
|
+
*
|
|
138
|
+
* Oxlint accepts both a bare specifier and `{ name, specifier }`, where `name`
|
|
139
|
+
* is the alias the plugin's rules are configured under. Vite+ writes the second
|
|
140
|
+
* form. The alias, when there is one, saves this lane from having to import the
|
|
141
|
+
* module to learn the plugin's namespace.
|
|
142
|
+
*/
|
|
143
|
+
function declaredJsPlugins(config) {
|
|
144
|
+
const declared = config?.jsPlugins;
|
|
145
|
+
if (!Array.isArray(declared)) return [];
|
|
146
|
+
const entries = [];
|
|
147
|
+
for (const entry of declared) {
|
|
148
|
+
if (typeof entry === "string" && entry.length > 0) {
|
|
149
|
+
entries.push({
|
|
150
|
+
specifier: entry,
|
|
151
|
+
name: null
|
|
152
|
+
});
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
if (entry !== null && typeof entry === "object" && typeof entry.specifier === "string") entries.push({
|
|
156
|
+
specifier: entry.specifier,
|
|
157
|
+
name: typeof entry.name === "string" && entry.name.length > 0 ? entry.name : null
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
return entries;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Every JavaScript plugin one configuration brings in, and whether the project
|
|
164
|
+
* turned this lane off.
|
|
165
|
+
*
|
|
166
|
+
* `extends` is followed because a project that keeps its shared rules in one
|
|
167
|
+
* file and its per-package config in another still expects its plugins to run.
|
|
168
|
+
* Missing this would not be a visible error; it would be the rule quietly not
|
|
169
|
+
* running, which is exactly the failure this lane exists to remove. Each plugin
|
|
170
|
+
* specifier travels with the directory of the config that declared it, because
|
|
171
|
+
* that is what it resolves against.
|
|
172
|
+
*/
|
|
173
|
+
async function collectLaneFacts(path, directoryOverride = null, seen = /* @__PURE__ */ new Set(), depth = 0) {
|
|
174
|
+
const facts = {
|
|
175
|
+
config: null,
|
|
176
|
+
jsPlugins: [],
|
|
177
|
+
optedOut: void 0
|
|
178
|
+
};
|
|
179
|
+
if (depth > 8 || seen.has(path)) return facts;
|
|
180
|
+
seen.add(path);
|
|
181
|
+
const config = await readOxlintConfig(path);
|
|
182
|
+
if (config === null) return facts;
|
|
183
|
+
facts.config = config;
|
|
184
|
+
const directory = directoryOverride ?? dirname(path);
|
|
185
|
+
if (Array.isArray(config.extends)) for (const specifier of config.extends) {
|
|
186
|
+
if (typeof specifier !== "string") continue;
|
|
187
|
+
const resolved = resolveSpecifier(specifier, directory);
|
|
188
|
+
if (!isAbsolute(resolved) || !existsSync(resolved)) continue;
|
|
189
|
+
const inherited = await collectLaneFacts(resolved, null, seen, depth + 1);
|
|
190
|
+
facts.jsPlugins.push(...inherited.jsPlugins);
|
|
191
|
+
if (inherited.optedOut !== void 0) facts.optedOut = inherited.optedOut;
|
|
192
|
+
}
|
|
193
|
+
for (const entry of declaredJsPlugins(config)) facts.jsPlugins.push({
|
|
194
|
+
...entry,
|
|
195
|
+
directory
|
|
196
|
+
});
|
|
197
|
+
if (Array.isArray(config.overrides)) for (const override of config.overrides) for (const entry of declaredJsPlugins(override)) facts.jsPlugins.push({
|
|
198
|
+
...entry,
|
|
199
|
+
directory
|
|
200
|
+
});
|
|
201
|
+
const own = config.settings?.oxcTsrx?.jsPluginsOnTsrx;
|
|
202
|
+
if (typeof own === "boolean") facts.optedOut = own === false;
|
|
203
|
+
return facts;
|
|
204
|
+
}
|
|
205
|
+
/** Resolve one plugin or extends specifier against the directory its config lives in. */
|
|
206
|
+
function resolveSpecifier(specifier, configDirectory) {
|
|
207
|
+
if (isAbsolute(specifier)) return specifier;
|
|
208
|
+
if (specifier.startsWith(".")) return resolve(configDirectory, specifier);
|
|
209
|
+
try {
|
|
210
|
+
return createRequire(join(configDirectory, "package.json")).resolve(specifier);
|
|
211
|
+
} catch {
|
|
212
|
+
return specifier;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* The plugin namespaces this project's `jsPlugins` contribute, or `null` when
|
|
217
|
+
* they cannot all be determined.
|
|
218
|
+
*
|
|
219
|
+
* A rule's diagnostic code is `<plugin meta.name>(<rule>)`, so this is what
|
|
220
|
+
* separates a diagnostic the user's own JavaScript produced from a built-in one
|
|
221
|
+
* that a `rules` entry re-enabled behind the categories this lane turns off.
|
|
222
|
+
* `null` means "do not filter by namespace", which is strictly more permissive
|
|
223
|
+
* and can only ever leave a duplicate in, never drop a user's rule.
|
|
224
|
+
*/
|
|
225
|
+
async function pluginNamespaces(declared) {
|
|
226
|
+
const namespaces = /* @__PURE__ */ new Set();
|
|
227
|
+
for (const { specifier, name: alias, directory } of declared) {
|
|
228
|
+
if (alias !== null) {
|
|
229
|
+
namespaces.add(alias);
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
const resolved = resolveSpecifier(specifier, directory);
|
|
233
|
+
try {
|
|
234
|
+
const module = await (isAbsolute(resolved) ? import(pathToFileURL(resolved).href) : import(resolved));
|
|
235
|
+
const name = module.default?.meta?.name ?? module.meta?.name;
|
|
236
|
+
if (typeof name !== "string" || name.length === 0) return null;
|
|
237
|
+
namespaces.add(name);
|
|
238
|
+
} catch {
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return namespaces;
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* A glob and the same glob with `.tsx` appended.
|
|
246
|
+
*
|
|
247
|
+
* The mirror names each projection `<authored name>.tsx`, so a project that
|
|
248
|
+
* wrote `overrides: [{ files: ["**\/*.tsrx"] }]` would match nothing there. This
|
|
249
|
+
* was measured rather than assumed: `**\/*.tsrx` does not match `demo.tsrx.tsx`,
|
|
250
|
+
* and `**\/*.tsrx.tsx` does.
|
|
251
|
+
*/
|
|
252
|
+
function projectedGlobs(globs) {
|
|
253
|
+
if (!Array.isArray(globs)) return globs;
|
|
254
|
+
const expanded = [];
|
|
255
|
+
for (const glob of globs) {
|
|
256
|
+
expanded.push(glob);
|
|
257
|
+
if (typeof glob === "string" && !expanded.includes(`${glob}.tsx`)) expanded.push(`${glob}.tsx`);
|
|
258
|
+
}
|
|
259
|
+
return expanded;
|
|
260
|
+
}
|
|
261
|
+
/** One `jsPlugins` entry with its specifier resolved, in either form Oxlint accepts. */
|
|
262
|
+
function absoluteJsPlugin(entry, configDirectory) {
|
|
263
|
+
if (typeof entry === "string") return resolveSpecifier(entry, configDirectory);
|
|
264
|
+
if (entry !== null && typeof entry === "object" && typeof entry.specifier === "string") return {
|
|
265
|
+
...entry,
|
|
266
|
+
specifier: resolveSpecifier(entry.specifier, configDirectory)
|
|
267
|
+
};
|
|
268
|
+
return entry;
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* The user's configuration as the projection run should see it.
|
|
272
|
+
*
|
|
273
|
+
* Everything Oxlint understands survives, because Oxlint is the thing resolving
|
|
274
|
+
* it. Four edits, each for one reason:
|
|
275
|
+
*
|
|
276
|
+
* * every built-in category off, so the native lane stays the only reporter of
|
|
277
|
+
* built-in rules and nothing is printed twice;
|
|
278
|
+
* * `jsPlugins` and `extends` made absolute, because the config is read from a
|
|
279
|
+
* different directory than the one it was written in;
|
|
280
|
+
* * `ignorePatterns` dropped, because the native lane has already applied them
|
|
281
|
+
* and they were written against `.tsrx` names the mirror does not use;
|
|
282
|
+
* * every `overrides` glob given a `.tsx` twin, so an override aimed at
|
|
283
|
+
* `.tsrx` still selects that file's projection.
|
|
284
|
+
*/
|
|
285
|
+
function projectionConfig(config, configDirectory) {
|
|
286
|
+
const projected = { ...config };
|
|
287
|
+
delete projected.$schema;
|
|
288
|
+
delete projected.ignorePatterns;
|
|
289
|
+
projected.categories = { ...config.categories ?? {} };
|
|
290
|
+
for (const category of BUILTIN_CATEGORIES) projected.categories[category] = "off";
|
|
291
|
+
if (Array.isArray(config.jsPlugins)) projected.jsPlugins = config.jsPlugins.map((entry) => absoluteJsPlugin(entry, configDirectory));
|
|
292
|
+
if (Array.isArray(config.extends)) projected.extends = config.extends.map((specifier) => typeof specifier === "string" ? resolveSpecifier(specifier, configDirectory) : specifier);
|
|
293
|
+
if (Array.isArray(config.overrides)) projected.overrides = config.overrides.map((override) => {
|
|
294
|
+
if (override === null || typeof override !== "object") return override;
|
|
295
|
+
const mapped = { ...override };
|
|
296
|
+
mapped.files = projectedGlobs(override.files);
|
|
297
|
+
if (override.excludeFiles !== void 0) mapped.excludeFiles = projectedGlobs(override.excludeFiles);
|
|
298
|
+
if (Array.isArray(override.jsPlugins)) mapped.jsPlugins = override.jsPlugins.map((entry) => absoluteJsPlugin(entry, configDirectory));
|
|
299
|
+
return mapped;
|
|
300
|
+
});
|
|
301
|
+
return projected;
|
|
302
|
+
}
|
|
303
|
+
/** The user's configuration with `jsPlugins` removed, for the native lane. */
|
|
304
|
+
function nativeLaneConfig(config) {
|
|
305
|
+
const stripped = { ...config };
|
|
306
|
+
delete stripped.jsPlugins;
|
|
307
|
+
if (Array.isArray(config.overrides)) stripped.overrides = config.overrides.map((override) => {
|
|
308
|
+
if (override === null || typeof override !== "object") return override;
|
|
309
|
+
const mapped = { ...override };
|
|
310
|
+
delete mapped.jsPlugins;
|
|
311
|
+
return mapped;
|
|
312
|
+
});
|
|
313
|
+
return stripped;
|
|
314
|
+
}
|
|
315
|
+
/** Where one authored path lives inside the mirror, relative to the mirror root. */
|
|
316
|
+
function mirrorRelativePath(cwd, path) {
|
|
317
|
+
const relativePath = relative(cwd, path);
|
|
318
|
+
if (relativePath !== "" && !relativePath.startsWith("..") && !isAbsolute(relativePath)) return `${relativePath}.tsx`;
|
|
319
|
+
const flattened = path.replace(/^[A-Za-z]:/u, "").split(/[\\/]/u).filter((segment) => segment.length > 0 && segment !== "..").join(sep);
|
|
320
|
+
return `${join("__outside_cwd__", flattened)}.tsx`;
|
|
321
|
+
}
|
|
322
|
+
async function writeMirrorFile(root, relativePath, contents) {
|
|
323
|
+
const absolute = join(root, relativePath);
|
|
324
|
+
await mkdir(dirname(absolute), { recursive: true });
|
|
325
|
+
await writeFile(absolute, contents);
|
|
326
|
+
return absolute;
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Decide whether the JavaScript plugin lane runs for this invocation, and set it
|
|
330
|
+
* up if it does.
|
|
331
|
+
*
|
|
332
|
+
* Returns `null` when there is nothing to do, or one of:
|
|
333
|
+
*
|
|
334
|
+
* * `{ status: "opted-out" }` — the project turned the lane off, so the native
|
|
335
|
+
* lane keeps `jsPlugins` and answers with its own refusal;
|
|
336
|
+
* * `{ status: "version-refused", message }` — the installed Oxlint is outside
|
|
337
|
+
* the supported range, so the command must stop rather than skip rules;
|
|
338
|
+
* * `{ status: "active", ... }` — ready to run.
|
|
339
|
+
*/
|
|
340
|
+
async function preparePluginLane({ cwd, files, viteConfig, explicitConfig }) {
|
|
341
|
+
if (files.length === 0) return null;
|
|
342
|
+
const nativeSource = viteConfig ? {
|
|
343
|
+
path: viteConfig.path,
|
|
344
|
+
base: viteConfig.base,
|
|
345
|
+
explicit: true,
|
|
346
|
+
directory: viteConfig.base
|
|
347
|
+
} : explicitConfig ? {
|
|
348
|
+
path: resolve(cwd, explicitConfig),
|
|
349
|
+
base: dirname(resolve(cwd, explicitConfig)),
|
|
350
|
+
explicit: true
|
|
351
|
+
} : (() => {
|
|
352
|
+
const discovered = findOxlintConfig(cwd);
|
|
353
|
+
return discovered === null ? null : {
|
|
354
|
+
path: discovered,
|
|
355
|
+
base: dirname(discovered),
|
|
356
|
+
explicit: false
|
|
357
|
+
};
|
|
358
|
+
})();
|
|
359
|
+
const configs = /* @__PURE__ */ new Map();
|
|
360
|
+
const laneFiles = [];
|
|
361
|
+
let sawOptOut = false;
|
|
362
|
+
for (const file of files) {
|
|
363
|
+
const path = nativeSource?.explicit ? nativeSource.path : findOxlintConfig(dirname(file));
|
|
364
|
+
if (path === null || path === void 0) continue;
|
|
365
|
+
let entry = configs.get(path);
|
|
366
|
+
if (entry === void 0) {
|
|
367
|
+
const directoryOverride = path === nativeSource?.path ? nativeSource.directory ?? null : null;
|
|
368
|
+
const facts = await collectLaneFacts(path, directoryOverride);
|
|
369
|
+
entry = {
|
|
370
|
+
path,
|
|
371
|
+
config: facts.config,
|
|
372
|
+
directory: directoryOverride ?? dirname(path),
|
|
373
|
+
jsPlugins: facts.jsPlugins,
|
|
374
|
+
stripsNative: declaredJsPlugins(facts.config).length > 0,
|
|
375
|
+
optedOut: facts.optedOut === true,
|
|
376
|
+
files: []
|
|
377
|
+
};
|
|
378
|
+
configs.set(path, entry);
|
|
379
|
+
}
|
|
380
|
+
if (entry.jsPlugins.length === 0) continue;
|
|
381
|
+
if (entry.optedOut) {
|
|
382
|
+
sawOptOut = true;
|
|
383
|
+
continue;
|
|
384
|
+
}
|
|
385
|
+
entry.files.push(file);
|
|
386
|
+
laneFiles.push(file);
|
|
387
|
+
}
|
|
388
|
+
const nativeConfigEntry = nativeSource === null ? null : configs.get(nativeSource.path);
|
|
389
|
+
const nativeNeedsStrip = Boolean(nativeConfigEntry && nativeConfigEntry.stripsNative && !nativeConfigEntry.optedOut);
|
|
390
|
+
if (laneFiles.length === 0) return sawOptOut ? { status: "opted-out" } : null;
|
|
391
|
+
const version = installedOxlintVersion();
|
|
392
|
+
if (!laneSupportsOxlintVersion(version)) return {
|
|
393
|
+
status: "version-refused",
|
|
394
|
+
message: oxlintVersionRefusal(version)
|
|
395
|
+
};
|
|
396
|
+
const active = [...configs.values()].filter((entry) => entry.files.length > 0);
|
|
397
|
+
const temporary = [];
|
|
398
|
+
let nativeConfig = null;
|
|
399
|
+
if (nativeNeedsStrip) {
|
|
400
|
+
const directory = await mkdtemp(join(tmpdir(), "oxc-tsrx-native-config-"));
|
|
401
|
+
temporary.push(directory);
|
|
402
|
+
const path = join(directory, ".oxlintrc.json");
|
|
403
|
+
await writeFile(path, `${JSON.stringify(nativeLaneConfig(nativeConfigEntry.config))}\n`);
|
|
404
|
+
nativeConfig = {
|
|
405
|
+
path,
|
|
406
|
+
base: nativeSource.base,
|
|
407
|
+
typeAware: viteConfig?.typeAware === true,
|
|
408
|
+
typeCheck: viteConfig?.typeCheck === true
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
return {
|
|
412
|
+
status: "active",
|
|
413
|
+
files: laneFiles,
|
|
414
|
+
fileCount: laneFiles.length,
|
|
415
|
+
nativeConfig,
|
|
416
|
+
notice: jsPluginDisclosure(laneFiles.length),
|
|
417
|
+
async run() {
|
|
418
|
+
return runPluginLane({
|
|
419
|
+
cwd,
|
|
420
|
+
configs: active,
|
|
421
|
+
nativeConfig,
|
|
422
|
+
explicit: Boolean(nativeSource?.explicit),
|
|
423
|
+
temporary
|
|
424
|
+
});
|
|
425
|
+
},
|
|
426
|
+
async cleanup() {
|
|
427
|
+
await Promise.all(temporary.map((directory) => rm(directory, {
|
|
428
|
+
recursive: true,
|
|
429
|
+
force: true
|
|
430
|
+
})));
|
|
431
|
+
}
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
async function emitProjections(cwd, files, nativeConfig) {
|
|
435
|
+
const args = ["--emit-plugin-projection"];
|
|
436
|
+
if (nativeConfig) args.push("--config", nativeConfig.path, "--config-base", nativeConfig.base);
|
|
437
|
+
const command = resolveNativeCommand("lint", [...args, ...files]);
|
|
438
|
+
const result = await runCaptured(command.executable, command.args, { cwd });
|
|
439
|
+
if (result.status !== 0) throw new Error(`the native TSRX projection needed for JS plugins failed:\n${result.stderr || result.stdout}`);
|
|
440
|
+
let parsed;
|
|
441
|
+
try {
|
|
442
|
+
parsed = JSON.parse(result.stdout);
|
|
443
|
+
} catch {
|
|
444
|
+
throw new Error(`the native TSRX projection needed for JS plugins returned non-JSON output:\n${result.stdout}`);
|
|
445
|
+
}
|
|
446
|
+
return Array.isArray(parsed.projections) ? parsed.projections : [];
|
|
447
|
+
}
|
|
448
|
+
async function mapDiagnostics(cwd, byFile) {
|
|
449
|
+
const command = resolveNativeCommand("lint", ["--map-plugin-diagnostics"]);
|
|
450
|
+
const request = JSON.stringify({ files: [...byFile].map(([path, diagnostics]) => ({
|
|
451
|
+
path,
|
|
452
|
+
diagnostics
|
|
453
|
+
})) });
|
|
454
|
+
const result = await runCaptured(command.executable, command.args, {
|
|
455
|
+
cwd,
|
|
456
|
+
input: request
|
|
457
|
+
});
|
|
458
|
+
if (result.status !== 0) throw new Error(`mapping JS plugin diagnostics back to authored .tsrx positions failed:\n${result.stderr || result.stdout}`);
|
|
459
|
+
let parsed;
|
|
460
|
+
try {
|
|
461
|
+
parsed = JSON.parse(result.stdout);
|
|
462
|
+
} catch {
|
|
463
|
+
throw new Error(`mapping JS plugin diagnostics back to authored .tsrx positions returned non-JSON output:\n${result.stdout}`);
|
|
464
|
+
}
|
|
465
|
+
return Array.isArray(parsed.files) ? parsed.files : [];
|
|
466
|
+
}
|
|
467
|
+
function diagnosticNamespace(diagnostic) {
|
|
468
|
+
const code = typeof diagnostic.code === "string" ? diagnostic.code : "";
|
|
469
|
+
const open = code.indexOf("(");
|
|
470
|
+
return open === -1 ? code : code.slice(0, open);
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* The failures Oxlint's plugin host itself reported, in the user's own terms.
|
|
474
|
+
*
|
|
475
|
+
* A rule that throws does not come back as a diagnostic on a file: Oxlint reports
|
|
476
|
+
* it with an empty `filename`, no `code`, and no labels, which is exactly the
|
|
477
|
+
* shape every other filter in this file drops. Dropping it too would mean a
|
|
478
|
+
* broken rule looks like a rule that found nothing, which is the failure this
|
|
479
|
+
* whole lane exists to remove. `paths` rewrites the mirror path Oxlint saw back
|
|
480
|
+
* to the file the developer opened.
|
|
481
|
+
*/
|
|
482
|
+
/**
|
|
483
|
+
* Mirror paths to the authored paths they stand for, each mirror path recorded
|
|
484
|
+
* under both the name it was written with and the one a `realpath` resolves it
|
|
485
|
+
* to. Oxlint reports the resolved name, and on macOS a temporary directory is
|
|
486
|
+
* always a symlink away from it.
|
|
487
|
+
*/
|
|
488
|
+
function authoredPathMap(pairs) {
|
|
489
|
+
const map = /* @__PURE__ */ new Map();
|
|
490
|
+
for (const [mirrorPath, authored] of pairs) {
|
|
491
|
+
map.set(mirrorPath, authored);
|
|
492
|
+
try {
|
|
493
|
+
map.set(realpathSync(mirrorPath), authored);
|
|
494
|
+
} catch {}
|
|
495
|
+
}
|
|
496
|
+
return map;
|
|
497
|
+
}
|
|
498
|
+
function pluginHostFailures(report, paths = /* @__PURE__ */ new Map()) {
|
|
499
|
+
const failures = [];
|
|
500
|
+
for (const diagnostic of report?.diagnostics ?? []) {
|
|
501
|
+
if (typeof diagnostic?.message !== "string" || diagnostic.message === "") continue;
|
|
502
|
+
if (diagnostic.filename !== void 0 && diagnostic.filename !== "") continue;
|
|
503
|
+
if ((diagnostic.labels ?? []).length > 0) continue;
|
|
504
|
+
if (typeof diagnostic.code === "string" && diagnostic.code !== "") continue;
|
|
505
|
+
let message = diagnostic.message.split(/\n\s+at /u, 1)[0].trim();
|
|
506
|
+
const rewrites = [...paths].sort(([left], [right]) => right.length - left.length);
|
|
507
|
+
for (const [from, to] of rewrites) message = message.split(from).join(to);
|
|
508
|
+
failures.push(message);
|
|
509
|
+
}
|
|
510
|
+
return failures;
|
|
511
|
+
}
|
|
512
|
+
async function runPluginLane({ cwd, configs, nativeConfig, explicit, temporary }) {
|
|
513
|
+
const projections = await emitProjections(cwd, configs.flatMap((entry) => entry.files), nativeConfig);
|
|
514
|
+
if (projections.length === 0) return {
|
|
515
|
+
diagnostics: [],
|
|
516
|
+
files: 0,
|
|
517
|
+
extraParses: 0,
|
|
518
|
+
unmapped: 0,
|
|
519
|
+
failures: []
|
|
520
|
+
};
|
|
521
|
+
const mirror = await mkdtemp(join(tmpdir(), "oxc-tsrx-js-plugins-"));
|
|
522
|
+
temporary.push(mirror);
|
|
523
|
+
const authoredByMirrorPath = /* @__PURE__ */ new Map();
|
|
524
|
+
const mirrored = [];
|
|
525
|
+
for (const projection of projections) {
|
|
526
|
+
if (typeof projection?.path !== "string" || typeof projection.projected !== "string") continue;
|
|
527
|
+
const relativePath = mirrorRelativePath(cwd, projection.path);
|
|
528
|
+
await writeMirrorFile(mirror, relativePath, projection.projected);
|
|
529
|
+
authoredByMirrorPath.set(relativePath, projection.path);
|
|
530
|
+
mirrored.push(relativePath);
|
|
531
|
+
}
|
|
532
|
+
if (mirrored.length === 0) return {
|
|
533
|
+
diagnostics: [],
|
|
534
|
+
files: 0,
|
|
535
|
+
extraParses: 0,
|
|
536
|
+
unmapped: 0,
|
|
537
|
+
failures: []
|
|
538
|
+
};
|
|
539
|
+
const namespaces = /* @__PURE__ */ new Set();
|
|
540
|
+
let namespacesKnown = true;
|
|
541
|
+
for (const entry of configs) {
|
|
542
|
+
const projected = projectionConfig(entry.config, entry.directory);
|
|
543
|
+
const relativeConfig = explicit ? ".oxlintrc.json" : (() => {
|
|
544
|
+
const candidate = relative(cwd, entry.path);
|
|
545
|
+
return candidate !== "" && !candidate.startsWith("..") && !isAbsolute(candidate) ? candidate : ".oxlintrc.json";
|
|
546
|
+
})();
|
|
547
|
+
await writeMirrorFile(mirror, relativeConfig, `${JSON.stringify(projected, null, 2)}\n`);
|
|
548
|
+
entry.mirrorConfig = relativeConfig;
|
|
549
|
+
const found = await pluginNamespaces(entry.jsPlugins);
|
|
550
|
+
if (found === null) namespacesKnown = false;
|
|
551
|
+
else for (const name of found) namespaces.add(name);
|
|
552
|
+
}
|
|
553
|
+
const oxlintArgs = [resolvePackageBinary("oxlint-current", "oxlint", import.meta.url), "--format=json"];
|
|
554
|
+
if (explicit) oxlintArgs.push("--config", configs[0].mirrorConfig);
|
|
555
|
+
const result = await runCaptured(process.execPath, [...oxlintArgs, ...mirrored], {
|
|
556
|
+
cwd: mirror,
|
|
557
|
+
env: process.env
|
|
558
|
+
});
|
|
559
|
+
if (result.status > 1) throw new Error(`running your JS plugins over the .tsrx projection failed:\n${result.stderr || result.stdout}`);
|
|
560
|
+
let report;
|
|
561
|
+
try {
|
|
562
|
+
report = JSON.parse(result.stdout);
|
|
563
|
+
} catch {
|
|
564
|
+
throw new Error(`running your JS plugins over the .tsrx projection returned non-JSON output:\n${result.stdout}${result.stderr}`);
|
|
565
|
+
}
|
|
566
|
+
const byFile = /* @__PURE__ */ new Map();
|
|
567
|
+
for (const relativePath of mirrored) byFile.set(authoredByMirrorPath.get(relativePath), []);
|
|
568
|
+
for (const diagnostic of report.diagnostics ?? []) {
|
|
569
|
+
const authored = authoredByMirrorPath.get(diagnostic.filename);
|
|
570
|
+
if (authored === void 0) continue;
|
|
571
|
+
const namespace = diagnosticNamespace(diagnostic);
|
|
572
|
+
if (namespace === "") continue;
|
|
573
|
+
if (namespacesKnown && !namespaces.has(namespace)) continue;
|
|
574
|
+
byFile.get(authored).push(diagnostic);
|
|
575
|
+
}
|
|
576
|
+
const nonEmpty = new Map([...byFile].filter(([, diagnostics]) => diagnostics.length > 0));
|
|
577
|
+
const diagnostics = [];
|
|
578
|
+
let unmapped = 0;
|
|
579
|
+
if (nonEmpty.size > 0) for (const file of await mapDiagnostics(cwd, nonEmpty)) {
|
|
580
|
+
if (Number.isSafeInteger(file.unmapped) && file.unmapped > 0) unmapped += file.unmapped;
|
|
581
|
+
for (const diagnostic of file.diagnostics ?? []) diagnostics.push({
|
|
582
|
+
...diagnostic,
|
|
583
|
+
filename: file.path
|
|
584
|
+
});
|
|
585
|
+
}
|
|
586
|
+
return {
|
|
587
|
+
diagnostics,
|
|
588
|
+
files: mirrored.length,
|
|
589
|
+
extraParses: mirrored.length,
|
|
590
|
+
unmapped,
|
|
591
|
+
failures: pluginHostFailures(report, authoredPathMap([...authoredByMirrorPath].map(([relativePath, authored]) => [join(mirror, relativePath), authored])))
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
/** The flag that turns this module into the editor's lane host. */
|
|
595
|
+
const LANE_HOST_FLAG = "--oxc-tsrx-js-plugin-lane-host";
|
|
596
|
+
/**
|
|
597
|
+
* The one line the editor session prints when the lane starts.
|
|
598
|
+
*
|
|
599
|
+
* An editor has no report to put a notice in front of, so this goes to the
|
|
600
|
+
* server's stderr, which every LSP client surfaces as its output log. It names
|
|
601
|
+
* the extra parse and the exact key that turns it off, the same way the command
|
|
602
|
+
* line's own notice does.
|
|
603
|
+
*/
|
|
604
|
+
function jsPluginEditorDisclosure() {
|
|
605
|
+
return "oxc-tsrx-lsp: running this project's Oxlint JS plugins on .tsrx by linting each file's TSX projection; this parses every linted .tsrx file once more. Disable with \"settings\": { \"oxcTsrx\": { \"jsPluginsOnTsrx\": false } }.";
|
|
606
|
+
}
|
|
607
|
+
/** One long-lived mirror, config set, and Oxlint invocation for an editor session. */
|
|
608
|
+
var EditorPluginLane = class {
|
|
609
|
+
cwd;
|
|
610
|
+
mirror;
|
|
611
|
+
configs;
|
|
612
|
+
constructor(cwd) {
|
|
613
|
+
this.cwd = resolve(cwd);
|
|
614
|
+
this.mirror = null;
|
|
615
|
+
this.configs = /* @__PURE__ */ new Map();
|
|
616
|
+
}
|
|
617
|
+
async mirrorRoot() {
|
|
618
|
+
if (this.mirror === null) this.mirror = await mkdtemp(join(tmpdir(), "oxc-tsrx-js-plugins-lsp-"));
|
|
619
|
+
return this.mirror;
|
|
620
|
+
}
|
|
621
|
+
/**
|
|
622
|
+
* The configuration governing one directory, resolved and mirrored once.
|
|
623
|
+
*
|
|
624
|
+
* A configuration file that changes during the session is not re-read here:
|
|
625
|
+
* the language server watches `.oxlintrc.json`, rebuilds its workspace tool on
|
|
626
|
+
* a change, and that drops this whole process along with the stale cache.
|
|
627
|
+
*/
|
|
628
|
+
async entryFor(directory) {
|
|
629
|
+
const path = findOxlintConfig(directory);
|
|
630
|
+
if (path === null) return { active: false };
|
|
631
|
+
const cached = this.configs.get(path);
|
|
632
|
+
if (cached !== void 0) return cached;
|
|
633
|
+
const facts = await collectLaneFacts(path);
|
|
634
|
+
let entry = { active: false };
|
|
635
|
+
if (facts.config !== null && facts.jsPlugins.length > 0 && facts.optedOut !== true) {
|
|
636
|
+
const mirror = await this.mirrorRoot();
|
|
637
|
+
const candidate = relative(this.cwd, path);
|
|
638
|
+
await writeMirrorFile(mirror, candidate !== "" && !candidate.startsWith("..") && !isAbsolute(candidate) ? candidate : ".oxlintrc.json", `${JSON.stringify(projectionConfig(facts.config, dirname(path)), null, 2)}\n`);
|
|
639
|
+
entry = {
|
|
640
|
+
active: true,
|
|
641
|
+
namespaces: await pluginNamespaces(facts.jsPlugins)
|
|
642
|
+
};
|
|
643
|
+
}
|
|
644
|
+
this.configs.set(path, entry);
|
|
645
|
+
return entry;
|
|
646
|
+
}
|
|
647
|
+
/**
|
|
648
|
+
* Run this project's JavaScript plugins over one projection.
|
|
649
|
+
*
|
|
650
|
+
* Returns Oxlint's own diagnostics with their label spans still measured in
|
|
651
|
+
* projection bytes. Mapping them to authored bytes is the caller's job,
|
|
652
|
+
* because the caller is the process that owns the span map.
|
|
653
|
+
*/
|
|
654
|
+
async lint(path, projection) {
|
|
655
|
+
const entry = await this.entryFor(dirname(path));
|
|
656
|
+
if (!entry.active) return [];
|
|
657
|
+
const mirror = await this.mirrorRoot();
|
|
658
|
+
const relativePath = mirrorRelativePath(this.cwd, path);
|
|
659
|
+
await writeMirrorFile(mirror, relativePath, projection);
|
|
660
|
+
const oxlintBinary = resolvePackageBinary("oxlint-current", "oxlint", import.meta.url);
|
|
661
|
+
const result = await runCaptured(process.execPath, [
|
|
662
|
+
oxlintBinary,
|
|
663
|
+
"--format=json",
|
|
664
|
+
relativePath
|
|
665
|
+
], {
|
|
666
|
+
cwd: mirror,
|
|
667
|
+
env: process.env
|
|
668
|
+
});
|
|
669
|
+
if (result.status > 1) throw new Error(`running your JS plugins over the .tsrx projection failed:\n${result.stderr || result.stdout}`);
|
|
670
|
+
let report;
|
|
671
|
+
try {
|
|
672
|
+
report = JSON.parse(result.stdout);
|
|
673
|
+
} catch {
|
|
674
|
+
throw new Error(`running your JS plugins over the .tsrx projection returned non-JSON output:\n${result.stdout}${result.stderr}`);
|
|
675
|
+
}
|
|
676
|
+
const failures = pluginHostFailures(report, authoredPathMap([[join(mirror, relativePath), path]]));
|
|
677
|
+
if (failures.length > 0) throw new Error(failures.join("\n"));
|
|
678
|
+
const diagnostics = [];
|
|
679
|
+
for (const diagnostic of report.diagnostics ?? []) {
|
|
680
|
+
if (diagnostic.filename !== relativePath) continue;
|
|
681
|
+
const namespace = diagnosticNamespace(diagnostic);
|
|
682
|
+
if (namespace === "") continue;
|
|
683
|
+
if (entry.namespaces !== null && !entry.namespaces.has(namespace)) continue;
|
|
684
|
+
const labels = [];
|
|
685
|
+
for (const label of diagnostic.labels ?? []) {
|
|
686
|
+
const offset = label?.span?.offset;
|
|
687
|
+
if (!Number.isSafeInteger(offset) || offset < 0) continue;
|
|
688
|
+
const length = label.span.length;
|
|
689
|
+
labels.push({
|
|
690
|
+
offset,
|
|
691
|
+
length: Number.isSafeInteger(length) && length > 0 ? length : 0
|
|
692
|
+
});
|
|
693
|
+
}
|
|
694
|
+
if (labels.length === 0) continue;
|
|
695
|
+
diagnostics.push({
|
|
696
|
+
code: typeof diagnostic.code === "string" ? diagnostic.code : null,
|
|
697
|
+
message: typeof diagnostic.message === "string" ? diagnostic.message : "",
|
|
698
|
+
severity: diagnostic.severity === "error" ? "error" : "warning",
|
|
699
|
+
help: typeof diagnostic.help === "string" ? diagnostic.help : null,
|
|
700
|
+
labels
|
|
701
|
+
});
|
|
702
|
+
}
|
|
703
|
+
return diagnostics;
|
|
704
|
+
}
|
|
705
|
+
async cleanup() {
|
|
706
|
+
if (this.mirror !== null) await rm(this.mirror, {
|
|
707
|
+
recursive: true,
|
|
708
|
+
force: true
|
|
709
|
+
});
|
|
710
|
+
}
|
|
711
|
+
};
|
|
712
|
+
/**
|
|
713
|
+
* Serve the editor's plugin lane over newline-delimited JSON on stdio.
|
|
714
|
+
*
|
|
715
|
+
* The first line out is the handshake: `{"ready":true,...}`, or
|
|
716
|
+
* `{"ready":false,"error":...}` when the installed Oxlint is outside the range
|
|
717
|
+
* this lane was established against. Refusing out loud is the point — an editor
|
|
718
|
+
* that quietly stopped running a developer's rule is the failure this whole lane
|
|
719
|
+
* exists to remove, and a squiggle that silently disappears is worse than one
|
|
720
|
+
* that never appeared.
|
|
721
|
+
*
|
|
722
|
+
* Every request is `{id, path, projection}` and every answer is either
|
|
723
|
+
* `{id, diagnostics}` or `{id, error}`. Requests are served one at a time and in
|
|
724
|
+
* order, so a burst of keystrokes cannot interleave two Oxlint runs over the
|
|
725
|
+
* same mirror file.
|
|
726
|
+
*/
|
|
727
|
+
async function runJsPluginLaneHost({ cwd = process.cwd(), input = process.stdin, output = process.stdout, errorOutput = process.stderr } = {}) {
|
|
728
|
+
const version = installedOxlintVersion();
|
|
729
|
+
if (!laneSupportsOxlintVersion(version)) {
|
|
730
|
+
output.write(`${JSON.stringify({
|
|
731
|
+
ready: false,
|
|
732
|
+
error: oxlintVersionRefusal(version)
|
|
733
|
+
})}\n`);
|
|
734
|
+
return 0;
|
|
735
|
+
}
|
|
736
|
+
const lane = new EditorPluginLane(cwd);
|
|
737
|
+
errorOutput.write(`${jsPluginEditorDisclosure()}\n`);
|
|
738
|
+
output.write(`${JSON.stringify({
|
|
739
|
+
ready: true,
|
|
740
|
+
oxlint: version
|
|
741
|
+
})}\n`);
|
|
742
|
+
let pending = Promise.resolve();
|
|
743
|
+
let buffer = "";
|
|
744
|
+
await new Promise((finished) => {
|
|
745
|
+
const drain = () => {
|
|
746
|
+
pending.then(() => finished(), () => finished());
|
|
747
|
+
};
|
|
748
|
+
input.setEncoding("utf8");
|
|
749
|
+
input.on("data", (chunk) => {
|
|
750
|
+
buffer += chunk;
|
|
751
|
+
for (;;) {
|
|
752
|
+
const newline = buffer.indexOf("\n");
|
|
753
|
+
if (newline === -1) break;
|
|
754
|
+
const line = buffer.slice(0, newline).trim();
|
|
755
|
+
buffer = buffer.slice(newline + 1);
|
|
756
|
+
if (line === "") continue;
|
|
757
|
+
let request;
|
|
758
|
+
try {
|
|
759
|
+
request = JSON.parse(line);
|
|
760
|
+
} catch {
|
|
761
|
+
continue;
|
|
762
|
+
}
|
|
763
|
+
pending = pending.then(async () => {
|
|
764
|
+
let answer;
|
|
765
|
+
try {
|
|
766
|
+
answer = {
|
|
767
|
+
id: request.id,
|
|
768
|
+
diagnostics: await lane.lint(String(request.path), String(request.projection))
|
|
769
|
+
};
|
|
770
|
+
} catch (error) {
|
|
771
|
+
answer = {
|
|
772
|
+
id: request.id,
|
|
773
|
+
error: error instanceof Error ? error.message : String(error)
|
|
774
|
+
};
|
|
775
|
+
}
|
|
776
|
+
output.write(`${JSON.stringify(answer)}\n`);
|
|
777
|
+
});
|
|
778
|
+
}
|
|
779
|
+
});
|
|
780
|
+
input.once("end", drain);
|
|
781
|
+
input.once("close", drain);
|
|
782
|
+
input.once("error", drain);
|
|
783
|
+
});
|
|
784
|
+
await lane.cleanup();
|
|
785
|
+
return 0;
|
|
786
|
+
}
|
|
787
|
+
/**
|
|
788
|
+
* Whether this module is the process entry point.
|
|
789
|
+
*
|
|
790
|
+
* `process.argv[1]` keeps the path the caller named while `import.meta.url`
|
|
791
|
+
* reports the real one, and the editor reaches this file through a package
|
|
792
|
+
* symlink often enough that comparing them raw would silently never match.
|
|
793
|
+
*/
|
|
794
|
+
function invokedAsLaneHost() {
|
|
795
|
+
if (!process.argv.includes("--oxc-tsrx-js-plugin-lane-host")) return false;
|
|
796
|
+
const entry = process.argv[1];
|
|
797
|
+
if (typeof entry !== "string" || entry === "") return false;
|
|
798
|
+
for (const candidate of [entry, (() => {
|
|
799
|
+
try {
|
|
800
|
+
return realpathSync(entry);
|
|
801
|
+
} catch {
|
|
802
|
+
return entry;
|
|
803
|
+
}
|
|
804
|
+
})()]) try {
|
|
805
|
+
if (pathToFileURL(candidate).href === import.meta.url) return true;
|
|
806
|
+
} catch {}
|
|
807
|
+
return false;
|
|
808
|
+
}
|
|
809
|
+
if (invokedAsLaneHost()) {
|
|
810
|
+
const index = process.argv.indexOf("--cwd");
|
|
811
|
+
runJsPluginLaneHost({ cwd: index === -1 ? process.cwd() : process.argv[index + 1] ?? process.cwd() }).then((status) => {
|
|
812
|
+
process.exitCode = status;
|
|
813
|
+
}, (error) => {
|
|
814
|
+
process.stderr.write(`oxc-tsrx-lsp: the JS plugin lane host stopped: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
815
|
+
process.exitCode = 1;
|
|
816
|
+
});
|
|
817
|
+
}
|
|
818
|
+
//#endregion
|
|
819
|
+
export { LANE_HOST_FLAG, OXLINT_JS_PLUGIN_LANE_BELOW, OXLINT_JS_PLUGIN_LANE_MINIMUM, installedOxlintVersion, jsPluginDisclosure, jsPluginEditorDisclosure, jsPluginUnmappedNote, laneSupportsOxlintVersion, mirrorRelativePath, nativeLaneConfig, oxlintVersionRefusal, parseOxlintConfigText, preparePluginLane, projectionConfig };
|