mirror of
https://github.com/hanzoai/babel.git
synced 2026-09-24 16:56:45 +00:00
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.
518 lines
12 KiB
JavaScript
518 lines
12 KiB
JavaScript
import { stripVTControlCharacters } from "node:util";
|
|
import { codeFrameColumns, highlight } from "../lib/index.js";
|
|
|
|
describe("@babel/code-frame", function () {
|
|
test("basic usage", function () {
|
|
const rawLines = ["class Foo {", " constructor()", "};"].join("\n");
|
|
expect(
|
|
codeFrameColumns(rawLines, { start: { line: 2, column: 15 } }),
|
|
).toEqual(
|
|
[
|
|
" 1 | class Foo {",
|
|
"> 2 | constructor()",
|
|
" | ^",
|
|
" 3 | };",
|
|
].join("\n"),
|
|
);
|
|
});
|
|
|
|
test("optional column number", function () {
|
|
const rawLines = ["class Foo {", " constructor()", "};"].join("\n");
|
|
expect(
|
|
codeFrameColumns(rawLines, { start: { line: 2, column: null } }),
|
|
).toEqual(
|
|
[" 1 | class Foo {", "> 2 | constructor()", " 3 | };"].join("\n"),
|
|
);
|
|
});
|
|
|
|
test("maximum context lines and padding", function () {
|
|
const rawLines = [
|
|
"/**",
|
|
" * Sums two numbers.",
|
|
" *",
|
|
" * @param a Number",
|
|
" * @param b Number",
|
|
" * @returns Number",
|
|
" */",
|
|
"",
|
|
"function sum(a, b) {",
|
|
" return a + b",
|
|
"}",
|
|
].join("\n");
|
|
expect(
|
|
codeFrameColumns(rawLines, { start: { line: 7, column: 1 } }),
|
|
).toEqual(
|
|
[
|
|
" 5 | * @param b Number",
|
|
" 6 | * @returns Number",
|
|
"> 7 | */",
|
|
" | ^",
|
|
" 8 |",
|
|
" 9 | function sum(a, b) {",
|
|
" 10 | return a + b",
|
|
].join("\n"),
|
|
);
|
|
});
|
|
|
|
test("no unnecessary padding due to one-off errors", function () {
|
|
const rawLines = [
|
|
"/**",
|
|
" * Sums two numbers.",
|
|
" *",
|
|
" * @param a Number",
|
|
" * @param b Number",
|
|
" * @returns Number",
|
|
" */",
|
|
"",
|
|
"function sum(a, b) {",
|
|
" return a + b",
|
|
"}",
|
|
].join("\n");
|
|
expect(
|
|
codeFrameColumns(rawLines, { start: { line: 6, column: 1 } }),
|
|
).toEqual(
|
|
[
|
|
" 4 | * @param a Number",
|
|
" 5 | * @param b Number",
|
|
"> 6 | * @returns Number",
|
|
" | ^",
|
|
" 7 | */",
|
|
" 8 |",
|
|
" 9 | function sum(a, b) {",
|
|
].join("\n"),
|
|
);
|
|
});
|
|
|
|
test("tabs", function () {
|
|
const rawLines = [
|
|
"\tclass Foo {",
|
|
"\t \t\t constructor\t(\t)",
|
|
"\t};",
|
|
].join("\n");
|
|
expect(
|
|
codeFrameColumns(rawLines, { start: { line: 2, column: 24 } }),
|
|
).toEqual(
|
|
[
|
|
" 1 | \tclass Foo {",
|
|
"> 2 | \t \t\t constructor\t(\t)",
|
|
" | \t \t\t \t \t ^",
|
|
" 3 | \t};",
|
|
].join("\n"),
|
|
);
|
|
});
|
|
|
|
test("opts.linesAbove", function () {
|
|
const rawLines = [
|
|
"/**",
|
|
" * Sums two numbers.",
|
|
" *",
|
|
" * @param a Number",
|
|
" * @param b Number",
|
|
" * @returns Number",
|
|
" */",
|
|
"",
|
|
"function sum(a, b) {",
|
|
" return a + b",
|
|
"}",
|
|
].join("\n");
|
|
expect(
|
|
codeFrameColumns(
|
|
rawLines,
|
|
{ start: { line: 7, column: 1 } },
|
|
{ linesAbove: 1 },
|
|
),
|
|
).toEqual(
|
|
[
|
|
" 6 | * @returns Number",
|
|
"> 7 | */",
|
|
" | ^",
|
|
" 8 |",
|
|
" 9 | function sum(a, b) {",
|
|
" 10 | return a + b",
|
|
].join("\n"),
|
|
);
|
|
});
|
|
|
|
test("opts.linesBelow", function () {
|
|
const rawLines = [
|
|
"/**",
|
|
" * Sums two numbers.",
|
|
" *",
|
|
" * @param a Number",
|
|
" * @param b Number",
|
|
" * @returns Number",
|
|
" */",
|
|
"",
|
|
"function sum(a, b) {",
|
|
" return a + b",
|
|
"}",
|
|
].join("\n");
|
|
expect(
|
|
codeFrameColumns(
|
|
rawLines,
|
|
{ start: { line: 7, column: 1 } },
|
|
{ linesBelow: 1 },
|
|
),
|
|
).toEqual(
|
|
[
|
|
" 5 | * @param b Number",
|
|
" 6 | * @returns Number",
|
|
"> 7 | */",
|
|
" | ^",
|
|
" 8 |",
|
|
].join("\n"),
|
|
);
|
|
});
|
|
|
|
test("opts.linesAbove and opts.linesBelow", function () {
|
|
const rawLines = [
|
|
"/**",
|
|
" * Sums two numbers.",
|
|
" *",
|
|
" * @param a Number",
|
|
" * @param b Number",
|
|
" * @returns Number",
|
|
" */",
|
|
"",
|
|
"function sum(a, b) {",
|
|
" return a + b",
|
|
"}",
|
|
].join("\n");
|
|
expect(
|
|
codeFrameColumns(
|
|
rawLines,
|
|
{ start: { line: 7, column: 1 } },
|
|
{ linesAbove: 1, linesBelow: 1 },
|
|
),
|
|
).toEqual(
|
|
[" 6 | * @returns Number", "> 7 | */", " | ^", " 8 |"].join("\n"),
|
|
);
|
|
});
|
|
|
|
test("opts.linesAbove no lines above", function () {
|
|
const rawLines = [
|
|
"class Foo {",
|
|
" constructor() {",
|
|
" console.log(arguments);",
|
|
" }",
|
|
"};",
|
|
].join("\n");
|
|
expect(
|
|
codeFrameColumns(rawLines, { start: { line: 2 } }, { linesAbove: 0 }),
|
|
).toEqual(
|
|
[
|
|
"> 2 | constructor() {",
|
|
" 3 | console.log(arguments);",
|
|
" 4 | }",
|
|
" 5 | };",
|
|
].join("\n"),
|
|
);
|
|
});
|
|
|
|
test("opts.linesBelow no lines below", function () {
|
|
const rawLines = [
|
|
"class Foo {",
|
|
" constructor() {",
|
|
" console.log(arguments);",
|
|
" }",
|
|
"};",
|
|
].join("\n");
|
|
expect(
|
|
codeFrameColumns(rawLines, { start: { line: 2 } }, { linesBelow: 0 }),
|
|
).toEqual([" 1 | class Foo {", "> 2 | constructor() {"].join("\n"));
|
|
});
|
|
|
|
test("opts.linesBelow single line", function () {
|
|
const rawLines = [
|
|
"class Foo {",
|
|
" constructor() {",
|
|
" console.log(arguments);",
|
|
" }",
|
|
"};",
|
|
].join("\n");
|
|
expect(
|
|
codeFrameColumns(
|
|
rawLines,
|
|
{ start: { line: 2 } },
|
|
{ linesAbove: 0, linesBelow: 0 },
|
|
),
|
|
).toEqual(["> 2 | constructor() {"].join("\n"));
|
|
});
|
|
|
|
test("basic usage, new API", function () {
|
|
const rawLines = ["class Foo {", " constructor()", "};"].join("\n");
|
|
expect(
|
|
codeFrameColumns(rawLines, { start: { line: 2, column: 15 } }),
|
|
).toEqual(
|
|
[
|
|
" 1 | class Foo {",
|
|
"> 2 | constructor()",
|
|
" | ^",
|
|
" 3 | };",
|
|
].join("\n"),
|
|
);
|
|
});
|
|
|
|
test("mark multiple columns", function () {
|
|
const rawLines = ["class Foo {", " constructor()", "};"].join("\n");
|
|
expect(
|
|
codeFrameColumns(rawLines, {
|
|
start: { line: 2, column: 2 },
|
|
end: { line: 2, column: 15 },
|
|
}),
|
|
).toEqual(
|
|
[
|
|
" 1 | class Foo {",
|
|
"> 2 | constructor()",
|
|
" | ^^^^^^^^^^^^^",
|
|
" 3 | };",
|
|
].join("\n"),
|
|
);
|
|
});
|
|
|
|
test("mark multiple columns across lines", function () {
|
|
const rawLines = ["class Foo {", " constructor() {", " }", "};"].join(
|
|
"\n",
|
|
);
|
|
expect(
|
|
codeFrameColumns(rawLines, {
|
|
start: { line: 2, column: 16 },
|
|
end: { line: 3, column: 3 },
|
|
}),
|
|
).toEqual(
|
|
[
|
|
" 1 | class Foo {",
|
|
"> 2 | constructor() {",
|
|
" | ^",
|
|
"> 3 | }",
|
|
" | ^^^",
|
|
" 4 | };",
|
|
].join("\n"),
|
|
);
|
|
});
|
|
|
|
test("mark multiple columns across multiple lines", function () {
|
|
const rawLines = [
|
|
"class Foo {",
|
|
" constructor() {",
|
|
" console.log(arguments);",
|
|
" }",
|
|
"};",
|
|
].join("\n");
|
|
expect(
|
|
codeFrameColumns(rawLines, {
|
|
start: { line: 2, column: 16 },
|
|
end: { line: 4, column: 3 },
|
|
}),
|
|
).toEqual(
|
|
[
|
|
" 1 | class Foo {",
|
|
"> 2 | constructor() {",
|
|
" | ^",
|
|
"> 3 | console.log(arguments);",
|
|
" | ^^^^^^^^^^^^^^^^^^^^^^^^^^^",
|
|
"> 4 | }",
|
|
" | ^^^",
|
|
" 5 | };",
|
|
].join("\n"),
|
|
);
|
|
});
|
|
|
|
test("mark across multiple lines without columns", function () {
|
|
const rawLines = [
|
|
"class Foo {",
|
|
" constructor() {",
|
|
" console.log(arguments);",
|
|
" }",
|
|
"};",
|
|
].join("\n");
|
|
expect(
|
|
codeFrameColumns(rawLines, { start: { line: 2 }, end: { line: 4 } }),
|
|
).toEqual(
|
|
[
|
|
" 1 | class Foo {",
|
|
"> 2 | constructor() {",
|
|
"> 3 | console.log(arguments);",
|
|
"> 4 | }",
|
|
" 5 | };",
|
|
].join("\n"),
|
|
);
|
|
});
|
|
|
|
test("opts.message", function () {
|
|
const rawLines = ["class Foo {", " constructor()", "};"].join("\n");
|
|
expect(
|
|
codeFrameColumns(
|
|
rawLines,
|
|
{ start: { line: 2, column: 15 } },
|
|
{
|
|
message: "Missing {",
|
|
},
|
|
),
|
|
).toEqual(
|
|
[
|
|
" 1 | class Foo {",
|
|
"> 2 | constructor()",
|
|
" | ^ Missing {",
|
|
" 3 | };",
|
|
].join("\n"),
|
|
);
|
|
});
|
|
|
|
test("opts.message without column", function () {
|
|
const rawLines = ["class Foo {", " constructor()", "};"].join("\n");
|
|
expect(
|
|
codeFrameColumns(
|
|
rawLines,
|
|
{ start: { line: 2 } },
|
|
{
|
|
message: "Missing {",
|
|
},
|
|
),
|
|
).toEqual(
|
|
[
|
|
" Missing {",
|
|
" 1 | class Foo {",
|
|
"> 2 | constructor()",
|
|
" 3 | };",
|
|
].join("\n"),
|
|
);
|
|
});
|
|
|
|
test("opts.message with multiple lines", function () {
|
|
const rawLines = [
|
|
"class Foo {",
|
|
" constructor() {",
|
|
" console.log(arguments);",
|
|
" }",
|
|
"};",
|
|
].join("\n");
|
|
expect(
|
|
codeFrameColumns(
|
|
rawLines,
|
|
{
|
|
start: { line: 2, column: 16 },
|
|
end: { line: 4, column: 3 },
|
|
},
|
|
{
|
|
message: "something about the constructor body",
|
|
},
|
|
),
|
|
).toEqual(
|
|
[
|
|
" 1 | class Foo {",
|
|
"> 2 | constructor() {",
|
|
" | ^",
|
|
"> 3 | console.log(arguments);",
|
|
" | ^^^^^^^^^^^^^^^^^^^^^^^^^^^",
|
|
"> 4 | }",
|
|
" | ^^^ something about the constructor body",
|
|
" 5 | };",
|
|
].join("\n"),
|
|
);
|
|
});
|
|
|
|
test("opts.message with multiple lines without columns", function () {
|
|
const rawLines = [
|
|
"class Foo {",
|
|
" constructor() {",
|
|
" console.log(arguments);",
|
|
" }",
|
|
"};",
|
|
].join("\n");
|
|
expect(
|
|
codeFrameColumns(
|
|
rawLines,
|
|
{ start: { line: 2 }, end: { line: 4 } },
|
|
{
|
|
message: "something about the constructor body",
|
|
},
|
|
),
|
|
).toEqual(
|
|
[
|
|
" something about the constructor body",
|
|
" 1 | class Foo {",
|
|
"> 2 | constructor() {",
|
|
"> 3 | console.log(arguments);",
|
|
"> 4 | }",
|
|
" 5 | };",
|
|
].join("\n"),
|
|
);
|
|
});
|
|
|
|
test("highlight works", function () {
|
|
const raw = "const a = 1";
|
|
const highlighted = highlight(raw);
|
|
|
|
expect(stripVTControlCharacters(highlighted)).toBe(raw);
|
|
expect(highlighted.length).toBeGreaterThan(raw.length);
|
|
});
|
|
|
|
test("opts.startLine", function () {
|
|
const rawLines = "const a = 1;\nconst b = 1";
|
|
expect(
|
|
codeFrameColumns(rawLines, { start: { line: 102 } }, { startLine: 101 }),
|
|
).toMatchInlineSnapshot(`
|
|
" 101 | const a = 1;
|
|
> 102 | const b = 1"
|
|
`);
|
|
});
|
|
|
|
test("should handle 0-based columns from Babel AST locations", function () {
|
|
const code = 'x = "foobar";';
|
|
// "foobar" (with quotes) starts at column 4 (0-based), ends at column 12 (0-based exclusive)
|
|
const loc = {
|
|
start: { line: 1, column: 4 },
|
|
end: { line: 1, column: 12 },
|
|
};
|
|
const result = codeFrameColumns(code, loc);
|
|
// The underline should cover "foobar" (with quotes) starting at the correct position
|
|
expect(result).toEqual(
|
|
['> 1 | x = "foobar";', " | ^^^^^^^^"].join("\n"),
|
|
);
|
|
});
|
|
|
|
test("should handle 0-based column 0", function () {
|
|
const code = '"foobar";';
|
|
const loc = {
|
|
start: { line: 1, column: 0 },
|
|
end: { line: 1, column: 8 },
|
|
};
|
|
const result = codeFrameColumns(code, loc);
|
|
expect(result).toEqual(['> 1 | "foobar";', " | ^^^^^^^^"].join("\n"));
|
|
});
|
|
|
|
test("middle lines in a 4+ line range use each line's own length", function () {
|
|
const rawLines = [
|
|
"class Foo {",
|
|
" constructor() {",
|
|
" short",
|
|
" a much longer middle line here",
|
|
" end",
|
|
" }",
|
|
"};",
|
|
].join("\n");
|
|
expect(
|
|
codeFrameColumns(rawLines, {
|
|
start: { line: 2, column: 16 },
|
|
end: { line: 6, column: 3 },
|
|
}),
|
|
).toEqual(
|
|
[
|
|
" 1 | class Foo {",
|
|
"> 2 | constructor() {",
|
|
" | ^",
|
|
"> 3 | short",
|
|
" | ^^^^^^^^^",
|
|
"> 4 | a much longer middle line here",
|
|
" | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^",
|
|
"> 5 | end",
|
|
" | ^^^^^^^",
|
|
"> 6 | }",
|
|
" | ^^^",
|
|
" 7 | };",
|
|
].join("\n"),
|
|
);
|
|
});
|
|
});
|