use crate::{context::LintContext, diagnostic::LintSummary, visitor::LintVisitor};
use vize_armature::Parser;
use vize_carton::Allocator;
use vize_carton::String;
use vize_carton::ToCompactString;
use super::config::{LintResult, Linter};
impl Linter {
#[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) = 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());
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 (content, byte_offset) = match extract_template_fast(source) {
Some(r) => r,
None => {
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;
}
}
}
result
}
}
#[inline]
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
}