Skip to content

Commit

Permalink
fix: prettify formatting issues
Browse files Browse the repository at this point in the history
- default embedded lang formatting
- rewrite closing slash only for known self closing tags
- fix style="" attr values broken on multiple lines
- fix broken <pre\n> closing tags
- undo ' escaping in attr values
- fix some attrs and closing > broken on a separate line
  • Loading branch information
cossssmin committed Jan 26, 2024
1 parent a72a5d4 commit a1be18b
Show file tree
Hide file tree
Showing 5 changed files with 103 additions and 9 deletions.
42 changes: 38 additions & 4 deletions src/transformers/prettify.js
Expand Up @@ -7,9 +7,25 @@ module.exports = async (html, config = {}, direct = false) => {
const defaultConfig = {
parser: 'html',
printWidth: 500,
embeddedLanguageFormatting: 'off',
htmlWhitespaceSensitivity: 'ignore',
xmlMode: get(config, 'posthtml.options.xmlMode', false)
xmlMode: get(config, 'posthtml.options.xmlMode', false),
rewriteSelfClosing: true,
selfClosingTags: [
'area',
'base',
'br',
'col',
'embed',
'hr',
'img',
'input',
'link',
'meta',
'param',
'source',
'track',
'wbr'
]
}

// Don't prettify if not explicitly enabled in config
Expand All @@ -27,9 +43,27 @@ module.exports = async (html, config = {}, direct = false) => {
}

const reFormat = (html, config) => {
if (/<!doctype html>/i.test(html) && !config.xmlMode) {
html = html.replace(/<(.+?)(\s\/)>/g, '<$1>')
if (/<!doctype html>/i.test(html) && !config.xmlMode && config.rewriteSelfClosing) {
html = html.replace(new RegExp(`<(${config.selfClosingTags.join('|')})\s?([^>]*?)\s?\/>`, 'g'), (match, p1, p2) => {
return `<${p1}${p2.trimEnd()}>`
})
}

return html
// Fix style="" attributes broken down on multiple lines
.replace(/(\s+style="\s+)([\s\S]*?)(\s+")/g, (match, p1, p2, p3) => {
return p1.replace(/\n\s+?(style)/g, ' $1').trimEnd()
+ p2.replace(/\s+/g, ' ').trim()
+ p3.trim()
})
// Fix closing </pre> tags broken down on multiple lines (</pre>\n\s+>)
.replace(/(<\/pre)\s+>/g, '$1>')
// Undo escaping of quotes in attribute values
.replace(/="(.*?)"/g, (match, p1) => {
return `="${p1.replace(/&quot;/g, '\'')}"`
})
// Fix <tag \n\s+{attrs}\n\s+> => <tag {attrs}>
.replace(/<([^>]+)\n\s*([^>]+)\n\s*>/g, '<$1 $2>')
// Fix <tag {attrs}\n[\s\t]*> => <tag {attrs}>
.replace(/<([^>]+)\n\s*>/g, '<$1>')
}
23 changes: 23 additions & 0 deletions test/expected/transformers/prettify.html
@@ -0,0 +1,23 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<link href="style.css" rel="stylesheet">
</head>
<body>
<br>
<br>
<img src="image.png">
<img src="image.png">
<hr class="my-4">
<hr class="my-4">
<div style="font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif; line-height: 1;">
<!--[if mso]>
<v:rect stroke="f" fillcolor="none" style="width: 600px" xmlns:v="urn:schemas-microsoft-com:vml">
<v:fill type="frame" src="https://via.placeholder.com/600x400" />
<v:textbox inset="0,0,0,0" style="mso-fit-shape-to-text: true"><div><![endif]-->
<img src="image.png">
<!--[if mso]></div></v:textbox></v:rect><![endif]-->
</div>
</body>
</html>
31 changes: 31 additions & 0 deletions test/fixtures/transformers/prettify.html
@@ -0,0 +1,31 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<link href="style.css" rel="stylesheet" />
</head>
<body>
<br />
<br/>
<img src="image.png" />
<img src="image.png"/>
<hr class="my-4" />
<hr class="my-4"/>
<div
style="
font-family:
ui-sans-serif,
system-ui,
-apple-system,
&quot;Segoe UI&quot;,
sans-serif;
line-height: 1; ">
<!--[if mso]>
<v:rect stroke="f" fillcolor="none" style="width: 600px" xmlns:v="urn:schemas-microsoft-com:vml">
<v:fill type="frame" src="https://via.placeholder.com/600x400" />
<v:textbox inset="0,0,0,0" style="mso-fit-shape-to-text: true"><div><![endif]-->
<img src="image.png" />
<!--[if mso]></div></v:textbox></v:rect><![endif]-->
</div>
</body>
</html>
15 changes: 10 additions & 5 deletions test/test-transformers.js
Expand Up @@ -240,13 +240,18 @@ test('base URL (object)', async t => {
})

test('prettify', async t => {
// With custom object config
const html = await Maizzle.prettify('<div><p>with options</p></div>', {printWidth: 12})
t.is(html, '<div>\n <p>\n with\n options\n </p>\n</div>\n')
const source = await fixture('prettify')

const html = await Maizzle.prettify(source, {
printWidth: 500,
rewriteSelfClosing: true
})

t.is(html.trim(), (await expected('prettify')))

// `prettify: true`
const html2 = await Maizzle.prettify('<!doctype html><meta name="test" /><div><p>prettify: true</p></div>', true)
t.is(html2, '<!doctype html>\n<meta name="test">\n<div><p>prettify: true</p></div>\n')
const html2 = await Maizzle.prettify('<!doctype html><meta name="test" /><custom /><div><p>prettify: true</p></div>', true)
t.is(html2, '<!doctype html>\n<meta name="test">\n<custom />\n<div><p>prettify: true</p></div>\n')

// No config
const html3 = await Maizzle.prettify('<div><p>test</p></div>')
Expand Down
1 change: 1 addition & 0 deletions xo.config.js
Expand Up @@ -6,6 +6,7 @@ module.exports = {
'max-params': 0,
'brace-style': 0,
'no-lonely-if': 0,
'no-useless-escape': 0,
'unicorn/no-reduce': 0,
'import/extensions': 0,
'operator-linebreak': 0,
Expand Down

0 comments on commit a1be18b

Please sign in to comment.