Summary
oxfmt selects the Angular formatter for HTML purely by filename suffix — *.component.html, as documented in language support ("Angular → *.component.html").
Angular v20 dropped the .component suffix from CLI-generated files. A template generated by the modern CLI is attendance.html, not attendance.component.html. Those files miss the suffix check, silently fall through to the plain HTML formatter, and every Angular control-flow block (@if / @for / @switch / @defer / @let) is treated as a plain text node and mangled.
The effect is that no Angular v20+ project can use oxfmt on its templates at all, and there is no configuration escape hatch.
Reproduction
Byte-identical input, only the filename differs:
IN='<div>
@switch (s()) {
@case (1) {
<b>one</b>
}
@default {
<i>none</i>
}
}
@defer (on viewport) {
<app-chart />
} @placeholder {
<span>...</span>
}
</div>'
echo "$IN" | oxfmt --stdin-filepath=foo.component.html
echo "$IN" | oxfmt --stdin-filepath=foo.html
foo.component.html — correct (Angular formatter)
<div>
@switch (s()) {
@case (1) {
<b>one</b>
}
@default {
<i>none</i>
}
}
@defer (on viewport) {
<app-chart />
} @placeholder {
<span>...</span>
}
</div>
foo.html — mangled (HTML formatter)
<div>
@switch (s()) { @case (1) {
<b>one</b>
} @default {
<i>none</i>
} } @defer (on viewport) {
<app-chart />
} @placeholder {
<span>...</span>
}
</div>
@case is hoisted onto the @switch line, closing braces merge into } }, and all block bodies lose their indentation level.
Detection probe
Same input, varying only --stdin-filepath:
--stdin-filepath |
Formatter used |
foo.component.html |
Angular ✅ |
foo.html |
HTML ❌ |
attendance.html (Angular v20+ CLI default) |
HTML ❌ |
src/app/features/attendance/attendance.html |
HTML ❌ |
foo.ng.html |
HTML ❌ |
foo.template.html |
HTML ❌ |
component.html |
HTML ❌ |
foo-component.html |
HTML ❌ |
foo.COMPONENT.html |
HTML ❌ |
Further mangling on *.html
All of these are idempotent — re-running oxfmt reproduces the same bad output, so this is a formatting-model mismatch rather than an instability bug.
1. @if / @else bodies de-indented
<!-- in -->
<div class="wrap">
@if (user(); as u) {
<p>{{ u.name }}</p>
} @else {
<p>none</p>
}
</div>
<!-- out -->
<div class="wrap">
@if (user(); as u) {
<p>{{ u.name }}</p>
} @else {
<p>none</p>
}
</div>
2. Nested blocks flatten completely
<!-- in -->
<div>
@if (a()) {
@for (x of xs(); track x) {
<span>{{ x }}</span>
}
}
</div>
<!-- out -->
<div>
@if (a()) { @for (x of xs(); track x) {
<span>{{ x }}</span>
} }
</div>
3. Block headers and @let prose-wrapped mid-expression
Because blocks are text nodes, the fill algorithm wraps inside @if (...) conditions and joins statements:
@let records = filteredRecords() | search: searchText : ["remark", "employeeName", "status"]; @if (records.length > 0
&& !isLoading() && hasPermission("Attendance") && selectedDate() !== null) {
At printWidth: 80 it also breaks inside interpolations:
{{ employee.designationName }} · {{
employee.departmentName }}
Why there is no workaround
.oxfmtrc.json has no parser option — confirmed against the shipped node_modules/oxfmt/configuration_schema.json, whose top-level keys include svelte and vueIndentScriptAndStyle but nothing for Angular. Prettier users solve exactly this with overrides: [{ files: "*.html", options: { parser: "angular" } }]; oxfmt has no equivalent.
- Renaming every template back to
*.component.html contradicts the Angular v20+ style guide and the CLI defaults.
overrides in .oxfmtrc.json do apply to these files — the options (printWidth, singleAttributePerLine, bracketSameLine) take effect. It is only the parser/formatter selection that cannot be influenced.
Suggested fix
Widen the Angular detection so it does not depend on a naming convention Angular has abandoned. Options, roughly in order of preference:
- Treat
*.html inside an Angular workspace as an Angular template (e.g. presence of angular.json, or @angular/core in package.json).
- Add an
angular config key alongside the existing svelte key so a project can opt in explicitly.
- At minimum, extend the suffix list beyond
*.component.html.
Note that the Angular formatter is a strict superset here — it round-trips plain HTML unchanged — so widening detection is low-risk for non-Angular .html in an Angular repo.
Related
Environment
|
|
oxfmt |
0.63.0 (current npm latest) |
| Angular |
22.1.2 |
| Package manager |
[email protected] |
| OS |
Windows 11 |
.oxfmtrc.json (relevant part):
Summary
oxfmtselects the Angular formatter for HTML purely by filename suffix —*.component.html, as documented in language support ("Angular →*.component.html").Angular v20 dropped the
.componentsuffix from CLI-generated files. A template generated by the modern CLI isattendance.html, notattendance.component.html. Those files miss the suffix check, silently fall through to the plain HTML formatter, and every Angular control-flow block (@if/@for/@switch/@defer/@let) is treated as a plain text node and mangled.The effect is that no Angular v20+ project can use
oxfmton its templates at all, and there is no configuration escape hatch.Reproduction
Byte-identical input, only the filename differs:
foo.component.html— correct (Angular formatter)foo.html— mangled (HTML formatter)@caseis hoisted onto the@switchline, closing braces merge into} }, and all block bodies lose their indentation level.Detection probe
Same input, varying only
--stdin-filepath:--stdin-filepathfoo.component.htmlfoo.htmlattendance.html(Angular v20+ CLI default)src/app/features/attendance/attendance.htmlfoo.ng.htmlfoo.template.htmlcomponent.htmlfoo-component.htmlfoo.COMPONENT.htmlFurther mangling on
*.htmlAll of these are idempotent — re-running
oxfmtreproduces the same bad output, so this is a formatting-model mismatch rather than an instability bug.1.
@if/@elsebodies de-indented2. Nested blocks flatten completely
3. Block headers and
@letprose-wrapped mid-expressionBecause blocks are text nodes, the fill algorithm wraps inside
@if (...)conditions and joins statements:@let records = filteredRecords() | search: searchText : ["remark", "employeeName", "status"]; @if (records.length > 0 && !isLoading() && hasPermission("Attendance") && selectedDate() !== null) {At
printWidth: 80it also breaks inside interpolations:{{ employee.designationName }} · {{ employee.departmentName }}Why there is no workaround
.oxfmtrc.jsonhas noparseroption — confirmed against the shippednode_modules/oxfmt/configuration_schema.json, whose top-level keys includesvelteandvueIndentScriptAndStylebut nothing for Angular. Prettier users solve exactly this withoverrides: [{ files: "*.html", options: { parser: "angular" } }];oxfmthas no equivalent.*.component.htmlcontradicts the Angular v20+ style guide and the CLI defaults.overridesin.oxfmtrc.jsondo apply to these files — the options (printWidth,singleAttributePerLine,bracketSameLine) take effect. It is only the parser/formatter selection that cannot be influenced.Suggested fix
Widen the Angular detection so it does not depend on a naming convention Angular has abandoned. Options, roughly in order of preference:
*.htmlinside an Angular workspace as an Angular template (e.g. presence ofangular.json, or@angular/coreinpackage.json).angularconfig key alongside the existingsveltekey so a project can opt in explicitly.*.component.html.Note that the Angular formatter is a strict superset here — it round-trips plain HTML unchanged — so widening detection is low-risk for non-Angular
.htmlin an Angular repo.Related
parseroption (withoverrides) #17852 —oxfmt: support parser option (with overrides). Related but distinct: that issue is about mapping non-standard extensions (.page.html,.wxml) onto an existing parser, and its discussion assumes plain.htmlAngular templates already work. This report needs no new option — the existing heuristic is simply out of date.@default never;in Angular templates #21548 —@default never;in Angular templates (closed, not planned). Prior art on Angular block syntax..ngmltemplate file type as an alternative to.htmlangular/angular#67545 — proposal for a dedicated Angular template extension, referenced by @leaysgur in oxfmt: supportparseroption (withoverrides) #17852. That proposal has seen no movement, and even if adopted it would not help the millions of existing*.htmltemplates.Environment
oxfmt0.63.0(current npmlatest)22.1.2[email protected].oxfmtrc.json(relevant part):{ "printWidth": 100, "singleQuote": true, "htmlWhitespaceSensitivity": "ignore", "overrides": [ { "files": ["*.html"], "options": { "printWidth": 120, "singleAttributePerLine": true, "bracketSameLine": true, "htmlWhitespaceSensitivity": "ignore" } } ] }