Native tsc: merge conflict markers after U+2028/U+2029 are not recognized (false TS1109 instead of TS1185)

Closed Beginner friendly
#65,107 1 comment 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

Nobody has claimed this yet.

Assessment

Difficulty
2/5
Estimated time
1-3 hours
Newbie friendliness
62/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
go, typescript
Domain
compilers

Research direction

The bug is in isConflictMarkerTrivia in tsc/internal/scanner/scanner.go, around lines 2422–2425, where the line-start fallback decodes text[:pos-2] instead of text[:pos]. Start by reproducing with a literal U+2028 before <<<<<<< HEAD and confirming TS1109 appears instead of TS1185. The fix is to decode the prior rune from text[:pos]; done when the LS and PS cases report TS1185 like the LF/CR control.

Written by the indexing model from the issue text.

Description

Won't Fix
🔎 Search Terms

merge conflict marker, TS1185, isConflictMarkerTrivia, U+2028, U+2029, line separator, paragraph separator, UTF-8

🕗 Version & Regression Information
  • This changed between classic TypeScript 5.9.3 and native tsc 7.1.0-dev
  • Verified on current main (Go / tsgo)
  • Markers after LF/CR still work; only LS/PS break recognition
⏯ Playground Link

N/A (needs a literal U+2028 / U+2029 in the source)

💻 Code
export {};
<<<<<<< HEAD

(U+2028 between {}; and <<<<<<<; same with U+2029. Also reproduces with ======= / >>>>>>>.)

tsc file.ts

Control (works on both):

export {};
<<<<<<< HEAD
🙁 Actual behavior

Native tsc treats the markers as ordinary tokens, e.g.:

error TS1109: Expression expected.

(and further cascade errors)

🙂 Expected behavior

Classic 5.9.3:

error TS1185: Merge conflict marker encountered.
Additional information about the issue

Root cause in tsc/internal/scanner/scanner.go (isConflictMarkerTrivia, ~2422–2425):

atLineStart := pos == 0 || stringutil.IsLineBreak(rune(text[pos-1]))
if !atLineStart && pos >= 2 {
    prev, _ := utf8.DecodeLastRuneInString(text[:pos-2]) // should be text[:pos]
    atLineStart = stringutil.IsLineBreak(prev)
}

After LS (E2 80 A8), text[pos-1] is 0xA8 (not a line break). The fallback decodes text[:pos-2] (ends at E2) instead of the full prior rune. Classic UTF-16 charCodeAt(pos-1) sees 0x2028 correctly.

Fix: utf8.DecodeLastRuneInString(text[:pos]).

Same UTF-8 line-break family as literal U+2028 in regex first-pass scanning.

Dominant language
Go
Stars
112k
Forks
15.4k
Avg merge
3d 1h
Merged PRs (30d)
136

Getting set up

Open in Codespaces

Starts the project's dev container in your browser, under your own GitHub account.

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from microsoft/TypeScript

All issues in microsoft/TypeScript

Similar issues

More Go issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.