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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
//! vue/warn-custom-block
//!
//! Warn about custom blocks in SFC files.
//!
//! Custom blocks (blocks other than `<script>`, `<template>`, `<style>`)
//! require additional configuration and tooling support. This rule warns
//! about their usage to ensure they are intentional.
//!
//! ## Common Custom Blocks
//!
//! - `<i18n>` - Vue I18n translations
//! - `<docs>` - Component documentation
//! - `<story>` - Storybook stories
//! - `<test>` - Component tests
//!
//! ## Examples
//!
//! ### Triggers Warning
//! ```vue
//! <i18n>
//! { "en": { "hello": "Hello" } }
//! </i18n>
//!
//! <docs>
//! # MyComponent
//! This is a custom component.
//! </docs>
//! ```
//!
//! ## Note
//!
//! This rule is informational. Custom blocks are valid and useful when
//! properly configured with the appropriate Vite/Webpack plugins.
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/warn-custom-block",
description: "Warn about custom blocks in SFC files",
category: RuleCategory::Recommended,
fixable: false,
default_severity: Severity::Warning,
};
/// Standard SFC block names
const STANDARD_BLOCKS: &[&str] = &["script", "template", "style"];
/// Warn about custom blocks
#[derive(Default)]
pub struct WarnCustomBlock;
impl Rule for WarnCustomBlock {
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 top-level blocks by looking for < at start of line or after >
let mut pos = 0;
while pos < source.len() {
// Find next < that could be a block start
if let Some(tag_start) = source[pos..].find('<') {
let abs_pos = pos + tag_start;
// Skip if this is a closing tag
if source[abs_pos..].starts_with("</") {
pos = abs_pos + 2;
continue;
}
// Get the tag name
let rest = &source[abs_pos + 1..];
let tag_end = rest
.find(|c: char| c.is_whitespace() || c == '>' || c == '/')
.unwrap_or(rest.len());
let tag_name = &rest[..tag_end];
// Check if this is a non-standard block at root level
// Only check if we're likely at root level (check for preceding whitespace/newline)
let before = &source[..abs_pos];
let is_root_level = before.is_empty()
|| before.ends_with('\n')
|| before.trim_end().ends_with('>') && !before.contains('<');
if is_root_level
&& !tag_name.is_empty()
&& !STANDARD_BLOCKS.contains(&tag_name)
&& tag_name
.chars()
.next()
.map(|c| c.is_lowercase())
.unwrap_or(false)
{
// Find the closing >
let close_pos = source[abs_pos..]
.find('>')
.map(|p| abs_pos + p + 1)
.unwrap_or(abs_pos + tag_end + 1);
ctx.report(
LintDiagnostic::warn(
META.name,
"Custom block detected. Ensure proper plugin configuration.",
abs_pos as u32,
close_pos as u32,
)
.with_help(
"Custom blocks require corresponding Vite/Webpack plugins to be processed",
),
);
}
pos = abs_pos + 1;
} else {
break;
}
}
}
}
#[cfg(test)]
mod tests {
// Tests would need SFC-level testing infrastructure
}