use memchr::memmem;
use crate::diagnostic::{LintDiagnostic, Severity};
use super::{ScriptLintResult, ScriptRule, ScriptRuleMeta};
static META: ScriptRuleMeta = ScriptRuleMeta {
name: "script/require-symbol-provide",
description: "Recommend using Symbol as injection key for provide/inject",
default_severity: Severity::Warning,
};
pub struct RequireSymbolProvide;
impl ScriptRule for RequireSymbolProvide {
fn meta(&self) -> &'static ScriptRuleMeta {
&META
}
fn check(&self, source: &str, offset: usize, result: &mut ScriptLintResult) {
let bytes = source.as_bytes();
self.check_call(source, bytes, offset, "provide(", result);
self.check_call(source, bytes, offset, "inject(", result);
}
}
impl RequireSymbolProvide {
fn check_call(
&self,
source: &str,
bytes: &[u8],
offset: usize,
pattern: &str,
result: &mut ScriptLintResult,
) {
let finder = memmem::Finder::new(pattern.as_bytes());
let mut search_start = 0;
while let Some(pos) = finder.find(&bytes[search_start..]) {
let abs_pos = search_start + pos;
search_start = abs_pos + pattern.len();
if abs_pos > 0 {
let prev_char = bytes[abs_pos - 1];
if prev_char.is_ascii_alphanumeric() || prev_char == b'_' {
continue;
}
}
let after = &source[abs_pos + pattern.len()..];
let trimmed = after.trim_start();
if trimmed.starts_with('\'') || trimmed.starts_with('"') || trimmed.starts_with('`') {
result.add_diagnostic(
LintDiagnostic::warn(
META.name,
"Consider using a Symbol key instead of a string literal",
(offset + abs_pos) as u32,
(offset + abs_pos + pattern.len()) as u32,
)
.with_help(
"Define an InjectionKey with Symbol: \
`export const MyKey: InjectionKey<MyType> = Symbol('myKey')`",
),
);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::rules::script::ScriptLinter;
fn create_linter() -> ScriptLinter {
let mut linter = ScriptLinter::new();
linter.add_rule(Box::new(RequireSymbolProvide));
linter
}
#[test]
fn test_valid_symbol_provide() {
let linter = create_linter();
let result = linter.lint("provide(UserKey, user)", 0);
assert_eq!(result.warning_count, 0);
}
#[test]
fn test_valid_symbol_inject() {
let linter = create_linter();
let result = linter.lint("const user = inject(UserKey)", 0);
assert_eq!(result.warning_count, 0);
}
#[test]
fn test_invalid_string_provide() {
let linter = create_linter();
let result = linter.lint("provide('user', userData)", 0);
assert_eq!(result.warning_count, 1);
assert!(result.diagnostics[0].message.contains("Symbol"));
}
#[test]
fn test_invalid_string_inject() {
let linter = create_linter();
let result = linter.lint("const user = inject('user')", 0);
assert_eq!(result.warning_count, 1);
}
#[test]
fn test_invalid_template_literal() {
let linter = create_linter();
let result = linter.lint("provide(`theme`, theme)", 0);
assert_eq!(result.warning_count, 1);
}
#[test]
fn test_no_provide_inject() {
let linter = create_linter();
let result = linter.lint("const x = ref(0)", 0);
assert_eq!(result.warning_count, 0);
}
}