use crate::{context::LintContext, diagnostic::LintSummary, visitor::LintVisitor};
use vize_armature::Parser;
use vize_carton::profile;
use vize_carton::Allocator;
use vize_carton::String;
use vize_carton::ToCompactString;
use super::config::{LintResult, Linter};
impl Linter {
fn lint_sfc_level(&self, source: &str, filename: &str) -> LintResult {
let capacity = (source.len() * 2).max(self.initial_capacity);
let allocator = Allocator::with_capacity(capacity);
let mut ctx = LintContext::with_locale(&allocator, source, filename, self.locale);
ctx.set_enabled_rules(self.enabled_rules.clone());
ctx.set_help_level(self.help_level);
for rule in self.registry.rules() {
ctx.current_rule = rule.meta().name;
profile!("patina.sfc.rule.run_on_sfc", rule.run_on_sfc(&mut ctx));
}
let error_count = ctx.error_count();
let warning_count = ctx.warning_count();
let diagnostics = ctx.into_diagnostics();
LintResult {
filename: filename.to_compact_string(),
diagnostics,
error_count,
warning_count,
}
}
fn merge_lint_results(
mut template_result: LintResult,
mut sfc_result: LintResult,
) -> LintResult {
if sfc_result.diagnostics.is_empty() {
return template_result;
}
if template_result.diagnostics.is_empty() {
return sfc_result;
}
template_result.error_count += sfc_result.error_count;
template_result.warning_count += sfc_result.warning_count;
template_result
.diagnostics
.append(&mut sfc_result.diagnostics);
template_result
.diagnostics
.sort_unstable_by_key(|diagnostic| (diagnostic.start, diagnostic.end));
template_result
}
#[inline]
pub fn lint_template(&self, source: &str, filename: &str) -> LintResult {
let capacity = (source.len() * 4).max(self.initial_capacity);
let allocator = Allocator::with_capacity(capacity);
self.lint_template_with_allocator(&allocator, source, filename)
}
pub fn lint_template_with_allocator(
&self,
allocator: &Allocator,
source: &str,
filename: &str,
) -> LintResult {
let parser = Parser::new(allocator.as_bump(), source);
let (root, _parse_errors) = profile!("patina.template.parse", parser.parse());
let mut ctx = LintContext::with_locale(allocator, source, filename, self.locale);
ctx.set_enabled_rules(self.enabled_rules.clone());
ctx.set_help_level(self.help_level);
let mut visitor = LintVisitor::new(&mut ctx, self.registry.rules());
profile!("patina.template.visit", visitor.visit_root(&root));
let error_count = ctx.error_count();
let warning_count = ctx.warning_count();
let diagnostics = ctx.into_diagnostics();
LintResult {
filename: filename.to_compact_string(),
diagnostics,
error_count,
warning_count,
}
}
pub fn lint_files(&self, files: &[(String, String)]) -> (Vec<LintResult>, LintSummary) {
let mut results = Vec::with_capacity(files.len());
let mut summary = LintSummary::default();
let mut allocator = Allocator::with_capacity(self.initial_capacity);
for (filename, source) in files {
let result = self.lint_template_with_allocator(&allocator, source, filename);
summary.error_count += result.error_count;
summary.warning_count += result.warning_count;
results.push(result);
allocator.reset();
}
summary.file_count = files.len();
(results, summary)
}
#[inline]
pub fn lint_sfc(&self, source: &str, filename: &str) -> LintResult {
let sfc_result = profile!(
"patina.sfc.level_rules",
self.lint_sfc_level(source, filename)
);
#[cfg(not(target_arch = "wasm32"))]
if super::native_type_aware::has_active_type_aware_rules(self) {
let template_result = profile!(
"patina.type_aware.lint_sfc_with_corsa",
super::native_type_aware::lint_sfc_with_corsa(self, source, filename)
);
return Self::merge_lint_results(template_result, sfc_result);
}
if super::script_rules::has_active_builtin_script_rules(self) {
let template_result = match profile!(
"patina.sfc.parse_for_script_rules",
super::script_rules::parse_sfc_for_lint(source, filename)
) {
Ok(descriptor) => {
profile!(
"patina.sfc.script_rules",
super::script_rules::lint_with_descriptor(self, filename, &descriptor)
)
}
Err(_) => {
if let Some((content, byte_offset)) = profile!(
"patina.template.extract_fast",
extract_template_fast(source)
) {
let mut fallback = self.lint_template(&content, filename);
if byte_offset > 0 {
for diag in &mut fallback.diagnostics {
diag.start += byte_offset;
diag.end += byte_offset;
for label in &mut diag.labels {
label.start += byte_offset;
label.end += byte_offset;
}
}
}
fallback
} else {
LintResult {
filename: filename.to_compact_string(),
diagnostics: Vec::new(),
error_count: 0,
warning_count: 0,
}
}
}
};
return Self::merge_lint_results(template_result, sfc_result);
}
let (content, byte_offset) = match profile!(
"patina.template.extract_fast",
extract_template_fast(source)
) {
Some(r) => r,
None => {
if sfc_result.has_diagnostics() {
return sfc_result;
}
return LintResult {
filename: filename.to_compact_string(),
diagnostics: Vec::new(),
error_count: 0,
warning_count: 0,
};
}
};
let mut result = self.lint_template(&content, filename);
if byte_offset > 0 {
for diag in &mut result.diagnostics {
diag.start += byte_offset;
diag.end += byte_offset;
for label in &mut diag.labels {
label.start += byte_offset;
label.end += byte_offset;
}
}
}
Self::merge_lint_results(result, sfc_result)
}
}
#[inline]
pub(crate) fn extract_template_fast(source: &str) -> Option<(String, u32)> {
let bytes = source.as_bytes();
let start_pattern = b"<template";
let start_idx = memchr::memmem::find(bytes, start_pattern)?;
let tag_end = memchr::memchr(b'>', &bytes[start_idx..])? + start_idx;
let content_start = tag_end + 1;
let mut depth = 1u32;
let mut pos = content_start;
while pos < bytes.len() && depth > 0 {
let next_lt = match memchr::memchr(b'<', &bytes[pos..]) {
Some(p) => pos + p,
None => break,
};
if bytes.len() > next_lt + 9 && &bytes[next_lt..next_lt + 9] == b"<template" {
if let Some(gt) = memchr::memchr(b'>', &bytes[next_lt..]) {
let tag_end_pos = next_lt + gt;
if tag_end_pos > 0 && bytes[tag_end_pos - 1] != b'/' {
depth += 1;
}
pos = tag_end_pos + 1;
} else {
pos = next_lt + 9;
}
} else if bytes.len() > next_lt + 11 && &bytes[next_lt..next_lt + 11] == b"</template>" {
depth -= 1;
if depth == 0 {
let content = std::str::from_utf8(&bytes[content_start..next_lt]).ok()?;
return Some((content.to_compact_string(), content_start as u32));
}
pos = next_lt + 11;
} else {
pos = next_lt + 1;
}
}
None
}