use crate::{ast_util::scopes::ScopeManager, standard_library::StandardLibrary};
use std::convert::TryInto;
use codespan_reporting::diagnostic::{
Diagnostic as CodespanDiagnostic, Label as CodespanLabel, Severity as CodespanSeverity,
};
use full_moon::{ast::Ast, node::Node};
use serde::de::DeserializeOwned;
pub mod almost_swapped;
pub mod bad_string_escape;
pub mod compare_nan;
pub mod constant_table_comparison;
pub mod deprecated;
pub mod divide_by_zero;
pub mod duplicate_keys;
pub mod empty_if;
pub mod empty_loop;
pub mod global_usage;
pub mod high_cyclomatic_complexity;
pub mod if_same_then_else;
pub mod ifs_same_cond;
pub mod invalid_lint_filter;
pub mod manual_table_clone;
pub mod mismatched_arg_count;
pub mod mixed_table;
pub mod multiple_statements;
pub mod must_use;
pub mod parenthese_conditions;
pub mod restricted_module_paths;
pub mod shadowing;
pub mod standard_library;
pub mod suspicious_reverse_loop;
pub mod type_check_inside_call;
pub mod unbalanced_assignments;
pub mod undefined_variable;
pub mod unscoped_variables;
pub mod unused_variable;
#[cfg(feature = "roblox")]
pub mod roblox_incorrect_color3_new_bounds;
#[cfg(feature = "roblox")]
pub mod roblox_incorrect_roact_usage;
#[cfg(feature = "roblox")]
pub mod roblox_manual_fromscale_or_fromoffset;
#[cfg(feature = "roblox")]
pub mod roblox_suspicious_udim2_new;
#[cfg(test)]
mod test_util;
pub trait Lint {
type Config: DeserializeOwned;
type Error: std::error::Error;
const SEVERITY: Severity;
const LINT_TYPE: LintType;
fn new(config: Self::Config) -> Result<Self, Self::Error>
where
Self: Sized;
fn pass(
&self,
ast: &full_moon::ast::Ast,
context: &Context,
ast_context: &AstContext,
) -> Vec<Diagnostic>;
}
pub enum LintType {
Complexity,
Correctness,
Performance,
Style,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Severity {
Allow,
Error,
Warning,
}
#[derive(Debug)]
pub struct Diagnostic {
pub code: &'static str,
pub message: String,
pub notes: Vec<String>,
pub primary_label: Label,
pub secondary_labels: Vec<Label>,
}
impl Diagnostic {
pub fn new(code: &'static str, message: String, primary_label: Label) -> Self {
Self {
code,
message,
primary_label,
notes: Vec::new(),
secondary_labels: Vec::new(),
}
}
pub fn new_complete(
code: &'static str,
message: String,
primary_label: Label,
notes: Vec<String>,
secondary_labels: Vec<Label>,
) -> Self {
Self {
code,
message,
notes,
primary_label,
secondary_labels,
}
}
pub fn into_codespan_diagnostic(
self,
file_id: codespan::FileId,
severity: CodespanSeverity,
) -> CodespanDiagnostic<codespan::FileId> {
let mut labels = Vec::with_capacity(1 + self.secondary_labels.len());
labels.push(self.primary_label.codespan_label(file_id));
labels.extend(&mut self.secondary_labels.iter().map(|label| {
CodespanLabel::secondary(file_id, codespan::Span::new(label.range.0, label.range.1))
.with_message(label.message.as_ref().unwrap_or(&"".to_owned()).to_owned())
}));
CodespanDiagnostic {
code: Some(self.code.to_owned()),
labels,
message: self.message.to_owned(),
notes: self.notes,
severity,
}
}
pub fn start_position(&self) -> u32 {
self.primary_label.range.0
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct Label {
pub message: Option<String>,
pub range: (u32, u32),
}
impl Label {
pub fn new<P: TryInto<u32>>(range: (P, P)) -> Label {
let range = (
range
.0
.try_into()
.unwrap_or_else(|_| panic!("TryInto failed for Label::new range")),
range
.1
.try_into()
.unwrap_or_else(|_| panic!("TryInto failed for Label::new range")),
);
Label {
range,
message: None,
}
}
pub fn from_node<N: Node>(node: N, message: Option<String>) -> Label {
let (start, end) = node.range().expect("node passed returned a None range");
Label {
message,
range: (start.bytes() as u32, end.bytes() as u32),
}
}
pub fn new_with_message<P: TryInto<u32>>(range: (P, P), message: String) -> Label {
let range = (
range
.0
.try_into()
.unwrap_or_else(|_| panic!("TryInto failed for Label::new range")),
range
.1
.try_into()
.unwrap_or_else(|_| panic!("TryInto failed for Label::new range")),
);
Label {
range,
message: Some(message),
}
}
pub fn codespan_label(&self, file_id: codespan::FileId) -> CodespanLabel<codespan::FileId> {
CodespanLabel::primary(
file_id.to_owned(),
codespan::Span::new(self.range.0, self.range.1),
)
.with_message(self.message.as_ref().unwrap_or(&"".to_owned()).to_owned())
}
}
#[derive(Clone, Debug)]
pub struct Context {
pub standard_library: StandardLibrary,
pub user_set_standard_library: Option<Vec<String>>,
}
impl Context {
#[cfg(feature = "roblox")]
pub fn is_roblox(&self) -> bool {
self.standard_library.name.as_deref() == Some("roblox")
}
#[cfg(not(feature = "roblox"))]
pub fn is_roblox(&self) -> bool {
false
}
}
#[derive(Debug)]
pub struct AstContext {
pub scope_manager: ScopeManager,
}
impl AstContext {
pub fn from_ast(ast: &Ast) -> Self {
Self {
scope_manager: ScopeManager::new(ast),
}
}
}