Skip to content

Commit 783f427

Browse files
authored
fix(template-compiler): consider tag name to know if attr is boolean (salesforce#1986)
* fix(template-compiler): consider tag name to know if attr is boolean * fix(template-compiler): remove capture as boolean attribute https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes * wip: review comment
1 parent 1e8aaf1 commit 783f427

File tree

9 files changed

+335
-23
lines changed

9 files changed

+335
-23
lines changed

packages/@lwc/engine-server/src/renderer.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ export const renderer: Renderer<HostNode, HostElement> = {
102102
const attrName = getAttrNameFromPropName(key);
103103

104104
// Handle all the boolean properties.
105-
if (isBooleanAttribute(attrName)) {
105+
if (isBooleanAttribute(attrName, node.name)) {
106106
return this.getAttribute(node, attrName) ?? false;
107107
}
108108

@@ -133,7 +133,7 @@ export const renderer: Renderer<HostNode, HostElement> = {
133133
const attrName = getAttrNameFromPropName(key);
134134

135135
// Handle all the boolean properties.
136-
if (isBooleanAttribute(attrName)) {
136+
if (isBooleanAttribute(attrName, node.name)) {
137137
return value === true
138138
? this.setAttribute(node, attrName, '')
139139
: this.removeAttribute(node, attrName);

packages/@lwc/shared/src/html-attributes.ts

Lines changed: 37 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,27 +7,45 @@
77
import { AriaAttrNameToPropNameMap, AriaPropNameToAttrNameMap } from './aria';
88
import { keys, hasOwnProperty } from './language';
99

10-
const BOOLEAN_ATTRIBUTES = new Set([
11-
'autofocus', // <button>, <input>, <keygen>, <select>, <textarea>
12-
'autoplay', // <audio>, <video>
13-
'capture', // <input type='file'>
14-
'checked', // <command>, <input>
15-
'disabled', // <button>, <command>, <fieldset>, <input>, <keygen>, <optgroup>, <option>, <select>, <textarea>
16-
'formnovalidate', // submit button
17-
'hidden', // Global attribute
18-
'loop', // <audio>, <bgsound>, <marquee>, <video>
19-
'multiple', // <input>, <select>
20-
'muted', // <audio>, <video>
21-
'novalidate', // <form>
22-
'open', // <details>
23-
'readonly', // <input>, <textarea>
24-
'required', // <input>, <select>, <textarea>
25-
'reversed', // <ol>
26-
'selected', // <option>
10+
/**
11+
* Maps boolean attribute name to supported tags: 'boolean attr name' => Set of allowed tag names that supports them.
12+
*/
13+
const BOOLEAN_ATTRIBUTES = new Map([
14+
['autofocus', new Set(['button', 'input', 'keygen', 'select', 'textarea'])],
15+
['autoplay', new Set(['audio', 'video'])],
16+
['checked', new Set(['command', 'input'])],
17+
[
18+
'disabled',
19+
new Set([
20+
'button',
21+
'command',
22+
'fieldset',
23+
'input',
24+
'keygen',
25+
'optgroup',
26+
'select',
27+
'textarea',
28+
]),
29+
],
30+
['formnovalidate', new Set(['button'])], // button[type=submit]
31+
['hidden', new Set()], // Global attribute
32+
['loop', new Set(['audio', 'bgsound', 'marquee', 'video'])],
33+
['multiple', new Set(['input', 'select'])],
34+
['muted', new Set(['audio', 'video'])],
35+
['novalidate', new Set(['form'])],
36+
['open', new Set(['details'])],
37+
['readonly', new Set(['input', 'textarea'])],
38+
['required', new Set(['input', 'select', 'textarea'])],
39+
['reversed', new Set(['ol'])],
40+
['selected', new Set(['option'])],
2741
]);
2842

29-
export function isBooleanAttribute(attrName: string): boolean {
30-
return BOOLEAN_ATTRIBUTES.has(attrName);
43+
export function isBooleanAttribute(attrName: string, tagName: string): boolean {
44+
const allowedTagNames = BOOLEAN_ATTRIBUTES.get(attrName);
45+
return (
46+
allowedTagNames !== undefined &&
47+
(allowedTagNames.size === 0 || allowedTagNames.has(tagName))
48+
);
3149
}
3250

3351
const GLOBAL_ATTRIBUTE = new Set([
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<template>
2+
<p hidden="true">x</p>
3+
4+
<ol reversed="true"></ol>
5+
<details open="true"></details>
6+
<form novalidate="true"></form>
7+
<video
8+
autoplay="true"
9+
loop="true"
10+
muted="true"
11+
></video>
12+
<input
13+
checked="true"
14+
multiple="true"
15+
readonly="true"
16+
required="true"
17+
title="foo" />
18+
<button
19+
autofocus="true"
20+
disabled="true"
21+
formnovalidate="true"
22+
></button>
23+
<select>
24+
<option selected="true"></option>
25+
</select>
26+
<x-foo
27+
hidden="true"
28+
></x-foo>
29+
</template>

packages/@lwc/template-compiler/src/__tests__/fixtures/attributes/boolean-attributes-invalid/expected.js

Whitespace-only changes.
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
{
2+
"warnings": [
3+
{
4+
"code": 1037,
5+
"level": 1,
6+
"location": {
7+
"column": 8,
8+
"length": 13,
9+
"line": 2,
10+
"start": 18
11+
},
12+
"message": "LWC1037: To set a boolean attributes, try <p hidden> instead of <p hidden=\"true\">. If the attribute is present, its value must either be the empty string or a value that is an ASCII case -insensitive match for the attribute's canonical name with no leading or trailing whitespace."
13+
},
14+
{
15+
"code": 1037,
16+
"level": 1,
17+
"location": {
18+
"column": 9,
19+
"length": 15,
20+
"line": 4,
21+
"start": 47
22+
},
23+
"message": "LWC1037: To set a boolean attributes, try <ol reversed> instead of <ol reversed=\"true\">. If the attribute is present, its value must either be the empty string or a value that is an ASCII case -insensitive match for the attribute's canonical name with no leading or trailing whitespace."
24+
},
25+
{
26+
"code": 1037,
27+
"level": 1,
28+
"location": {
29+
"column": 14,
30+
"length": 11,
31+
"line": 5,
32+
"start": 82
33+
},
34+
"message": "LWC1037: To set a boolean attributes, try <details open> instead of <details open=\"true\">. If the attribute is present, its value must either be the empty string or a value that is an ASCII case -insensitive match for the attribute's canonical name with no leading or trailing whitespace."
35+
},
36+
{
37+
"code": 1037,
38+
"level": 1,
39+
"location": {
40+
"column": 11,
41+
"length": 17,
42+
"line": 6,
43+
"start": 115
44+
},
45+
"message": "LWC1037: To set a boolean attributes, try <form novalidate> instead of <form novalidate=\"true\">. If the attribute is present, its value must either be the empty string or a value that is an ASCII case -insensitive match for the attribute's canonical name with no leading or trailing whitespace."
46+
},
47+
{
48+
"code": 1037,
49+
"level": 1,
50+
"location": {
51+
"column": 13,
52+
"length": 15,
53+
"line": 8,
54+
"start": 164
55+
},
56+
"message": "LWC1037: To set a boolean attributes, try <video autoplay> instead of <video autoplay=\"true\">. If the attribute is present, its value must either be the empty string or a value that is an ASCII case -insensitive match for the attribute's canonical name with no leading or trailing whitespace."
57+
},
58+
{
59+
"code": 1037,
60+
"level": 1,
61+
"location": {
62+
"column": 13,
63+
"length": 11,
64+
"line": 9,
65+
"start": 192
66+
},
67+
"message": "LWC1037: To set a boolean attributes, try <video loop> instead of <video loop=\"true\">. If the attribute is present, its value must either be the empty string or a value that is an ASCII case -insensitive match for the attribute's canonical name with no leading or trailing whitespace."
68+
},
69+
{
70+
"code": 1037,
71+
"level": 1,
72+
"location": {
73+
"column": 13,
74+
"length": 12,
75+
"line": 10,
76+
"start": 216
77+
},
78+
"message": "LWC1037: To set a boolean attributes, try <video muted> instead of <video muted=\"true\">. If the attribute is present, its value must either be the empty string or a value that is an ASCII case -insensitive match for the attribute's canonical name with no leading or trailing whitespace."
79+
},
80+
{
81+
"code": 1037,
82+
"level": 1,
83+
"location": {
84+
"column": 13,
85+
"length": 14,
86+
"line": 13,
87+
"start": 266
88+
},
89+
"message": "LWC1037: To set a boolean attributes, try <input checked> instead of <input checked=\"true\">. If the attribute is present, its value must either be the empty string or a value that is an ASCII case -insensitive match for the attribute's canonical name with no leading or trailing whitespace."
90+
},
91+
{
92+
"code": 1037,
93+
"level": 1,
94+
"location": {
95+
"column": 13,
96+
"length": 15,
97+
"line": 14,
98+
"start": 293
99+
},
100+
"message": "LWC1037: To set a boolean attributes, try <input multiple> instead of <input multiple=\"true\">. If the attribute is present, its value must either be the empty string or a value that is an ASCII case -insensitive match for the attribute's canonical name with no leading or trailing whitespace."
101+
},
102+
{
103+
"code": 1037,
104+
"level": 1,
105+
"location": {
106+
"column": 13,
107+
"length": 15,
108+
"line": 15,
109+
"start": 321
110+
},
111+
"message": "LWC1037: To set a boolean attributes, try <input readonly> instead of <input readonly=\"true\">. If the attribute is present, its value must either be the empty string or a value that is an ASCII case -insensitive match for the attribute's canonical name with no leading or trailing whitespace."
112+
},
113+
{
114+
"code": 1037,
115+
"level": 1,
116+
"location": {
117+
"column": 13,
118+
"length": 15,
119+
"line": 16,
120+
"start": 349
121+
},
122+
"message": "LWC1037: To set a boolean attributes, try <input required> instead of <input required=\"true\">. If the attribute is present, its value must either be the empty string or a value that is an ASCII case -insensitive match for the attribute's canonical name with no leading or trailing whitespace."
123+
},
124+
{
125+
"code": 1037,
126+
"level": 1,
127+
"location": {
128+
"column": 13,
129+
"length": 16,
130+
"line": 19,
131+
"start": 416
132+
},
133+
"message": "LWC1037: To set a boolean attributes, try <button autofocus> instead of <button autofocus=\"true\">. If the attribute is present, its value must either be the empty string or a value that is an ASCII case -insensitive match for the attribute's canonical name with no leading or trailing whitespace."
134+
},
135+
{
136+
"code": 1037,
137+
"level": 1,
138+
"location": {
139+
"column": 13,
140+
"length": 15,
141+
"line": 20,
142+
"start": 445
143+
},
144+
"message": "LWC1037: To set a boolean attributes, try <button disabled> instead of <button disabled=\"true\">. If the attribute is present, its value must either be the empty string or a value that is an ASCII case -insensitive match for the attribute's canonical name with no leading or trailing whitespace."
145+
},
146+
{
147+
"code": 1037,
148+
"level": 1,
149+
"location": {
150+
"column": 13,
151+
"length": 21,
152+
"line": 21,
153+
"start": 473
154+
},
155+
"message": "LWC1037: To set a boolean attributes, try <button formnovalidate> instead of <button formnovalidate=\"true\">. If the attribute is present, its value must either be the empty string or a value that is an ASCII case -insensitive match for the attribute's canonical name with no leading or trailing whitespace."
156+
},
157+
{
158+
"code": 1037,
159+
"level": 1,
160+
"location": {
161+
"column": 17,
162+
"length": 15,
163+
"line": 24,
164+
"start": 539
165+
},
166+
"message": "LWC1037: To set a boolean attributes, try <option selected> instead of <option selected=\"true\">. If the attribute is present, its value must either be the empty string or a value that is an ASCII case -insensitive match for the attribute's canonical name with no leading or trailing whitespace."
167+
},
168+
{
169+
"code": 1037,
170+
"level": 1,
171+
"location": {
172+
"column": 13,
173+
"length": 13,
174+
"line": 27,
175+
"start": 602
176+
},
177+
"message": "LWC1037: To set a boolean attributes, try <x-foo hidden> instead of <x-foo hidden=\"true\">. If the attribute is present, its value must either be the empty string or a value that is an ASCII case -insensitive match for the attribute's canonical name with no leading or trailing whitespace."
178+
}
179+
]
180+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<template>
2+
<p hidden>x</p>
3+
4+
<x-foo
5+
autofocus="true"
6+
autoplay="true"
7+
capture="true"
8+
checked="true"
9+
disabled="true"
10+
formnovalidate="true"
11+
loop="true"
12+
multiple="true"
13+
muted="true"
14+
novalidate="true"
15+
open="true"
16+
readonly="true"
17+
required="true"
18+
reversed="true"
19+
selected="true"
20+
></x-foo>
21+
<input readonly={getReadOnly} disabled title="foo" />
22+
</template>
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import _xFoo from "x/foo";
2+
import { registerTemplate } from "lwc";
3+
4+
function tmpl($api, $cmp, $slotset, $ctx) {
5+
const { t: api_text, h: api_element, c: api_custom_element } = $api;
6+
return [
7+
api_element(
8+
"p",
9+
{
10+
props: {
11+
hidden: true,
12+
},
13+
key: 0,
14+
},
15+
[api_text("x")]
16+
),
17+
api_custom_element(
18+
"x-foo",
19+
_xFoo,
20+
{
21+
props: {
22+
autofocus: "true",
23+
autoplay: "true",
24+
capture: "true",
25+
checked: "true",
26+
disabled: "true",
27+
formnovalidate: "true",
28+
loop: "true",
29+
multiple: "true",
30+
muted: "true",
31+
noValidate: "true",
32+
open: "true",
33+
readOnly: "true",
34+
required: "true",
35+
reversed: "true",
36+
selected: "true",
37+
},
38+
key: 1,
39+
},
40+
[]
41+
),
42+
api_element(
43+
"input",
44+
{
45+
attrs: {
46+
title: "foo",
47+
},
48+
props: {
49+
readOnly: $cmp.getReadOnly,
50+
disabled: true,
51+
},
52+
key: 2,
53+
},
54+
[]
55+
),
56+
];
57+
}
58+
59+
export default registerTemplate(tmpl);
60+
tmpl.stylesheets = [];
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"warnings": []
3+
}

0 commit comments

Comments
 (0)