@tsrx/oxc 0.9.0 → 0.10.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 CHANGED
@@ -1,5 +1,5 @@
1
1
  <p align="center">
2
- <a href="https://compiled.run/oxc-tsrx">
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://compiled.run/oxc-tsrx) &nbsp;·&nbsp; [**Getting started**](https://compiled.run/oxc-tsrx/guide/getting-started) &nbsp;·&nbsp; [**Playground**](https://compiled.run/oxc-tsrx/playground)
29
+ [**Docs**](https://oxc.tsrx.dev) &nbsp;·&nbsp; [**Getting started**](https://oxc.tsrx.dev/guide/getting-started) &nbsp;·&nbsp; [**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://compiled.run/oxc-tsrx/guide/getting-started#try-it-with-vite).
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://compiled.run/oxc-tsrx/reference/platform-support).
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://compiled.run/oxc-tsrx/guide/tsrx-syntax) shows each one). Anything
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://compiled.run/oxc-tsrx/integrations/editor).
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://compiled.run/oxc-tsrx/guide/parsing) has the shape it returns.
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://compiled.run/oxc-tsrx/integrations/custom-js-plugins).
124
+ plugins](https://oxc.tsrx.dev/integrations/custom-js-plugins).
125
125
 
126
126
  ## Documentation
127
127
 
128
- - [Getting started](https://compiled.run/oxc-tsrx/guide/getting-started): install, first file, first run.
129
- - [Configuration](https://compiled.run/oxc-tsrx/integrations/configuration): every supported setting.
130
- - [CLI reference](https://compiled.run/oxc-tsrx/reference/cli): commands, flags, and exit codes.
131
- - [Platform support](https://compiled.run/oxc-tsrx/reference/platform-support): which of the eight published platforms are tested on every change.
132
- - [Limitations](https://compiled.run/oxc-tsrx/reference/limitations): what is not claimed yet.
133
- - [Provider protocol](https://compiled.run/oxc-tsrx/architecture/provider-protocol): the `oxc.provider` block, what reads it today, and how a plain install reaches released hosts.
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
@@ -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
@@ -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 (decision.owner === "project") {
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" && !directlySelected(projectManifest, slot.name)) {
566
- if (await exists(backupPath(modules, slot))) return {
567
- slot,
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 collisions = status.slots.filter((slot) => slot.state === "collision");
1362
- if (collisions.length > 0) throw new Error(`refusing to replace unowned package slot(s): ${collisions.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`);
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) {
@@ -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, [resolveCanonicalOxlintBinary(), ...args], childOptions);
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.9.0";
6
+ const PACKAGE_VERSION = "0.10.0";
7
7
  const parserManifest = Object.freeze({
8
8
  name: "@tsrx/oxc",
9
9
  version: PACKAGE_VERSION,
@@ -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.9.0",
3
+ "version": "0.10.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://compiled.run/oxc-tsrx",
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.9.0",
132
- "@tsrx/oxc-darwin-x64": "0.9.0",
133
- "@tsrx/oxc-linux-arm64-gnu": "0.9.0",
134
- "@tsrx/oxc-linux-arm64-musl": "0.9.0",
135
- "@tsrx/oxc-linux-x64-gnu": "0.9.0",
136
- "@tsrx/oxc-linux-x64-musl": "0.9.0",
137
- "@tsrx/oxc-win32-arm64-msvc": "0.9.0",
138
- "@tsrx/oxc-win32-x64-msvc": "0.9.0"
131
+ "@tsrx/oxc-darwin-arm64": "0.10.0",
132
+ "@tsrx/oxc-darwin-x64": "0.10.0",
133
+ "@tsrx/oxc-linux-arm64-gnu": "0.10.0",
134
+ "@tsrx/oxc-linux-arm64-musl": "0.10.0",
135
+ "@tsrx/oxc-linux-x64-gnu": "0.10.0",
136
+ "@tsrx/oxc-linux-x64-musl": "0.10.0",
137
+ "@tsrx/oxc-win32-arm64-msvc": "0.10.0",
138
+ "@tsrx/oxc-win32-x64-msvc": "0.10.0"
139
139
  },
140
140
  "engines": {
141
141
  "node": "^20.19.0 || >=22.12.0"