Repository: casey/just
Pull request #780 (2021-03-29 06:33:20)

Turn `=` deprecation warning into a hard error
It's been around two and a half years, and many versions, since this
warning was first introduced, so it feels reasonable to finally turn it
into a hard error. It will remain a special-cased error for a little
while.


Patch:
diff --git a/src/analyzer.rs b/src/analyzer.rs
index 2d7f8c8a..c425da7d 100644
--- a/src/analyzer.rs
+++ b/src/analyzer.rs
@@ -202,8 +202,8 @@ mod tests {
 
   analysis_error! {
     name: duplicate_alias,
-    input: "alias foo = bar\nalias foo = baz",
-    offset: 22,
+    input: "alias foo := bar\nalias foo := baz",
+    offset: 23,
     line: 1,
     column: 6,
     width: 3,
@@ -212,7 +212,7 @@ mod tests {
 
   analysis_error! {
     name: unknown_alias_target,
-    input: "alias foo = bar\n",
+    input: "alias foo := bar\n",
     offset: 6,
     line: 0,
     column: 6,
@@ -222,7 +222,7 @@ mod tests {
 
   analysis_error! {
     name: alias_shadows_recipe_before,
-    input: "bar: \n  echo bar\nalias foo = bar\nfoo:\n  echo foo",
+    input: "bar: \n  echo bar\nalias foo := bar\nfoo:\n  echo foo",
     offset: 23,
     line: 2,
     column: 6,
@@ -232,7 +232,7 @@ mod tests {
 
   analysis_error! {
     name: alias_shadows_recipe_after,
-    input: "foo:\n  echo foo\nalias foo = bar\nbar:\n  echo bar",
+    input: "foo:\n  echo foo\nalias foo := bar\nbar:\n  echo bar",
     offset: 22,
     line: 2,
     column: 6,
@@ -272,8 +272,8 @@ mod tests {
 
   analysis_error! {
     name:   parameter_shadows_varible,
-    input:  "foo = \"h\"\na foo:",
-    offset:  12,
+    input:  "foo := \"h\"\na foo:",
+    offset:  13,
     line:   1,
     column: 2,
     width:  3,
@@ -292,8 +292,8 @@ mod tests {
 
   analysis_error! {
     name:   duplicate_variable,
-    input:  "a = \"0\"\na = \"0\"",
-    offset:  8,
+    input:  "a := \"0\"\na := \"0\"",
+    offset: 9,
     line:   1,
     column: 0,
     width:  1,
diff --git a/src/assignment_resolver.rs b/src/assignment_resolver.rs
index 9e84c751..e6db4b2d 100644
--- a/src/assignment_resolver.rs
+++ b/src/assignment_resolver.rs
@@ -111,7 +111,7 @@ mod tests {
 
   analysis_error! {
     name:   circular_variable_dependency,
-    input:   "a = b\nb = a",
+    input:   "a := b\nb := a",
     offset:  0,
     line:   0,
     column: 0,
@@ -121,8 +121,8 @@ mod tests {
 
   analysis_error! {
     name:   self_variable_dependency,
-    input:  "a = a",
-    offset:  0,
+    input:  "a := a",
+    offset: 0,
     line:   0,
     column: 0,
     width:  1,
@@ -131,10 +131,10 @@ mod tests {
 
   analysis_error! {
     name:   unknown_expression_variable,
-    input:  "x = yy",
-    offset:  4,
+    input:  "x := yy",
+    offset: 5,
     line:   0,
-    column: 4,
+    column: 5,
     width:  2,
     kind:   UndefinedVariable{variable: "yy"},
   }
diff --git a/src/compilation_error.rs b/src/compilation_error.rs
index cf0b4e06..c4f9e5e7 100644
--- a/src/compilation_error.rs
+++ b/src/compilation_error.rs
@@ -62,6 +62,16 @@ impl Display for CompilationError<'_> {
         };
         writeln!(f, "`\\{}` is not a valid escape sequence", representation)?;
       },
+      DeprecatedEquals => {
+        writeln!(
+          f,
+          "`=` in assignments, exports, and aliases has been phased out on favor of `:=`"
+        )?;
+        writeln!(
+          f,
+          "Please see this issue for more details: https://github.com/casey/just/issues/379"
+        )?;
+      },
       DuplicateParameter { recipe, parameter } => {
         writeln!(
           f,
diff --git a/src/compilation_error_kind.rs b/src/compilation_error_kind.rs
index d01a6321..7763cf62 100644
--- a/src/compilation_error_kind.rs
+++ b/src/compilation_error_kind.rs
@@ -20,6 +20,7 @@ pub(crate) enum CompilationErrorKind<'src> {
     min:        usize,
     max:        usize,
   },
+  DeprecatedEquals,
   DuplicateAlias {
     alias: &'src str,
     first: usize,
diff --git a/src/evaluator.rs b/src/evaluator.rs
index 39f4d6ac..c3cd7ab0 100644
--- a/src/evaluator.rs
+++ b/src/evaluator.rs
@@ -275,8 +275,8 @@ mod tests {
   run_error! {
     name: export_assignment_backtick,
     src: r#"
-      export exported_variable = "A"
-      b = `echo $exported_variable`
+      export exported_variable := "A"
+      b := `echo $exported_variable`
 
       recipe:
         echo {{b}}
diff --git a/src/justfile.rs b/src/justfile.rs
index 7b1fcbee..d17ac5cc 100644
--- a/src/justfile.rs
+++ b/src/justfile.rs
@@ -6,7 +6,7 @@ pub(crate) struct Justfile<'src> {
   pub(crate) assignments: Table<'src, Assignment<'src>>,
   pub(crate) aliases:     Table<'src, Alias<'src>>,
   pub(crate) settings:    Settings<'src>,
-  pub(crate) warnings:    Vec<Warning<'src>>,
+  pub(crate) warnings:    Vec<Warning>,
 }
 
 impl<'src> Justfile<'src> {
@@ -557,10 +557,10 @@ mod tests {
   run_error! {
     name: export_failure,
     src: r#"
-      export foo = "a"
-      baz = "c"
-      export bar = "b"
-      export abc = foo + bar + baz
+      export foo := "a"
+      baz := "c"
+      export bar := "b"
+      export abc := foo + bar + baz
 
       wut:
         echo $foo $bar $baz
diff --git a/src/module.rs b/src/module.rs
index 9f453ee6..a2f06402 100644
--- a/src/module.rs
+++ b/src/module.rs
@@ -12,5 +12,5 @@ pub(crate) struct Module<'src> {
   /// Items in the justfile
   pub(crate) items:    Vec<Item<'src>>,
   /// Non-fatal warnings encountered during parsing
-  pub(crate) warnings: Vec<Warning<'src>>,
+  pub(crate) warnings: Vec<Warning>,
 }
diff --git a/src/node.rs b/src/node.rs
index 4e3baaa8..433d45c2 100644
--- a/src/node.rs
+++ b/src/node.rs
@@ -204,10 +204,8 @@ impl<'src> Node<'src> for Set<'src> {
   }
 }
 
-impl<'src> Node<'src> for Warning<'src> {
+impl<'src> Node<'src> for Warning {
   fn tree(&self) -> Tree<'src> {
-    match self {
-      Warning::DeprecatedEquals { .. } => Tree::atom("warning").push("deprecated_equals"),
-    }
+    unreachable!()
   }
 }
diff --git a/src/parser.rs b/src/parser.rs
index 765eb8ac..25f4c6e9 100644
--- a/src/parser.rs
+++ b/src/parser.rs
@@ -308,7 +308,6 @@ impl<'tokens, 'src> Parser<'tokens, 'src> {
   /// Parse a justfile, consumes self
   fn parse_justfile(mut self) -> CompilationResult<'src, Module<'src>> {
     let mut items = Vec::new();
-    let mut warnings = Vec::new();
 
     let mut doc = None;
 
@@ -325,10 +324,7 @@ impl<'tokens, 'src> Parser<'tokens, 'src> {
         match Keyword::from_lexeme(next.lexeme()) {
           Some(Keyword::Alias) =>
             if self.next_are(&[Identifier, Identifier, Equals]) {
-              warnings.push(Warning::DeprecatedEquals {
-                equals: self.get(2)?,
-              });
-              items.push(Item::Alias(self.parse_alias()?));
+              return Err(self.get(2)?.error(CompilationErrorKind::DeprecatedEquals));
             } else if self.next_are(&[Identifier, Identifier, ColonEquals]) {
               items.push(Item::Alias(self.parse_alias()?));
             } else {
@@ -336,11 +332,7 @@ impl<'tokens, 'src> Parser<'tokens, 'src> {
             },
           Some(Keyword::Export) =>
             if self.next_are(&[Identifier, Identifier, Equals]) {
-              warnings.push(Warning::DeprecatedEquals {
-                equals: self.get(2)?,
-              });
-              self.presume_keyword(Keyword::Export)?;
-              items.push(Item::Assignment(self.parse_assignment(true)?));
+              return Err(self.get(2)?.error(CompilationErrorKind::DeprecatedEquals));
             } else if self.next_are(&[Identifier, Identifier, ColonEquals]) {
               self.presume_keyword(Keyword::Export)?;
               items.push(Item::Assignment(self.parse_assignment(true)?));
@@ -358,10 +350,7 @@ impl<'tokens, 'src> Parser<'tokens, 'src> {
             },
           _ =>
             if self.next_are(&[Identifier, Equals]) {
-              warnings.push(Warning::DeprecatedEquals {
-                equals: self.get(1)?,
-              });
-              items.push(Item::Assignment(self.parse_assignment(false)?));
+              return Err(self.get(1)?.error(CompilationErrorKind::DeprecatedEquals));
             } else if self.next_are(&[Identifier, ColonEquals]) {
               items.push(Item::Assignment(self.parse_assignment(false)?));
             } else {
@@ -385,7 +374,10 @@ impl<'tokens, 'src> Parser<'tokens, 'src> {
         self.tokens.len() - self.next,
       ))?)
     } else {
-      Ok(Module { items, warnings })
+      Ok(Module {
+        warnings: Vec::new(),
+        items,
+      })
     }
   }
 
@@ -882,10 +874,9 @@ mod tests {
 
   test! {
     name: alias_equals,
-    text: "alias t = test",
+    text: "alias t := test",
     tree: (justfile
       (alias t test)
-      (warning deprecated_equals)
     ),
   }
 
@@ -897,10 +888,9 @@ mod tests {
 
   test! {
     name: export_equals,
-    text: r#"export x = "hello""#,
+    text: r#"export x := "hello""#,
     tree: (justfile
       (assignment #export x "hello")
-      (warning deprecated_equals)
     ),
   }
 
@@ -912,10 +902,9 @@ mod tests {
 
   test! {
     name: assignment_equals,
-    text: r#"x = "hello""#,
+    text: r#"x := "hello""#,
     tree: (justfile
       (assignment x "hello")
-      (warning deprecated_equals)
     ),
   }
 
@@ -1691,23 +1680,23 @@ mod tests {
   }
 
   error! {
-    name: alias_syntax_multiple_rhs,
-    input: "alias foo = bar baz",
-    offset: 16,
-    line: 0,
-    column: 16,
-    width: 3,
-    kind: UnexpectedToken { expected: vec![Comment, Eof, Eol], found: Identifier },
+    name:   alias_syntax_multiple_rhs,
+    input:  "alias foo := bar baz",
+    offset: 17,
+    line:   0,
+    column: 17,
+    width:  3,
+    kind:   UnexpectedToken { expected: vec![Comment, Eof, Eol], found: Identifier },
   }
 
   error! {
-    name: alias_syntax_no_rhs,
-    input: "alias foo = \n",
-    offset: 12,
-    line: 0,
-    column: 12,
-    width: 1,
-    kind: UnexpectedToken {expected: vec![Identifier], found:Eol},
+    name:   alias_syntax_no_rhs,
+    input:  "alias foo := \n",
+    offset: 13,
+    line:   0,
+    column: 13,
+    width:  1,
+    kind:   UnexpectedToken {expected: vec![Identifier], found:Eol},
   }
 
   error! {
@@ -1774,10 +1763,10 @@ mod tests {
 
   error! {
     name:   unclosed_parenthesis_in_expression,
-    input:  "x = foo(",
-    offset: 8,
+    input:  "x := foo(",
+    offset: 9,
     line:   0,
-    column: 8,
+    column: 9,
     width:  0,
     kind: UnexpectedToken{
       expected: vec![Backtick, Identifier, ParenL, ParenR, StringCooked, StringRaw],
@@ -1952,10 +1941,10 @@ mod tests {
 
   error! {
     name:   unknown_function,
-    input:  "a = foo()",
-    offset: 4,
+    input:  "a := foo()",
+    offset: 5,
     line:   0,
-    column: 4,
+    column: 5,
     width:  3,
     kind:   UnknownFunction{function: "foo"},
   }
diff --git a/src/recipe_resolver.rs b/src/recipe_resolver.rs
index 394ef73d..e062bd24 100644
--- a/src/recipe_resolver.rs
+++ b/src/recipe_resolver.rs
@@ -166,8 +166,8 @@ mod tests {
 
   analysis_error! {
     name:   unknown_second_interpolation_variable,
-    input:  "wtf=\"x\"\nx:\n echo\n foo {{wtf}} {{ lol }}",
-    offset: 33,
+    input:  "wtf:=\"x\"\nx:\n echo\n foo {{wtf}} {{ lol }}",
+    offset: 34,
     line:   3,
     column: 16,
     width:  3,
diff --git a/src/warning.rs b/src/warning.rs
index 87a57835..4ea9b51a 100644
--- a/src/warning.rs
+++ b/src/warning.rs
@@ -1,40 +1,22 @@
 use crate::common::*;
 
-use Warning::*;
-
 #[derive(Debug, PartialEq)]
-pub(crate) enum Warning<'src> {
-  DeprecatedEquals { equals: Token<'src> },
-}
+pub(crate) enum Warning {}
 
-impl<'src> Warning<'src> {
-  fn context(&self) -> Option<&Token<'src>> {
-    match self {
-      DeprecatedEquals { equals } => Some(equals),
-    }
+impl Warning {
+  fn context(&self) -> Option<&Token> {
+    #![allow(clippy::unused_self)]
+    unreachable!()
   }
 }
 
-impl Display for Warning<'_> {
+impl Display for Warning {
   fn fmt(&self, f: &mut Formatter) -> fmt::Result {
     let warning = Color::fmt(f).warning();
     let message = Color::fmt(f).message();
 
     write!(f, "{} {}", warning.paint("warning:"), message.prefix())?;
 
-    match self {
-      DeprecatedEquals { .. } => {
-        writeln!(
-          f,
-          "`=` in assignments, exports, and aliases is being phased out on favor of `:=`"
-        )?;
-        write!(
-          f,
-          "Please see this issue for more details: https://github.com/casey/just/issues/379"
-        )?;
-      },
-    }
-
     write!(f, "{}", message.suffix())?;
 
     if let Some(token) = self.context() {


Test patch:
diff --git a/tests/misc.rs b/tests/misc.rs
index 18d8236d..bb957fd4 100644
--- a/tests/misc.rs
+++ b/tests/misc.rs
@@ -2178,15 +2178,14 @@ test! {
     default:
       echo {{foo}}
   ",
-  stdout: "bar\n",
   stderr: "
-    warning: `=` in assignments, exports, and aliases is being phased out on favor of `:=`
+    error: `=` in assignments, exports, and aliases has been phased out on favor of `:=`
     Please see this issue for more details: https://github.com/casey/just/issues/379
       |
     1 | foo = 'bar'
       |     ^
-    echo bar
   ",
+  status: EXIT_FAILURE,
 }
 
 test! {
@@ -2197,15 +2196,14 @@ test! {
     default:
       echo $FOO
     ",
-  stdout: "bar\n",
   stderr: "
-    warning: `=` in assignments, exports, and aliases is being phased out on favor of `:=`
+    error: `=` in assignments, exports, and aliases has been phased out on favor of `:=`
     Please see this issue for more details: https://github.com/casey/just/issues/379
       |
     1 | export FOO = 'bar'
       |            ^
-    echo $FOO
   ",
+  status: EXIT_FAILURE,
 }
 
 test! {
@@ -2217,15 +2215,14 @@ test! {
       echo default
   ",
   args: ("foo"),
-  stdout: "default\n",
   stderr: "
-    warning: `=` in assignments, exports, and aliases is being phased out on favor of `:=`
+    error: `=` in assignments, exports, and aliases has been phased out on favor of `:=`
     Please see this issue for more details: https://github.com/casey/just/issues/379
       |
     1 | alias foo = default
       |           ^
-    echo default
   ",
+  status: EXIT_FAILURE,
 }
 
 test! {
diff --git a/tests/quiet.rs b/tests/quiet.rs
index 1f3058e8..3a2a7666 100644
--- a/tests/quiet.rs
+++ b/tests/quiet.rs
@@ -65,16 +65,6 @@ default:
   status:   100,
 }
 
-test! {
-  name: warning,
-  justfile: "
-    foo = 'bar'
-
-    baz:
-  ",
-  args: ("--quiet"),
-}
-
 test! {
   name: choose_none,
   justfile: "",