Files
Nehemiya WickramasingheandGitHub 936a3bc8b6 breaking(code-frame): use 0-based columns to match Babel AST locations (#1… (#17849)
fix(code-frame): use 0-based columns to match Babel AST locations (#17316)

`codeFrameColumns` treated the `column` property as 1-based, but Babel
AST locations use 0-based columns. This caused the caret/underline to
be off by one character when passing AST `.loc` objects directly.

- Removed the `- 1` offset in marker spacing calculation
- Removed the `+ 1` in multiline start marker count
- Changed default column sentinel from `0` to `null` so that
  `column: 0` is treated as a valid position
- Updated falsiness checks (`!startColumn`) to null checks
- Removed `+ 1` workarounds in babel-core and babel-traverse callers

This is a breaking change for external consumers of `@babel/code-frame`
who passed 1-based columns manually.
2026-05-11 09:38:53 -04:00

215 lines
5.4 KiB
JavaScript

import { stripVTControlCharacters, styleText } from "node:util";
import { codeFrameColumns } from "../lib/index.js";
const gutter = input => styleText("gray", input, { validateStream: false });
const yellow = input => styleText("yellow", input, { validateStream: false });
const marker = input =>
styleText(["red", "bold"], input, { validateStream: false });
const reset = input => styleText("reset", input, { validateStream: false });
const cyan = input => styleText("cyan", input, { validateStream: false });
function stubColorSupport(supported) {
const original = process.env.FORCE_COLOR;
beforeEach(function () {
process.env.FORCE_COLOR = supported ? "" : "false";
});
afterEach(function () {
process.env.FORCE_COLOR = original;
});
}
describe("highlight", function () {
describe("when colors are supported", () => {
stubColorSupport(true);
test("opts.highlightCode", function () {
const rawLines = "console.log('babel')";
const result = codeFrameColumns(
rawLines,
{ start: { line: 1, column: 8 } },
{ highlightCode: true },
);
const stripped = stripVTControlCharacters(result);
expect(result.length).toBeGreaterThan(stripped.length);
expect(stripped).toEqual(
["> 1 | console.log('babel')", " | ^"].join("\n"),
);
const codeResult = result.match(/console.*?babel/)[0];
const codeStripped = stripVTControlCharacters(codeResult);
expect(codeResult.length).toBeGreaterThan(codeStripped.length);
});
test("opts.highlightCode with multiple columns and lines", function () {
// prettier-ignore
const rawLines = [
"function a(b, c) {",
" return b + c;",
"}"
].join("\n");
const result = codeFrameColumns(
rawLines,
{
start: {
line: 1,
column: 0,
},
end: {
line: 3,
column: 1,
},
},
{
highlightCode: true,
message: "Message about things",
},
);
const stripped = stripVTControlCharacters(result);
expect(result.length).toBeGreaterThan(stripped.length);
expect(stripped).toEqual(
// prettier-ignore
[
"> 1 | function a(b, c) {",
" | ^^^^^^^^^^^^^^^^^^",
"> 2 | return b + c;",
" | ^^^^^^^^^^^^^^^",
"> 3 | }",
" | ^ Message about things",
].join('\n'),
);
});
test("opts.forceColor", function () {
const rawLines = ["", "", "", ""].join("\n");
expect(
codeFrameColumns(
rawLines,
{ start: { line: 3, column: null } },
{
linesAbove: 1,
linesBelow: 1,
forceColor: true,
},
),
).toEqual(
reset(
[
" " + gutter(" 2 |"),
marker(">") + gutter(" 3 |"),
" " + gutter(" 4 |"),
].join("\n"),
),
);
});
test("jsx", function () {
const rawLines = ["<div />"].join("\n");
expect(
JSON.stringify(
codeFrameColumns(
rawLines,
{ start: { line: 0, column: null } },
{
linesAbove: 1,
linesBelow: 1,
forceColor: true,
},
),
),
).toEqual(
JSON.stringify(
reset(
" " +
gutter(" 1 |") +
" " +
yellow("<") +
yellow("div") +
" " +
yellow("/") +
yellow(">"),
),
),
);
});
it("unicode capitalized", function () {
const rawLines = ["var 𐐔𐐯𐑅𐐨𐑉𐐯𐐻, deseret;"].join("\n");
expect(
JSON.stringify(
codeFrameColumns(
rawLines,
{ start: { line: 0, column: null } },
{
linesAbove: 1,
linesBelow: 1,
forceColor: true,
},
),
),
).toEqual(
JSON.stringify(
reset(
" " +
gutter(" 1 |") +
" " +
cyan("var") +
" " +
yellow("𐐔𐐯𐑅𐐨𐑉𐐯𐐻") +
yellow(",") +
" " +
"deseret" +
yellow(";"),
),
),
);
});
});
describe("when colors are not supported", () => {
stubColorSupport(false);
test("opts.highlightCode", function () {
const rawLines = "console.log('babel')";
const result = codeFrameColumns(
rawLines,
{ start: { line: 1, column: 8 } },
{ highlightCode: true },
);
const stripped = stripVTControlCharacters(result);
expect(result).toBe(stripped);
expect(stripped).toEqual(
["> 1 | console.log('babel')", " | ^"].join("\n"),
);
});
test("opts.forceColor", function () {
const rawLines = ["", "", "", ""].join("\n");
expect(
codeFrameColumns(
rawLines,
{ start: { line: 3, column: null } },
{
linesAbove: 1,
linesBelow: 1,
forceColor: true,
},
),
).toEqual(
reset(
[
" " + gutter(" 2 |"),
marker(">") + gutter(" 3 |"),
" " + gutter(" 4 |"),
].join("\n"),
),
);
});
});
});