Compare commits
1 Commits
8b5af32d22
...
c4ea12b3ce
| Author | SHA1 | Date | |
|---|---|---|---|
| c4ea12b3ce |
@@ -8,7 +8,6 @@ rust_library(
|
|||||||
"file_store.rs",
|
"file_store.rs",
|
||||||
"parser.rs",
|
"parser.rs",
|
||||||
"rules.rs",
|
"rules.rs",
|
||||||
"rules/let_order.rs",
|
|
||||||
"rules/manual_inherit.rs",
|
"rules/manual_inherit.rs",
|
||||||
"rules/manual_inherit_from.rs",
|
"rules/manual_inherit_from.rs",
|
||||||
"rules/unquoted_uri.rs",
|
"rules/unquoted_uri.rs",
|
||||||
|
|||||||
@@ -3,9 +3,7 @@ use crate::ast;
|
|||||||
use crate::rules::manual_inherit::ManualInherit;
|
use crate::rules::manual_inherit::ManualInherit;
|
||||||
use crate::rules::manual_inherit_from::ManualInheritFrom;
|
use crate::rules::manual_inherit_from::ManualInheritFrom;
|
||||||
use crate::rules::unquoted_uri::UnquotedUri;
|
use crate::rules::unquoted_uri::UnquotedUri;
|
||||||
use crate::scope_graph::ScopeGraph;
|
|
||||||
|
|
||||||
pub mod let_order;
|
|
||||||
pub mod manual_inherit;
|
pub mod manual_inherit;
|
||||||
pub mod manual_inherit_from;
|
pub mod manual_inherit_from;
|
||||||
pub mod unquoted_uri;
|
pub mod unquoted_uri;
|
||||||
@@ -21,22 +19,15 @@ pub struct Finding {
|
|||||||
pub rule_id: &'static str,
|
pub rule_id: &'static str,
|
||||||
pub range: ast::SourceRange,
|
pub range: ast::SourceRange,
|
||||||
pub message: &'static str,
|
pub message: &'static str,
|
||||||
pub binding: Option<String>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for Diagnostic {
|
impl std::fmt::Display for Diagnostic {
|
||||||
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
write!(
|
write!(
|
||||||
formatter,
|
formatter,
|
||||||
"[{}] {}",
|
"{}: {}: {}",
|
||||||
self.finding.rule_id, self.finding.message
|
self.finding.range, self.finding.rule_id, self.finding.message,
|
||||||
)?;
|
)
|
||||||
|
|
||||||
if let Some(binding) = &self.finding.binding {
|
|
||||||
write!(formatter, ": {binding}")?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,7 +49,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) -> Vec<Diagnostic> {
|
||||||
let node_diagnostics = file
|
let node_diagnostics = file
|
||||||
.nodes
|
.nodes
|
||||||
.iter()
|
.iter()
|
||||||
@@ -75,16 +66,8 @@ 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 {
|
|
||||||
rule_id: "let_order",
|
|
||||||
range: finding.range,
|
|
||||||
message: "Let bindings must follow dependency order",
|
|
||||||
binding: Some(finding.binding),
|
|
||||||
});
|
|
||||||
|
|
||||||
node_diagnostics
|
node_diagnostics
|
||||||
.chain(token_diagnostics)
|
.chain(token_diagnostics)
|
||||||
.chain(let_order_diagnostics)
|
|
||||||
.map(|finding| Diagnostic {
|
.map(|finding| Diagnostic {
|
||||||
file_id: file.file_id,
|
file_id: file.file_id,
|
||||||
finding,
|
finding,
|
||||||
@@ -103,7 +86,6 @@ where
|
|||||||
rule_id: R::ID,
|
rule_id: R::ID,
|
||||||
range: node.range,
|
range: node.range,
|
||||||
message,
|
message,
|
||||||
binding: None,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,6 +100,5 @@ where
|
|||||||
rule_id: R::ID,
|
rule_id: R::ID,
|
||||||
range: token.range,
|
range: token.range,
|
||||||
message,
|
message,
|
||||||
binding: None,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,71 +0,0 @@
|
|||||||
use crate::{
|
|
||||||
ast,
|
|
||||||
scope_graph::{LetScope, NodeId, ScopeGraph},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub struct Finding {
|
|
||||||
pub range: ast::SourceRange,
|
|
||||||
pub binding: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn check(graph: &ScopeGraph) -> Vec<Finding> {
|
|
||||||
graph
|
|
||||||
.let_scopes()
|
|
||||||
.iter()
|
|
||||||
.flat_map(|let_scope| {
|
|
||||||
let expected = expected_order(graph, let_scope);
|
|
||||||
let positions = expected
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.map(|(position, declaration)| (*declaration, position))
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
let_scope
|
|
||||||
.bindings
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.filter_map(move |(position, binding)| {
|
|
||||||
positions
|
|
||||||
.iter()
|
|
||||||
.find(|(declaration, _)| *declaration == binding.declaration)
|
|
||||||
.filter(|(_, expected_position)| position != *expected_position)
|
|
||||||
.map(|_| Finding {
|
|
||||||
range: binding.range,
|
|
||||||
binding: binding.name.clone(),
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn expected_order(graph: &ScopeGraph, let_scope: &LetScope) -> Vec<NodeId> {
|
|
||||||
let mut order = Vec::new();
|
|
||||||
visit_references(graph, &let_scope.entry_references, let_scope, &mut order);
|
|
||||||
order
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_references(
|
|
||||||
graph: &ScopeGraph,
|
|
||||||
references: &[NodeId],
|
|
||||||
let_scope: &LetScope,
|
|
||||||
order: &mut Vec<NodeId>,
|
|
||||||
) {
|
|
||||||
for reference in references {
|
|
||||||
for declaration in graph.resolve(*reference) {
|
|
||||||
let Some(binding) = let_scope
|
|
||||||
.bindings
|
|
||||||
.iter()
|
|
||||||
.find(|binding| binding.declaration == declaration)
|
|
||||||
else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
|
|
||||||
if order.contains(&declaration) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
order.push(declaration);
|
|
||||||
visit_references(graph, &binding.references, let_scope, order);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
18
src/xatu/testdata/clean.nix
vendored
18
src/xatu/testdata/clean.nix
vendored
@@ -1,22 +1,6 @@
|
|||||||
{ pkgs }:
|
{ pkgs }:
|
||||||
|
|
||||||
let
|
{
|
||||||
first = 1;
|
|
||||||
second = first;
|
|
||||||
in {
|
|
||||||
firstField = first;
|
|
||||||
secondField = second;
|
|
||||||
nested = let
|
|
||||||
innerSecond = innerFirst;
|
|
||||||
innerFirst = first;
|
|
||||||
in {
|
|
||||||
result = innerSecond;
|
|
||||||
};
|
|
||||||
parameter = first: let
|
|
||||||
usesParameter = first;
|
|
||||||
in {
|
|
||||||
result = usesParameter;
|
|
||||||
};
|
|
||||||
path = ./. + "/foo";
|
path = ./. + "/foo";
|
||||||
url = "https://example.com";
|
url = "https://example.com";
|
||||||
inherit pkgs;
|
inherit pkgs;
|
||||||
|
|||||||
14
src/xatu/testdata/lint_findings.nix
vendored
14
src/xatu/testdata/lint_findings.nix
vendored
@@ -1,17 +1,5 @@
|
|||||||
let
|
{
|
||||||
second = first;
|
|
||||||
first = 1;
|
|
||||||
nested = let
|
|
||||||
nestedFirst = first;
|
|
||||||
nestedSecond = nestedFirst;
|
|
||||||
in {
|
|
||||||
result = nestedSecond;
|
|
||||||
};
|
|
||||||
in {
|
|
||||||
package = package;
|
package = package;
|
||||||
from = pkgs.from;
|
from = pkgs.from;
|
||||||
url = https://example.com;
|
url = https://example.com;
|
||||||
firstField = first;
|
|
||||||
secondField = second;
|
|
||||||
nestedField = nested;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,6 @@ mod parser;
|
|||||||
mod rules;
|
mod rules;
|
||||||
mod scope_graph;
|
mod scope_graph;
|
||||||
|
|
||||||
use scope_graph::ScopeGraph;
|
|
||||||
|
|
||||||
pub use file_store::{FileId, FileStore, SourceFile};
|
pub use file_store::{FileId, FileStore, SourceFile};
|
||||||
pub use rules::{Diagnostic, Finding};
|
pub use rules::{Diagnostic, Finding};
|
||||||
|
|
||||||
@@ -25,34 +23,13 @@ impl std::fmt::Display for Report {
|
|||||||
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
for diagnostic in &self.diagnostics {
|
for diagnostic in &self.diagnostics {
|
||||||
let file = self.files.get(diagnostic.file_id);
|
let file = self.files.get(diagnostic.file_id);
|
||||||
let (start, end) = line_range(&file.source, diagnostic.finding.range);
|
writeln!(formatter, "{}:{diagnostic}", file.path.display())?;
|
||||||
let location = if start == end {
|
|
||||||
start.to_string()
|
|
||||||
} else {
|
|
||||||
format!("{start}-{end}")
|
|
||||||
};
|
|
||||||
writeln!(formatter, "{}:{location} {diagnostic}", file.path.display())?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn line_range(source: &str, range: ast::SourceRange) -> (usize, usize) {
|
|
||||||
let line_at = |offset| {
|
|
||||||
source
|
|
||||||
.bytes()
|
|
||||||
.take(offset)
|
|
||||||
.filter(|byte| *byte == b'\n')
|
|
||||||
.count()
|
|
||||||
+ 1
|
|
||||||
};
|
|
||||||
let start = line_at(range.start);
|
|
||||||
let end = line_at(range.end);
|
|
||||||
|
|
||||||
(start, end)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Analyzes source files and returns the diagnostics Xatu finds.
|
/// Analyzes source files and returns the diagnostics Xatu finds.
|
||||||
pub fn future_sight(files: Vec<SourceFile>) -> status::Result<Report> {
|
pub fn future_sight(files: Vec<SourceFile>) -> status::Result<Report> {
|
||||||
let mut store = FileStore::default();
|
let mut store = FileStore::default();
|
||||||
@@ -70,8 +47,7 @@ pub fn future_sight(files: Vec<SourceFile>) -> status::Result<Report> {
|
|||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
let graph = ScopeGraph::from_file(&parsed);
|
diagnostics.extend(rules::run_rules(&parsed));
|
||||||
diagnostics.extend(rules::run_rules(&parsed, &graph));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Report {
|
Ok(Report {
|
||||||
|
|||||||
@@ -41,19 +41,6 @@ fn xatu_rejects_invalid_source() -> googletest::Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[gtest]
|
|
||||||
fn xatu_reports_out_of_order_let_bindings() -> googletest::Result<()> {
|
|
||||||
let report = xatu::future_sight(vec![fixture_source("testdata/lint_findings.nix")?]).or_fail()?;
|
|
||||||
|
|
||||||
let let_order_count = report
|
|
||||||
.diagnostics
|
|
||||||
.iter()
|
|
||||||
.filter(|diagnostic| diagnostic.finding.rule_id == "let_order")
|
|
||||||
.count();
|
|
||||||
expect_that!(let_order_count, eq(4));
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[gtest]
|
#[gtest]
|
||||||
fn xatu_reports_unquoted_uri() -> googletest::Result<()> {
|
fn xatu_reports_unquoted_uri() -> googletest::Result<()> {
|
||||||
let report = xatu::future_sight(vec![fixture_source("testdata/lint_findings.nix")?]).or_fail()?;
|
let report = xatu::future_sight(vec![fixture_source("testdata/lint_findings.nix")?]).or_fail()?;
|
||||||
|
|||||||
Reference in New Issue
Block a user