fix(natu): preserve NixOS cfg bindings
This commit was merged in pull request #104.
This commit is contained in:
@@ -111,7 +111,7 @@ pub fn future_sight(files: Vec<SourceFile>) -> status::Result<Report> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let graph = ScopeGraph::from_file(&parsed);
|
let graph = ScopeGraph::from_file(&parsed);
|
||||||
diagnostics.extend(rules::run_rules(&parsed, &graph));
|
diagnostics.extend(rules::run_rules(&parsed, &graph, &file.path));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Report {
|
Ok(Report {
|
||||||
|
|||||||
@@ -65,6 +65,18 @@ fn natu_reports_out_of_order_let_bindings() -> googletest::Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[gtest]
|
||||||
|
fn natu_preserves_nixos_cfg_bindings() -> googletest::Result<()> {
|
||||||
|
let report = natu::future_sight(vec![SourceFile {
|
||||||
|
path: PathBuf::from("configs/nixos/test.nix"),
|
||||||
|
source: "{ config, ... }: let\n cfg = config.example;\n value = cfg;\nin value\n".into(),
|
||||||
|
}])
|
||||||
|
.or_fail()?;
|
||||||
|
|
||||||
|
expect_that!(report.is_clean(), is_true());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[gtest]
|
#[gtest]
|
||||||
fn natu_reports_unquoted_uri() -> googletest::Result<()> {
|
fn natu_reports_unquoted_uri() -> googletest::Result<()> {
|
||||||
let report = natu::future_sight(vec![fixture_source("testdata/lint_findings.nix")?]).or_fail()?;
|
let report = natu::future_sight(vec![fixture_source("testdata/lint_findings.nix")?]).or_fail()?;
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
use std::path::Path;
|
||||||
|
|
||||||
use crate::FileId;
|
use crate::FileId;
|
||||||
use crate::ast;
|
use crate::ast;
|
||||||
use crate::rules::manual_inherit::ManualInherit;
|
use crate::rules::manual_inherit::ManualInherit;
|
||||||
@@ -59,7 +61,7 @@ pub trait TokenRule {
|
|||||||
fn check(token: &Self::Token) -> Option<&'static str>;
|
fn check(token: &Self::Token) -> Option<&'static str>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_rules(file: &ast::ParsedFile, graph: &ScopeGraph) -> Vec<Diagnostic> {
|
pub fn run_rules(file: &ast::ParsedFile, graph: &ScopeGraph, path: &Path) -> Vec<Diagnostic> {
|
||||||
let node_diagnostics = file
|
let node_diagnostics = file
|
||||||
.nodes
|
.nodes
|
||||||
.iter()
|
.iter()
|
||||||
@@ -76,13 +78,17 @@ pub fn run_rules(file: &ast::ParsedFile, graph: &ScopeGraph) -> Vec<Diagnostic>
|
|||||||
.flat_map(|token| [run_token_rule::<UnquotedUri>(token)])
|
.flat_map(|token| [run_token_rule::<UnquotedUri>(token)])
|
||||||
.flatten();
|
.flatten();
|
||||||
|
|
||||||
let let_order_diagnostics = let_order::check(graph).into_iter().map(|finding| Finding {
|
let ignore_cfg =
|
||||||
rule_id: "let_order",
|
path.starts_with("configs/nixos") || path.to_string_lossy().contains("/configs/nixos/");
|
||||||
range: finding.range,
|
let let_order_diagnostics = let_order::check(graph, ignore_cfg)
|
||||||
message: "Let bindings must follow dependency order",
|
.into_iter()
|
||||||
binding: None,
|
.map(|finding| Finding {
|
||||||
expected_order: Some(finding.expected_order),
|
rule_id: "let_order",
|
||||||
});
|
range: finding.range,
|
||||||
|
message: "Let bindings must follow dependency order",
|
||||||
|
binding: None,
|
||||||
|
expected_order: Some(finding.expected_order),
|
||||||
|
});
|
||||||
|
|
||||||
node_diagnostics
|
node_diagnostics
|
||||||
.chain(token_diagnostics)
|
.chain(token_diagnostics)
|
||||||
|
|||||||
@@ -8,36 +8,42 @@ pub struct Finding {
|
|||||||
pub expected_order: Vec<String>,
|
pub expected_order: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn check(graph: &ScopeGraph) -> Vec<Finding> {
|
pub fn check(graph: &ScopeGraph, ignore_cfg: bool) -> Vec<Finding> {
|
||||||
graph
|
graph
|
||||||
.let_scopes()
|
.let_scopes()
|
||||||
.iter()
|
.iter()
|
||||||
.flat_map(|let_scope| {
|
.flat_map(|let_scope| {
|
||||||
let expected = expected_order(graph, let_scope);
|
let expected = expected_order(graph, let_scope)
|
||||||
let is_ordered = expected.iter().enumerate().all(|(position, declaration)| {
|
.into_iter()
|
||||||
let_scope
|
.filter(|declaration| !ignore_cfg || binding_name(let_scope, *declaration) != "cfg")
|
||||||
.bindings
|
.collect::<Vec<_>>();
|
||||||
.get(position)
|
let actual = let_scope
|
||||||
.is_some_and(|binding| binding.declaration == *declaration)
|
.bindings
|
||||||
});
|
.iter()
|
||||||
|
.filter(|binding| !ignore_cfg || binding.name != "cfg")
|
||||||
|
.map(|binding| binding.declaration)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
(!is_ordered).then(|| Finding {
|
(!actual.starts_with(&expected)).then(|| Finding {
|
||||||
range: let_scope.range,
|
range: let_scope.range,
|
||||||
expected_order: expected
|
expected_order: expected
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|declaration| {
|
.map(|declaration| binding_name(let_scope, *declaration).to_owned())
|
||||||
let_scope
|
|
||||||
.bindings
|
|
||||||
.iter()
|
|
||||||
.find(|binding| binding.declaration == *declaration)
|
|
||||||
.map(|binding| binding.name.clone())
|
|
||||||
})
|
|
||||||
.collect(),
|
.collect(),
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn binding_name(let_scope: &LetScope, declaration: NodeId) -> &str {
|
||||||
|
&let_scope
|
||||||
|
.bindings
|
||||||
|
.iter()
|
||||||
|
.find(|binding| binding.declaration == declaration)
|
||||||
|
.expect("let declaration")
|
||||||
|
.name
|
||||||
|
}
|
||||||
|
|
||||||
fn expected_order(graph: &ScopeGraph, let_scope: &LetScope) -> Vec<NodeId> {
|
fn expected_order(graph: &ScopeGraph, let_scope: &LetScope) -> Vec<NodeId> {
|
||||||
let mut order = Vec::new();
|
let mut order = Vec::new();
|
||||||
visit_references(graph, &let_scope.entry_references, let_scope, &mut order);
|
visit_references(graph, &let_scope.entry_references, let_scope, &mut order);
|
||||||
|
|||||||
Reference in New Issue
Block a user