@tsrx/oxc 0.9.0 → 0.11.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/README.md +14 -14
- package/dist/bin/oxc-tsrx.js +4 -1
- package/dist/bin/oxlint.js +7 -4
- package/dist/compat.d.ts +14 -0
- package/dist/compat.js +47 -24
- package/dist/format-cli.js +7 -4
- package/dist/lint-cli.js +6 -3
- package/dist/lint-js-plugins.js +10 -4
- package/dist/lint-prestart.js +1 -1
- package/dist/oxlint-lsp-multiplexer.js +6 -1
- package/dist/parser.js +1 -1
- package/dist/runtime.js +72 -10
- package/dist/tsrx-core-compat/facade.js +1 -1
- package/package.json +10 -10
package/README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<p align="center">
|
|
2
|
-
<a href="https://
|
|
2
|
+
<a href="https://oxc.tsrx.dev">
|
|
3
3
|
<img alt="OXC for TSRX" width="600" src="https://raw.githubusercontent.com/tsrx-org/oxc/HEAD/.github/assets/readme-hero.png">
|
|
4
4
|
</a>
|
|
5
5
|
</p>
|
|
@@ -26,7 +26,7 @@ written in Rust. This package teaches them to read `.tsrx` too.
|
|
|
26
26
|
|
|
27
27
|
_OXC for TSRX is the official OXC integration maintained by the TSRX project._
|
|
28
28
|
|
|
29
|
-
[**Docs**](https://
|
|
29
|
+
[**Docs**](https://oxc.tsrx.dev) · [**Getting started**](https://oxc.tsrx.dev/guide/getting-started) · [**Playground**](https://oxc.tsrx.dev/playground)
|
|
30
30
|
|
|
31
31
|
## Install
|
|
32
32
|
|
|
@@ -37,11 +37,11 @@ npm install --save-dev @tsrx/oxc
|
|
|
37
37
|
That is the whole setup, for the command line and for your editor. You get
|
|
38
38
|
`oxlint` and `oxfmt`, the real [OXC](https://oxc.rs) commands, now able to read
|
|
39
39
|
`.tsrx`, with no config file, no ignore file, and no install script. [Vite+ needs
|
|
40
|
-
one more command](https://
|
|
40
|
+
one more command](https://oxc.tsrx.dev/guide/getting-started#try-it-with-vite).
|
|
41
41
|
|
|
42
42
|
You do not need Rust installed: the install downloads a ready-built program for
|
|
43
43
|
your machine, one of eight published for macOS, Linux, and Windows. See
|
|
44
|
-
[Platform support](https://
|
|
44
|
+
[Platform support](https://oxc.tsrx.dev/reference/platform-support).
|
|
45
45
|
|
|
46
46
|
## Usage
|
|
47
47
|
|
|
@@ -64,7 +64,7 @@ These are the TSRX blocks the linter and formatter understand: `@{` blocks,
|
|
|
64
64
|
`@if` / `@else if` / `@else`, `@for` / `@empty`, `@switch` / `@case` /
|
|
65
65
|
`@default`, `@try` / `@pending` / `@catch`, tags whose name is an expression,
|
|
66
66
|
`<{expression}>`, and plain `<style>` blocks
|
|
67
|
-
([TSRX syntax](https://
|
|
67
|
+
([TSRX syntax](https://oxc.tsrx.dev/guide/tsrx-syntax) shows each one). Anything
|
|
68
68
|
outside that list **fails closed**: the command stops and says what it found and
|
|
69
69
|
where, rather than guessing and maybe producing wrong output.
|
|
70
70
|
|
|
@@ -82,7 +82,7 @@ underlines the same problems the terminal reports. One catch: the TSRX
|
|
|
82
82
|
toolchain's own extension owns `.tsrx`, and the OXC extension lists no
|
|
83
83
|
activation event for it, so it does not start on its own. Open any JavaScript,
|
|
84
84
|
TypeScript, or JSON file in the project once, and `.tsrx` works for the rest of
|
|
85
|
-
the session. See the [editor guide](https://
|
|
85
|
+
the session. See the [editor guide](https://oxc.tsrx.dev/integrations/editor).
|
|
86
86
|
|
|
87
87
|
## Using it from your own code
|
|
88
88
|
|
|
@@ -94,7 +94,7 @@ import { format } from "@tsrx/oxc/format";
|
|
|
94
94
|
|
|
95
95
|
The parser hands back the structure of your file and the formatter hands back
|
|
96
96
|
tidied text, so you can build your own tools on the same reader the commands
|
|
97
|
-
use. [Parsing](https://
|
|
97
|
+
use. [Parsing](https://oxc.tsrx.dev/guide/parsing) has the shape it returns.
|
|
98
98
|
None of these compiles `.tsrx` either; that stays your framework's TSRX
|
|
99
99
|
plugin's job.
|
|
100
100
|
|
|
@@ -121,16 +121,16 @@ editor both say when they have done that, and
|
|
|
121
121
|
so it refuses `jsPlugins` and names `oxlint` as the command that can.
|
|
122
122
|
`@tsrx/oxc/lint/plugins-dev` is for *writing* a plugin, since it re-exports the
|
|
123
123
|
`RuleTester` from `oxlint`. See [Custom JavaScript
|
|
124
|
-
plugins](https://
|
|
124
|
+
plugins](https://oxc.tsrx.dev/integrations/custom-js-plugins).
|
|
125
125
|
|
|
126
126
|
## Documentation
|
|
127
127
|
|
|
128
|
-
- [Getting started](https://
|
|
129
|
-
- [Configuration](https://
|
|
130
|
-
- [CLI reference](https://
|
|
131
|
-
- [Platform support](https://
|
|
132
|
-
- [Limitations](https://
|
|
133
|
-
- [Provider protocol](https://
|
|
128
|
+
- [Getting started](https://oxc.tsrx.dev/guide/getting-started): install, first file, first run.
|
|
129
|
+
- [Configuration](https://oxc.tsrx.dev/integrations/configuration): every supported setting.
|
|
130
|
+
- [CLI reference](https://oxc.tsrx.dev/reference/cli): commands, flags, and exit codes.
|
|
131
|
+
- [Platform support](https://oxc.tsrx.dev/reference/platform-support): which of the eight published platforms are tested on every change.
|
|
132
|
+
- [Limitations](https://oxc.tsrx.dev/reference/limitations): what is not claimed yet.
|
|
133
|
+
- [Provider protocol](https://oxc.tsrx.dev/architecture/provider-protocol): the `oxc.provider` block, what reads it today, and how a plain install reaches released hosts.
|
|
134
134
|
|
|
135
135
|
`@tsrx/oxc` is the only package to depend on. The eight `@tsrx/oxc-*`
|
|
136
136
|
packages are platform binaries in `optionalDependencies`, and you never name one
|
package/dist/bin/oxc-tsrx.js
CHANGED
|
@@ -20,7 +20,10 @@ setup, status, and remove are the temporary compatibility bridge for consumers
|
|
|
20
20
|
whose resolver is not provider-aware yet. The bridge creates the exact
|
|
21
21
|
oxc-parser, oxlint, and oxfmt package-name facades those consumers need, and one
|
|
22
22
|
fourth slot for the editor. Transitive official packages in those exact slots are
|
|
23
|
-
preserved and restored; direct or unrecognized packages are never replaced.
|
|
23
|
+
preserved and restored; direct or unrecognized packages are never replaced. An
|
|
24
|
+
official oxlint or oxfmt you declare yourself keeps its slot and its command
|
|
25
|
+
name (reported as a collision, with what that means); setup still writes the
|
|
26
|
+
editor slot so the official OXC extension serves .tsrx through this package.
|
|
24
27
|
|
|
25
28
|
The editor slot is the one thing setup writes outside node_modules. When
|
|
26
29
|
node_modules/.bin/oxlint belongs to another tool, the official OXC extension
|
package/dist/bin/oxlint.js
CHANGED
|
@@ -8,13 +8,16 @@ try {
|
|
|
8
8
|
const { decideCanonicalCommand, deferralNotice, runOfficialCommand } = await import("../canonical-command.js");
|
|
9
9
|
const args = process.argv.slice(2);
|
|
10
10
|
const decision = await decideCanonicalCommand("oxlint");
|
|
11
|
-
if (
|
|
11
|
+
if (args.some((argument) => argument.split("=", 1)[0] === "--lsp")) {
|
|
12
|
+
const { runOxlintLspMultiplexer } = await import("../oxlint-lsp-multiplexer.js");
|
|
13
|
+
process.exitCode = await runOxlintLspMultiplexer(args, decision.owner === "project" ? { canonical: {
|
|
14
|
+
binPath: decision.binPath,
|
|
15
|
+
version: decision.officialVersion
|
|
16
|
+
} } : {});
|
|
17
|
+
} else if (decision.owner === "project") {
|
|
12
18
|
const notice = deferralNotice(decision, args);
|
|
13
19
|
if (notice !== null) console.error(notice);
|
|
14
20
|
await runOfficialCommand(decision);
|
|
15
|
-
} else if (args.some((argument) => argument.split("=", 1)[0] === "--lsp")) {
|
|
16
|
-
const { runOxlintLspMultiplexer } = await import("../oxlint-lsp-multiplexer.js");
|
|
17
|
-
process.exitCode = await runOxlintLspMultiplexer(args);
|
|
18
21
|
} else {
|
|
19
22
|
const { canRunCanonicalOxlint, importDeclaredPackageBinary, planCanonicalOxlintComposition } = await import("../lint-invocation.js");
|
|
20
23
|
if (canRunCanonicalOxlint(args)) await importDeclaredPackageBinary("oxlint-current", "oxlint", import.meta.url);
|
package/dist/compat.d.ts
CHANGED
|
@@ -8,8 +8,22 @@ export type OxcTsrxCompatibilityState =
|
|
|
8
8
|
export interface OxcTsrxCompatibilitySlot {
|
|
9
9
|
readonly name: "oxc-parser" | "oxlint" | "oxfmt";
|
|
10
10
|
readonly capability: "parser" | "lint" | "format";
|
|
11
|
+
/** The command name the slot's package publishes, or null for the parser slot. */
|
|
12
|
+
readonly binary: "oxlint" | "oxfmt" | null;
|
|
11
13
|
readonly path: string;
|
|
12
14
|
readonly state: OxcTsrxCompatibilityState;
|
|
15
|
+
/**
|
|
16
|
+
* Why a `collision` slot is left alone. `direct-dependency` is the project's
|
|
17
|
+
* own official package; `setup` works around it and the report explains it.
|
|
18
|
+
* The other kinds are trees the bridge no longer owns and `setup` refuses.
|
|
19
|
+
*/
|
|
20
|
+
readonly collision?:
|
|
21
|
+
| "direct-dependency"
|
|
22
|
+
| "foreign-package"
|
|
23
|
+
| "reinstalled-over-facade"
|
|
24
|
+
| "facade-without-backup";
|
|
25
|
+
/** What occupies a `collision` slot, as its own package.json names it. */
|
|
26
|
+
readonly occupant?: { readonly name: string | null; readonly version: string | null };
|
|
13
27
|
readonly replacedPackage?: {
|
|
14
28
|
readonly name: string;
|
|
15
29
|
readonly version: string;
|
package/dist/compat.js
CHANGED
|
@@ -561,14 +561,22 @@ async function inspectSlot(modules, slot, providerVersion, projectManifest) {
|
|
|
561
561
|
};
|
|
562
562
|
const manifest = await readJson(join(destination, "package.json")).catch(() => null);
|
|
563
563
|
const metadata = compatibilityMetadata(manifest);
|
|
564
|
+
const occupant = {
|
|
565
|
+
name: typeof manifest?.name === "string" ? manifest.name : null,
|
|
566
|
+
version: typeof manifest?.version === "string" ? manifest.version : null
|
|
567
|
+
};
|
|
568
|
+
const collision = (kind) => ({
|
|
569
|
+
slot,
|
|
570
|
+
destination,
|
|
571
|
+
state: "collision",
|
|
572
|
+
metadata: null,
|
|
573
|
+
collision: kind,
|
|
574
|
+
occupant
|
|
575
|
+
});
|
|
564
576
|
if (!metadata || metadata.capability !== slot.capability) {
|
|
565
|
-
if (manifest?.name === slot.name && typeof manifest.version === "string"
|
|
566
|
-
if (
|
|
567
|
-
|
|
568
|
-
destination,
|
|
569
|
-
state: "collision",
|
|
570
|
-
metadata: null
|
|
571
|
-
};
|
|
577
|
+
if (manifest?.name === slot.name && typeof manifest.version === "string") {
|
|
578
|
+
if (directlySelected(projectManifest, slot.name)) return collision("direct-dependency");
|
|
579
|
+
if (await exists(backupPath(modules, slot))) return collision("reinstalled-over-facade");
|
|
572
580
|
return {
|
|
573
581
|
slot,
|
|
574
582
|
destination,
|
|
@@ -580,19 +588,9 @@ async function inspectSlot(modules, slot, providerVersion, projectManifest) {
|
|
|
580
588
|
}
|
|
581
589
|
};
|
|
582
590
|
}
|
|
583
|
-
return
|
|
584
|
-
slot,
|
|
585
|
-
destination,
|
|
586
|
-
state: "collision",
|
|
587
|
-
metadata: null
|
|
588
|
-
};
|
|
591
|
+
return collision("foreign-package");
|
|
589
592
|
}
|
|
590
|
-
if (metadata.replacedPackage && !await exists(backupPath(modules, slot))) return
|
|
591
|
-
slot,
|
|
592
|
-
destination,
|
|
593
|
-
state: "collision",
|
|
594
|
-
metadata: null
|
|
595
|
-
};
|
|
593
|
+
if (metadata.replacedPackage && !await exists(backupPath(modules, slot))) return collision("facade-without-backup");
|
|
596
594
|
return {
|
|
597
595
|
slot,
|
|
598
596
|
destination,
|
|
@@ -1345,12 +1343,17 @@ async function compatibilityStatus(options = {}) {
|
|
|
1345
1343
|
packageManager: await detectPackageManager(projectRoot, options.userAgent),
|
|
1346
1344
|
providerVersion: provider.manifest.version,
|
|
1347
1345
|
selectedFrom: provider.selectedFrom,
|
|
1348
|
-
slots: slots.map(({ slot, destination, state, replacedPackage }) => ({
|
|
1346
|
+
slots: slots.map(({ slot, destination, state, replacedPackage, collision, occupant }) => ({
|
|
1349
1347
|
name: slot.name,
|
|
1350
1348
|
capability: slot.capability,
|
|
1349
|
+
binary: slot.binary,
|
|
1351
1350
|
path: destination,
|
|
1352
1351
|
state,
|
|
1353
|
-
...replacedPackage ? { replacedPackage } : {}
|
|
1352
|
+
...replacedPackage ? { replacedPackage } : {},
|
|
1353
|
+
...collision ? {
|
|
1354
|
+
collision,
|
|
1355
|
+
occupant
|
|
1356
|
+
} : {}
|
|
1354
1357
|
})),
|
|
1355
1358
|
editorSlot: await inspectEditorSlot(projectRoot, provider.root, modules, options),
|
|
1356
1359
|
languageSupport: await inspectLanguageSupport(projectRoot, modules)
|
|
@@ -1358,8 +1361,8 @@ async function compatibilityStatus(options = {}) {
|
|
|
1358
1361
|
}
|
|
1359
1362
|
async function setupCompatibility(options = {}) {
|
|
1360
1363
|
const status = await compatibilityStatus(options);
|
|
1361
|
-
const
|
|
1362
|
-
if (
|
|
1364
|
+
const refused = status.slots.filter((slot) => slot.state === "collision" && slot.collision !== "direct-dependency");
|
|
1365
|
+
if (refused.length > 0) throw new Error(`refusing to replace unowned package slot(s): ${refused.map((slot) => slot.name).join(", ")}. Installing on top of the existing node_modules does not free the slot, so run rm -rf node_modules, install again, and run ${PROVIDER} setup again`);
|
|
1363
1366
|
const modules = join(status.projectRoot, "node_modules");
|
|
1364
1367
|
if (!await exists(modules)) throw new Error(`node_modules is missing under ${status.projectRoot}; install dependencies first`);
|
|
1365
1368
|
let tsconfigWrite = null;
|
|
@@ -1407,7 +1410,7 @@ async function setupCompatibility(options = {}) {
|
|
|
1407
1410
|
languageSupport,
|
|
1408
1411
|
...tsconfigWrite ? { tsconfigWrite } : {},
|
|
1409
1412
|
changed,
|
|
1410
|
-
unchanged: [...status.slots.filter((slot) => slot.state === "active").map((slot) => slot.name), ...editorWritten ? [] : [status.editorSlot.name]]
|
|
1413
|
+
unchanged: [...status.slots.filter((slot) => slot.state === "active" || slot.state === "collision").map((slot) => slot.name), ...editorWritten ? [] : [status.editorSlot.name]]
|
|
1411
1414
|
};
|
|
1412
1415
|
}
|
|
1413
1416
|
async function removeCompatibility(options = {}) {
|
|
@@ -1466,6 +1469,20 @@ async function removeCompatibility(options = {}) {
|
|
|
1466
1469
|
removed
|
|
1467
1470
|
};
|
|
1468
1471
|
}
|
|
1472
|
+
/**
|
|
1473
|
+
* What a package-slot collision means for the reader, by kind. Only the direct
|
|
1474
|
+
* dependency gets prose in the report, because it is the one a reader can act on
|
|
1475
|
+
* two different ways and the one that used to be a silent failure: the command
|
|
1476
|
+
* names stay with their package, so the drop-in `oxlint`/`oxfmt` skip `.tsrx`
|
|
1477
|
+
* with nothing on the command line saying so under pnpm.
|
|
1478
|
+
*/
|
|
1479
|
+
const SLOT_COLLISION_EXPLANATION = Object.freeze({ "direct-dependency": (slot) => {
|
|
1480
|
+
const version = slot.occupant?.version ? ` ${slot.occupant.version}` : "";
|
|
1481
|
+
if (!slot.binary) return `${slot.name}${version} is declared in your package.json, so that slot is yours and was left alone. Import @tsrx/oxc/parser for .tsrx.`;
|
|
1482
|
+
const leaf = slot.name === "oxlint" ? "oxc-tsrx-lint" : "oxc-tsrx-fmt";
|
|
1483
|
+
const editor = slot.name === "oxlint" ? ` The editor is wired separately: with "${EDITOR_SLOT.key}" written, the official OXC extension serves .tsrx through this package and keeps ordinary files on your own oxlint.` : " The editor is unaffected: .tsrx formatting rides on the oxlint --lsp connection.";
|
|
1484
|
+
return `${slot.name}${version} is declared in your package.json, so the ${slot.binary} command belongs to it: on the command line it runs exactly as it did before ${PROVIDER} was installed, and it does not read .tsrx files. Run ${leaf} for .tsrx, or remove the direct ${slot.name} dependency and let this package serve both, at the ${slot.name} version this package bundles rather than the one you pinned.${editor}`;
|
|
1485
|
+
} });
|
|
1469
1486
|
const EDITOR_SLOT_EXPLANATION = Object.freeze({
|
|
1470
1487
|
active: (slot, projectRoot) => `${toPosix(relative(projectRoot, slot.path))} carries "${slot.key}": "${slot.value}". This is the one file setup writes outside node_modules; it merges that single key and never edits package.json or tsconfig.json.`,
|
|
1471
1488
|
stale: (slot, projectRoot) => `${toPosix(relative(projectRoot, slot.path))} carries a "${slot.key}" this package wrote that no longer resolves here; setup refreshes it to "${slot.value}".`,
|
|
@@ -1585,6 +1602,12 @@ function formatCompatibilityReport(result) {
|
|
|
1585
1602
|
const gutter = ` ${`${name}:`.padEnd(nameWidth + 1)} `;
|
|
1586
1603
|
lines.push(`${gutter}${paint(label, SLOT_STATE_STYLE[state] ?? "cyan", color)}`);
|
|
1587
1604
|
}
|
|
1605
|
+
for (const slot of result.slots) {
|
|
1606
|
+
const explain = slot.state === "collision" ? SLOT_COLLISION_EXPLANATION[slot.collision] : null;
|
|
1607
|
+
if (!explain) continue;
|
|
1608
|
+
lines.push("");
|
|
1609
|
+
for (const line of wrapReportText(explain(slot), " ", " ", width)) lines.push(paint(line, "dim", color));
|
|
1610
|
+
}
|
|
1588
1611
|
if (editor) {
|
|
1589
1612
|
const explain = EDITOR_SLOT_EXPLANATION[editor.state];
|
|
1590
1613
|
if (explain) {
|
package/dist/format-cli.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { resolvePackageBinary } from "./package-binary.js";
|
|
2
1
|
import { runCaptured, runPassthrough } from "./process.js";
|
|
3
|
-
import {
|
|
2
|
+
import { resolvePackageBinary } from "./package-binary.js";
|
|
3
|
+
import { argumentValue, canonicalToolEnvironment, discoverTsrxFiles, isViteConfigPath, pathArguments, prepareVitePlusConfig, removeExplicitTsrx, replaceConfigArgument, resolveNativeCommand } from "./runtime.js";
|
|
4
4
|
import { VALUE_OPTIONS, parseOxfmtInvocation, parseOxfmtOption } from "./format-invocation.js";
|
|
5
5
|
import { isAbsolute, relative } from "node:path";
|
|
6
6
|
import { readFileSync } from "node:fs";
|
|
@@ -231,7 +231,7 @@ async function runCli(args, options = {}) {
|
|
|
231
231
|
}
|
|
232
232
|
}
|
|
233
233
|
const positions = invocation.positionals;
|
|
234
|
-
const files = await discoverTsrxFiles(positions, cwd);
|
|
234
|
+
const files = await discoverTsrxFiles(positions, cwd, "format");
|
|
235
235
|
if (files.length > 0 || hasTsrxPositional(positions)) {
|
|
236
236
|
const unknown = unknownCanonicalOption(args);
|
|
237
237
|
if (unknown !== null) {
|
|
@@ -242,13 +242,15 @@ async function runCli(args, options = {}) {
|
|
|
242
242
|
const explicitConfig = argumentValue(args, /* @__PURE__ */ new Set(["-c", "--config"]));
|
|
243
243
|
const bridgeViteConfig = explicitConfig === null || isViteConfigPath(explicitConfig);
|
|
244
244
|
const viteConfig = files.length > 0 && bridgeViteConfig ? await prepareVitePlusConfig("fmt", cwd, isViteConfigPath(explicitConfig) ? explicitConfig : null) : null;
|
|
245
|
+
let nativePaths = null;
|
|
245
246
|
try {
|
|
246
247
|
const stripped = removeExplicitTsrx(args, VALUE_OPTIONS);
|
|
247
248
|
const shouldRunUpstream = !stripped.hadPositionals || stripped.remainingPositionals > 0;
|
|
248
249
|
const upstream = resolvePackageBinary("oxfmt-current", "oxfmt", import.meta.url);
|
|
249
250
|
const useMaterializedUpstreamConfig = Boolean(viteConfig && !viteConfig.requiresAuthoredBase);
|
|
250
251
|
const upstreamArgs = useMaterializedUpstreamConfig ? replaceConfigArgument(stripped.args, viteConfig.path) : stripped.args;
|
|
251
|
-
|
|
252
|
+
nativePaths = files.length > 0 ? await pathArguments(withCwdRelativePaths(files, cwd)) : null;
|
|
253
|
+
const nativeArgs = nativePaths ? nativeArguments(args, nativePaths.args, viteConfig) : null;
|
|
252
254
|
const nativeCommand = nativeArgs ? resolveNativeCommand("format", nativeArgs) : null;
|
|
253
255
|
const [upstreamResult, nativeResult] = await Promise.all([shouldRunUpstream ? runCaptured(process.execPath, [upstream, ...upstreamArgs], {
|
|
254
256
|
cwd,
|
|
@@ -269,6 +271,7 @@ async function runCli(args, options = {}) {
|
|
|
269
271
|
process.stderr.write(attributeNativeErrors(mergeFormatStderr(upstreamResult, nativeResult, stdout)));
|
|
270
272
|
return Math.max(upstreamResult.status, nativeResult.status);
|
|
271
273
|
} finally {
|
|
274
|
+
await nativePaths?.cleanup();
|
|
272
275
|
await viteConfig?.cleanup();
|
|
273
276
|
}
|
|
274
277
|
}
|
package/dist/lint-cli.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { resolvePackageBinary } from "./package-binary.js";
|
|
2
1
|
import { runCaptured, runPassthrough } from "./process.js";
|
|
3
|
-
import {
|
|
2
|
+
import { resolvePackageBinary } from "./package-binary.js";
|
|
3
|
+
import { argumentValue, canonicalToolEnvironment, discoverTsrxFiles, ensureSupportedOutput, isViteConfigPath, pathArguments, prepareVitePlusConfig, removeExplicitTsrx, replaceConfigArgument, resolveNativeCommand } from "./runtime.js";
|
|
4
4
|
import { DELEGATE_ONLY, VALUE_OPTIONS, parseOxlintInvocation, parseOxlintOption, withOxlintOutputFormat } from "./lint-invocation.js";
|
|
5
5
|
import { jsPluginUnmappedNote, preparePluginLane } from "./lint-js-plugins.js";
|
|
6
6
|
import { readFile } from "node:fs/promises";
|
|
@@ -379,6 +379,7 @@ async function runCli(args, options = {}) {
|
|
|
379
379
|
}
|
|
380
380
|
const pluginLaneActive = pluginLane?.status === "active";
|
|
381
381
|
if (pluginLaneActive && !args.includes("--silent")) process.stderr.write(`${pluginLane.notice}\n`);
|
|
382
|
+
let nativePaths = null;
|
|
382
383
|
try {
|
|
383
384
|
const stripped = removeExplicitTsrx(args, VALUE_OPTIONS);
|
|
384
385
|
const shouldRunUpstream = !stripped.hadPositionals || stripped.remainingPositionals > 0;
|
|
@@ -396,7 +397,8 @@ async function runCli(args, options = {}) {
|
|
|
396
397
|
let upstreamArgs = withOxlintOutputFormat(stripped.args, "json");
|
|
397
398
|
if (useMaterializedUpstreamConfig) upstreamArgs = replaceConfigArgument(upstreamArgs, viteConfig.path);
|
|
398
399
|
const nativeResolvedConfig = pluginLane?.nativeConfig ?? viteConfig;
|
|
399
|
-
|
|
400
|
+
nativePaths = files.length > 0 ? await pathArguments(files) : null;
|
|
401
|
+
const nativeArgs = nativePaths ? nativeArguments(args, nativePaths.args, nativeResolvedConfig) : null;
|
|
400
402
|
const nativeCommand = nativeArgs ? resolveNativeCommand("lint", nativeArgs) : null;
|
|
401
403
|
let upstreamPromise;
|
|
402
404
|
if (!shouldRunUpstream) upstreamPromise = Promise.resolve({
|
|
@@ -480,6 +482,7 @@ async function runCli(args, options = {}) {
|
|
|
480
482
|
return Math.max(upstreamResult.status, nativeResult.status, denyWarnings && warnings > 0 ? 1 : 0, exceedsMaximum ? 1 : 0, pluginErrors ? 1 : 0);
|
|
481
483
|
} finally {
|
|
482
484
|
await pluginLane?.cleanup?.();
|
|
485
|
+
await nativePaths?.cleanup();
|
|
483
486
|
await viteConfig?.cleanup();
|
|
484
487
|
}
|
|
485
488
|
}
|
package/dist/lint-js-plugins.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { resolvePackageBinary } from "./package-binary.js";
|
|
2
1
|
import { runCaptured } from "./process.js";
|
|
3
|
-
import {
|
|
2
|
+
import { resolvePackageBinary } from "./package-binary.js";
|
|
3
|
+
import { pathArguments, resolveNativeCommand } from "./runtime.js";
|
|
4
4
|
import { createRequire } from "node:module";
|
|
5
5
|
import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
6
6
|
import { dirname, isAbsolute, join, parse, relative, resolve, sep } from "node:path";
|
|
@@ -434,8 +434,14 @@ async function preparePluginLane({ cwd, files, viteConfig, explicitConfig }) {
|
|
|
434
434
|
async function emitProjections(cwd, files, nativeConfig) {
|
|
435
435
|
const args = ["--emit-plugin-projection"];
|
|
436
436
|
if (nativeConfig) args.push("--config", nativeConfig.path, "--config-base", nativeConfig.base);
|
|
437
|
-
const
|
|
438
|
-
|
|
437
|
+
const paths = await pathArguments(files);
|
|
438
|
+
let result;
|
|
439
|
+
try {
|
|
440
|
+
const command = resolveNativeCommand("lint", [...args, ...paths.args]);
|
|
441
|
+
result = await runCaptured(command.executable, command.args, { cwd });
|
|
442
|
+
} finally {
|
|
443
|
+
await paths.cleanup();
|
|
444
|
+
}
|
|
439
445
|
if (result.status !== 0) throw new Error(`the native TSRX projection needed for JS plugins failed:\n${result.stderr || result.stdout}`);
|
|
440
446
|
let parsed;
|
|
441
447
|
try {
|
package/dist/lint-prestart.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { resolvePackageBinary } from "./package-binary.js";
|
|
2
1
|
import { runCaptured } from "./process.js";
|
|
2
|
+
import { resolvePackageBinary } from "./package-binary.js";
|
|
3
3
|
//#region src/lint-prestart.ts
|
|
4
4
|
function startCanonicalOxlint(args, cwd = process.cwd(), env = process.env) {
|
|
5
5
|
const binary = resolvePackageBinary("oxlint-current", "oxlint", import.meta.url);
|
|
@@ -566,6 +566,11 @@ async function runOxlintLspMultiplexer(args, options = {}) {
|
|
|
566
566
|
const spawnProcess = options.spawn ?? spawn;
|
|
567
567
|
const clientError = options.clientError ?? process.stderr;
|
|
568
568
|
const cwd = options.cwd ?? process.cwd();
|
|
569
|
+
const canonicalBinary = options.canonical?.binPath ?? resolveCanonicalOxlintBinary();
|
|
570
|
+
if (options.canonical?.binPath) {
|
|
571
|
+
const version = options.canonical.version ? ` ${options.canonical.version}` : "";
|
|
572
|
+
clientError.write(`oxlint (oxc-tsrx): ordinary documents are served by the official oxlint${version} this project declares (${options.canonical.binPath}); .tsrx documents by the native server\n`);
|
|
573
|
+
}
|
|
569
574
|
const childOptions = {
|
|
570
575
|
cwd,
|
|
571
576
|
env: {
|
|
@@ -584,7 +589,7 @@ async function runOxlintLspMultiplexer(args, options = {}) {
|
|
|
584
589
|
cwd: index.root
|
|
585
590
|
} : childOptions;
|
|
586
591
|
const providers = providerLspSessions(index, spawnProcess, providerOptions);
|
|
587
|
-
const canonical = spawnProcess(process.execPath, [
|
|
592
|
+
const canonical = spawnProcess(process.execPath, [canonicalBinary, ...args], childOptions);
|
|
588
593
|
const multiplexer = createOxlintLspMultiplexer({
|
|
589
594
|
providerRoot: providerOptions === childOptions ? null : index.root,
|
|
590
595
|
clientInput: options.clientInput ?? process.stdin,
|
package/dist/parser.js
CHANGED
|
@@ -3,7 +3,7 @@ import { isTsrxBinaryProgram, parseTrustedTsrxProgram, parseTsrxProgram } from "
|
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
//#region src/parser.ts
|
|
5
5
|
const require = createRequire(import.meta.url);
|
|
6
|
-
const PACKAGE_VERSION = "0.
|
|
6
|
+
const PACKAGE_VERSION = "0.11.0";
|
|
7
7
|
const parserManifest = Object.freeze({
|
|
8
8
|
name: "@tsrx/oxc",
|
|
9
9
|
version: PACKAGE_VERSION,
|
package/dist/runtime.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { nativePackageName, nativeTargetForHost } from "./native-targets.js";
|
|
2
|
-
import { resolvePackageBinary } from "./package-binary.js";
|
|
3
2
|
import { runCaptured, runPassthrough } from "./process.js";
|
|
3
|
+
import { resolvePackageBinary } from "./package-binary.js";
|
|
4
4
|
import { createRequire } from "node:module";
|
|
5
5
|
import { mkdtemp, readFile, rm, stat, writeFile } from "node:fs/promises";
|
|
6
6
|
import { dirname, isAbsolute, join, parse, resolve, sep } from "node:path";
|
|
@@ -254,7 +254,7 @@ function slash(path) {
|
|
|
254
254
|
function hasMagic(path) {
|
|
255
255
|
return /[*?[\]{}()!]/u.test(path);
|
|
256
256
|
}
|
|
257
|
-
async function classifyPattern(raw, cwd, positives, patterns) {
|
|
257
|
+
async function classifyPattern(raw, cwd, positives, patterns, directories) {
|
|
258
258
|
const negative = raw.startsWith("!");
|
|
259
259
|
const value = negative ? raw.slice(1) : raw;
|
|
260
260
|
const absolute = isAbsolute(value) ? value : resolve(cwd, value);
|
|
@@ -266,31 +266,93 @@ async function classifyPattern(raw, cwd, positives, patterns) {
|
|
|
266
266
|
return;
|
|
267
267
|
}
|
|
268
268
|
if (metadata.isDirectory()) {
|
|
269
|
-
patterns.push(
|
|
269
|
+
if (negative) patterns.push(`!${slash(join(absolute, "**/*.tsrx"))}`);
|
|
270
|
+
else directories.push(absolute);
|
|
270
271
|
return;
|
|
271
272
|
}
|
|
272
273
|
} catch {}
|
|
273
274
|
patterns.push(`${negative ? "!" : ""}${slash(value)}`);
|
|
274
275
|
}
|
|
275
|
-
async function classifyPatterns(inputs, cwd, positives, patterns) {
|
|
276
|
+
async function classifyPatterns(inputs, cwd, positives, patterns, directories) {
|
|
276
277
|
const classified = await Promise.all(inputs.map(async (input) => {
|
|
277
278
|
const entryPositives = /* @__PURE__ */ new Set();
|
|
278
279
|
const entryPatterns = [];
|
|
279
|
-
|
|
280
|
+
const entryDirectories = [];
|
|
281
|
+
await classifyPattern(input, cwd, entryPositives, entryPatterns, entryDirectories);
|
|
280
282
|
return {
|
|
281
283
|
entryPositives,
|
|
282
|
-
entryPatterns
|
|
284
|
+
entryPatterns,
|
|
285
|
+
entryDirectories
|
|
283
286
|
};
|
|
284
287
|
}));
|
|
285
|
-
for (const { entryPositives, entryPatterns } of classified) {
|
|
288
|
+
for (const { entryPositives, entryPatterns, entryDirectories } of classified) {
|
|
286
289
|
for (const positive of entryPositives) positives.add(positive);
|
|
287
290
|
for (const pattern of entryPatterns) patterns.push(pattern);
|
|
291
|
+
for (const directory of entryDirectories) directories.push(directory);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* A file list as arguments the native leaf accepts. A short list travels on the
|
|
296
|
+
* command line as before. A long one is written to a temporary file and named
|
|
297
|
+
* with `--paths-file`, because a host's argument limit is reached by a real
|
|
298
|
+
* repository (E2BIG past about a megabyte on macOS and Linux, a 32 KiB command
|
|
299
|
+
* line on Windows, which a few hundred paths fill). The caller disposes of it.
|
|
300
|
+
*/
|
|
301
|
+
async function pathArguments(files, budget = 24e3) {
|
|
302
|
+
if (files.reduce((total, file) => total + Buffer.byteLength(file) + 1, 0) <= budget) return {
|
|
303
|
+
args: [...files],
|
|
304
|
+
cleanup: async () => {}
|
|
305
|
+
};
|
|
306
|
+
const directory = await mkdtemp(join(tmpdir(), "oxc-tsrx-paths-"));
|
|
307
|
+
const path = join(directory, "paths.txt");
|
|
308
|
+
await writeFile(path, `${files.join("\n")}\n`);
|
|
309
|
+
return {
|
|
310
|
+
args: ["--paths-file", path],
|
|
311
|
+
cleanup: () => rm(directory, {
|
|
312
|
+
recursive: true,
|
|
313
|
+
force: true
|
|
314
|
+
})
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Directories are walked by the native leaf, on the same `ignore` crate
|
|
319
|
+
* canonical Oxlint walks with, so `.gitignore` and `.ignore` files are honoured
|
|
320
|
+
* and `node_modules` and `.git` are never entered. Without that, a monorepo
|
|
321
|
+
* that keeps gitignored copies of itself handed every one of them to the leaf.
|
|
322
|
+
* Returns null when no native package is installed, and the caller falls back
|
|
323
|
+
* to the plain glob so the run can still reach the error that names the missing
|
|
324
|
+
* package.
|
|
325
|
+
*/
|
|
326
|
+
async function walkWithNativeLeaf(directories, cwd, kind) {
|
|
327
|
+
let command;
|
|
328
|
+
try {
|
|
329
|
+
command = resolveNativeCommand(kind, ["--discover"]);
|
|
330
|
+
} catch {
|
|
331
|
+
return null;
|
|
332
|
+
}
|
|
333
|
+
const paths = await pathArguments(directories);
|
|
334
|
+
try {
|
|
335
|
+
const result = await runCaptured(command.executable, [...command.args, ...paths.args], { cwd });
|
|
336
|
+
if (result.status !== 0) throw new Error(`.tsrx discovery failed: ${(result.stderr || result.stdout).trim()}`);
|
|
337
|
+
const report = JSON.parse(result.stdout);
|
|
338
|
+
if (!Array.isArray(report?.files)) throw new Error(".tsrx discovery returned no file list");
|
|
339
|
+
return report.files.map((file) => resolve(file));
|
|
340
|
+
} finally {
|
|
341
|
+
await paths.cleanup();
|
|
288
342
|
}
|
|
289
343
|
}
|
|
290
|
-
|
|
344
|
+
/**
|
|
345
|
+
* `kind` names the leaf that walks directories, `lint` or `format`: both
|
|
346
|
+
* answer `--discover` identically, and each command resolves its own binary.
|
|
347
|
+
*/
|
|
348
|
+
async function discoverTsrxFiles(positionals, cwd = process.cwd(), kind = "lint") {
|
|
291
349
|
const positives = /* @__PURE__ */ new Set();
|
|
292
350
|
const patterns = [];
|
|
293
|
-
|
|
351
|
+
const directories = [];
|
|
352
|
+
await classifyPatterns(positionals.length === 0 ? ["."] : positionals, cwd, positives, patterns, directories);
|
|
353
|
+
const walked = directories.length > 0 && !patterns.some((pattern) => pattern.startsWith("!")) ? await walkWithNativeLeaf(directories, cwd, kind) : null;
|
|
354
|
+
if (walked === null) for (const directory of directories) patterns.push(slash(join(directory, "**/*.tsrx")));
|
|
355
|
+
else for (const file of walked) positives.add(file);
|
|
294
356
|
if (patterns.length > 0) {
|
|
295
357
|
const { glob } = await import("tinyglobby");
|
|
296
358
|
const matches = await glob(patterns, {
|
|
@@ -320,4 +382,4 @@ function ensureSupportedOutput(format, files) {
|
|
|
320
382
|
if (files.length > 0 && format !== "default" && format !== "json") throw new Error(`OXC for TSRX currently combines default and json lint output; ${format} is unavailable for mixed .tsrx runs`);
|
|
321
383
|
}
|
|
322
384
|
//#endregion
|
|
323
|
-
export { argumentValue, canonicalToolEnvironment, discoverTsrxFiles, ensureSupportedOutput, isViteConfigPath, platformPackage, prepareVitePlusConfig, removeExplicitTsrx, replaceConfigArgument, resolveNativeBinary, resolveNativeCommand, resolvePackageBinary, runCaptured, runPassthrough };
|
|
385
|
+
export { argumentValue, canonicalToolEnvironment, discoverTsrxFiles, ensureSupportedOutput, isViteConfigPath, pathArguments, platformPackage, prepareVitePlusConfig, removeExplicitTsrx, replaceConfigArgument, resolveNativeBinary, resolveNativeCommand, resolvePackageBinary, runCaptured, runPassthrough };
|
|
@@ -949,7 +949,7 @@ function materializeCompatibilityProgram(program, source, filename, loose, posit
|
|
|
949
949
|
if (value.pending != null && value.pendingKeyword == null) value.pendingKeyword = keywordSpan(source, "@pending", value.block?.end ?? value.start, value.pending?.end ?? value.end, positionAt);
|
|
950
950
|
if (value.handler != null && value.handlerKeyword == null) value.handlerKeyword = keywordSpan(source, "@catch", value.pending?.end ?? value.block?.end ?? value.start, value.handler?.end ?? value.end, positionAt);
|
|
951
951
|
}
|
|
952
|
-
if (value.type === "JSXStyleElement" && typeof value.css === "string") {
|
|
952
|
+
if (value.type === "JSXStyleElement" && typeof value.css === "string" && value.openingElement?.selfClosing !== true) {
|
|
953
953
|
const style = parse_style(value.css, {
|
|
954
954
|
filename,
|
|
955
955
|
line: value.openingElement?.loc?.start?.line ?? value.loc?.start?.line ?? 1,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tsrx/oxc",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "The official OXC integration for TSRX — Rust-native Oxlint and Oxfmt for .tsrx, JavaScript, TypeScript, and JSX",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"formatter",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"tsrx",
|
|
15
15
|
"typescript"
|
|
16
16
|
],
|
|
17
|
-
"homepage": "https://
|
|
17
|
+
"homepage": "https://oxc.tsrx.dev",
|
|
18
18
|
"bugs": {
|
|
19
19
|
"url": "https://github.com/tsrx-org/oxc/issues"
|
|
20
20
|
},
|
|
@@ -128,14 +128,14 @@
|
|
|
128
128
|
"tinyglobby": "0.2.17"
|
|
129
129
|
},
|
|
130
130
|
"optionalDependencies": {
|
|
131
|
-
"@tsrx/oxc-darwin-arm64": "0.
|
|
132
|
-
"@tsrx/oxc-darwin-x64": "0.
|
|
133
|
-
"@tsrx/oxc-linux-arm64-gnu": "0.
|
|
134
|
-
"@tsrx/oxc-linux-arm64-musl": "0.
|
|
135
|
-
"@tsrx/oxc-linux-x64-gnu": "0.
|
|
136
|
-
"@tsrx/oxc-linux-x64-musl": "0.
|
|
137
|
-
"@tsrx/oxc-win32-arm64-msvc": "0.
|
|
138
|
-
"@tsrx/oxc-win32-x64-msvc": "0.
|
|
131
|
+
"@tsrx/oxc-darwin-arm64": "0.11.0",
|
|
132
|
+
"@tsrx/oxc-darwin-x64": "0.11.0",
|
|
133
|
+
"@tsrx/oxc-linux-arm64-gnu": "0.11.0",
|
|
134
|
+
"@tsrx/oxc-linux-arm64-musl": "0.11.0",
|
|
135
|
+
"@tsrx/oxc-linux-x64-gnu": "0.11.0",
|
|
136
|
+
"@tsrx/oxc-linux-x64-musl": "0.11.0",
|
|
137
|
+
"@tsrx/oxc-win32-arm64-msvc": "0.11.0",
|
|
138
|
+
"@tsrx/oxc-win32-x64-msvc": "0.11.0"
|
|
139
139
|
},
|
|
140
140
|
"engines": {
|
|
141
141
|
"node": "^20.19.0 || >=22.12.0"
|