use std::collections::HashMap;
use std::collections::HashSet;
use crate::codemap::CodeMap;
use crate::codemap::Pos;
use crate::codemap::Span;
static LINT_SUPPRESISON_PREFIX: &str = "starlark-lint-disable ";
#[derive(Debug, Clone)]
struct SuppressionInfo {
token_span: Span,
effective_span: Span,
suppress_next_line: bool,
}
#[derive(Debug, Clone)]
pub(crate) struct LintSuppressions {
suppressions: HashMap<String, Vec<SuppressionInfo>>,
}
impl LintSuppressions {
pub(crate) fn is_suppressed(&self, issue_short_name: &str, issue_span: Span) -> bool {
self.suppressions
.get(issue_short_name)
.map(|suppression_spans| {
suppression_spans.iter().any(
|SuppressionInfo {
token_span,
effective_span,
suppress_next_line,
}| {
if *suppress_next_line &&
(issue_span.end() - 1) == token_span.end()
{
false
} else {
issue_span.intersects(*effective_span)
}
},
)
})
== Some(true)
}
}
#[derive(Default)]
struct ParseState {
token_spans: Vec<Span>,
effective_spans: Vec<Span>,
short_names: HashSet<String>,
last_line: usize,
}
impl ParseState {
fn is_empty(&self) -> bool {
self.token_spans.is_empty()
&& self.effective_spans.is_empty()
&& self.short_names.is_empty()
}
}
pub(crate) struct LintSuppressionsBuilder {
state: ParseState,
suppressions: LintSuppressions,
}
impl LintSuppressionsBuilder {
pub(crate) fn new() -> Self {
Self {
state: ParseState::default(),
suppressions: LintSuppressions {
suppressions: HashMap::new(),
},
}
}
pub(crate) fn parse_comment(
&mut self,
codemap: &CodeMap,
comment: &str,
start: usize,
end: usize,
) {
let parsed_short_names = parse_lint_suppressions(comment);
if !parsed_short_names.is_empty() || !self.state.short_names.is_empty() {
if let (Ok(start_pos), Ok(end_pos)) = (start.try_into(), end.try_into()) {
let token_span = Span::new(Pos::new(start_pos), Pos::new(end_pos));
let line = codemap.find_line(Pos::new(start_pos));
let effective_span = codemap.line_span_trim_newline(line);
self.state.short_names.extend(parsed_short_names);
self.state.token_spans.push(token_span);
self.state.effective_spans.push(effective_span);
self.state.last_line = line;
}
}
}
pub(crate) fn end_of_comment_block(&mut self, codemap: &CodeMap) {
if !self.state.short_names.is_empty() {
self.update_lint_suppressions(codemap);
}
}
pub(crate) fn build(self) -> LintSuppressions {
assert!(self.state.is_empty());
self.suppressions
}
fn update_lint_suppressions(&mut self, codemap: &CodeMap) {
let state = std::mem::take(&mut self.state);
let number_of_tokens = state.token_spans.len();
let token_span = Span::merge_all(state.token_spans.into_iter());
let mut effective_span = Span::merge_all(state.effective_spans.into_iter());
let source_before_token =
codemap.source_span(Span::new(effective_span.begin(), token_span.begin()));
let suppress_next_line = number_of_tokens > 1
|| effective_span == token_span
|| (effective_span.end() == token_span.end() && source_before_token.trim().is_empty());
if suppress_next_line {
if let Some(next_line_span) = codemap.line_span_opt(state.last_line + 1) {
effective_span = effective_span.merge(next_line_span);
}
}
for name in state.short_names {
self.suppressions
.suppressions
.entry(name)
.or_default()
.push(SuppressionInfo {
token_span,
effective_span,
suppress_next_line,
});
}
}
}
fn parse_lint_suppressions(comment_line: &str) -> Vec<String> {
let mut res = Vec::new();
if let Some(short_names) = comment_line
.trim_start()
.strip_prefix(LINT_SUPPRESISON_PREFIX)
{
for name in short_names.split([' ', ',']) {
let trimmed = name.trim();
if !trimmed.is_empty() {
res.push(trimmed.to_owned());
}
}
}
res
}