Files
oxc-project__oxc/packages/codegen/src-js/print/source_map.ts
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

56 lines
2.0 KiB
TypeScript

// Source map generation.
//
// `write` records the output offset and original position of every mapped node as it goes,
// and this converts those offsets to generated line/column in one pass at the end.
import { debugAssert, typeAssertIs } from "../asserts.ts";
import type { Mapping, MutableMapping, SourceMapGenerator } from "./options.ts";
import type { State } from "../state.ts";
/**
* Convert the offsets recorded during printing into mappings, and hand them to the generator.
*
* Printing records only an output offset per mapped node, which is why this walks the output once
* at the end counting newlines, rather than tracking a line and column throughout.
*
* @param sourceMap - Generator the mappings are added to, which the caller was given in its options
*/
export function emitMappings(state: State, sourceMap: SourceMapGenerator): void {
debugAssert(
state.mapOffsets !== null && state.mapPositions !== null && state.mapNames !== null,
"Source map arrays should exist when a `sourceMap` was given",
);
const { output, mapOffsets, mapPositions, mapNames } = state;
const source = sourceMap.file || sourceMap._file;
// The `generated` and `mapping` objects are reused across `addMapping` calls to avoid generating ephemeral objects
const generated = { line: 1, column: 0 };
const mapping: MutableMapping = { original: null, generated, name: undefined, source };
let line = 1;
let lineStart = 0;
let nextNewline = output.indexOf("\n");
if (nextNewline === -1) nextNewline = Infinity;
const { length } = mapOffsets;
for (let i = 0; i < length; i++) {
const offset = mapOffsets[i];
while (offset > nextNewline) {
line++;
lineStart = nextNewline + 1;
const next = output.indexOf("\n", lineStart);
nextNewline = next === -1 ? Infinity : next;
}
generated.line = line;
generated.column = offset - lineStart;
mapping.original = mapPositions[i];
mapping.name = mapNames[i];
typeAssertIs<Mapping>(mapping);
sourceMap.addMapping(mapping);
}
}