Skip to content

Commit

Permalink
Config: warn on parent case mismatch (#559)
Browse files Browse the repository at this point in the history
  • Loading branch information
BrettMayson authored Sep 19, 2023
1 parent e3359ce commit 4bb3fe8
Show file tree
Hide file tree
Showing 4 changed files with 162 additions and 16 deletions.
6 changes: 3 additions & 3 deletions libs/config/src/analyze/codes/ce7_missing_parent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,19 @@ use hemtt_common::reporting::{Code, Processed};

use crate::Class;

pub struct MissingParrent {
pub struct MissingParent {
class: Class,
}

impl MissingParrent {
impl MissingParent {
pub const fn new(class: Class) -> Self {
Self { class }
}
}

// TODO: maybe we could have a `did you mean` here without too much trouble?

impl Code for MissingParrent {
impl Code for MissingParent {
fn ident(&self) -> &'static str {
"CE7"
}
Expand Down
87 changes: 87 additions & 0 deletions libs/config/src/analyze/codes/cw1_parent_case.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
use ariadne::{sources, ColorGenerator, Fmt, Label, Report};
use hemtt_common::reporting::{Code, Processed};

use crate::Class;

pub struct ParentCase {
class: Class,
parent: Class,
}

impl ParentCase {
pub const fn new(class: Class, parent: Class) -> Self {
Self { class, parent }
}
}

// TODO: maybe we could have a `did you mean` here without too much trouble?

impl Code for ParentCase {
fn ident(&self) -> &'static str {
"CW1"
}

fn message(&self) -> String {
"parent case does not match parent definition".to_string()
}

fn label_message(&self) -> String {
"class's parent does not match parent defintion case".to_string()
}

fn help(&self) -> Option<String> {
Some(format!(
"change the parent case to match the parent definition: `{}`",
self.parent.name().as_str()
))
}

fn generate_processed_report(&self, processed: &Processed) -> Option<String> {
let class_parent = self.class.parent()?;
let map = processed.mapping(self.class.name().span.start).unwrap();
let token = map.token();
let class_parent_map = processed.mapping(class_parent.span.start).unwrap();
let class_parent_token = class_parent_map.token();
let parent_map = processed.mapping(self.parent.name().span.start).unwrap();
let parent_token = parent_map.token();
let mut out = Vec::new();
let mut colors = ColorGenerator::new();
let color_class = colors.next();
let color_parent = colors.next();
Report::build(
ariadne::ReportKind::Warning,
token.position().path().as_str(),
map.original_column(),
)
.with_code(self.ident())
.with_message(self.message())
.with_label(
Label::new((
class_parent_token.position().path().to_string(),
class_parent_token.position().start().0..class_parent_token.position().end().0,
))
.with_message(self.label_message())
.with_color(color_class),
)
.with_label(
Label::new((
parent_token.position().path().to_string(),
parent_token.position().start().0..parent_token.position().end().0,
))
.with_message("parent definition here")
.with_color(color_parent),
)
.with_help(format!(
"change the {} to match the parent definition `{}`",
"parent case".fg(color_class),
self.parent.name().as_str().fg(color_parent)
))
.finish()
.write_for_stdout(sources(processed.sources_adrianne()), &mut out)
.unwrap();
Some(String::from_utf8(out).unwrap())
}

#[cfg(feature = "lsp")]
fn generate_processed_lsp(&self, processed: &Processed) -> Vec<(vfs::VfsPath, Diagnostic)> {}
}
2 changes: 2 additions & 0 deletions libs/config/src/analyze/codes/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ pub mod ce4_missing_semicolon;
pub mod ce5_unexpected_array;
pub mod ce6_expected_array;
pub mod ce7_missing_parent;

pub mod cw1_parent_case;
83 changes: 70 additions & 13 deletions libs/config/src/analyze/config.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,28 @@
use std::collections::HashSet;
use std::collections::{HashMap, HashSet};

use hemtt_common::reporting::{Code, Processed};

use crate::{Class, Config, Property};

use super::{codes::ce7_missing_parent::MissingParrent, Analyze};
use super::{
codes::{ce7_missing_parent::MissingParent, cw1_parent_case::ParentCase},
Analyze,
};

impl Analyze for Config {
fn valid(&self) -> bool {
self.0.iter().all(Analyze::valid)
}

fn warnings(&self, processed: &Processed) -> Vec<Box<dyn Code>> {
self.0
let mut warnings = self
.0
.iter()
.flat_map(|p| p.warnings(processed))
.collect::<Vec<_>>()
.collect::<Vec<_>>();
let mut defined = HashMap::new();
warnings.extend(external_missing_warn(&self.0, &mut defined));
warnings
}

fn errors(&self, processed: &Processed) -> Vec<Box<dyn Code>> {
Expand All @@ -25,34 +32,84 @@ impl Analyze for Config {
.flat_map(|p| p.errors(processed))
.collect::<Vec<_>>();
let mut defined = HashSet::new();
errors.extend(external_missing(&self.0, &mut defined));
errors.extend(external_missing_error(&self.0, &mut defined));
errors
}
}

fn external_missing(properties: &[Property], defined: &mut HashSet<String>) -> Vec<Box<dyn Code>> {
fn external_missing_error(
properties: &[Property],
defined: &mut HashSet<String>,
) -> Vec<Box<dyn Code>> {
let mut errors: Vec<Box<dyn Code>> = Vec::new();
for property in properties {
if let Property::Class(c) = property {
match c {
Class::External { name } => {
if !defined.contains(&name.value) {
defined.insert(name.value.clone());
let name = name.value.to_lowercase();
if !defined.contains(&name) {
defined.insert(name);
}
}
Class::Local {
parent, properties, ..
name,
parent,
properties,
} => {
let name = name.value.to_lowercase();
if let Some(parent) = parent {
if !defined.contains(&parent.value) {
errors.push(Box::new(MissingParrent::new(c.clone())));
let parent = parent.value.to_lowercase();
if parent != name && !defined.contains(&parent) {
errors.push(Box::new(MissingParent::new(c.clone())));
}
}
defined.insert(c.name().value.clone());
errors.extend(external_missing(properties, defined));
defined.insert(name);
errors.extend(external_missing_error(properties, defined));
}
}
}
}
errors
}

fn external_missing_warn(
properties: &[Property],
defined: &mut HashMap<String, Class>,
) -> Vec<Box<dyn Code>> {
let mut warnings: Vec<Box<dyn Code>> = Vec::new();
for property in properties {
if let Property::Class(c) = property {
match c {
Class::External { name } => {
let name = name.value.to_lowercase();
defined.entry(name).or_insert_with(|| c.clone());
}
Class::Local {
name,
parent,
properties,
} => {
let name_lower = name.value.to_lowercase();
if let Some(parent) = parent {
let parent_lower = parent.value.to_lowercase();
if parent_lower != name_lower {
if let Some(parent_class) = defined.get(&parent_lower) {
if parent_class.name().value != parent.value {
warnings.push(Box::new(ParentCase::new(
c.clone(),
parent_class.clone(),
)));
}
}
} else if parent.value != name.value {
warnings.push(Box::new(ParentCase::new(c.clone(), c.clone())));
}
}
defined.insert(name_lower, c.clone());
warnings.extend(external_missing_warn(properties, defined));
}
}
}
}
warnings
}

0 comments on commit 4bb3fe8

Please sign in to comment.