1+ use crate :: problem:: npv_169;
2+ use crate :: ratchet:: RatchetState ;
13use relative_path:: RelativePath ;
24use relative_path:: RelativePathBuf ;
5+
6+ use rnix:: SyntaxKind ;
7+ use rowan:: ast:: AstNode ;
38use std:: collections:: BTreeMap ;
49use std:: path:: Path ;
510
@@ -8,46 +13,72 @@ use crate::validation::ResultIteratorExt;
813use crate :: validation:: Validation :: Success ;
914use crate :: { nix_file, ratchet, structure, validation} ;
1015
11- /// Runs check on all Nix files, returning a ratchet result for each
16+ /// Finds the first `with` expression in the syntax tree whose body contains another scope-defining
17+ /// construct (`with`, `let...in`, or attribute set). Such `with` expressions are problematic because
18+ /// they shadow variables in the enclosing scope, making it impossible for static analysis tools to
19+ /// determine which bindings are in scope.
20+ ///
21+ /// Returns `Some(node)` for the first offending `with` node, or `None` if no such node exists.
22+ fn find_invalid_withs ( syntax : & rnix:: SyntaxNode ) -> Option < rnix:: SyntaxNode > {
23+ syntax
24+ . descendants ( )
25+ . filter ( |node| node. kind ( ) == rnix:: SyntaxKind :: NODE_WITH )
26+ . filter ( |node| {
27+ node. descendants ( )
28+ . map ( |child| {
29+ // Skip the `with` node itself; we only care about its descendants.
30+ if child == * node {
31+ return None ;
32+ }
33+ // Any of these nested constructs means the `with` may shadow bindings
34+ // that the inner scope tries to reference.
35+ match child. kind ( ) {
36+ SyntaxKind :: NODE_WITH => Some ( node) ,
37+ SyntaxKind :: NODE_LET_IN => Some ( node) ,
38+ SyntaxKind :: NODE_ATTR_SET => Some ( node) ,
39+ _ => None ,
40+ }
41+ } )
42+ . any ( |node| node. is_some ( ) )
43+ } )
44+ . take ( 1 )
45+ . last ( )
46+ }
47+
48+ /// Runs ratchet checks on all Nix files in the Nixpkgs tree, returning a ratchet result for each.
1249pub fn check_files (
1350 nixpkgs_path : & Path ,
1451 nix_file_store : & mut NixFileStore ,
1552) -> validation:: Result < BTreeMap < RelativePathBuf , ratchet:: File > > {
16- process_nix_files ( nixpkgs_path, nix_file_store, |_nix_file| {
17- // Noop for now, only boilerplate to make it easier to add future file-based checks
18- Ok ( Success ( ratchet:: File { } ) )
53+ process_nix_files ( nixpkgs_path, nix_file_store, |nix_file| {
54+ Ok ( Success ( ratchet:: File {
55+ top_level_with : check_top_level_with ( nixpkgs_path, nix_file) ,
56+ } ) )
1957 } )
2058}
2159
22- /// Processes all Nix files in a Nixpkgs directory according to a given function `f`, collecting the
23- /// results into a mapping from each file to a ratchet value.
24- fn process_nix_files (
60+ /// Checks a single Nix file for top-level `with` expressions that contain nested scope-defining
61+ /// constructs. Returns [`RatchetState::Loose`] with a problem if such a `with` is found, or
62+ /// [`RatchetState::Tight`] if the file is clean.
63+ fn check_top_level_with (
2564 nixpkgs_path : & Path ,
26- nix_file_store : & mut NixFileStore ,
27- f : impl Fn ( & nix_file:: NixFile ) -> validation:: Result < ratchet:: File > ,
28- ) -> validation:: Result < BTreeMap < RelativePathBuf , ratchet:: File > > {
29- // Get all Nix files
30- let files = {
31- let mut files = vec ! [ ] ;
32- collect_nix_files ( nixpkgs_path, & RelativePathBuf :: new ( ) , & mut files) ?;
33- files
34- } ;
35-
36- let results = files
37- . into_iter ( )
38- . map ( |path| {
39- // Get the (optionally-cached) parsed Nix file
40- let nix_file = nix_file_store. get ( & path. to_path ( nixpkgs_path) ) ?;
41- let result = f ( nix_file) ?;
42- let val = result. map ( |ratchet| ( path, ratchet) ) ;
43- Ok :: < _ , anyhow:: Error > ( val)
44- } )
45- . collect_vec ( ) ?;
46-
47- Ok ( validation:: sequence ( results) . map ( |entries| {
48- // Convert the Vec to a BTreeMap
49- entries. into_iter ( ) . collect ( )
50- } ) )
65+ nix_file : & nix_file:: NixFile ,
66+ ) -> RatchetState < ratchet:: DoesNotIntroduceToplevelWiths > {
67+ if let Some ( offending_with) = find_invalid_withs ( nix_file. syntax_root . syntax ( ) ) {
68+ let relative_path = RelativePathBuf :: from_path (
69+ nix_file. path . clone ( ) . strip_prefix ( nixpkgs_path) . unwrap ( ) ,
70+ )
71+ . unwrap ( ) ;
72+ RatchetState :: Loose (
73+ npv_169:: TopLevelWithMayShadowVariablesAndBreakStaticChecks :: new (
74+ relative_path,
75+ offending_with. to_string ( ) ,
76+ )
77+ . into ( ) ,
78+ )
79+ } else {
80+ RatchetState :: Tight
81+ }
5182}
5283
5384/// Recursively collects all Nix files in the relative `dir` within `base`
@@ -63,7 +94,7 @@ fn collect_nix_files(
6394
6495 let absolute_path = entry. path ( ) ;
6596
66- // We'll get to every file based on directory recursion, no need to follow symlinks.
97+ // We reach every file via directory recursion, no need to follow symlinks.
6798 if absolute_path. is_symlink ( ) {
6899 continue ;
69100 }
@@ -75,3 +106,30 @@ fn collect_nix_files(
75106 }
76107 Ok ( ( ) )
77108}
109+
110+ /// Processes all Nix files in a Nixpkgs directory according to a given function `f`, collecting the
111+ /// results into a mapping from each file to a ratchet value.
112+ fn process_nix_files < F : Fn ( & nix_file:: NixFile ) -> validation:: Result < ratchet:: File > > (
113+ nixpkgs_path : & Path ,
114+ nix_file_store : & mut NixFileStore ,
115+ f : F ,
116+ ) -> validation:: Result < BTreeMap < RelativePathBuf , ratchet:: File > > {
117+ // Get all Nix files
118+ let files = {
119+ let mut files = vec ! [ ] ;
120+ collect_nix_files ( nixpkgs_path, & RelativePathBuf :: new ( ) , & mut files) ?;
121+ files
122+ } ;
123+
124+ let file_results: Vec < validation:: Validation < ( RelativePathBuf , ratchet:: File ) > > = files
125+ . into_iter ( )
126+ . map ( |path| {
127+ // Get the (optionally-cached) parsed Nix file
128+ let nix_file = nix_file_store. get ( & path. to_path ( nixpkgs_path) ) ?;
129+ let val = f ( nix_file) ?. map ( |file| ( path, file) ) ;
130+ Ok :: < _ , anyhow:: Error > ( val)
131+ } )
132+ . collect_vec ( ) ?;
133+
134+ Ok ( validation:: sequence ( file_results) . map ( |entries| entries. into_iter ( ) . collect ( ) ) )
135+ }
0 commit comments