mod helpers;
mod state;
pub use state::{DisabledRange, ElementContext, SsrMode};
use crate::diagnostic::{HelpLevel, LintDiagnostic, Severity};
use std::borrow::Cow;
use vize_carton::String;
use vize_carton::{
directive::DirectiveSeverity,
i18n::{t, t_fmt, Locale},
Allocator, CompactString, FxHashMap, FxHashSet,
};
use vize_croquis::Croquis;
pub struct LintContext<'a> {
allocator: &'a Allocator,
pub source: &'a str,
pub filename: &'a str,
locale: Locale,
pub(crate) diagnostics: Vec<LintDiagnostic>,
pub current_rule: &'static str,
pub(crate) element_stack: Vec<ElementContext>,
pub(crate) scope_variables: FxHashSet<CompactString>,
pub(crate) error_count: usize,
pub(crate) warning_count: usize,
disabled_all: Vec<DisabledRange>,
disabled_rules: FxHashMap<CompactString, Vec<DisabledRange>>,
line_offsets: Vec<u32>,
enabled_rules: Option<FxHashSet<String>>,
pub(crate) analysis: Option<&'a Croquis>,
ssr_mode: SsrMode,
pub(crate) help_level: HelpLevel,
expected_error_lines: FxHashSet<u32>,
severity_overrides: FxHashMap<u32, DirectiveSeverity>,
}
impl<'a> LintContext<'a> {
const INITIAL_DIAGNOSTICS_CAPACITY: usize = 16;
const INITIAL_STACK_CAPACITY: usize = 32;
#[inline]
pub fn new(allocator: &'a Allocator, source: &'a str, filename: &'a str) -> Self {
Self::with_locale(allocator, source, filename, Locale::default())
}
#[inline]
pub fn with_locale(
allocator: &'a Allocator,
source: &'a str,
filename: &'a str,
locale: Locale,
) -> Self {
Self {
allocator,
source,
filename,
locale,
diagnostics: Vec::with_capacity(Self::INITIAL_DIAGNOSTICS_CAPACITY),
current_rule: "",
element_stack: Vec::with_capacity(Self::INITIAL_STACK_CAPACITY),
scope_variables: FxHashSet::default(),
error_count: 0,
warning_count: 0,
disabled_all: Vec::new(),
disabled_rules: FxHashMap::default(),
line_offsets: Self::compute_line_offsets(source),
enabled_rules: None,
analysis: None,
ssr_mode: SsrMode::default(),
help_level: HelpLevel::default(),
expected_error_lines: FxHashSet::default(),
severity_overrides: FxHashMap::default(),
}
}
#[inline]
pub fn with_analysis(
allocator: &'a Allocator,
source: &'a str,
filename: &'a str,
analysis: &'a Croquis,
) -> Self {
Self {
allocator,
source,
filename,
locale: Locale::default(),
diagnostics: Vec::with_capacity(Self::INITIAL_DIAGNOSTICS_CAPACITY),
current_rule: "",
element_stack: Vec::with_capacity(Self::INITIAL_STACK_CAPACITY),
scope_variables: FxHashSet::default(),
error_count: 0,
warning_count: 0,
disabled_all: Vec::new(),
disabled_rules: FxHashMap::default(),
line_offsets: Self::compute_line_offsets(source),
enabled_rules: None,
analysis: Some(analysis),
ssr_mode: SsrMode::default(),
help_level: HelpLevel::default(),
expected_error_lines: FxHashSet::default(),
severity_overrides: FxHashMap::default(),
}
}
#[inline]
pub fn set_analysis(&mut self, analysis: &'a Croquis) {
self.analysis = Some(analysis);
}
#[inline]
pub fn analysis(&self) -> Option<&Croquis> {
self.analysis
}
#[inline]
pub fn has_analysis(&self) -> bool {
self.analysis.is_some()
}
#[inline]
pub fn set_ssr_mode(&mut self, mode: SsrMode) {
self.ssr_mode = mode;
}
#[inline]
pub fn ssr_mode(&self) -> SsrMode {
self.ssr_mode
}
#[inline]
pub fn is_ssr_enabled(&self) -> bool {
self.ssr_mode == SsrMode::Enabled
}
#[inline]
pub fn set_help_level(&mut self, level: HelpLevel) {
self.help_level = level;
}
#[inline]
pub fn help_level(&self) -> HelpLevel {
self.help_level
}
#[inline]
pub fn set_enabled_rules(&mut self, enabled: Option<FxHashSet<String>>) {
self.enabled_rules = enabled;
}
#[inline]
pub fn is_rule_enabled(&self, rule_name: &str) -> bool {
match &self.enabled_rules {
Some(set) => set.contains(rule_name),
None => true,
}
}
#[inline]
pub fn locale(&self) -> Locale {
self.locale
}
#[inline]
pub fn t(&self, key: &str) -> Cow<'static, str> {
t(self.locale, key)
}
#[inline]
pub fn t_fmt(&self, key: &str, vars: &[(&str, &str)]) -> String {
t_fmt(self.locale, key, vars).into()
}
fn compute_line_offsets(source: &str) -> Vec<u32> {
let mut offsets = vec![0];
for (i, c) in source.char_indices() {
if c == '\n' {
offsets.push((i + 1) as u32);
}
}
offsets
}
#[inline]
pub fn offset_to_line(&self, offset: u32) -> u32 {
match self.line_offsets.binary_search(&offset) {
Ok(line) => (line + 1) as u32,
Err(line) => line as u32,
}
}
#[inline]
pub fn allocator(&self) -> &'a Allocator {
self.allocator
}
#[inline]
pub fn alloc_str(&self, s: &str) -> &'a str {
self.allocator.alloc_str(s)
}
#[inline]
pub fn report(&mut self, mut diagnostic: LintDiagnostic) {
if !self.is_rule_enabled(diagnostic.rule_name) {
return;
}
let line = self.offset_to_line(diagnostic.start);
if self.is_disabled_at(diagnostic.rule_name, line) {
return;
}
if self.expected_error_lines.remove(&line) {
return;
}
if let Some(override_severity) = self.severity_overrides.remove(&line) {
match override_severity {
DirectiveSeverity::Off => return,
DirectiveSeverity::Warn => diagnostic.severity = Severity::Warning,
DirectiveSeverity::Error => diagnostic.severity = Severity::Error,
}
}
match diagnostic.severity {
Severity::Error => self.error_count += 1,
Severity::Warning => self.warning_count += 1,
}
self.diagnostics.push(diagnostic);
}
#[inline]
fn is_disabled_at(&self, rule_name: &str, line: u32) -> bool {
for range in &self.disabled_all {
if line >= range.start_line {
if let Some(end) = range.end_line {
if line <= end {
return true;
}
} else {
return true;
}
}
}
if let Some(ranges) = self.disabled_rules.get(rule_name) {
for range in ranges {
if line >= range.start_line {
if let Some(end) = range.end_line {
if line <= end {
return true;
}
} else {
return true;
}
}
}
}
false
}
pub fn disable_all(&mut self, start_line: u32, end_line: Option<u32>) {
self.disabled_all.push(DisabledRange {
start_line,
end_line,
});
}
pub fn disable_rules(&mut self, rules: &[&str], start_line: u32, end_line: Option<u32>) {
for rule in rules {
let range = DisabledRange {
start_line,
end_line,
};
self.disabled_rules
.entry(CompactString::from(*rule))
.or_default()
.push(range);
}
}
pub fn disable_next_line(&mut self, current_line: u32) {
self.disable_all(current_line + 1, Some(current_line + 1));
}
pub fn disable_rules_next_line(&mut self, rules: &[&str], current_line: u32) {
self.disable_rules(rules, current_line + 1, Some(current_line + 1));
}
pub fn push_ignore_region(&mut self, line: u32) {
self.disable_all(line, None);
}
pub fn pop_ignore_region(&mut self, line: u32) {
for range in self.disabled_all.iter_mut().rev() {
if range.end_line.is_none() {
range.end_line = Some(line);
return;
}
}
}
pub fn expect_error_next_line(&mut self, current_line: u32) {
self.expected_error_lines.insert(current_line + 1);
}
pub fn set_severity_override_next_line(
&mut self,
current_line: u32,
severity: DirectiveSeverity,
) {
self.severity_overrides.insert(current_line + 1, severity);
}
}