Skip to content

Commit 4bb3fe8

Browse files
authored
Config: warn on parent case mismatch (#559)
1 parent e3359ce commit 4bb3fe8

File tree

4 files changed

+162
-16
lines changed

4 files changed

+162
-16
lines changed

libs/config/src/analyze/codes/ce7_missing_parent.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,19 @@ use hemtt_common::reporting::{Code, Processed};
33

44
use crate::Class;
55

6-
pub struct MissingParrent {
6+
pub struct MissingParent {
77
class: Class,
88
}
99

10-
impl MissingParrent {
10+
impl MissingParent {
1111
pub const fn new(class: Class) -> Self {
1212
Self { class }
1313
}
1414
}
1515

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

18-
impl Code for MissingParrent {
18+
impl Code for MissingParent {
1919
fn ident(&self) -> &'static str {
2020
"CE7"
2121
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
use ariadne::{sources, ColorGenerator, Fmt, Label, Report};
2+
use hemtt_common::reporting::{Code, Processed};
3+
4+
use crate::Class;
5+
6+
pub struct ParentCase {
7+
class: Class,
8+
parent: Class,
9+
}
10+
11+
impl ParentCase {
12+
pub const fn new(class: Class, parent: Class) -> Self {
13+
Self { class, parent }
14+
}
15+
}
16+
17+
// TODO: maybe we could have a `did you mean` here without too much trouble?
18+
19+
impl Code for ParentCase {
20+
fn ident(&self) -> &'static str {
21+
"CW1"
22+
}
23+
24+
fn message(&self) -> String {
25+
"parent case does not match parent definition".to_string()
26+
}
27+
28+
fn label_message(&self) -> String {
29+
"class's parent does not match parent defintion case".to_string()
30+
}
31+
32+
fn help(&self) -> Option<String> {
33+
Some(format!(
34+
"change the parent case to match the parent definition: `{}`",
35+
self.parent.name().as_str()
36+
))
37+
}
38+
39+
fn generate_processed_report(&self, processed: &Processed) -> Option<String> {
40+
let class_parent = self.class.parent()?;
41+
let map = processed.mapping(self.class.name().span.start).unwrap();
42+
let token = map.token();
43+
let class_parent_map = processed.mapping(class_parent.span.start).unwrap();
44+
let class_parent_token = class_parent_map.token();
45+
let parent_map = processed.mapping(self.parent.name().span.start).unwrap();
46+
let parent_token = parent_map.token();
47+
let mut out = Vec::new();
48+
let mut colors = ColorGenerator::new();
49+
let color_class = colors.next();
50+
let color_parent = colors.next();
51+
Report::build(
52+
ariadne::ReportKind::Warning,
53+
token.position().path().as_str(),
54+
map.original_column(),
55+
)
56+
.with_code(self.ident())
57+
.with_message(self.message())
58+
.with_label(
59+
Label::new((
60+
class_parent_token.position().path().to_string(),
61+
class_parent_token.position().start().0..class_parent_token.position().end().0,
62+
))
63+
.with_message(self.label_message())
64+
.with_color(color_class),
65+
)
66+
.with_label(
67+
Label::new((
68+
parent_token.position().path().to_string(),
69+
parent_token.position().start().0..parent_token.position().end().0,
70+
))
71+
.with_message("parent definition here")
72+
.with_color(color_parent),
73+
)
74+
.with_help(format!(
75+
"change the {} to match the parent definition `{}`",
76+
"parent case".fg(color_class),
77+
self.parent.name().as_str().fg(color_parent)
78+
))
79+
.finish()
80+
.write_for_stdout(sources(processed.sources_adrianne()), &mut out)
81+
.unwrap();
82+
Some(String::from_utf8(out).unwrap())
83+
}
84+
85+
#[cfg(feature = "lsp")]
86+
fn generate_processed_lsp(&self, processed: &Processed) -> Vec<(vfs::VfsPath, Diagnostic)> {}
87+
}

libs/config/src/analyze/codes/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,5 @@ pub mod ce4_missing_semicolon;
55
pub mod ce5_unexpected_array;
66
pub mod ce6_expected_array;
77
pub mod ce7_missing_parent;
8+
9+
pub mod cw1_parent_case;

libs/config/src/analyze/config.rs

Lines changed: 70 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,28 @@
1-
use std::collections::HashSet;
1+
use std::collections::{HashMap, HashSet};
22

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

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

7-
use super::{codes::ce7_missing_parent::MissingParrent, Analyze};
7+
use super::{
8+
codes::{ce7_missing_parent::MissingParent, cw1_parent_case::ParentCase},
9+
Analyze,
10+
};
811

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

1417
fn warnings(&self, processed: &Processed) -> Vec<Box<dyn Code>> {
15-
self.0
18+
let mut warnings = self
19+
.0
1620
.iter()
1721
.flat_map(|p| p.warnings(processed))
18-
.collect::<Vec<_>>()
22+
.collect::<Vec<_>>();
23+
let mut defined = HashMap::new();
24+
warnings.extend(external_missing_warn(&self.0, &mut defined));
25+
warnings
1926
}
2027

2128
fn errors(&self, processed: &Processed) -> Vec<Box<dyn Code>> {
@@ -25,34 +32,84 @@ impl Analyze for Config {
2532
.flat_map(|p| p.errors(processed))
2633
.collect::<Vec<_>>();
2734
let mut defined = HashSet::new();
28-
errors.extend(external_missing(&self.0, &mut defined));
35+
errors.extend(external_missing_error(&self.0, &mut defined));
2936
errors
3037
}
3138
}
3239

33-
fn external_missing(properties: &[Property], defined: &mut HashSet<String>) -> Vec<Box<dyn Code>> {
40+
fn external_missing_error(
41+
properties: &[Property],
42+
defined: &mut HashSet<String>,
43+
) -> Vec<Box<dyn Code>> {
3444
let mut errors: Vec<Box<dyn Code>> = Vec::new();
3545
for property in properties {
3646
if let Property::Class(c) = property {
3747
match c {
3848
Class::External { name } => {
39-
if !defined.contains(&name.value) {
40-
defined.insert(name.value.clone());
49+
let name = name.value.to_lowercase();
50+
if !defined.contains(&name) {
51+
defined.insert(name);
4152
}
4253
}
4354
Class::Local {
44-
parent, properties, ..
55+
name,
56+
parent,
57+
properties,
4558
} => {
59+
let name = name.value.to_lowercase();
4660
if let Some(parent) = parent {
47-
if !defined.contains(&parent.value) {
48-
errors.push(Box::new(MissingParrent::new(c.clone())));
61+
let parent = parent.value.to_lowercase();
62+
if parent != name && !defined.contains(&parent) {
63+
errors.push(Box::new(MissingParent::new(c.clone())));
4964
}
5065
}
51-
defined.insert(c.name().value.clone());
52-
errors.extend(external_missing(properties, defined));
66+
defined.insert(name);
67+
errors.extend(external_missing_error(properties, defined));
5368
}
5469
}
5570
}
5671
}
5772
errors
5873
}
74+
75+
fn external_missing_warn(
76+
properties: &[Property],
77+
defined: &mut HashMap<String, Class>,
78+
) -> Vec<Box<dyn Code>> {
79+
let mut warnings: Vec<Box<dyn Code>> = Vec::new();
80+
for property in properties {
81+
if let Property::Class(c) = property {
82+
match c {
83+
Class::External { name } => {
84+
let name = name.value.to_lowercase();
85+
defined.entry(name).or_insert_with(|| c.clone());
86+
}
87+
Class::Local {
88+
name,
89+
parent,
90+
properties,
91+
} => {
92+
let name_lower = name.value.to_lowercase();
93+
if let Some(parent) = parent {
94+
let parent_lower = parent.value.to_lowercase();
95+
if parent_lower != name_lower {
96+
if let Some(parent_class) = defined.get(&parent_lower) {
97+
if parent_class.name().value != parent.value {
98+
warnings.push(Box::new(ParentCase::new(
99+
c.clone(),
100+
parent_class.clone(),
101+
)));
102+
}
103+
}
104+
} else if parent.value != name.value {
105+
warnings.push(Box::new(ParentCase::new(c.clone(), c.clone())));
106+
}
107+
}
108+
defined.insert(name_lower, c.clone());
109+
warnings.extend(external_missing_warn(properties, defined));
110+
}
111+
}
112+
}
113+
}
114+
warnings
115+
}

0 commit comments

Comments
 (0)