use memchr::memmem;
use super::{ScriptLintResult, ScriptRule, ScriptRuleMeta};
use crate::diagnostic::{LintDiagnostic, Severity};
static META: ScriptRuleMeta = ScriptRuleMeta {
name: "script/no-internal-imports",
description: "Disallow importing from Vue internal modules",
default_severity: Severity::Error,
};
const INTERNAL_PATTERNS: &[&[u8]] = &[
b"/dist/", b"/src/", b"/esm/", b"vue.esm", b"vue.cjs", b"vue.runtime", ];
pub struct NoInternalImports;
impl ScriptRule for NoInternalImports {
fn meta(&self) -> &'static ScriptRuleMeta {
&META
}
#[inline]
fn check(&self, source: &str, offset: usize, result: &mut ScriptLintResult) {
let bytes = source.as_bytes();
if memmem::find(bytes, b"vue").is_none() {
return;
}
let from_finder = memmem::Finder::new(b"from");
let mut search_start = 0;
while let Some(from_pos) = from_finder.find(&bytes[search_start..]) {
let abs_from_pos = search_start + from_pos;
let after_from = &bytes[abs_from_pos + 4..];
let trimmed_start = skip_whitespace(after_from);
if trimmed_start >= after_from.len() {
search_start = abs_from_pos + 4;
continue;
}
let quote = after_from[trimmed_start];
if quote != b'\'' && quote != b'"' {
search_start = abs_from_pos + 4;
continue;
}
let specifier_start = trimmed_start + 1;
let Some(quote_end) = memchr::memchr(quote, &after_from[specifier_start..]) else {
search_start = abs_from_pos + 4;
continue;
};
let module_specifier = &after_from[specifier_start..specifier_start + quote_end];
if !contains_vue(module_specifier) {
search_start = abs_from_pos + 4 + specifier_start + quote_end;
continue;
}
for pattern in INTERNAL_PATTERNS {
if memmem::find(module_specifier, pattern).is_some() {
let spec_abs_start = abs_from_pos + 4 + specifier_start;
let start = offset + spec_abs_start;
let end = start + module_specifier.len();
result.add_diagnostic(
LintDiagnostic::error(
META.name,
"Importing from internal Vue module is forbidden",
start as u32,
end as u32,
)
.with_help("Import from 'vue' directly instead of internal modules"),
);
break;
}
}
search_start = abs_from_pos + 4 + specifier_start + quote_end;
}
}
}
#[inline]
fn skip_whitespace(bytes: &[u8]) -> usize {
let mut i = 0;
while i < bytes.len() && bytes[i].is_ascii_whitespace() {
i += 1;
}
i
}
#[inline]
fn contains_vue(bytes: &[u8]) -> bool {
memmem::find(bytes, b"vue").is_some() || memmem::find(bytes, b"@vue/").is_some()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_valid_vue_import() {
let source = "import { ref } from 'vue'";
let rule = NoInternalImports;
let mut result = ScriptLintResult::default();
rule.check(source, 0, &mut result);
assert_eq!(result.error_count, 0);
}
#[test]
fn test_invalid_dist_import() {
let source = "import { ref } from 'vue/dist/vue.esm-bundler.js'";
let rule = NoInternalImports;
let mut result = ScriptLintResult::default();
rule.check(source, 0, &mut result);
assert_eq!(result.error_count, 1);
}
#[test]
fn test_invalid_runtime_core_dist() {
let source = "import { ref } from '@vue/runtime-core/dist/runtime-core.esm-bundler.js'";
let rule = NoInternalImports;
let mut result = ScriptLintResult::default();
rule.check(source, 0, &mut result);
assert_eq!(result.error_count, 1);
}
#[test]
fn test_valid_vue_package_import() {
let source = "import { ref } from '@vue/reactivity'";
let rule = NoInternalImports;
let mut result = ScriptLintResult::default();
rule.check(source, 0, &mut result);
assert_eq!(result.error_count, 0);
}
#[test]
fn test_non_vue_import() {
let source = "import { foo } from 'lodash/dist/lodash.js'";
let rule = NoInternalImports;
let mut result = ScriptLintResult::default();
rule.check(source, 0, &mut result);
assert_eq!(result.error_count, 0);
}
#[test]
fn test_double_quote_import() {
let source = r#"import { ref } from "vue/dist/vue.esm-bundler.js""#;
let rule = NoInternalImports;
let mut result = ScriptLintResult::default();
rule.check(source, 0, &mut result);
assert_eq!(result.error_count, 1);
}
#[test]
fn test_vue_esm_pattern() {
let source = "import { ref } from 'vue.esm.js'";
let rule = NoInternalImports;
let mut result = ScriptLintResult::default();
rule.check(source, 0, &mut result);
assert_eq!(result.error_count, 1);
}
}