Skip to content

Fix stream parser to handle contiguous opening brackets correctly#59

Merged
KnightNiwrem merged 4 commits into
grammyjs:masterfrom
KnightNiwrem:master
Apr 10, 2026
Merged

Fix stream parser to handle contiguous opening brackets correctly#59
KnightNiwrem merged 4 commits into
grammyjs:masterfrom
KnightNiwrem:master

Conversation

@KnightNiwrem

Copy link
Copy Markdown
Member

This pull request improves the robustness of the HTMLStreamParser by handling cases where multiple consecutive < characters appear in the input, ensuring that malformed or ambiguous tag openings/closings are treated as plain text. It also adds comprehensive tests to cover these edge cases.

Parser robustness improvements

  • Updated the parser logic in src/stream-html-to-format.ts to detect when a < character appears while already parsing a potential tag (open, close, or ambiguous), treating the previous partial tag as plain text and restarting tag detection with the new <. This prevents malformed tags from being incorrectly parsed as valid HTML entities. [1] [2] [3]

Test coverage enhancements

  • Added multiple tests in test/stream-html-to-format.test.ts to verify correct handling of contiguous < characters, partial open/close tags, and edge cases where tag boundaries occur across input chunks. These tests ensure the parser correctly flushes partial tags as plain text and maintains accurate entity extraction.

KnightNiwrem and others added 4 commits April 9, 2026 11:55
…ing brackets

When the HTMLStreamParser encounters << before a tag name, the first <
should be treated as plain text while the second < starts tag detection.
Previously both < characters were flushed together, causing the
subsequent valid tag to be treated as plain text.

Closes #17

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
fix(stream-parser): flush previous < as plain text on contiguous opening brackets
…E_TAG_NAME modes

The fix from #17 only covered the DECISION_OPEN_TAG_NAME_OR_CLOSE_TAG_NAME
mode. The same bug existed in OPEN_TAG_NAME and CLOSE_TAG_NAME modes where
an unexpected < would get flushed as plain text instead of restarting tag
detection.

The CLOSE_TAG_SEEK_END case is deferred to #22.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
…and-close-tag-name-modes

fix(stream-parser): handle < as tag restart in OPEN_TAG_NAME and CLOSE_TAG_NAME modes
Copilot AI review requested due to automatic review settings April 10, 2026 03:57

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

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 strengthens HTMLStreamParser’s streaming robustness by treating consecutive < characters encountered mid-tag-detection as plain text and restarting tag detection, and it adds regression tests for these malformed/ambiguous sequences.

Changes:

  • Restart tag detection when a new < appears while parsing an open tag name, close tag name, or the initial < decision state—flushing the partial tag buffer into plain text.
  • Add new stream-focused tests covering contiguous <, partial tag fragments, and chunk-boundary edge cases.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
src/stream-html-to-format.ts Adds “flush partial tag + restart on <” logic in tag-name/decision modes to avoid mis-parsing malformed sequences.
test/stream-html-to-format.test.ts Adds regression tests to validate correct plain-text flushing and entity offsets when malformed tag openings/closings occur.

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

Comment on lines 469 to +479
private addCharInCloseTagNameMode(char: string): void {
// If we encounter another `<`, the current partial close tag was plain text.
// Flush it and restart tag detection with this new `<`.
if (char === "<") {
this.text += this.fullTagOrEntityBufferText;
this.fullTagOrEntityBufferText = char;
this.workingBufferText = "";
this.mode =
HTML_STREAM_PARSER_MODE.DECISION_OPEN_TAG_NAME_OR_CLOSE_TAG_NAME;
return;
}
Comment on lines +134 to +146
it("evaluates contiguous opening brackets as plain text", () => {
const parser = new HTMLStreamParser();
parser.add("<<b");
parser.add(">bold</b>");

const formatted = parser.toFormattedString();

assertEquals(formatted.rawText, "<bold");
assertEquals(formatted.rawEntities.length, 1);
assertEquals(formatted.rawEntities[0]?.type, "bold");
assertEquals(formatted.rawEntities[0]?.offset, 1);
assertEquals(formatted.rawEntities[0]?.length, 4);
});
Comment on lines +148 to +153
it("flushes partial open tag and restarts on <: <b<b>bold</b>", () => {
const parser = new HTMLStreamParser();
parser.add("<b<b>bold</b>");

const formatted = parser.toFormattedString();

@KnightNiwrem
KnightNiwrem merged commit 7864603 into grammyjs:master Apr 10, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants