Commit Graph

12 Commits

Author SHA1 Message Date
overlookmotel c96a69fff8 refactor(packages/codegen): add writeWithMapNamedJSXNoLast (#25980)
Writing mappings for JSX identifiers requires different logic than for normal `Identifiers`.

- Add specialized function `writeWithMapNamedJSXNoLast` for writing `JSXIdentifier`s.
- Move the logic for getting original name of a `JSXIdentifier` into a separate function.
- Branch on an `isJSXIdentifier` param passed in to `markMapNamed`, to decide which function to use to get original identifier name.

The main motivation is to avoid having to pass `node` into `originalNameFromSource`, where reading `node.type` is a polymorphic access (because `node` could be an `Identifier`, an `PrivateIdentifier` or `JSXIdentifier`). This is part of a broader move to remove the reliance of all `write` and `markMap` functions on receiving polymorphic/megamorphic `node` params (later PRs in this stack).
2026-08-24 09:05:20 +00:00
overlookmotel 6c5ad1b723 fix(codegen): only add mapping names for PrivateIdentifiers which have changed (#25958)
A `PrivateIdentifier`'s span includes the leading `#`, but the `name` the AST holds does not.

The source map name was read from the whole span and then compared to `name` from AST, so the check which omits the mapping's `name` when it wasn't renamed compared `#foo` against `foo`. It never matched, so a named mapping was created for _every_ private identifier, renamed or not.

This PR fixes that in both Rust `oxc_codegen` and JS `oxc-codegen`, by comparing correctly including the `#`.

This also makes mapping names correct when a normal `Identifier` has been replaced with a `PrivateIdentifier` (`this.foo` -> `this.#foo)`, or vice-versa (`this.#foo` -> `this.foo`). New tests cover both directions.
2026-08-21 14:08:12 +00:00
camc314 526c2b3fc4 fix(codegen): remove deprecated test APIs (#25889)
## Summary
- replace deprecated Vitest `toThrowError` assertions with `toThrow`
- enable `typescript/no-deprecated` for `packages/codegen`
2026-08-18 16:41:35 +00:00
camc314 fc4e18edf9 refactor(codegen): use it.for for generated tests (#25874)
## Summary
- replace manual test-registration loops with Vitest parameterized APIs
- preserve per-fixture skip behavior with `.for` and parameterized suite options
- retain imperative loops that exercise fixture contents rather than register tests
2026-08-18 15:38:24 +00:00
camc314 0fbcf6420b fix(codegen): validate sourcemap options (#25860)
## Summary
- require a string sourceText when sourcemaps are enabled
- reject non-string supplied sourceFilename values before map generation
- cover invalid and valid source-map option combinations
2026-08-18 13:57:46 +00:00
overlookmotel 6a7eb602ed feat(packages/codegen): alter capitalization of sourceFilename option (#25854)
Follow-on after [#25585](/github/pr-or-issue/oxc-project/oxc/25585).

Rename `options.sourceFileName` to `sourceFilename`. We use both capitalizations internal Rust code, but always `filename` in external JS APIs (Oxlint plugins etc).

NOTE: this is not marked as a breaking change as it was merged before the feature was released.
2026-08-18 13:16:15 +00:00
overlookmotel 578a81105d refactor(packages/codegen): printSync return consistent object shape (#25853)
Follow-on after #25585.

Nit. Make `printSync` return an object with consistent shape. With `sourcemap: false` it now returns `{ code: string, map: null }` instead of just `{ code: string }`.

Co-authored-by: Cameron <cameron.clark@hey.com>
2026-08-18 13:04:06 +00:00
overlookmotel 58f7ab9c1d docs(packages/codegen): reformat docs and comments (#25848)
Follow-on after #25585.

Pure style change. I hate the way LLMs put line breaks in weird places like after "the" - it makes comments hard to read. And what's with all the colons and semicolons?

No substantive changes, just reformatting comments and docs to avoid this unreadable formatting.
2026-08-18 12:24:56 +00:00
camc314 300763f328 feat(packages/codegen): add full source map support (#25585)
## Summary

Align `oxc-codegen` source maps with Rust `oxc_codegen`, using the Rust implementation as the conformance oracle.

This PR:

- changes `printSync` to return `{ code, map? }`;
- returns a standard Source Map v3 object;
- exposes Rust’s generated source map through `codegen_conformance`;
- compares the complete JS and Rust source maps;
- supports Oxc `start`/`end` offsets and TS-ESTree `range` offsets;
- uses `sourceText` to convert UTF-16 offsets and recover original identifier spellings;
- validates both maps and no-maps builds in both `preserveParens` modes;
- aligns mapping placement across JavaScript, TypeScript, JSX, templates, imports, destructuring, delimiters, and postfix chains;
- handles astral characters and all ECMAScript line terminators.
`loc`-only ASTs are not supported for source maps.

## API change

```ts
const { code, map } = printSync(program, {
  sourcemap: true,
  sourceFileName: "input.js",
  sourceText,
});
```

`map` is a Source Map v3 object:

```ts
interface SourceMap {
  version: 3;
  mappings: string;
  names: string[];
  sources: string[];
  sourcesContent?: string[];
}
```

## Implementation

Mapped writes collect only:

- the generated UTF-16 offset;
- the original source offset;
- the optional original identifier name.

After printing, the generator builds line tables for the generated output and `sourceText`, converts the offsets to line/column positions, suppresses duplicate source positions, and encodes the result as base64 VLQ.

The no-maps builds continue to remove mapping-specific writes at build time.

## Validation flow

```mermaid
flowchart TB
    fixture["Fixture<br/>filename + sourceText + parse options"]

    fixture --> rustParse
    fixture --> jsParse

    subgraph rust["Rust oracle"]
        direction TB
        rustParse["Parse with oxc_parser"]
        normalize["Normalize Rust-only AST information"]
        rustPrint["Print with oxc_codegen<br/>source maps enabled"]
        rustResult["Expected result<br/>{ code, map }"]

        rustParse --> normalize
        normalize --> rustPrint
        rustPrint --> rustResult
    end

    subgraph js["JavaScript candidate"]
        direction TB
        jsParse["Parse ESTree with oxc-parser<br/>start/end or range offsets"]

        plainPrint["Print with no-maps build"]
        plainResult["Actual code"]

        mappedPrint["Print with maps-enabled build"]
        deferred["Record generated offsets,<br/>source offsets, and names"]
        encode["Convert UTF-16 offsets to positions<br/>deduplicate and encode VLQ"]
        mappedResult["Actual result<br/>{ code, map }"]

        jsParse --> plainPrint
        plainPrint --> plainResult

        jsParse --> mappedPrint
        mappedPrint --> deferred
        deferred --> encode
        encode --> mappedResult
    end

    rustParse --> parseGate
    jsParse --> parseGate

    rustResult --> codeGate
    plainResult --> codeGate
    mappedResult --> codeGate

    rustResult --> mapGate
    mappedResult --> mapGate

    parseGate["Parse agreement<br/>Rust success requires no JS errors"]
    codeGate["Code equality<br/>both JS builds equal Rust"]
    mapGate["Source Map v3 equality<br/>mappings + names + sources + content"]

    parseGate --> pass["Fixture passes"]
    codeGate --> pass
    mapGate --> pass
```

Each fixture runs with both `preserveParens: false` and `preserveParens: true`.

## AST normalization

Rust’s AST contains information that ESTree cannot represent. The Rust AST is normalized before printing so the comparison measures code generation rather than representation differences.

This includes:

- parenthesized function and arrow-expression flags;
- `import "m"` versus `import {} from "m"`;
- absent versus empty import-attribute clauses;
- `assert` versus `with` attribute keywords;
- the unavailable ESTree span for a complete `with` clause.
2026-08-18 11:41:34 +00:00
camc314 4cc7ea4595 fix(codegen): reject invalid indent options (#25807)
## Summary

 - throw a TypeError when indent is empty, contains characters other than spaces or tabs, or is not a string
 - preserve the existing default tab indentation and unchanged printIndent hot path
 - update API documentation and validation coverage
2026-08-17 14:22:56 +00:00
overlookmotel 365274ea16 feat(packages/codegen)!: printSync return an object (#25720)
`oxc-codegen`'s current API requires passing in a `SourceMapGenerator` as an option to generate source maps. This is currently really slow, and I imagine we'll want to generate source maps internally (perhaps in Rust), and return a source map string from `printSync` instead.

Alter `printSync`'s return type now, to support that without a breaking change later.

- Before: `function printSync(node: ESTree.Node, options?: Options): string`
- After: `function printSync(node: ESTree.Node, options?: Options): { code: string }`

Later on, it can return `{ code: string, map: string }` and that won't break existing users.
2026-08-15 15:53:54 +00:00
overlookmotel a4478e960a feat(codegen): add oxc-codegen package (#25488)
Add `oxc-codegen` package - a printer written in TS.

### Features

- Prints JS, JSX, TS, TSX.
- Matches `oxc_codegen` exactly on all Test262, TypeScript, Acorn-JSX fixtures.
- Very fast.

### Deficiencies

- Pretty-printing only (no compact/minified output).
- No support for printing comments.
- Source map support is semi-there, but largely untested, and poor API (design doc has some ideas on how to improve it).

### This PR adds

- The printer code.
- Conformance testing infra.
- Further tests for edge cases.
- README.
- Design doc - how it differs from `oxc_codegen` and why, what produces the perf.

### This PR doesn't add

- Not integrated into CI or release pipeline.

### Notes

This started as a slop port of `oxc_codegen`. I've iterated on that base, cleaned it up a lot, and at least skimmed through all of it. I think it's in fairly decent shape.

The design doc is I think worth a read. It explains the rationale for the choices, in particular the "1 codebase, 4 builds" approach.

I've put it in a new top-level directory called `packages`. It's not an app, so didn't seem to me to fit alongside `apps/oxlint` and `apps/oxfmt`. It's pure JS, so not like e.g. `napi/parser`. And it felt too heavyweight to put into `npm` dir. But maybe there's a better place for it.

Co-authored-by: Cameron <cameron.clark@hey.com>
2026-08-12 13:41:31 +00:00