use memchr::memmem;
use crate::diagnostic::{LintDiagnostic, Severity};
use super::{ScriptLintResult, ScriptRule, ScriptRuleMeta};
static META: ScriptRuleMeta = ScriptRuleMeta {
name: "script/no-import-compiler-macros",
description: "Disallow importing Vue compiler macros that are auto-imported",
default_severity: Severity::Error,
};
const COMPILER_MACROS: &[&str] = &[
"defineProps",
"defineEmits",
"defineExpose",
"defineOptions",
"defineSlots",
"defineModel",
"withDefaults",
];
pub struct NoImportCompilerMacros;
impl ScriptRule for NoImportCompilerMacros {
fn meta(&self) -> &'static ScriptRuleMeta {
&META
}
fn check(&self, source: &str, offset: usize, result: &mut ScriptLintResult) {
let bytes = source.as_bytes();
if memmem::find(bytes, b"from 'vue'").is_none()
&& memmem::find(bytes, b"from \"vue\"").is_none()
{
return;
}
let import_finder = memmem::Finder::new(b"import ");
let mut search_start = 0;
while let Some(pos) = import_finder.find(&bytes[search_start..]) {
let abs_pos = search_start + pos;
search_start = abs_pos + 7;
let rest = &source[abs_pos..];
let import_end = rest.find('\n').unwrap_or(rest.len());
let import_line = &rest[..import_end];
if !import_line.contains("from 'vue'") && !import_line.contains("from \"vue\"") {
continue;
}
for macro_name in COMPILER_MACROS {
if import_line.contains(macro_name) {
if let Some(macro_pos) = import_line.find(macro_name) {
result.add_diagnostic(
LintDiagnostic::error(
META.name,
format!(
"Do not import '{}' - compiler macros are automatically available in <script setup>",
macro_name
),
(offset + abs_pos + macro_pos) as u32,
(offset + abs_pos + macro_pos + macro_name.len()) as u32,
)
.with_help(
"Remove the macro from the import statement. Compiler macros are auto-imported.",
),
);
}
}
}
}
}
}
#[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(NoImportCompilerMacros));
linter
}
#[test]
fn test_valid_no_compiler_macros() {
let linter = create_linter();
let result = linter.lint("import { ref, computed } from 'vue'", 0);
assert_eq!(result.error_count, 0);
}
#[test]
fn test_invalid_import_define_props() {
let linter = create_linter();
let result = linter.lint("import { defineProps } from 'vue'", 0);
assert_eq!(result.error_count, 1);
assert!(result.diagnostics[0].message.contains("defineProps"));
}
#[test]
fn test_invalid_import_multiple_macros() {
let linter = create_linter();
let result = linter.lint("import { defineProps, defineEmits } from 'vue'", 0);
assert_eq!(result.error_count, 2);
}
#[test]
fn test_valid_usage_without_import() {
let linter = create_linter();
let result = linter.lint("const props = defineProps<Props>()", 0);
assert_eq!(result.error_count, 0);
}
#[test]
fn test_valid_other_package() {
let linter = create_linter();
let result = linter.lint("import { defineProps } from 'other-package'", 0);
assert_eq!(result.error_count, 0);
}
}