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.
Summary
extractPreservedBlocks()builds its preserved-block pattern from a bare tag name, so thepattern for
aalso matches any tag whose name starts witha—<address>,<abbr>,<article>,<aside>,<audio>,<area>.When that happens the lazy body
(.*?)runs on to the next genuine</a>, swallowing thereal 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, inextractPreservedBlocks()(dist/print/html-embed-formatter.js:183in the published 0.1.0 build):With
tag = "a", the sub-pattern<a([^>]*)>matches<address>: the literal<amatches,and
ddressis consumed by[^>]*.DEFAULT_PRESERVED_TAGSis["pre", "a", "code", "font"],so
ais the only entry that collides in practice.Reproduction
AddressDoc.mo— the HTML in this annotation is valid:The HTML is not at fault
Feeding the same fragment directly to Prettier's HTML parser formats without complaint:
The malformed input is produced inside the plugin. Calling
prepareHTMLForPrettier()on thefragment above returns:
Everything from
<address>through the</a>of the example.org link has been replaced by asingle placeholder. The
</p>that the parser rejects is an artifact of that substitution — ithad a matching
<p>before the plugin ran.Impact on real libraries
Formatting every
.mofile in the MSL via the Prettier API (plugin 0.1.0, default options):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 islikely to recur across libraries.
Suggested fix
Anchor the tag name so it cannot be a prefix of a longer name:
A
(?=[\s>])lookahead requires the character after the tag name to be whitespace or>, whichadmits
<a href=…>and<a>while rejecting<address>.Verification of the fix
Patching only that line in the published
dist/:AddressDoc.moformats successfully, with<address>preserved and the<a>element stillcorrectly treated as a preserved block:
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-modelicaprettierweb-tree-sitterNode 24 is outside the declared
enginesrange (^20 || ^22). The defect is a purestring/regex issue in
extractPreservedBlocks()and does not depend on the Node version — theprepareHTMLForPrettier()output above is deterministic.