mirror of
https://github.com/swc-project/swc.git
synced 2024-12-24 14:16:12 +03:00
feat(html): Support boolean attributes (#4258)
This commit is contained in:
parent
e91be102d7
commit
8640c8bd43
@ -11,7 +11,7 @@ pub struct TokenAndSpan {
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Attribute {
|
||||
pub name: JsWord,
|
||||
pub value: JsWord,
|
||||
pub value: Option<JsWord>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
|
@ -98,18 +98,17 @@ where
|
||||
for attribute in attributes {
|
||||
start_tag.push(' ');
|
||||
start_tag.push_str(&attribute.name);
|
||||
|
||||
if let Some(value) = &attribute.value {
|
||||
start_tag.push('=');
|
||||
|
||||
let quote = if attribute.value.contains('"') {
|
||||
'\''
|
||||
} else {
|
||||
'"'
|
||||
};
|
||||
let quote = if value.contains('"') { '\'' } else { '"' };
|
||||
|
||||
start_tag.push(quote);
|
||||
start_tag.push_str(&attribute.value);
|
||||
start_tag.push_str(value);
|
||||
start_tag.push(quote);
|
||||
}
|
||||
}
|
||||
|
||||
if *self_closing {
|
||||
start_tag.push('/');
|
||||
@ -132,8 +131,16 @@ where
|
||||
for attribute in attributes {
|
||||
start_tag.push(' ');
|
||||
start_tag.push_str(&attribute.name);
|
||||
|
||||
if let Some(value) = &attribute.value {
|
||||
start_tag.push('=');
|
||||
start_tag.push_str(&attribute.value);
|
||||
|
||||
let quote = if value.contains('"') { '\'' } else { '"' };
|
||||
|
||||
start_tag.push(quote);
|
||||
start_tag.push_str(value);
|
||||
start_tag.push(quote);
|
||||
}
|
||||
}
|
||||
|
||||
start_tag.push('>');
|
||||
|
12
crates/swc_html_codegen/tests/fixture/attributes/input.html
Normal file
12
crates/swc_html_codegen/tests/fixture/attributes/input.html
Normal file
@ -0,0 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
|
||||
<label><input type=checkbox checked name=cheese disabled> Cheese</label>
|
||||
<label><input type=checkbox checked=checked name=cheese disabled=disabled> Cheese</label>
|
||||
<label><input type='checkbox' checked name=cheese disabled=""> Cheese</label>
|
||||
<label><input type= checkbox checked name = cheese disabled> Cheese</label>
|
||||
<label><input type =checkbox checked name = cheese disabled> Cheese</label>
|
||||
<label><input type = checkbox checked name = cheese disabled> Cheese</label>
|
||||
</body>
|
||||
</html>
|
12
crates/swc_html_codegen/tests/fixture/attributes/output.html
Normal file
12
crates/swc_html_codegen/tests/fixture/attributes/output.html
Normal file
@ -0,0 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
|
||||
<label><input type="checkbox" checked name="cheese" disabled> Cheese</label>
|
||||
<label><input type="checkbox" checked="checked" name="cheese" disabled="disabled"> Cheese</label>
|
||||
<label><input type="checkbox" checked name="cheese" disabled> Cheese</label>
|
||||
<label><input type="checkbox" checked name="cheese" disabled> Cheese</label>
|
||||
<label><input type="checkbox" checked name="cheese" disabled> Cheese</label>
|
||||
<label><input type="checkbox" checked name="cheese" disabled> Cheese</label>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
|
||||
<label><input type="checkbox" checked name="cheese" disabled> Cheese</label>
|
||||
<label><input type="checkbox" checked="checked" name="cheese" disabled="disabled"> Cheese</label>
|
||||
<label><input type="checkbox" checked name="cheese" disabled> Cheese</label>
|
||||
<label><input type="checkbox" checked name="cheese" disabled> Cheese</label>
|
||||
<label><input type="checkbox" checked name="cheese" disabled> Cheese</label>
|
||||
<label><input type="checkbox" checked name="cheese" disabled> Cheese</label>
|
||||
</body>
|
||||
</html>
|
@ -1757,6 +1757,7 @@ where
|
||||
// Start a new attribute in the current tag token. Set that attribute's name
|
||||
// to the current input character, and its value to the empty string. Switch
|
||||
// to the attribute name state.
|
||||
// We set `None` for `value` to support boolean attributes in AST
|
||||
Some(c @ '=') => {
|
||||
self.emit_error(ErrorKind::UnexpectedEqualsSignBeforeAttributeName);
|
||||
if let Some(ref mut token) = self.cur_token {
|
||||
@ -1764,13 +1765,13 @@ where
|
||||
Token::StartTag { attributes, .. } => {
|
||||
attributes.push(Attribute {
|
||||
name: c.to_string().into(),
|
||||
value: "".into(),
|
||||
value: None,
|
||||
});
|
||||
}
|
||||
Token::EndTag { attributes, .. } => {
|
||||
attributes.push(Attribute {
|
||||
name: c.to_string().into(),
|
||||
value: "".into(),
|
||||
value: None,
|
||||
});
|
||||
}
|
||||
_ => {}
|
||||
@ -1781,19 +1782,20 @@ where
|
||||
// Anything else
|
||||
// Start a new attribute in the current tag token. Set that attribute name
|
||||
// and value to the empty string. Reconsume in the attribute name state.
|
||||
// We set `None` for `value` to support boolean attributes in AST
|
||||
_ => {
|
||||
if let Some(ref mut token) = self.cur_token {
|
||||
match token {
|
||||
Token::StartTag { attributes, .. } => {
|
||||
attributes.push(Attribute {
|
||||
name: "".into(),
|
||||
value: "".into(),
|
||||
value: None,
|
||||
});
|
||||
}
|
||||
Token::EndTag { attributes, .. } => {
|
||||
attributes.push(Attribute {
|
||||
name: "".into(),
|
||||
value: "".into(),
|
||||
value: None,
|
||||
});
|
||||
}
|
||||
_ => {}
|
||||
@ -1943,6 +1945,7 @@ where
|
||||
// Anything else
|
||||
// Start a new attribute in the current tag token. Set that attribute name
|
||||
// and value to the empty string. Reconsume in the attribute name state.
|
||||
// We set `None` for `value` to support boolean attributes in AST
|
||||
_ => {
|
||||
if let Some(ref mut token) = self.cur_token {
|
||||
match token {
|
||||
@ -1950,7 +1953,7 @@ where
|
||||
| Token::EndTag { attributes, .. } => {
|
||||
attributes.push(Attribute {
|
||||
name: "".into(),
|
||||
value: "".into(),
|
||||
value: None,
|
||||
});
|
||||
}
|
||||
_ => {}
|
||||
@ -1970,9 +1973,7 @@ where
|
||||
// U+000C FORM FEED (FF)
|
||||
// U+0020 SPACE
|
||||
// Ignore the character.
|
||||
Some('\x09' | '\x0a' | '\x0c' | '\x20') => {
|
||||
self.state = State::BeforeAttributeName;
|
||||
}
|
||||
Some('\x09' | '\x0a' | '\x0c' | '\x20') => {}
|
||||
// U+0022 QUOTATION MARK (")
|
||||
// Switch to the attribute value (double-quoted) state.
|
||||
Some('"') => {
|
||||
@ -2028,10 +2029,16 @@ where
|
||||
if let Some(attribute) = attributes.last_mut() {
|
||||
let mut new_value = String::new();
|
||||
|
||||
new_value.push_str(&attribute.value);
|
||||
match &attribute.value {
|
||||
Some(value) => {
|
||||
new_value.push_str(value);
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
|
||||
new_value.push(REPLACEMENT_CHARACTER);
|
||||
|
||||
attribute.value = new_value.into();
|
||||
attribute.value = Some(new_value.into());
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
@ -2054,10 +2061,16 @@ where
|
||||
if let Some(attribute) = attributes.last_mut() {
|
||||
let mut new_value = String::new();
|
||||
|
||||
new_value.push_str(&attribute.value);
|
||||
match &attribute.value {
|
||||
Some(value) => {
|
||||
new_value.push_str(value);
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
|
||||
new_value.push(c);
|
||||
|
||||
attribute.value = new_value.into();
|
||||
attribute.value = Some(new_value.into());
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
@ -2095,10 +2108,16 @@ where
|
||||
if let Some(attribute) = attributes.last_mut() {
|
||||
let mut new_value = String::new();
|
||||
|
||||
new_value.push_str(&attribute.value);
|
||||
match &attribute.value {
|
||||
Some(value) => {
|
||||
new_value.push_str(value);
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
|
||||
new_value.push(REPLACEMENT_CHARACTER);
|
||||
|
||||
attribute.value = new_value.into();
|
||||
attribute.value = Some(new_value.into());
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
@ -2121,10 +2140,16 @@ where
|
||||
if let Some(attribute) = attributes.last_mut() {
|
||||
let mut new_value = String::new();
|
||||
|
||||
new_value.push_str(&attribute.value);
|
||||
match &attribute.value {
|
||||
Some(value) => {
|
||||
new_value.push_str(value);
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
|
||||
new_value.push(c);
|
||||
|
||||
attribute.value = new_value.into();
|
||||
attribute.value = Some(new_value.into());
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
@ -2171,10 +2196,16 @@ where
|
||||
if let Some(attribute) = attributes.last_mut() {
|
||||
let mut new_value = String::new();
|
||||
|
||||
new_value.push_str(&attribute.value);
|
||||
match &attribute.value {
|
||||
Some(value) => {
|
||||
new_value.push_str(value);
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
|
||||
new_value.push(REPLACEMENT_CHARACTER);
|
||||
|
||||
attribute.value = new_value.into();
|
||||
attribute.value = Some(new_value.into());
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
@ -2207,10 +2238,16 @@ where
|
||||
if let Some(attribute) = attributes.last_mut() {
|
||||
let mut new_value = String::new();
|
||||
|
||||
new_value.push_str(&attribute.value);
|
||||
match &attribute.value {
|
||||
Some(value) => {
|
||||
new_value.push_str(value);
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
|
||||
new_value.push(c);
|
||||
|
||||
attribute.value = new_value.into();
|
||||
attribute.value = Some(new_value.into());
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
@ -4416,10 +4453,16 @@ where
|
||||
if let Some(attribute) = attributes.last_mut() {
|
||||
let mut new_value = String::new();
|
||||
|
||||
new_value.push_str(&attribute.value);
|
||||
match &attribute.value {
|
||||
Some(value) => {
|
||||
new_value.push_str(value);
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
|
||||
new_value.push(c);
|
||||
|
||||
attribute.value = new_value.into();
|
||||
attribute.value = Some(new_value.into());
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
|
@ -56,7 +56,7 @@
|
||||
5 | <a href=https://www.w3schools.com>This is a link</a>
|
||||
`----
|
||||
|
||||
x StartTag { tag_name: Atom('a' type=inline), self_closing: false, attributes: [Attribute { name: Atom('href' type=inline), value: Atom('https://www.w3schools.com' type=dynamic) }] }
|
||||
x StartTag { tag_name: Atom('a' type=inline), self_closing: false, attributes: [Attribute { name: Atom('href' type=inline), value: Some(Atom('https://www.w3schools.com' type=dynamic)) }] }
|
||||
,-[$DIR/tests/fixture/attribute-without-quotes/input.html:5:1]
|
||||
5 | <a href=https://www.w3schools.com>This is a link</a>
|
||||
: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
@ -415,9 +415,9 @@
|
||||
8 | <img src="w3schools.jpg" alt="W3Schools.com" width="104" height="142">
|
||||
`----
|
||||
|
||||
x StartTag { tag_name: Atom('img' type=inline), self_closing: false, attributes: [Attribute { name: Atom('src' type=inline), value: Atom('w3schools.jpg' type=dynamic) }, Attribute { name:
|
||||
| Atom('alt' type=inline), value: Atom('W3Schools.com' type=dynamic) }, Attribute { name: Atom('width' type=inline), value: Atom('104' type=inline) }, Attribute { name: Atom('height' type=inline),
|
||||
| value: Atom('142' type=inline) }] }
|
||||
x StartTag { tag_name: Atom('img' type=inline), self_closing: false, attributes: [Attribute { name: Atom('src' type=inline), value: Some(Atom('w3schools.jpg' type=dynamic)) }, Attribute { name:
|
||||
| Atom('alt' type=inline), value: Some(Atom('W3Schools.com' type=dynamic)) }, Attribute { name: Atom('width' type=inline), value: Some(Atom('104' type=inline)) }, Attribute { name: Atom('height'
|
||||
| type=inline), value: Some(Atom('142' type=inline)) }] }
|
||||
,-[$DIR/tests/fixture/images/input.html:8:1]
|
||||
8 | <img src="w3schools.jpg" alt="W3Schools.com" width="104" height="142">
|
||||
: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
@ -8,7 +8,7 @@
|
||||
5 | `-> </script>
|
||||
`----
|
||||
|
||||
x StartTag { tag_name: Atom('script' type=inline), self_closing: false, attributes: [Attribute { name: Atom('type' type=static), value: Atom('text/javascript' type=dynamic) }] }
|
||||
x StartTag { tag_name: Atom('script' type=inline), self_closing: false, attributes: [Attribute { name: Atom('type' type=static), value: Some(Atom('text/javascript' type=dynamic)) }] }
|
||||
,-[$DIR/tests/fixture/script-cdata/input.html:1:1]
|
||||
1 | <script type="text/javascript">
|
||||
: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
@ -58,7 +58,7 @@
|
||||
5 | <h2 title="I'm a header">The title Attribute</h2>
|
||||
`----
|
||||
|
||||
x StartTag { tag_name: Atom('h2' type=inline), self_closing: false, attributes: [Attribute { name: Atom('title' type=inline), value: Atom('I'm a header' type=dynamic) }] }
|
||||
x StartTag { tag_name: Atom('h2' type=inline), self_closing: false, attributes: [Attribute { name: Atom('title' type=inline), value: Some(Atom('I'm a header' type=dynamic)) }] }
|
||||
,-[$DIR/tests/fixture/title-attribute/input.html:5:1]
|
||||
5 | <h2 title="I'm a header">The title Attribute</h2>
|
||||
: ^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
@ -197,7 +197,7 @@
|
||||
7 | <p title="I'm a tooltip">Mouse over this paragraph, to display the title attribute as a tooltip.</p>
|
||||
`----
|
||||
|
||||
x StartTag { tag_name: Atom('p' type=inline), self_closing: false, attributes: [Attribute { name: Atom('title' type=inline), value: Atom('I'm a tooltip' type=dynamic) }] }
|
||||
x StartTag { tag_name: Atom('p' type=inline), self_closing: false, attributes: [Attribute { name: Atom('title' type=inline), value: Some(Atom('I'm a tooltip' type=dynamic)) }] }
|
||||
,-[$DIR/tests/fixture/title-attribute/input.html:7:1]
|
||||
7 | <p title="I'm a tooltip">Mouse over this paragraph, to display the title attribute as a tooltip.</p>
|
||||
: ^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
Loading…
Reference in New Issue
Block a user