Repository: casey/just
Pull request #2092 (2024-05-26 01:06:23)

List unsorted imported recipes first by import depth and offset


Patch:
diff --git a/src/compiler.rs b/src/compiler.rs
index 066c135b..9eaf985b 100644
--- a/src/compiler.rs
+++ b/src/compiler.rs
@@ -23,6 +23,7 @@ impl Compiler {
       let mut ast = Parser::parse(
         current.file_depth,
         &current.path,
+        &current.import_offsets,
         &current.namepath,
         current.submodule_depth,
         &tokens,
@@ -94,7 +95,7 @@ impl Compiler {
                 });
               }
               *absolute = Some(import.clone());
-              stack.push(current.import(import));
+              stack.push(current.import(import, path.offset));
             } else if !*optional {
               return Err(Error::MissingImportFile { path: *path });
             }
@@ -172,6 +173,7 @@ impl Compiler {
     let ast = Parser::parse(
       0,
       &PathBuf::new(),
+      &[],
       &Namepath::default(),
       0,
       &tokens,
diff --git a/src/justfile.rs b/src/justfile.rs
index 57fdb54e..a74a94c1 100644
--- a/src/justfile.rs
+++ b/src/justfile.rs
@@ -495,16 +495,7 @@ impl<'src> Justfile<'src> {
       .collect::<Vec<&Recipe<Dependency>>>();
 
     if config.unsorted {
-      recipes.sort_by_key(|recipe| {
-        (
-          self
-            .loaded
-            .iter()
-            .position(|path| path == recipe.name.path)
-            .unwrap(),
-          recipe.name.offset,
-        )
-      });
+      recipes.sort_by_key(|recipe| (&recipe.import_offsets, recipe.name.offset));
     }
 
     recipes
diff --git a/src/parser.rs b/src/parser.rs
index 9b907d34..3a05fea5 100644
--- a/src/parser.rs
+++ b/src/parser.rs
@@ -27,6 +27,7 @@ pub(crate) struct Parser<'run, 'src> {
   expected_tokens: BTreeSet<TokenKind>,
   file_depth: u32,
   file_path: &'run Path,
+  import_offsets: Vec<usize>,
   module_namepath: &'run Namepath<'src>,
   next_token: usize,
   recursion_depth: usize,
@@ -40,6 +41,7 @@ impl<'run, 'src> Parser<'run, 'src> {
   pub(crate) fn parse(
     file_depth: u32,
     file_path: &'run Path,
+    import_offsets: &[usize],
     module_namepath: &'run Namepath<'src>,
     submodule_depth: u32,
     tokens: &'run [Token<'src>],
@@ -49,6 +51,7 @@ impl<'run, 'src> Parser<'run, 'src> {
       expected_tokens: BTreeSet::new(),
       file_depth,
       file_path,
+      import_offsets: import_offsets.to_vec(),
       module_namepath,
       next_token: 0,
       recursion_depth: 0,
@@ -801,6 +804,7 @@ impl<'run, 'src> Parser<'run, 'src> {
       doc,
       file_depth: self.file_depth,
       file_path: self.file_path.into(),
+      import_offsets: self.import_offsets.clone(),
       name,
       namepath: self.module_namepath.join(name),
       parameters: positional.into_iter().chain(variadic).collect(),
@@ -1040,6 +1044,7 @@ mod tests {
     let justfile = Parser::parse(
       0,
       &PathBuf::new(),
+      &[],
       &Namepath::default(),
       0,
       &tokens,
@@ -1086,6 +1091,7 @@ mod tests {
     match Parser::parse(
       0,
       &PathBuf::new(),
+      &[],
       &Namepath::default(),
       0,
       &tokens,
diff --git a/src/recipe.rs b/src/recipe.rs
index f974e394..62c6d279 100644
--- a/src/recipe.rs
+++ b/src/recipe.rs
@@ -27,6 +27,8 @@ pub(crate) struct Recipe<'src, D = Dependency<'src>> {
   pub(crate) file_depth: u32,
   #[serde(skip)]
   pub(crate) file_path: PathBuf,
+  #[serde(skip)]
+  pub(crate) import_offsets: Vec<usize>,
   pub(crate) name: Name<'src>,
   pub(crate) namepath: Namepath<'src>,
   pub(crate) parameters: Vec<Parameter<'src>>,
diff --git a/src/source.rs b/src/source.rs
index 00754215..f81ae0ba 100644
--- a/src/source.rs
+++ b/src/source.rs
@@ -4,6 +4,7 @@ use super::*;
 pub(crate) struct Source<'src> {
   pub(crate) file_depth: u32,
   pub(crate) file_path: Vec<PathBuf>,
+  pub(crate) import_offsets: Vec<usize>,
   pub(crate) namepath: Namepath<'src>,
   pub(crate) path: PathBuf,
   pub(crate) submodule_depth: u32,
@@ -15,6 +16,7 @@ impl<'src> Source<'src> {
     Self {
       file_depth: 0,
       file_path: vec![path.into()],
+      import_offsets: Vec::new(),
       namepath: Namepath::default(),
       path: path.into(),
       submodule_depth: 0,
@@ -22,7 +24,7 @@ impl<'src> Source<'src> {
     }
   }
 
-  pub(crate) fn import(&self, path: PathBuf) -> Self {
+  pub(crate) fn import(&self, path: PathBuf, import_offset: usize) -> Self {
     Self {
       file_depth: self.file_depth + 1,
       file_path: self
@@ -31,6 +33,12 @@ impl<'src> Source<'src> {
         .into_iter()
         .chain(iter::once(path.clone()))
         .collect(),
+      import_offsets: self
+        .import_offsets
+        .iter()
+        .copied()
+        .chain(iter::once(import_offset))
+        .collect(),
       namepath: self.namepath.clone(),
       path,
       submodule_depth: self.submodule_depth,
@@ -47,6 +55,7 @@ impl<'src> Source<'src> {
         .into_iter()
         .chain(iter::once(path.clone()))
         .collect(),
+      import_offsets: Vec::new(),
       namepath: self.namepath.join(name),
       path: path.clone(),
       submodule_depth: self.submodule_depth + 1,
diff --git a/src/unresolved_recipe.rs b/src/unresolved_recipe.rs
index 80d49045..af076f20 100644
--- a/src/unresolved_recipe.rs
+++ b/src/unresolved_recipe.rs
@@ -51,6 +51,7 @@ impl<'src> UnresolvedRecipe<'src> {
       doc: self.doc,
       file_depth: self.file_depth,
       file_path: self.file_path,
+      import_offsets: self.import_offsets,
       name: self.name,
       namepath: self.namepath,
       parameters: self.parameters,


Test patch:
diff --git a/src/testing.rs b/src/testing.rs
index 9a60ecd8..a5977692 100644
--- a/src/testing.rs
+++ b/src/testing.rs
@@ -62,6 +62,7 @@ pub(crate) fn analysis_error(
   let ast = Parser::parse(
     0,
     &PathBuf::new(),
+    &[],
     &Namepath::default(),
     0,
     &tokens,
diff --git a/tests/list.rs b/tests/list.rs
index 2b40ca47..9d073608 100644
--- a/tests/list.rs
+++ b/tests/list.rs
@@ -131,3 +131,126 @@ fn nested_modules_are_properly_indented() {
     )
     .run();
 }
+
+#[test]
+fn unsorted_list_order() {
+  Test::new()
+    .write("a.just", "a:")
+    .write("b.just", "b:")
+    .write("c.just", "c:")
+    .write("d.just", "d:")
+    .justfile(
+      "
+        import 'a.just'
+        import 'b.just'
+        import 'c.just'
+        import 'd.just'
+        x:
+        y:
+        z:
+      ",
+    )
+    .args(["--list", "--unsorted"])
+    .stdout(
+      "
+        Available recipes:
+            x
+            y
+            z
+            a
+            b
+            c
+            d
+      ",
+    )
+    .run();
+
+  Test::new()
+    .write("a.just", "a:")
+    .write("b.just", "b:")
+    .write("c.just", "c:")
+    .write("d.just", "d:")
+    .justfile(
+      "
+        x:
+        y:
+        z:
+        import 'd.just'
+        import 'c.just'
+        import 'b.just'
+        import 'a.just'
+      ",
+    )
+    .args(["--list", "--unsorted"])
+    .stdout(
+      "
+        Available recipes:
+            x
+            y
+            z
+            d
+            c
+            b
+            a
+      ",
+    )
+    .run();
+
+  Test::new()
+    .write("a.just", "a:\nimport 'e.just'")
+    .write("b.just", "b:\nimport 'f.just'")
+    .write("c.just", "c:\nimport 'g.just'")
+    .write("d.just", "d:\nimport 'h.just'")
+    .write("e.just", "e:")
+    .write("f.just", "f:")
+    .write("g.just", "g:")
+    .write("h.just", "h:")
+    .justfile(
+      "
+        x:
+        y:
+        z:
+        import 'd.just'
+        import 'c.just'
+        import 'b.just'
+        import 'a.just'
+      ",
+    )
+    .args(["--list", "--unsorted"])
+    .stdout(
+      "
+        Available recipes:
+            x
+            y
+            z
+            d
+            h
+            c
+            g
+            b
+            f
+            a
+            e
+      ",
+    )
+    .run();
+
+  Test::new()
+    .write("task1.just", "task1:")
+    .write("task2.just", "task2:")
+    .justfile(
+      "
+        import 'task1.just'
+        import 'task2.just'
+      ",
+    )
+    .args(["--list", "--unsorted"])
+    .stdout(
+      "
+        Available recipes:
+            task1
+            task2
+      ",
+    )
+    .run();
+}