Skip to content

fix(code-frame): use 0-based columns to match Babel AST locations (#1…#17849

Merged
JLHwung merged 1 commit into
babel:mainfrom
nehemiyawicks:fix/code-frame-zero-based-columns
May 11, 2026
Merged

fix(code-frame): use 0-based columns to match Babel AST locations (#1…#17849
JLHwung merged 1 commit into
babel:mainfrom
nehemiyawicks:fix/code-frame-zero-based-columns

Conversation

@nehemiyawicks
Copy link
Copy Markdown
Contributor

Q A
Fixed Issues? Fixes #17316
Patch: Bug Fix? 👍
Major: Breaking Change? Potentially
Minor: New Feature?
Tests Added + Pass? Yes
Documentation PR Link N/A
Any Dependency Changes? No
License MIT

Summary

Fixes #17316

codeFrameColumns currently treats columns as 1-based, while Babel AST locations are 0-based. This mismatch causes caret/underline markers to be off by one character when codeFrameColumns is called with AST locations directly.

This PR updates @babel/code-frame to treat columns as 0-based, and removes the corresponding + 1 compensations in internal callers (@babel/core, @babel/traverse) that were working around the previous behavior.

Changes

@babel/code-frame (common.ts)

  • Changed default column sentinel from 0 to null so that column 0 is a valid position
  • Replaced falsy checks (!startColumn, if (startColumn)) with explicit null checks (startColumn == null, startColumn != null)
  • Removed + 1 compensation in marker length calculation
  • Removed - 1 offset in marker spacing calculation

@babel/core and @babel/traverse

  • Removed column: loc.column + 1 in 3 call sites that were converting 0-based AST columns to 1-based before passing them to codeFrameColumns

Tests

  • Updated existing test inputs from 1-based to 0-based columns
  • Added 2 new tests verifying correct behavior with 0-based columns (including column 0)

Breaking change note

This may be a breaking change for external consumers who manually pass 1-based column values to codeFrameColumns.

Consumers passing Babel AST .loc objects (the primary use case) will now receive correct results without manual adjustment.

If preferred, this change can be targeted for Babel 8.

Test plan

  • All @babel/code-frame tests pass (31 tests)
  • All @babel/core tests pass (483 tests)
  • All @babel/parser tests pass (16,083 tests)
  • All @babel/traverse tests pass (650 tests)
  • Verified manually with @babel/parser output for both valid code (AST loc) and syntax errors (error loc)

@babel-bot
Copy link
Copy Markdown
Collaborator

babel-bot commented Mar 3, 2026

Build successful! You can test your changes in the REPL here: https://babeljs.io/repl/build/61475

@nehemiyawicks nehemiyawicks force-pushed the fix/code-frame-zero-based-columns branch from 0a17403 to 4b94a8d Compare March 3, 2026 08:29
@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Mar 3, 2026

Open in StackBlitz

commit: 455553e

Copy link
Copy Markdown
Member

@liuxingbaoyu liuxingbaoyu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

Comment thread packages/babel-code-frame/src/common.ts Outdated
@liuxingbaoyu liuxingbaoyu added the PR: Breaking Change 💥 A type of pull request used for our changelog categories for next major release label Apr 25, 2026
Copilot AI review requested due to automatic review settings April 25, 2026 12:04
@nehemiyawicks nehemiyawicks force-pushed the fix/code-frame-zero-based-columns branch from 4b94a8d to 166282f Compare April 25, 2026 12:04
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR aligns @babel/code-frame column handling with Babel AST locations by treating columns as 0-based, and updates internal Babel callers/tests to remove prior + 1 compensations.

Changes:

  • Update @babel/code-frame marker calculations to interpret column as 0-based (including allowing column 0 as a valid value).
  • Remove loc.column + 1 adjustments in @babel/core and @babel/traverse call sites.
  • Update and extend @babel/code-frame tests to use 0-based columns (including new coverage for column 0).

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
packages/babel-traverse/src/path/replacement.ts Removes + 1 adjustment when passing parser error locations into codeFrameColumns.
packages/babel-core/src/transformation/file/file.ts Removes + 1 adjustments for node locs passed into codeFrameColumns.
packages/babel-core/src/parser/index.ts Removes + 1 adjustment for parser error locations used in code frames.
packages/babel-code-frame/test/index.js Updates existing tests to 0-based columns and adds new tests for AST-style locations including column 0.
packages/babel-code-frame/test/color-detection.js Updates highlight tests to use 0-based columns and column 0.
packages/babel-code-frame/src/common.ts Switches missing-column sentinel to null and updates spacing/length calculations for 0-based columns.
Comments suppressed due to low confidence (2)

packages/babel-code-frame/src/common.ts:65

  • getMarkerLines now uses column: null as a sentinel and tests pass column: null, but the exported Location/NodeLocation types still require column: number. This makes the public TS types inconsistent with the runtime API and forces @ts-expect-error workarounds. Consider updating Location so column is optional or number | null (and similarly for loc.end) so consumers can pass column 0 and also omit columns without type casts.
  const startLoc: Location = {
    // @ts-expect-error default value
    column: null,
    // @ts-expect-error default value
    line: -1,
    ...loc.start,
  };

packages/babel-code-frame/src/common.ts:106

  • In the multi-line case, middle-line marker lengths are computed using source[lineNumber - i].length. Since lineNumber is startLine + i, lineNumber - i always equals startLine, so for spans of more than 3 lines every middle line will incorrectly use the first line’s length. This should use the current line’s length (e.g. the same indexing approach as the first-line branch) so markers cover the full middle line correctly.
      } else {
        const sourceLength = source[lineNumber - i].length;

        markerLines[lineNumber] = [0, sourceLength];
      }

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/babel-code-frame/src/common.ts
@nehemiyawicks nehemiyawicks force-pushed the fix/code-frame-zero-based-columns branch from 166282f to 3817e92 Compare May 5, 2026 03:23
@JLHwung JLHwung requested a review from Copilot May 5, 2026 13:12
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/babel-code-frame/src/common.ts
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (2)

packages/babel-code-frame/src/common.ts:65

  • getMarkerLines now uses column: null as the sentinel for “no column”, and tests/usage in this repo pass column: null, but the exported Location/NodeLocation types still require column: number. This forces @ts-expect-error internally and means TS consumers can’t represent the supported null/omitted-column cases without casting. Consider updating the public types (e.g., column?: number | null) so the runtime API and typings stay in sync, and so column 0 remains a valid numeric position.
  const startLoc: Location = {
    // @ts-expect-error default value
    column: null,
    // @ts-expect-error default value
    line: -1,
    ...loc.start,
  };

packages/babel-code-frame/src/common.ts:117

  • In the single-line range case, markerLines[startLine] = [startColumn, endColumn - startColumn] will produce NaN (and later break marker rendering) if either startColumn or endColumn is null/undefined. Since this PR explicitly introduces null as a valid sentinel for missing columns, it would be safer to guard here (e.g., treat missing columns as “no column”/whole-line highlight, or normalize missing startColumn to 0 when endColumn is provided).
    if (startColumn === endColumn) {
      if (startColumn != null) {
        markerLines[startLine] = [startColumn, 0];
      } else {
        markerLines[startLine] = true;
      }
    } else {
      markerLines[startLine] = [startColumn, endColumn - startColumn];
    }

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

…bel#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.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (1)

packages/babel-code-frame/src/common.ts:66

  • getMarkerLines now relies on column: null as the sentinel to represent “no column” (and the tests also pass column: null), but the TypeScript Location/NodeLocation types still declare column: number, forcing @ts-expect-error here. This leaks into the public codeFrameColumns signature and makes it hard for TS consumers to express “no column” or column: null without casts. Consider updating the types (e.g., column?: number | null) so the implementation and public API are type-safe and you can drop the @ts-expect-error defaults.
  const startLoc: Location = {
    // @ts-expect-error default value
    column: null,
    // @ts-expect-error default value
    line: -1,
    ...loc.start,
  };
  const endLoc: Location = {

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Copy Markdown
Contributor

@JLHwung JLHwung left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you. The changes now look good to me. Could you open a docs PR to babel/website?

@nehemiyawicks
Copy link
Copy Markdown
Contributor Author

Thanks for the review. Docs PR opened: babel/website#3200

nehemiyawicks added a commit to nehemiyawicks/website that referenced this pull request May 5, 2026
Updates the codeFrameColumns examples to use 0-based column values
matching the API change in babel/babel#17849, and adds a Babel 7 to
Babel 8 migration note.
nehemiyawicks added a commit to nehemiyawicks/website that referenced this pull request May 5, 2026
Add @babel/code-frame entry under v8-migration-api.md for the
0-based column change in babel/babel#17849.
nehemiyawicks added a commit to nehemiyawicks/website that referenced this pull request May 5, 2026
Add @babel/code-frame entry under v8-migration-api.md for the
0-based column change in babel/babel#17849.
@nicolo-ribaudo nicolo-ribaudo removed their request for review May 6, 2026 13:37
nehemiyawicks added a commit to nehemiyawicks/website that referenced this pull request May 11, 2026
Add @babel/code-frame entry under v8-migration-api.md for the
0-based column change in babel/babel#17849.
@JLHwung JLHwung merged commit e77fa07 into babel:main May 11, 2026
59 of 60 checks passed
@nicolo-ribaudo
Copy link
Copy Markdown
Member

@SimenB This should be the last breaking change that can affect Jest

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

PR: Breaking Change 💥 A type of pull request used for our changelog categories for next major release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Off-by-one columns in babel/code-frame

6 participants