Skip to content

Commit

Permalink
pw3_padded_arg, pragma support
Browse files Browse the repository at this point in the history
  • Loading branch information
BrettMayson committed Oct 26, 2023
1 parent b025c7b commit 3e26635
Show file tree
Hide file tree
Showing 14 changed files with 729 additions and 27 deletions.
33 changes: 33 additions & 0 deletions book/analysis/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,28 @@

HEMTT will provide warnings for common issues in your config, in both the preprocessing and rapifying stages.

## Warning Suppression

Currently, HEMTT only allows the suppression of certain preprocessor warnings. To suppress a warning, use the following structure:

```cpp
#pragma hemtt supress { warning code } { scope = line }
```

The warning code can be one of the following:

| Code | Description |
| ---- | ----------- |
| pw3_padded_arg | Padded argument |

The scope can be one of the following:

| Scope | Description |
| ----- | ----------- |
| line | Suppresses the warning for the next line |
| file | Suppresses the warning for the remainder of the current file, not including includes |
| config | Suppresses the warning for the remainder of the current config, including includes |

## Preprocessor Warnings

### [PW1] Redefine Macro
Expand All @@ -28,6 +50,17 @@ It may also appear when a macro is defined in a file that is included more than

This warning is emitted when `config.cpp` is not all lowercase, e.g. `Config.cpp`.

### [PW3] Padded Argument

This warning is emitted when an argument to a macro is padded with spaces.

```cpp
#define Introduction(var1, var2) var1, meet var2
HELLO(Jim, Bob)
```
This would produce `Jim, meet Bob` instead of `Jim, meet Bob`. (Note the extra space before `Bob`).
## Rapify Warnings
### [CW1] Parent Case Mismatch
Expand Down
2 changes: 1 addition & 1 deletion libs/config/tests/warnings/cw1_parent_case/stdout.ansi
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
 │ ╰───── parent definition here
2 │ class Local: imported {
 │ ────┬───
 │ ╰───── class's parent does not match parent defintion case
 │ ╰───── class's parent does not match parent definition case
 │
 │ Help: change the parent case to match the parent definition `Imported`
───╯
19 changes: 19 additions & 0 deletions libs/preprocessor/src/codes/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
#![allow(missing_docs)]

use strsim::levenshtein;

pub mod pe10_function_as_value;
pub mod pe11_expected_function_or_value;
pub mod pe12_include_not_found;
Expand All @@ -11,7 +13,10 @@ pub mod pe15_if_invalid_operator;
pub mod pe16_if_incompatible_types;
pub mod pe17_double_else;
pub mod pe18_eoi_ifstate;
pub mod pe19_pragma_unknown;
pub mod pe1_unexpected_token;
pub mod pe20_pragma_invalid_scope;
pub mod pe21_pragma_invalid_suppress;
pub mod pe2_unexpected_eof;
pub mod pe3_expected_ident;
pub mod pe4_unknown_directive;
Expand All @@ -23,3 +28,17 @@ pub mod pe9_function_call_argument_count;

pub mod pw1_redefine;
pub mod pw2_invalid_config_case;
pub mod pw3_padded_arg;

pub fn similar_values<'a>(search: &str, haystack: &'a [&str]) -> Vec<&'a str> {
let mut similar = haystack
.iter()
.map(|v| (v, levenshtein(v, search)))
.collect::<Vec<_>>();
similar.sort_by_key(|(_, v)| *v);
similar.retain(|s| s.1 <= 3);
if similar.len() > 3 {
similar.truncate(3);
}
similar.into_iter().map(|(n, _)| *n).collect::<Vec<_>>()
}
122 changes: 122 additions & 0 deletions libs/preprocessor/src/codes/pe19_pragma_unknown.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
use ariadne::{ColorGenerator, Fmt, Label, Report, ReportKind, Source};
use hemtt_common::reporting::{Annotation, AnnotationLevel, Code, Token};
use tracing::error;

use super::similar_values;

#[allow(unused)]
/// An unknown `#pragma` directive
///
/// ```cpp
/// #pragma hemtt unknown
/// ```
pub struct PragmaUnknown {
/// The [`Token`] of the unknown directive
pub(crate) token: Box<Token>,
}

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

fn token(&self) -> Option<&Token> {
Some(&self.token)
}

fn message(&self) -> String {
format!(
"unknown `{}` pragma command",
self.token.symbol().to_string(),
)
}

fn label_message(&self) -> String {
format!(
"unknown `{}` pragma command",
self.token.symbol().to_string()
)
}

fn help(&self) -> Option<String> {
let similar = similar_values(self.token.to_string().as_str(), &["suppress"]);
if similar.is_empty() {
None
} else {
Some(format!(
"Did you mean {}?",
similar
.iter()
.map(|s| format!("`{s}`"))
.collect::<Vec<_>>()
.join(", ")
))
}
}

fn report_generate(&self) -> Option<String> {
let mut colors = ColorGenerator::default();
let color_token = colors.next();
let mut out = Vec::new();
let mut report = Report::build(
ReportKind::Error,
self.token.position().path().as_str(),
self.token.position().start().offset(),
)
.with_code(self.ident())
.with_message(self.message())
.with_label(
Label::new((
self.token.position().path().as_str(),
self.token.position().start().offset()..self.token.position().end().offset(),
))
.with_color(color_token)
.with_message(format!(
"unknown `{}` pragma command",
self.token.symbol().to_string().fg(color_token)
)),
);
if let Some(help) = self.help() {
report = report.with_help(help);
}
if let Err(e) = report.finish().write_for_stdout(
(
self.token.position().path().as_str(),
Source::from(
self.token
.position()
.path()
.read_to_string()
.unwrap_or_default(),
),
),
&mut out,
) {
error!("while reporting: {e}");
return None;
}
Some(String::from_utf8(out).unwrap_or_default())
}

fn ci_generate(&self) -> Vec<Annotation> {
vec![self.annotation(
AnnotationLevel::Error,
self.token.position().path().as_str().to_string(),
self.token.position(),
)]
}

#[cfg(feature = "lsp")]
fn generate_lsp(&self) -> Option<(VfsPath, Diagnostic)> {
let Some(path) = self.token.position().path() else {
return None;
};
Some((
path.clone(),
self.diagnostic(Range {
start: self.token.position().start().to_lsp() - 1,
end: self.token.position().end().to_lsp(),
}),
))
}
}
137 changes: 137 additions & 0 deletions libs/preprocessor/src/codes/pe20_pragma_invalid_scope.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
use ariadne::{ColorGenerator, Fmt, Label, Report, ReportKind, Source};
use hemtt_common::reporting::{Annotation, AnnotationLevel, Code, Token};
use tracing::error;

use super::similar_values;

#[allow(unused)]
/// An unknown `#pragma` directive
///
/// ```cpp
/// #pragma hemtt unknown
/// ```
pub struct PragmaInvalidScope {
/// The [`Token`] of the scope
pub(crate) token: Box<Token>,
/// Are we in the root config?
pub(crate) root: bool,
}

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

fn token(&self) -> Option<&Token> {
Some(&self.token)
}

fn message(&self) -> String {
format!(
"unknown #pragma scope `{}`",
self.token.symbol().to_string(),
)
}

fn label_message(&self) -> String {
format!(
"unknown #pragma scope `{}`",
self.token.symbol().to_string()
)
}

fn help(&self) -> Option<String> {
let scopes = if self.root {
vec!["line", "file", "config"]
} else {
vec!["line", "file"]
};
let did_you_mean = {
let similar = similar_values(self.token.to_string().as_str(), &scopes);
if similar.is_empty() {
String::new()
} else {
format!(
", did you mean {}?",
similar
.iter()
.map(|s| format!("`{s}`"))
.collect::<Vec<_>>()
.join(", ")
)
}
};
let scopes = scopes
.iter()
.map(|s| format!("`{s}`"))
.collect::<Vec<_>>()
.join(", ");
Some(format!("Valid scopes here: {scopes}{did_you_mean}"))
}

fn report_generate(&self) -> Option<String> {
let mut colors = ColorGenerator::default();
let color_token = colors.next();
let mut out = Vec::new();
let mut report = Report::build(
ReportKind::Error,
self.token.position().path().as_str(),
self.token.position().start().offset(),
)
.with_code(self.ident())
.with_message(self.message())
.with_label(
Label::new((
self.token.position().path().as_str(),
self.token.position().start().offset()..self.token.position().end().offset(),
))
.with_color(color_token)
.with_message(format!(
"unknown #pragma scope `{}`",
self.token.symbol().to_string().fg(color_token)
)),
);
if let Some(help) = self.help() {
report = report.with_help(help);
}
if let Err(e) = report.finish().write_for_stdout(
(
self.token.position().path().as_str(),
Source::from(
self.token
.position()
.path()
.read_to_string()
.unwrap_or_default(),
),
),
&mut out,
) {
error!("while reporting: {e}");
return None;
}
Some(String::from_utf8(out).unwrap_or_default())
}

fn ci_generate(&self) -> Vec<Annotation> {
vec![self.annotation(
AnnotationLevel::Error,
self.token.position().path().as_str().to_string(),
self.token.position(),
)]
}

#[cfg(feature = "lsp")]
fn generate_lsp(&self) -> Option<(VfsPath, Diagnostic)> {
let Some(path) = self.token.position().path() else {
return None;
};
Some((
path.clone(),
self.diagnostic(Range {
start: self.token.position().start().to_lsp() - 1,
end: self.token.position().end().to_lsp(),
}),
))
}
}
Loading

0 comments on commit 3e26635

Please sign in to comment.