Skip to content

Commit 223f06c

Browse files
committed
feat(html_analyze): implement noSyncScripts
1 parent 8bc4f99 commit 223f06c

File tree

9 files changed

+155
-6
lines changed

9 files changed

+155
-6
lines changed

crates/biome_html_analyze/src/lint/nursery.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@
33
//! Generated file, do not edit by hand, see `xtask/codegen`
44
55
use biome_analyze::declare_lint_group;
6+
pub mod no_sync_scripts;
67
pub mod use_vue_valid_v_bind;
78
pub mod use_vue_valid_v_else;
89
pub mod use_vue_valid_v_else_if;
910
pub mod use_vue_valid_v_html;
1011
pub mod use_vue_valid_v_if;
1112
pub mod use_vue_valid_v_on;
12-
declare_lint_group! { pub Nursery { name : "nursery" , rules : [self :: use_vue_valid_v_bind :: UseVueValidVBind , self :: use_vue_valid_v_else :: UseVueValidVElse , self :: use_vue_valid_v_else_if :: UseVueValidVElseIf , self :: use_vue_valid_v_html :: UseVueValidVHtml , self :: use_vue_valid_v_if :: UseVueValidVIf , self :: use_vue_valid_v_on :: UseVueValidVOn ,] } }
13+
declare_lint_group! { pub Nursery { name : "nursery" , rules : [self :: no_sync_scripts :: NoSyncScripts , self :: use_vue_valid_v_bind :: UseVueValidVBind , self :: use_vue_valid_v_else :: UseVueValidVElse , self :: use_vue_valid_v_else_if :: UseVueValidVElseIf , self :: use_vue_valid_v_html :: UseVueValidVHtml , self :: use_vue_valid_v_if :: UseVueValidVIf , self :: use_vue_valid_v_on :: UseVueValidVOn ,] } }
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
use biome_analyze::{
2+
Ast, Rule, RuleDiagnostic, RuleSource, context::RuleContext, declare_lint_rule,
3+
};
4+
use biome_console::markup;
5+
use biome_html_syntax::HtmlOpeningElement;
6+
use biome_rowan::AstNode;
7+
use biome_rule_options::no_sync_scripts::NoSyncScriptsOptions;
8+
9+
declare_lint_rule! {
10+
/// Prevent the usage of synchronous scripts.
11+
///
12+
/// A synchronous script can impact your webpage performance, read more on how to [Efficiently load third-party JavaScript](https://web.dev/articles/efficiently-load-third-party-javascript).
13+
///
14+
/// ## Examples
15+
///
16+
/// ### Invalid
17+
///
18+
/// ```html,expect_diagnostic
19+
/// <script src=""></script>
20+
/// ```
21+
///
22+
/// ### Valid
23+
///
24+
/// ```html
25+
/// <script src="" async></script>
26+
/// <script src="" defer></script>
27+
/// ```
28+
///
29+
pub NoSyncScripts {
30+
version: "next",
31+
name: "noSyncScripts",
32+
language: "html",
33+
recommended: false,
34+
sources: &[RuleSource::EslintNext("no-sync-scripts").same()],
35+
}
36+
}
37+
38+
impl Rule for NoSyncScripts {
39+
type Query = Ast<HtmlOpeningElement>;
40+
type State = ();
41+
type Signals = Option<Self::State>;
42+
type Options = NoSyncScriptsOptions;
43+
44+
fn run(ctx: &RuleContext<Self>) -> Option<Self::State> {
45+
let binding = ctx.query();
46+
47+
let name = binding.name().ok()?;
48+
let value_token = name.value_token().ok()?;
49+
if value_token.text_trimmed() != "script" {
50+
return None;
51+
}
52+
53+
let attributes = binding.attributes();
54+
if attributes.find_by_name("src").is_none()
55+
|| attributes.find_by_name("async").is_some()
56+
|| attributes.find_by_name("defer").is_some()
57+
{
58+
return None;
59+
}
60+
61+
Some(())
62+
}
63+
64+
fn diagnostic(ctx: &RuleContext<Self>, _state: &Self::State) -> Option<RuleDiagnostic> {
65+
let node = ctx.query();
66+
Some(
67+
RuleDiagnostic::new(
68+
rule_category!(),
69+
node.range(),
70+
markup! {
71+
"Unexpected synchronous script."
72+
},
73+
)
74+
.note(markup! {
75+
"Synchronous scripts can impact your webpage performance. Add the \"async\" or \"defer\" attribute."
76+
}),
77+
)
78+
}
79+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<script src=""></script>
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
---
2+
source: crates/biome_html_analyze/tests/spec_tests.rs
3+
expression: invalid.html
4+
---
5+
# Input
6+
```html
7+
<script src=""></script>
8+
9+
```
10+
11+
# Diagnostics
12+
```
13+
invalid.html:1:1 lint/nursery/noSyncScripts ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
14+
15+
i Unexpected synchronous script.
16+
17+
> 1 │ <script src=""></script>
18+
│ ^^^^^^^^^^^^^^^
19+
2 │
20+
21+
i Synchronous scripts can impact your webpage performance. Add the "async" or "defer" attribute.
22+
23+
24+
```
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/* should not generate diagnostics */
2+
<noscript></noscript>
3+
4+
<script></script>
5+
6+
<script src="" async></script>
7+
8+
<script src="" defer></script>
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
source: crates/biome_html_analyze/tests/spec_tests.rs
3+
expression: valid.html
4+
---
5+
# Input
6+
```html
7+
/* should not generate diagnostics */
8+
<noscript></noscript>
9+
10+
<script></script>
11+
12+
<script src="" async></script>
13+
14+
<script src="" defer></script>
15+
16+
```

crates/biome_html_syntax/src/attr_ext.rs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1-
use crate::{AnyHtmlAttributeInitializer, inner_string_text};
2-
use biome_rowan::Text;
1+
use crate::{
2+
AnyHtmlAttribute, AnyHtmlAttributeInitializer, HtmlAttribute, HtmlAttributeList,
3+
inner_string_text,
4+
};
5+
use biome_rowan::{AstNodeList, Text};
36

47
impl AnyHtmlAttributeInitializer {
58
/// Returns the string value of the attribute, if available, without quotes.
@@ -15,3 +18,17 @@ impl AnyHtmlAttributeInitializer {
1518
}
1619
}
1720
}
21+
22+
impl HtmlAttributeList {
23+
pub fn find_by_name(&self, name_to_lookup: &str) -> Option<HtmlAttribute> {
24+
self.iter().find_map(|attribute| {
25+
if let AnyHtmlAttribute::HtmlAttribute(attribute) = attribute
26+
&& let Ok(name) = attribute.name()
27+
&& name.value_token().ok()?.text_trimmed() == name_to_lookup
28+
{
29+
return Some(attribute);
30+
}
31+
None
32+
})
33+
}
34+
}

crates/biome_js_analyze/src/lint/nursery/no_sync_scripts.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,10 @@ fn validate_name(node: &AnyJsxElementName) -> Option<()> {
7070
}
7171

7272
fn validate_attributes(list: &JsxAttributeList) -> Option<()> {
73-
list.find_by_name("src")?;
74-
75-
if list.find_by_name("async").is_some() || list.find_by_name("defer").is_some() {
73+
if list.find_by_name("src").is_none()
74+
|| list.find_by_name("async").is_some()
75+
|| list.find_by_name("defer").is_some()
76+
{
7677
return None;
7778
}
7879

justfile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,10 +144,12 @@ test-lintrule name:
144144
just _touch crates/biome_json_analyze/tests/spec_tests.rs
145145
just _touch crates/biome_css_analyze/tests/spec_tests.rs
146146
just _touch crates/biome_graphql_analyze/tests/spec_tests.rs
147+
just _touch crates/biome_html_analyze/tests/spec_tests.rs
147148
cargo test -p biome_js_analyze -- {{snakecase(name)}} --show-output
148149
cargo test -p biome_json_analyze -- {{snakecase(name)}} --show-output
149150
cargo test -p biome_css_analyze -- {{snakecase(name)}} --show-output
150151
cargo test -p biome_graphql_analyze -- {{snakecase(name)}} --show-output
152+
cargo test -p biome_html_analyze -- {{snakecase(name)}} --show-output
151153

152154
# Tests a lint rule. The name of the rule needs to be camel case
153155
test-transformation name:

0 commit comments

Comments
 (0)