use crate::ast_parser::parse_program;
use crate::context::Context;
use crate::diagnostic::LintDiagnostic;
use crate::ignore_directives::parse_file_ignore_directives;
use crate::performance_mark::PerformanceMark;
use crate::rules::{ban_unknown_rule_code::BanUnknownRuleCode, LintRule};
use deno_ast::diagnostics::Diagnostic;
use deno_ast::MediaType;
use deno_ast::ParsedSource;
use deno_ast::{ModuleSpecifier, ParseDiagnostic};
use std::borrow::Cow;
use std::collections::HashSet;
use std::sync::Arc;
pub struct LinterOptions {
pub rules: Vec<Box<dyn LintRule>>,
pub all_rule_codes: HashSet<Cow<'static, str>>,
pub custom_ignore_file_directive: Option<&'static str>,
pub custom_ignore_diagnostic_directive: Option<&'static str>,
}
#[derive(Debug)]
pub struct Linter {
ctx: LinterContext,
}
#[derive(Debug)]
pub(crate) struct LinterContext {
pub ignore_file_directive: &'static str,
pub ignore_diagnostic_directive: &'static str,
pub check_unknown_rules: bool,
pub rules: Vec<Box<dyn LintRule>>,
pub all_rule_codes: HashSet<Cow<'static, str>>,
}
impl LinterContext {
fn new(options: LinterOptions) -> Self {
let mut rules = options.rules;
crate::rules::sort_rules_by_priority(&mut rules);
let check_unknown_rules = rules
.iter()
.any(|a| a.code() == (BanUnknownRuleCode).code());
LinterContext {
ignore_file_directive: options
.custom_ignore_file_directive
.unwrap_or("deno-lint-ignore-file"),
ignore_diagnostic_directive: options
.custom_ignore_file_directive
.unwrap_or("deno-lint-ignore"),
check_unknown_rules,
rules,
all_rule_codes: options.all_rule_codes,
}
}
}
#[derive(Default)]
pub struct ExternalLinterResult {
pub diagnostics: Vec<LintDiagnostic>,
pub rules: Vec<Cow<'static, str>>,
}
pub type ExternalLinterCb =
Arc<dyn Fn(ParsedSource) -> Option<ExternalLinterResult>>;
pub struct LintFileOptions {
pub specifier: ModuleSpecifier,
pub source_code: String,
pub media_type: MediaType,
pub config: LintConfig,
pub external_linter: Option<ExternalLinterCb>,
}
#[derive(Debug, Clone)]
pub struct LintConfig {
pub default_jsx_factory: Option<String>,
pub default_jsx_fragment_factory: Option<String>,
}
impl Linter {
pub fn new(options: LinterOptions) -> Self {
let ctx = LinterContext::new(options);
Linter { ctx }
}
pub fn lint_file(
&self,
options: LintFileOptions,
) -> Result<(ParsedSource, Vec<LintDiagnostic>), ParseDiagnostic> {
let _mark = PerformanceMark::new("Linter::lint");
let parse_result = {
let _mark = PerformanceMark::new("ast_parser.parse_program");
parse_program(options.specifier, options.media_type, options.source_code)
};
let parsed_source = parse_result?;
let diagnostics = self.lint_inner(
&parsed_source,
options.config.default_jsx_factory,
options.config.default_jsx_fragment_factory,
options.external_linter,
);
Ok((parsed_source, diagnostics))
}
pub fn lint_with_ast(
&self,
parsed_source: &ParsedSource,
config: LintConfig,
maybe_external_linter: Option<ExternalLinterCb>,
) -> Vec<LintDiagnostic> {
let _mark = PerformanceMark::new("Linter::lint_with_ast");
self.lint_inner(
parsed_source,
config.default_jsx_factory,
config.default_jsx_fragment_factory,
maybe_external_linter,
)
}
fn collect_diagnostics(
&self,
mut context: Context,
external_rule_codes: Vec<Cow<'static, str>>,
) -> Vec<LintDiagnostic> {
let _mark = PerformanceMark::new("Linter::collect_diagnostics");
let mut diagnostics = context.check_ignore_directive_usage();
let mut all_rules = self.ctx.all_rule_codes.clone();
all_rules.extend(external_rule_codes.iter().cloned());
let enabled_rules: HashSet<Cow<'static, str>> = external_rule_codes
.into_iter()
.chain(self.ctx.rules.iter().map(|r| r.code().into()))
.collect();
diagnostics.extend(context.ban_unknown_rule_code(&all_rules));
diagnostics.extend(context.ban_unused_ignore(&enabled_rules));
diagnostics.sort_by(|a, b| {
let a_range = a.range.as_ref().map(|r| r.range.start);
let b_range = b.range.as_ref().map(|r| r.range.start);
match a_range.cmp(&b_range) {
std::cmp::Ordering::Equal => a.code().cmp(&b.code()),
cmp => cmp,
}
});
diagnostics
}
fn lint_inner(
&self,
parsed_source: &ParsedSource,
default_jsx_factory: Option<String>,
default_jsx_fragment_factory: Option<String>,
maybe_external_linter: Option<ExternalLinterCb>,
) -> Vec<LintDiagnostic> {
let _mark = PerformanceMark::new("Linter::lint_inner");
let diagnostics = parsed_source.with_view(|pg| {
let file_ignore_directive =
parse_file_ignore_directives(self.ctx.ignore_file_directive, pg);
if let Some(ignore_directive) = file_ignore_directive.as_ref() {
if ignore_directive.ignore_all() {
return vec![];
}
}
let mut context = Context::new(
&self.ctx,
parsed_source.clone(),
pg,
file_ignore_directive,
default_jsx_factory,
default_jsx_fragment_factory,
);
for rule in self.ctx.rules.iter() {
rule.lint_program_with_ast_view(&mut context, pg);
}
let mut external_rule_codes = vec![];
if let Some(cb) = maybe_external_linter {
if let Some(external_linter_result) = cb(parsed_source.clone()) {
context.add_external_diagnostics(&external_linter_result.diagnostics);
external_rule_codes = external_linter_result.rules;
}
}
self.collect_diagnostics(context, external_rule_codes)
});
diagnostics
}
}