1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
//! vue/single-style-block
//!
//! Recommend having a single style block in Vue SFCs.
//!
//! Multiple style blocks can make CSS harder to organize and maintain.
//! Using a single style block encourages better structure and makes
//! it easier to understand the component's styling.
//!
//! ## Exceptions
//!
//! This rule does not warn when style blocks have different purposes:
//! - One scoped and one global style block
//!
//! ## Examples
//!
//! Bad:
//! ```vue
//! <style scoped>
//! .component { color: red; }
//! </style>
//!
//! <style scoped>
//! .other { color: blue; }
//! </style>
//! ```
//!
//! Good:
//! ```vue
//! <style scoped>
//! .component { color: red; }
//! .other { color: blue; }
//! </style>
//! ```
use crate::context::LintContext;
use crate::diagnostic::{LintDiagnostic, Severity};
use crate::rule::{Rule, RuleCategory, RuleMeta};
use vize_relief::ast::RootNode;
static META: RuleMeta = RuleMeta {
name: "vue/single-style-block",
description: "Recommend having a single style block",
category: RuleCategory::Recommended,
fixable: false,
default_severity: Severity::Warning,
};
/// Single style block rule
#[derive(Default)]
pub struct SingleStyleBlock;
impl Rule for SingleStyleBlock {
fn meta(&self) -> &'static RuleMeta {
&META
}
fn run_on_template<'a>(&self, ctx: &mut LintContext<'a>, _root: &RootNode<'a>) {
let source = ctx.source;
// Find all <style tags and collect their info
#[derive(Debug)]
struct StyleBlock {
pos: usize,
scoped: bool,
}
let mut style_blocks: Vec<StyleBlock> = Vec::new();
let mut pos = 0;
while let Some(style_start) = source[pos..].find("<style") {
let abs_pos = pos + style_start;
pos = abs_pos + 6;
// Find the closing >
if let Some(tag_end) = source[abs_pos..].find('>') {
let tag_content = &source[abs_pos..abs_pos + tag_end + 1];
let scoped = tag_content.contains("scoped");
style_blocks.push(StyleBlock {
pos: abs_pos,
scoped,
});
}
}
// If there are multiple style blocks
if style_blocks.len() > 1 {
// Check if they're all the same type (all scoped or all non-scoped)
let all_scoped = style_blocks.iter().all(|s| s.scoped);
let all_non_scoped = style_blocks.iter().all(|s| !s.scoped);
// Only warn if all style blocks have the same scoped status
// (having one scoped and one global is a valid pattern)
if all_scoped || all_non_scoped {
// Warn on the second and subsequent style blocks
for style in style_blocks.iter().skip(1) {
ctx.report(
LintDiagnostic::warn(
META.name,
"Consider merging multiple style blocks into one",
style.pos as u32,
(style.pos + 6) as u32, // "<style"
)
.with_help("Multiple style blocks of the same type can be merged for better organization"),
);
}
}
}
}
}
#[cfg(test)]
mod tests {
// Tests would need SFC-level testing infrastructure
}