Support format html event attributes (#17909)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: fisker <lionkay@gmail.com>
This commit is contained in:
15 files changed
+291
-38
No files matched your search
@@ -0,0 +1,26 @@
|
||||
#### Format inline event handler (#17909 by @kovsu)
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
```html
|
||||
<!-- Input -->
|
||||
<button
|
||||
type="button"
|
||||
onclick="console .log( 'Hello, this is my old-fashioned event handler!')"
|
||||
>Press me</button>
|
||||
|
||||
<!-- Prettier stable -->
|
||||
<button
|
||||
type="button"
|
||||
onclick="console .log( 'Hello, this is my old-fashioned event handler!')"
|
||||
>
|
||||
Press me
|
||||
</button>
|
||||
|
||||
<!-- Prettier main -->
|
||||
<button
|
||||
type="button"
|
||||
onclick="console.log('Hello, this is my old-fashioned event handler!')"
|
||||
>
|
||||
Press me
|
||||
</button>
|
||||
```
|
||||
@@ -38,6 +38,7 @@
|
||||
"@babel/types": "7.28.4",
|
||||
"@glimmer/syntax": "0.95.0",
|
||||
"@prettier/cli": "0.9.0",
|
||||
"@prettier/html-event-attributes": "0.0.1",
|
||||
"@prettier/html-tags": "1.1.0",
|
||||
"@prettier/parse-srcset": "3.1.0",
|
||||
"@typescript-eslint/typescript-estree": "8.44.1",
|
||||
|
||||
@@ -2,6 +2,7 @@ import { group } from "../../document/builders.js";
|
||||
import { mapDoc } from "../../document/utils.js";
|
||||
import printAngularAttribute from "./angular-attributes.js";
|
||||
import printClassNames from "./class-names.js";
|
||||
import printEventHandler from "./event-handler.js";
|
||||
import printSrcset from "./srcset.js";
|
||||
import { printStyleAttribute } from "./style.js";
|
||||
import printVueAttribute from "./vue-attributes.js";
|
||||
@@ -36,6 +37,7 @@ function printAttribute(path, options) {
|
||||
for (const getValuePrinter of [
|
||||
printSrcset,
|
||||
printStyleAttribute,
|
||||
printEventHandler,
|
||||
printClassNames,
|
||||
printVueAttribute,
|
||||
printAngularAttribute,
|
||||
|
||||
@@ -2,14 +2,16 @@ import { getUnescapedAttributeValue } from "../utils/index.js";
|
||||
|
||||
function printClassNames(path, options) {
|
||||
const { node } = path;
|
||||
const value = getUnescapedAttributeValue(node);
|
||||
|
||||
if (
|
||||
node.fullName === "class" &&
|
||||
!options.parentParser &&
|
||||
!value.includes("{{")
|
||||
options.parentParser ||
|
||||
node.fullName !== "class" ||
|
||||
node.value.includes("{{")
|
||||
) {
|
||||
return () => value.trim().split(/\s+/u).join(" ");
|
||||
return;
|
||||
}
|
||||
|
||||
return () => getUnescapedAttributeValue(node).trim().split(/\s+/u).join(" ");
|
||||
}
|
||||
|
||||
export default printClassNames;
|
||||
@@ -0,0 +1,27 @@
|
||||
import htmlEventAttributesArray from "@prettier/html-event-attributes";
|
||||
import { getUnescapedAttributeValue } from "../utils/index.js";
|
||||
import { formatAttributeValue } from "./utils.js";
|
||||
|
||||
const htmlEventAttributes = new Set(htmlEventAttributesArray);
|
||||
|
||||
function printEventHandler(path, options) {
|
||||
const { node } = path;
|
||||
|
||||
if (
|
||||
options.parentParser ||
|
||||
!htmlEventAttributes.has(node.fullName) ||
|
||||
node.value.includes("{{")
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
return (textToDoc) =>
|
||||
formatAttributeValue(
|
||||
getUnescapedAttributeValue(node),
|
||||
textToDoc,
|
||||
{ parser: "babel", __isHtmlInlineEventHandler: true },
|
||||
() => false,
|
||||
);
|
||||
}
|
||||
|
||||
export default printEventHandler;
|
||||
@@ -3,17 +3,21 @@ import { printExpand } from "./utils.js";
|
||||
|
||||
function printStyleAttribute(path, options) {
|
||||
const { node } = path;
|
||||
const text = getUnescapedAttributeValue(path.node).trim();
|
||||
if (
|
||||
node.fullName === "style" &&
|
||||
!options.parentParser &&
|
||||
!text.includes("{{")
|
||||
node.fullName !== "style" ||
|
||||
options.parentParser ||
|
||||
node.value.includes("{{")
|
||||
) {
|
||||
return async (textToDoc) =>
|
||||
printExpand(
|
||||
await textToDoc(text, { parser: "css", __isHTMLStyleAttribute: true }),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
return async (textToDoc) =>
|
||||
printExpand(
|
||||
await textToDoc(getUnescapedAttributeValue(node), {
|
||||
parser: "css",
|
||||
__isHTMLStyleAttribute: true,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
export { printStyleAttribute };
|
||||
@@ -4,28 +4,38 @@ import {
|
||||
unwrapVueEventBindingTsNode,
|
||||
} from "../utils/vue-event-binding.js";
|
||||
import {
|
||||
isSingleHtmlEventHandlerExpressionStatement,
|
||||
isSingleJsxExpressionStatementInMarkdown,
|
||||
isSingleVueEventBindingExpressionStatement,
|
||||
} from "./semicolon.js";
|
||||
|
||||
function printExpressionStatement(path, options, print) {
|
||||
const parts = [print("expression")];
|
||||
|
||||
function shouldPrintSemicolon(path, options) {
|
||||
if (isSingleVueEventBindingExpressionStatement(path, options)) {
|
||||
const expression = unwrapVueEventBindingTsNode(path.node.expression);
|
||||
if (
|
||||
return (
|
||||
isVueEventBindingFunctionExpression(expression) ||
|
||||
isVueEventBindingMemberExpression(expression)
|
||||
) {
|
||||
parts.push(";");
|
||||
}
|
||||
} else if (isSingleJsxExpressionStatementInMarkdown(path, options)) {
|
||||
// Do not append semicolon after the only JSX element in a program
|
||||
} else if (options.semi) {
|
||||
parts.push(";");
|
||||
);
|
||||
}
|
||||
|
||||
return parts;
|
||||
if (!options.semi) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
// Do not append semicolon after the only JSX element in a program
|
||||
isSingleJsxExpressionStatementInMarkdown(path, options) ||
|
||||
// Do not append semicolon after the only HTML event binding expression in a program
|
||||
isSingleHtmlEventHandlerExpressionStatement(path, options)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function printExpressionStatement(path, options, print) {
|
||||
return [print("expression"), shouldPrintSemicolon(path, options) ? ";" : ""];
|
||||
}
|
||||
|
||||
export { printExpressionStatement };
|
||||
@@ -10,7 +10,8 @@ function shouldPrintLeadingSemicolon(path, options) {
|
||||
if (
|
||||
options.semi ||
|
||||
isSingleJsxExpressionStatementInMarkdown(path, options) ||
|
||||
isSingleVueEventBindingExpressionStatement(path, options)
|
||||
isSingleVueEventBindingExpressionStatement(path, options) ||
|
||||
isSingleHtmlEventHandlerExpressionStatement(path, options)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
@@ -89,27 +90,35 @@ function expressionNeedsASIProtection(path, options) {
|
||||
);
|
||||
}
|
||||
|
||||
function isSingleJsxExpressionStatementInMarkdown({ node, parent }, options) {
|
||||
const isSingleExpressionStatement = ({ node, parent }) =>
|
||||
node.type === "ExpressionStatement" &&
|
||||
parent.type === "Program" &&
|
||||
parent.body.length === 1;
|
||||
|
||||
function isSingleJsxExpressionStatementInMarkdown(path, options) {
|
||||
return (
|
||||
(options.parentParser === "markdown" || options.parentParser === "mdx") &&
|
||||
node.type === "ExpressionStatement" &&
|
||||
isJsxElement(node.expression) &&
|
||||
parent.type === "Program" &&
|
||||
parent.body.length === 1
|
||||
isSingleExpressionStatement(path) &&
|
||||
isJsxElement(path.node.expression)
|
||||
);
|
||||
}
|
||||
|
||||
function isSingleVueEventBindingExpressionStatement({ node, parent }, options) {
|
||||
function isSingleHtmlEventHandlerExpressionStatement(path, options) {
|
||||
return (
|
||||
options.__isHtmlInlineEventHandler && isSingleExpressionStatement(path)
|
||||
);
|
||||
}
|
||||
|
||||
function isSingleVueEventBindingExpressionStatement(path, options) {
|
||||
return (
|
||||
(options.parser === "__vue_event_binding" ||
|
||||
options.parser === "__vue_ts_event_binding") &&
|
||||
node.type === "ExpressionStatement" &&
|
||||
parent.type === "Program" &&
|
||||
parent.body.length === 1
|
||||
isSingleExpressionStatement(path)
|
||||
);
|
||||
}
|
||||
|
||||
export {
|
||||
isSingleHtmlEventHandlerExpressionStatement,
|
||||
isSingleJsxExpressionStatementInMarkdown,
|
||||
isSingleVueEventBindingExpressionStatement,
|
||||
shouldPrintLeadingSemicolon,
|
||||
|
||||
@@ -537,6 +537,98 @@ printWidth: 80
|
||||
================================================================================
|
||||
`;
|
||||
|
||||
exports[`event-attributes.html format 1`] = `
|
||||
====================================options=====================================
|
||||
parsers: ["html"]
|
||||
printWidth: 80
|
||||
| printWidth
|
||||
=====================================input======================================
|
||||
<button onclick="alert( '1')">click me!</button>
|
||||
|
||||
<div onclick="handleClick( event )" onmouseover="showTooltip( this )" onmouseout="hideTooltip( )">Hover me</div>
|
||||
|
||||
<img onerror='handleError( this )' onload="imageLoaded( event )" />
|
||||
|
||||
<button onclick="(event) => { console.log('clicked'); handleClick( event ); }">Modern JS</button>
|
||||
|
||||
<div onclick="processData( { id: 123, name: 'test' }, [ 1, 2, 3 ] )">Data</div>
|
||||
|
||||
<button onclick="api.user.get( 123 ).then( data => updateUI( data ) )">API Call</button>
|
||||
|
||||
<input onfocus="highlightField( this )" onblur="validateField( this.value )" />
|
||||
<select onchange="filterResults( this.value, this.selectedIndex )">
|
||||
<option>Option 1</option>
|
||||
</select>
|
||||
|
||||
<button onclick="
|
||||
alert(1); alert(2);
|
||||
">Click</button>
|
||||
|
||||
<button onclick="
|
||||
return false
|
||||
">Click</button>
|
||||
|
||||
<button onclick="
|
||||
console.log(arguments)
|
||||
">Click</button>
|
||||
|
||||
<button onclick="
|
||||
console.log(onclick)
|
||||
">Click</button>
|
||||
|
||||
=====================================output=====================================
|
||||
<button onclick="alert('1')">click me!</button>
|
||||
|
||||
<div
|
||||
onclick="handleClick(event)"
|
||||
onmouseover="showTooltip(this)"
|
||||
onmouseout="hideTooltip()"
|
||||
>
|
||||
Hover me
|
||||
</div>
|
||||
|
||||
<img onerror="handleError(this)" onload="imageLoaded(event)" />
|
||||
|
||||
<button
|
||||
onclick="
|
||||
(event) => {
|
||||
console.log('clicked');
|
||||
handleClick(event);
|
||||
}
|
||||
"
|
||||
>
|
||||
Modern JS
|
||||
</button>
|
||||
|
||||
<div onclick="processData({ id: 123, name: 'test' }, [1, 2, 3])">Data</div>
|
||||
|
||||
<button onclick="api.user.get(123).then((data) => updateUI(data))">
|
||||
API Call
|
||||
</button>
|
||||
|
||||
<input onfocus="highlightField(this)" onblur="validateField(this.value)" />
|
||||
<select onchange="filterResults(this.value, this.selectedIndex)">
|
||||
<option>Option 1</option>
|
||||
</select>
|
||||
|
||||
<button
|
||||
onclick="
|
||||
alert(1);
|
||||
alert(2);
|
||||
"
|
||||
>
|
||||
Click
|
||||
</button>
|
||||
|
||||
<button onclick="return false;">Click</button>
|
||||
|
||||
<button onclick="console.log(arguments)">Click</button>
|
||||
|
||||
<button onclick="console.log(onclick)">Click</button>
|
||||
|
||||
================================================================================
|
||||
`;
|
||||
|
||||
exports[`single-quotes.html format 1`] = `
|
||||
====================================options=====================================
|
||||
parsers: ["html"]
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
<button onclick="alert( '1')">click me!</button>
|
||||
|
||||
<div onclick="handleClick( event )" onmouseover="showTooltip( this )" onmouseout="hideTooltip( )">Hover me</div>
|
||||
|
||||
<img onerror='handleError( this )' onload="imageLoaded( event )" />
|
||||
|
||||
<button onclick="(event) => { console.log('clicked'); handleClick( event ); }">Modern JS</button>
|
||||
|
||||
<div onclick="processData( { id: 123, name: 'test' }, [ 1, 2, 3 ] )">Data</div>
|
||||
|
||||
<button onclick="api.user.get( 123 ).then( data => updateUI( data ) )">API Call</button>
|
||||
|
||||
<input onfocus="highlightField( this )" onblur="validateField( this.value )" />
|
||||
<select onchange="filterResults( this.value, this.selectedIndex )">
|
||||
<option>Option 1</option>
|
||||
</select>
|
||||
|
||||
<button onclick="
|
||||
alert(1); alert(2);
|
||||
">Click</button>
|
||||
|
||||
<button onclick="
|
||||
return false
|
||||
">Click</button>
|
||||
|
||||
<button onclick="
|
||||
console.log(arguments)
|
||||
">Click</button>
|
||||
|
||||
<button onclick="
|
||||
console.log(onclick)
|
||||
">Click</button>
|
||||
@@ -0,0 +1,27 @@
|
||||
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
|
||||
|
||||
exports[`event-attributes.html - {"semi":false} format 1`] = `
|
||||
====================================options=====================================
|
||||
parsers: ["html"]
|
||||
printWidth: 80
|
||||
semi: false
|
||||
| printWidth
|
||||
=====================================input======================================
|
||||
<button
|
||||
onclick="[foo, bar].forEach((fn) => void fn())"
|
||||
></button>
|
||||
<button
|
||||
onclick="[foo, bar].forEach((fn) => void fn()); alert(1)"
|
||||
></button>
|
||||
|
||||
=====================================output=====================================
|
||||
<button onclick="[foo, bar].forEach((fn) => void fn())"></button>
|
||||
<button
|
||||
onclick="
|
||||
;[foo, bar].forEach((fn) => void fn())
|
||||
alert(1)
|
||||
"
|
||||
></button>
|
||||
|
||||
================================================================================
|
||||
`;
|
||||
@@ -0,0 +1,6 @@
|
||||
<button
|
||||
onclick="[foo, bar].forEach((fn) => void fn())"
|
||||
></button>
|
||||
<button
|
||||
onclick="[foo, bar].forEach((fn) => void fn()); alert(1)"
|
||||
></button>
|
||||
@@ -0,0 +1 @@
|
||||
runFormatTest(import.meta, ["html"], { semi: false });
|
||||
@@ -656,21 +656,27 @@ printWidth: 1
|
||||
<menu>
|
||||
<li>
|
||||
<button
|
||||
onclick="copy()"
|
||||
onclick="
|
||||
copy()
|
||||
"
|
||||
>
|
||||
Copy
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button
|
||||
onclick="cut()"
|
||||
onclick="
|
||||
cut()
|
||||
"
|
||||
>
|
||||
Cut
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button
|
||||
onclick="paste()"
|
||||
onclick="
|
||||
paste()
|
||||
"
|
||||
>
|
||||
Paste
|
||||
</button>
|
||||
|
||||
@@ -2296,6 +2296,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@prettier/html-event-attributes@npm:0.0.1":
|
||||
version: 0.0.1
|
||||
resolution: "@prettier/html-event-attributes@npm:0.0.1"
|
||||
checksum: 10/28c88092b06bf5a0ed2b9e8e4b7abf55eff409e64e0ebc958dd115f8c06c0667c2cb2df1bd4c55d28df58d481eadc7465e7138566b91288cafefc4b5ea048eb9
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@prettier/html-tags@npm:1.1.0":
|
||||
version: 1.1.0
|
||||
resolution: "@prettier/html-tags@npm:1.1.0"
|
||||
@@ -7536,6 +7543,7 @@ __metadata:
|
||||
"@eslint/js": "npm:9.35.0"
|
||||
"@glimmer/syntax": "npm:0.95.0"
|
||||
"@prettier/cli": "npm:0.9.0"
|
||||
"@prettier/html-event-attributes": "npm:0.0.1"
|
||||
"@prettier/html-tags": "npm:1.1.0"
|
||||
"@prettier/parse-srcset": "npm:3.1.0"
|
||||
"@stylistic/eslint-plugin": "npm:5.3.1"
|
||||
|
||||
Reference in new issue
Block a user