initial diagnostics implementation
This commit is contained in:
parent
f2b57488fe
commit
2a45d0b983
4 changed files with 1326 additions and 9 deletions
85
Cargo.lock
generated
85
Cargo.lock
generated
|
@ -2,6 +2,15 @@
|
||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 4
|
version = 4
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aho-corasick"
|
||||||
|
version = "1.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anstream"
|
name = "anstream"
|
||||||
version = "0.6.13"
|
version = "0.6.13"
|
||||||
|
@ -189,6 +198,12 @@ dependencies = [
|
||||||
"hashbrown",
|
"hashbrown",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itoa"
|
||||||
|
version = "1.0.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lazy_static"
|
name = "lazy_static"
|
||||||
version = "1.5.0"
|
version = "1.5.0"
|
||||||
|
@ -245,6 +260,12 @@ dependencies = [
|
||||||
"logos-codegen",
|
"logos-codegen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memchr"
|
||||||
|
version = "2.7.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nff"
|
name = "nff"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
@ -253,6 +274,9 @@ dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
"cstree",
|
"cstree",
|
||||||
"logos",
|
"logos",
|
||||||
|
"regex",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
"text-size",
|
"text-size",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
]
|
]
|
||||||
|
@ -307,6 +331,29 @@ dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex"
|
||||||
|
version = "1.11.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
|
||||||
|
dependencies = [
|
||||||
|
"aho-corasick",
|
||||||
|
"memchr",
|
||||||
|
"regex-automata",
|
||||||
|
"regex-syntax",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-automata"
|
||||||
|
version = "0.4.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
|
||||||
|
dependencies = [
|
||||||
|
"aho-corasick",
|
||||||
|
"memchr",
|
||||||
|
"regex-syntax",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex-syntax"
|
name = "regex-syntax"
|
||||||
version = "0.8.5"
|
version = "0.8.5"
|
||||||
|
@ -322,6 +369,12 @@ dependencies = [
|
||||||
"semver",
|
"semver",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ryu"
|
||||||
|
version = "1.0.20"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "scopeguard"
|
name = "scopeguard"
|
||||||
version = "1.2.0"
|
version = "1.2.0"
|
||||||
|
@ -334,6 +387,38 @@ version = "1.0.26"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0"
|
checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde"
|
||||||
|
version = "1.0.219"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
|
||||||
|
dependencies = [
|
||||||
|
"serde_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_derive"
|
||||||
|
version = "1.0.219"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_json"
|
||||||
|
version = "1.0.140"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
|
||||||
|
dependencies = [
|
||||||
|
"itoa",
|
||||||
|
"memchr",
|
||||||
|
"ryu",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "smallvec"
|
name = "smallvec"
|
||||||
version = "1.15.0"
|
version = "1.15.0"
|
||||||
|
|
|
@ -12,3 +12,6 @@ thiserror = "2.0"
|
||||||
logos = "0.15"
|
logos = "0.15"
|
||||||
cstree = "0.12"
|
cstree = "0.12"
|
||||||
text-size = "1.1"
|
text-size = "1.1"
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
serde_json = "1.0"
|
||||||
|
regex = "1.11.1"
|
||||||
|
|
1141
src/diagnostic.rs
Normal file
1141
src/diagnostic.rs
Normal file
File diff suppressed because it is too large
Load diff
106
src/main.rs
106
src/main.rs
|
@ -1,5 +1,6 @@
|
||||||
mod ast;
|
mod ast;
|
||||||
mod cst;
|
mod cst;
|
||||||
|
mod diagnostic;
|
||||||
mod lexer;
|
mod lexer;
|
||||||
mod parser;
|
mod parser;
|
||||||
mod syntax;
|
mod syntax;
|
||||||
|
@ -12,6 +13,7 @@ use std::path::Path;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::cst::CstBuilder;
|
use crate::cst::CstBuilder;
|
||||||
|
use crate::diagnostic::{DiagnosticAnalyzer, DiagnosticConfig};
|
||||||
use crate::lexer::NftablesLexer;
|
use crate::lexer::NftablesLexer;
|
||||||
use crate::parser::Parser as NftablesParser;
|
use crate::parser::Parser as NftablesParser;
|
||||||
use crate::syntax::{FormatConfig, IndentStyle, NftablesFormatter};
|
use crate::syntax::{FormatConfig, IndentStyle, NftablesFormatter};
|
||||||
|
@ -28,7 +30,7 @@ enum FormatterError {
|
||||||
Io(#[from] io::Error),
|
Io(#[from] io::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug, Clone)]
|
||||||
#[command(
|
#[command(
|
||||||
name = "nff",
|
name = "nff",
|
||||||
version = "0.1.0",
|
version = "0.1.0",
|
||||||
|
@ -63,6 +65,34 @@ struct Args {
|
||||||
/// Check syntax only, don't format
|
/// Check syntax only, don't format
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
check: bool,
|
check: bool,
|
||||||
|
|
||||||
|
/// Run diagnostics and show issues (syntax, style, best practices)
|
||||||
|
#[arg(long)]
|
||||||
|
diagnostics: bool,
|
||||||
|
|
||||||
|
/// Output diagnostics in JSON format (useful for tooling integration)
|
||||||
|
#[arg(long)]
|
||||||
|
json: bool,
|
||||||
|
|
||||||
|
/// Include style warnings in diagnostics
|
||||||
|
#[arg(long, default_value = "true")]
|
||||||
|
style_warnings: bool,
|
||||||
|
|
||||||
|
/// Include best practice recommendations in diagnostics
|
||||||
|
#[arg(long, default_value = "true")]
|
||||||
|
best_practices: bool,
|
||||||
|
|
||||||
|
/// Include performance hints in diagnostics
|
||||||
|
#[arg(long, default_value = "true")]
|
||||||
|
performance_hints: bool,
|
||||||
|
|
||||||
|
/// Include security warnings in diagnostics
|
||||||
|
#[arg(long, default_value = "true")]
|
||||||
|
security_warnings: bool,
|
||||||
|
|
||||||
|
/// Diagnostic modules to run (comma-separated: lexical,syntax,style,semantic)
|
||||||
|
#[arg(long, value_delimiter = ',')]
|
||||||
|
modules: Option<Vec<String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process_nftables_config(args: Args) -> Result<()> {
|
fn process_nftables_config(args: Args) -> Result<()> {
|
||||||
|
@ -116,7 +146,59 @@ fn process_nftables_config(args: Args) -> Result<()> {
|
||||||
eprintln!();
|
eprintln!();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse
|
// Run diagnostics if requested (do this early to catch parse errors)
|
||||||
|
if args.diagnostics || args.json {
|
||||||
|
let diagnostic_config = DiagnosticConfig {
|
||||||
|
enable_style_warnings: args.style_warnings,
|
||||||
|
enable_best_practices: args.best_practices,
|
||||||
|
enable_performance_hints: args.performance_hints,
|
||||||
|
enable_security_warnings: args.security_warnings,
|
||||||
|
max_line_length: 120,
|
||||||
|
max_empty_lines: if args.optimize { 1 } else { 2 },
|
||||||
|
preferred_indent: Some(match args.indent {
|
||||||
|
IndentStyle::Tabs => "tabs".to_string(),
|
||||||
|
IndentStyle::Spaces => "spaces".to_string(),
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
let analyzer = DiagnosticAnalyzer::new(diagnostic_config);
|
||||||
|
|
||||||
|
let diagnostics = if let Some(modules) = &args.modules {
|
||||||
|
let module_names: Vec<&str> = modules.iter().map(|s| s.as_str()).collect();
|
||||||
|
analyzer.analyze_with_modules(&source, &args.file, &module_names)
|
||||||
|
} else {
|
||||||
|
analyzer.analyze(&source, &args.file)
|
||||||
|
};
|
||||||
|
|
||||||
|
if args.json {
|
||||||
|
// Output JSON format for tooling integration
|
||||||
|
match diagnostics.to_json() {
|
||||||
|
Ok(json) => println!("{}", json),
|
||||||
|
Err(e) => {
|
||||||
|
if args.json {
|
||||||
|
// Even JSON serialization errors should be in JSON format when --json is used
|
||||||
|
let error_json =
|
||||||
|
format!(r#"{{"error": "JSON serialization failed: {}"}}"#, e);
|
||||||
|
println!("{}", error_json);
|
||||||
|
} else {
|
||||||
|
eprintln!("Error serializing diagnostics to JSON: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Output human-readable format
|
||||||
|
println!("{}", diagnostics.to_human_readable());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exit with non-zero code if there are errors
|
||||||
|
if diagnostics.has_errors() {
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse (only if not doing diagnostics)
|
||||||
let ruleset = if args.debug {
|
let ruleset = if args.debug {
|
||||||
// Use error-recovery parsing for debug mode
|
// Use error-recovery parsing for debug mode
|
||||||
let (parsed_ruleset, errors) = NftablesParser::parse_with_errors(&source);
|
let (parsed_ruleset, errors) = NftablesParser::parse_with_errors(&source);
|
||||||
|
@ -177,14 +259,20 @@ fn process_nftables_config(args: Args) -> Result<()> {
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
let args = Args::parse();
|
let args = Args::parse();
|
||||||
|
|
||||||
if let Err(e) = process_nftables_config(args) {
|
if let Err(e) = process_nftables_config(args.clone()) {
|
||||||
eprintln!("Error: {}", e);
|
if args.json {
|
||||||
|
// Output error in JSON format when --json flag is used
|
||||||
|
let error_json = format!(r#"{{"error": "{}"}}"#, e);
|
||||||
|
println!("{}", error_json);
|
||||||
|
} else {
|
||||||
|
eprintln!("Error: {}", e);
|
||||||
|
|
||||||
// Print the error chain
|
// Print the error chain
|
||||||
let mut current = e.source();
|
let mut current = e.source();
|
||||||
while let Some(cause) = current {
|
while let Some(cause) = current {
|
||||||
eprintln!(" Caused by: {}", cause);
|
eprintln!(" Caused by: {}", cause);
|
||||||
current = cause.source();
|
current = cause.source();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue