Skip to content

Commit 55a5d69

Browse files
committed
Update prettier and post-process to fix single character closing tags.
1 parent 8fbe5bc commit 55a5d69

6 files changed

Lines changed: 87 additions & 11 deletions

File tree

‎packages/ts-css/package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,6 @@
4040
},
4141
"dependencies": {
4242
"@purestack/ts-util": "workspace:^",
43-
"prettier": "^3.8.1"
43+
"prettier": "^3.9.10"
4444
}
4545
}

‎packages/ts-format/package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
"LICENSE"
3939
],
4040
"dependencies": {
41-
"prettier": "^3.8.1",
41+
"prettier": "^3.9.10",
4242
"typescript-parser": "npm:typescript@6.0.3"
4343
},
4444
"scripts": {
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { describe, expect, it } from 'vitest'
2+
import { normalizeClosingTagSpacing } from './html'
3+
import { formatSource } from './index'
4+
5+
describe('closing tag formatting', () => {
6+
it.each(['page.mdx', 'view.ts'])(
7+
'keeps closing tags together in %s and remains stable on repeated formatting',
8+
async (filePath) => {
9+
const markup =
10+
'<Flex><a href="/a-long-documentation-path/">Read the documentation</a><BtnLink href="/another-long-path/">Start building</BtnLink></Flex>'
11+
const source = filePath.endsWith('.ts')
12+
? `const view = html\`${markup}\``
13+
: markup
14+
const options = { filePath, printWidth: 30 }
15+
const result = await formatSource(source, options)
16+
expect(result.diagnostics).toEqual([])
17+
expect(result.code).toContain('</a>')
18+
expect(result.code).toContain('</BtnLink>')
19+
expect(result.code).not.toMatch(/<\/[\w:.-]+\s+>/)
20+
expect((await formatSource(result.code, options)).code).toBe(result.code)
21+
},
22+
)
23+
24+
it('preserves whitespace between inline elements', async () => {
25+
const source =
26+
'<span>Before</span><a href="/a-long-documentation-path/">Link</a> <em>After</em>'
27+
const result = await formatSource(source, { printWidth: 25 })
28+
expect(result.diagnostics).toEqual([])
29+
expect(result.code).toContain('Before</span><a')
30+
expect(result.code).toMatch(/Link<\/a>\s+<em/)
31+
})
32+
33+
it('leaves closing-tag examples in attributes, comments and raw text untouched', () => {
34+
const source =
35+
'<Panel example="</a\n>"><!-- </a\n> --><script type="text/plain">"</a\n>"</script><textarea></a\n></textarea><a>Link</a\r\n ></Panel>'
36+
expect(normalizeClosingTagSpacing(source)).toBe(
37+
source.replace('Link</a\r\n >', 'Link</a>'),
38+
)
39+
})
40+
41+
it('joins closing tags inside PascalCase components named like raw-text tags', () => {
42+
const source =
43+
'<Title><a>Link</a\n ></Title\n><Style><em>Note</em\n></Style><title></a\n></title>'
44+
expect(normalizeClosingTagSpacing(source)).toBe(
45+
'<Title><a>Link</a></Title><Style><em>Note</em></Style><title></a\n></title>',
46+
)
47+
})
48+
})

‎packages/ts-format/src/html.ts‎

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,24 @@ import htmlPlugin from 'prettier/plugins/html'
22
import postcssPlugin from 'prettier/plugins/postcss'
33
import prettier from 'prettier/standalone'
44

5+
const RAW_TEXT_TAG =
6+
'script|style|textarea|title|xmp|iframe|noembed|noframes|plaintext'
7+
const TAG_BODY = String.raw`(?:"[^"]*"|'[^']*'|[^"'>])*>`
8+
9+
/**
10+
* Skips comments, raw text and opening tags; captures closing tags broken before `>`.
11+
* Case-sensitive so PascalCase components such as `<Title>` are not raw text.
12+
*/
13+
const BROKEN_CLOSING_TAG = new RegExp(
14+
[
15+
String.raw`<!--[\s\S]*?-->`,
16+
String.raw`<(${RAW_TEXT_TAG})\b${TAG_BODY}[\s\S]*?</\1\s*>`,
17+
String.raw`<[A-Za-z]${TAG_BODY}`,
18+
String.raw`(</[^\s>]+)\s*\n\s*>`,
19+
].join('|'),
20+
'g',
21+
)
22+
523
export interface HtmlFormattingOptions {
624
printWidth: number
725
tabSize: number
@@ -12,14 +30,24 @@ export async function formatHtmlFragment(
1230
source: string,
1331
options: HtmlFormattingOptions,
1432
) {
15-
return prettier.format(source, {
33+
const formatted = await prettier.format(source, {
1634
htmlWhitespaceSensitivity: 'css',
1735
parser: 'html',
1836
plugins: [htmlPlugin, postcssPlugin],
1937
printWidth: options.printWidth,
2038
tabWidth: options.tabSize,
2139
useTabs: options.useTabs,
2240
})
41+
return normalizeClosingTagSpacing(formatted)
42+
}
43+
44+
/** Keep closing tags readable without inserting whitespace into inline content. */
45+
export function normalizeClosingTagSpacing(source: string) {
46+
return source.replace(
47+
BROKEN_CLOSING_TAG,
48+
(match, _rawTextTag: string, closingTag?: string) =>
49+
closingTag ? `${closingTag}>` : match,
50+
)
2351
}
2452

2553
export function getLineIndent(line: string) {

‎packages/ts-html/package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,6 @@
4040
},
4141
"dependencies": {
4242
"@purestack/ts-util": "workspace:^",
43-
"prettier": "^3.8.1"
43+
"prettier": "^3.9.10"
4444
}
4545
}

‎yarn.lock‎

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1060,15 +1060,15 @@ __metadata:
10601060
resolution: "@purestack/ts-css@workspace:packages/ts-css"
10611061
dependencies:
10621062
"@purestack/ts-util": "workspace:^"
1063-
prettier: "npm:^3.8.1"
1063+
prettier: "npm:^3.9.10"
10641064
languageName: unknown
10651065
linkType: soft
10661066

10671067
"@purestack/ts-format@workspace:^, @purestack/ts-format@workspace:packages/ts-format":
10681068
version: 0.0.0-use.local
10691069
resolution: "@purestack/ts-format@workspace:packages/ts-format"
10701070
dependencies:
1071-
prettier: "npm:^3.8.1"
1071+
prettier: "npm:^3.9.10"
10721072
typescript-parser: "npm:typescript@6.0.3"
10731073
languageName: unknown
10741074
linkType: soft
@@ -1089,7 +1089,7 @@ __metadata:
10891089
resolution: "@purestack/ts-html@workspace:packages/ts-html"
10901090
dependencies:
10911091
"@purestack/ts-util": "workspace:^"
1092-
prettier: "npm:^3.8.1"
1092+
prettier: "npm:^3.9.10"
10931093
languageName: unknown
10941094
linkType: soft
10951095

@@ -4735,12 +4735,12 @@ __metadata:
47354735
languageName: node
47364736
linkType: hard
47374737

4738-
"prettier@npm:^3.8.1":
4739-
version: 3.8.1
4740-
resolution: "prettier@npm:3.8.1"
4738+
"prettier@npm:^3.9.10":
4739+
version: 3.9.10
4740+
resolution: "prettier@npm:3.9.10"
47414741
bin:
47424742
prettier: bin/prettier.cjs
4743-
checksum: 10c0/33169b594009e48f570471271be7eac7cdcf88a209eed39ac3b8d6d78984039bfa9132f82b7e6ba3b06711f3bfe0222a62a1bfb87c43f50c25a83df1b78a2c42
4743+
checksum: 10c0/0b3531593aa6fb36ac6baf629cb058bda472c59a6938774f931fe55283a07c62e6f56e621b043a82d8a3e21fc284c08c34f9e539fc70072635f79b7bae1a1067
47444744
languageName: node
47454745
linkType: hard
47464746

0 commit comments

Comments
 (0)