Skip to content

Preserved-tag regex matches <address> as <a>, corrupting valid HTML documentation #2

Description

@AnHeuermann

Summary

extractPreservedBlocks() builds its preserved-block pattern from a bare tag name, so the
pattern for a also matches any tag whose name starts with a<address>, <abbr>,
<article>, <aside>, <audio>, <area>.

When that happens the lazy body (.*?) runs on to the next genuine </a>, swallowing the
real closing tag and any markup in between. The placeholder that replaces the span leaves
orphaned closing tags behind, and Prettier's HTML parser then rejects HTML that was
perfectly well-formed on input.

The result is a hard failure: prettier.format() throws and the file cannot be formatted at all.

Location

src/print/html-embed-formatter.ts, in extractPreservedBlocks() (dist/print/html-embed-formatter.js:183 in the published 0.1.0 build):

const pattern = new RegExp(`<${tag}([^>]*)>(.*?)<\\/${tag}>`, "gis");

With tag = "a", the sub-pattern <a([^>]*)> matches <address>: the literal <a matches,
and ddress is consumed by [^>]*. DEFAULT_PRESERVED_TAGS is ["pre", "a", "code", "font"],
so a is the only entry that collides in practice.

Reproduction

AddressDoc.mo — the HTML in this annotation is valid:

model AddressDoc "Valid HTML that the plugin corrupts via <address>"
  Real y;
equation
  y = time;
  annotation (Documentation(info="<html>
<address>
Author: Some One<br>
Some Company<br>
</address>
<p>
First paragraph.
</p>
<p>
A link <a href=\"https://example.org\">Example</a> follows.
</p>
</html>"));
end AddressDoc;
$ npx modelica-format AddressDoc.mo
Unexpected closing tag "p". It may happen when the tag has already been closed by another tag. (11:1)
   9 | ___
  10 | _______________________________________________### follows.
> 11 | </p>
     | ^^^^
  12 | </html>

Error: HTML formatting failed: embed formatter error (see above for details)
$ echo $?
1

The HTML is not at fault

Feeding the same fragment directly to Prettier's HTML parser formats without complaint:

await prettier.format(fragment, { parser: "html" });   // OK

The malformed input is produced inside the plugin. Calling prepareHTMLForPrettier() on the
fragment above returns:

___PSBL0_
_______________________________________________
______________
__________
___
_______________
____
___
_________________________________________________________### follows.
</p>

Everything from <address> through the </a> of the example.org link has been replaced by a
single placeholder. The </p> that the parser rejects is an artifact of that substitution — it
had a matching <p> before the plugin ran.

Impact on real libraries

Formatting every .mo file in the MSL via the Prettier API (plugin 0.1.0, default options):

Library Files Failures Attributable to this bug
Modelica Standard Library 4.1.0 2552 1 1

The MSL failure is Modelica/Media/Water/IF97_Utilities.mo.
That single file contains 12 <address> blocks (lines 86, 149, 181, 1712, 3853, 4347, 5010,
5664, 5770, 6044, 6168, 8717); the one at line 6168 is followed by an <a href=…> at line 6183,
which is what closes the accidental match.

<address> is a common idiom in Modelica documentation for author/affiliation blocks, so this is
likely to recur across libraries.

Suggested fix

Anchor the tag name so it cannot be a prefix of a longer name:

const pattern = new RegExp(`<${tag}(?=[\\s>])([^>]*)>(.*?)<\\/${tag}>`, "gis");

A (?=[\s>]) lookahead requires the character after the tag name to be whitespace or >, which
admits <a href=…> and <a> while rejecting <address>.

Verification of the fix

Patching only that line in the published dist/:

  • AddressDoc.mo formats successfully, with <address> preserved and the <a> element still
    correctly treated as a preserved block:

    <address>
      Author: Some One<br />
      Some Company<br />
    </address>
    <p>First paragraph.</p>
    <p>A link
    <a href=\"https://example.org\">Example</a> follows.</p>
    
  • Modelica Standard Library 4.1.0: 2552 files, 0 failures (was 1).

A regression test using an <address> block followed by an <a> element would guard this.

Environment

prettier-plugin-modelica 0.1.0
prettier 3.9.6
web-tree-sitter 0.26.13
Node v24.18.0
OS Linux 6.6.87.2 (WSL2)

Node 24 is outside the declared engines range (^20 || ^22). The defect is a pure
string/regex issue in extractPreservedBlocks() and does not depend on the Node version — the
prepareHTMLForPrettier() output above is deterministic.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions