From 28520c5abacf65ead8eccb4255731b7a2f682fa8 Mon Sep 17 00:00:00 2001
From: Mariano de Achaval <mariano.achaval@mulesoft.com>
Date: Fri, 31 Mar 2023 13:11:23 -0300
Subject: [PATCH] Adding a way to validate if a script is valid or not (#79)

* Adding a way to validate if a script is valid or not

* Update readme
---
 README.md                                     |  6 +-
 .../main/java/org/mule/weave/cli/DWCLI.java   |  5 +-
 .../cli/pico/AbstractPicoExecCommand.java     | 10 +--
 .../mule/weave/cli/pico/PicoAddWizard.java    |  4 +-
 .../org/mule/weave/cli/pico/PicoRepl.java     |  2 +-
 .../mule/weave/cli/pico/PicoRunScript.java    |  6 +-
 .../org/mule/weave/cli/pico/PicoRunSpell.java |  2 +-
 .../weave/cli/pico/PicoValidateScript.java    | 85 +++++++++++++++++++
 .../org/mule/weave/cli/pico/PicoWizard.java   | 21 +++++
 .../mule/weave/dwnative/NativeRuntime.scala   |  2 +-
 .../cli/commands/VerifyWeaveCommand.scala     | 45 ++++++++++
 11 files changed, 172 insertions(+), 16 deletions(-)
 create mode 100644 native-cli/src/main/java/org/mule/weave/cli/pico/PicoValidateScript.java
 create mode 100644 native-cli/src/main/java/org/mule/weave/cli/pico/PicoWizard.java
 create mode 100644 native-cli/src/main/scala/org/mule/weave/dwnative/cli/commands/VerifyWeaveCommand.scala

diff --git a/README.md b/README.md
index 706153d..f60e2dd 100644
--- a/README.md
+++ b/README.md
@@ -85,7 +85,7 @@ dw help
 ```
 
 ```bash
- ____   __  ____  __   _  _  ____   __   _  _  ____
+  ____   __  ____  __   _  _  ____   __   _  _  ____
 (    \ / _\(_  _)/ _\ / )( \(  __) / _\ / )( \(  __)
  ) D (/    \ )( /    \\ /\ / ) _) /    \\ \/ / ) _)
 (____/\_/\_/(__)\_/\_/(_/\_)(____)\_/\_/ \__/ (____)
@@ -94,7 +94,9 @@ Usage: <main class> [-hV] [COMMAND]
   -V, --version   Print version information and exit.
 Commands:
   run            Runs provided DW script.
-  add-wizard     Adds a new Wizard to your network of trusted wizards.
+  wizard         Wizard actions.
+    add            Adds a new Wizard to your network of trusted wizards.
+  validate       Validate if a script is valid or not.
   migrate        Translates a DW1 script into a DW2 script.
   spell          Runs the specified Spell.
     create         Creates a new spell with the given name.
diff --git a/native-cli/src/main/java/org/mule/weave/cli/DWCLI.java b/native-cli/src/main/java/org/mule/weave/cli/DWCLI.java
index c3166cf..48e4896 100644
--- a/native-cli/src/main/java/org/mule/weave/cli/DWCLI.java
+++ b/native-cli/src/main/java/org/mule/weave/cli/DWCLI.java
@@ -9,7 +9,9 @@
 import org.mule.weave.cli.pico.PicoRunSpell;
 import org.mule.weave.cli.pico.PicoMigrate;
 import org.mule.weave.cli.pico.PicoUpdateSpells;
+import org.mule.weave.cli.pico.PicoValidateScript;
 import org.mule.weave.cli.pico.PicoVersionProvider;
+import org.mule.weave.cli.pico.PicoWizard;
 import org.mule.weave.dwnative.cli.Console;
 import org.mule.weave.dwnative.cli.DefaultConsole$;
 import picocli.CommandLine;
@@ -44,7 +46,8 @@ public void run(String[] args, Console console) {
             mixinStandardHelpOptions = true,
             subcommands = {
                     PicoRunScript.class,
-                    PicoAddWizard.class,
+                    PicoWizard.class,
+                    PicoValidateScript.class,
                     PicoMigrate.class,
                     PicoRunSpell.class,
                     CommandLine.HelpCommand.class,
diff --git a/native-cli/src/main/java/org/mule/weave/cli/pico/AbstractPicoExecCommand.java b/native-cli/src/main/java/org/mule/weave/cli/pico/AbstractPicoExecCommand.java
index f973bf4..490cec7 100644
--- a/native-cli/src/main/java/org/mule/weave/cli/pico/AbstractPicoExecCommand.java
+++ b/native-cli/src/main/java/org/mule/weave/cli/pico/AbstractPicoExecCommand.java
@@ -51,7 +51,7 @@ public static <A, B> Map<A, B> toScalaMap(java.util.Map<A, B> m) {
         );
     }
 
-    public String fileToString(File f) {
+    public static String fileToString(File f) {
         try {
             return Files.readString(f.toPath(), StandardCharsets.UTF_8);
         } catch (IOException e) {
@@ -73,19 +73,19 @@ public Integer call() {
         return doCall();
     }
 
-    protected Option<DataWeaveVersion> calculateRuntimeVersion() {
+    public static Option<DataWeaveVersion> calculateRuntimeVersion(String languageLevel1, CommandLine.Model.CommandSpec spec1) {
         Option<DataWeaveVersion> dataWeaveVersionOption;
         try {
-            dataWeaveVersionOption = Option.apply(languageLevel).map((s) -> DataWeaveVersion.apply(s));
+            dataWeaveVersionOption = Option.apply(languageLevel1).map((s) -> DataWeaveVersion.apply(s));
             if (dataWeaveVersionOption.isDefined()) {
                 DataWeaveVersion dataWeaveVersion = dataWeaveVersionOption.get();
                 DataWeaveVersion currentVersion = DataWeaveVersion.apply();
                 if (dataWeaveVersion.$greater(currentVersion)) {
-                    throw new CommandLine.ParameterException(spec.commandLine(), "Invalid language level, cannot be higher than " + currentVersion.toString());
+                    throw new CommandLine.ParameterException(spec1.commandLine(), "Invalid language level, cannot be higher than " + currentVersion.toString());
                 }
             }
         } catch (NumberFormatException e) {
-            throw new CommandLine.ParameterException(spec.commandLine(), "Invalid language-level option value : `" + languageLevel + "`.");
+            throw new CommandLine.ParameterException(spec1.commandLine(), "Invalid language-level option value : `" + languageLevel1 + "`.");
         }
         return dataWeaveVersionOption;
     }
diff --git a/native-cli/src/main/java/org/mule/weave/cli/pico/PicoAddWizard.java b/native-cli/src/main/java/org/mule/weave/cli/pico/PicoAddWizard.java
index 539dd0e..ab28b7f 100644
--- a/native-cli/src/main/java/org/mule/weave/cli/pico/PicoAddWizard.java
+++ b/native-cli/src/main/java/org/mule/weave/cli/pico/PicoAddWizard.java
@@ -8,8 +8,10 @@
 
 import java.util.concurrent.Callable;
 
+
+
 @CommandLine.Command(
-        name = "add-wizard",
+        name = "add",
         description =
                 "Adds a new Wizard to your network of trusted wizards."
 
diff --git a/native-cli/src/main/java/org/mule/weave/cli/pico/PicoRepl.java b/native-cli/src/main/java/org/mule/weave/cli/pico/PicoRepl.java
index 1aa358f..d41b8aa 100644
--- a/native-cli/src/main/java/org/mule/weave/cli/pico/PicoRepl.java
+++ b/native-cli/src/main/java/org/mule/weave/cli/pico/PicoRepl.java
@@ -31,7 +31,7 @@ protected Integer doCall() {
                 Optional.ofNullable(inputs).map((s) -> toScalaMap(s)).orElse(Map$.MODULE$.<String, File>empty()),
                 Optional.ofNullable(literalInput).map((s) -> toScalaMap(s)).orElse(Map$.MODULE$.<String, String>empty()),
                 Option.apply(privileges).map((s) -> JavaConverters.asScalaBuffer(s).toSeq()),
-                calculateRuntimeVersion()
+                calculateRuntimeVersion(languageLevel, spec)
         );
         ReplCommand replCommand = new ReplCommand(replConfiguration, console);
         return replCommand.exec();
diff --git a/native-cli/src/main/java/org/mule/weave/cli/pico/PicoRunScript.java b/native-cli/src/main/java/org/mule/weave/cli/pico/PicoRunScript.java
index 4bf5fe1..add645f 100644
--- a/native-cli/src/main/java/org/mule/weave/cli/pico/PicoRunScript.java
+++ b/native-cli/src/main/java/org/mule/weave/cli/pico/PicoRunScript.java
@@ -19,9 +19,7 @@
 
 @CommandLine.Command(
         name = "run",
-        description =
-                "Runs provided DW script."
-
+        description = "Runs provided DW script."
 )
 public class PicoRunScript extends AbstractPicoRunCommand {
 
@@ -55,7 +53,7 @@ protected Integer doCall() {
             throw new CommandLine.ParameterException(spec.commandLine(), msg);
         }
 
-        Option<DataWeaveVersion> dataWeaveVersionOption = calculateRuntimeVersion();
+        Option<DataWeaveVersion> dataWeaveVersionOption = calculateRuntimeVersion(languageLevel, spec);
         final WeaveRunnerConfig config = new WeaveRunnerConfig(
                 new String[0],
                 eval,
diff --git a/native-cli/src/main/java/org/mule/weave/cli/pico/PicoRunSpell.java b/native-cli/src/main/java/org/mule/weave/cli/pico/PicoRunSpell.java
index eae5200..24f1fac 100644
--- a/native-cli/src/main/java/org/mule/weave/cli/pico/PicoRunSpell.java
+++ b/native-cli/src/main/java/org/mule/weave/cli/pico/PicoRunSpell.java
@@ -127,7 +127,7 @@ protected Integer doCall() {
         final SpellDependencyManager manager = new SpellDependencyManager(spellFolder, console);
         final Function1<NativeRuntime, DependencyResolutionResult[]> resolver = (nr) -> manager.resolveDependencies(nr);
 
-        Option<DataWeaveVersion> dataWeaveVersionOption = calculateRuntimeVersion();
+        Option<DataWeaveVersion> dataWeaveVersionOption = calculateRuntimeVersion(languageLevel, spec);
 
         final WeaveRunnerConfig config = WeaveRunnerConfig.apply(
                 new String[]{srcFolder.getAbsolutePath()},
diff --git a/native-cli/src/main/java/org/mule/weave/cli/pico/PicoValidateScript.java b/native-cli/src/main/java/org/mule/weave/cli/pico/PicoValidateScript.java
new file mode 100644
index 0000000..d22a902
--- /dev/null
+++ b/native-cli/src/main/java/org/mule/weave/cli/pico/PicoValidateScript.java
@@ -0,0 +1,85 @@
+package org.mule.weave.cli.pico;
+
+import org.mule.weave.dwnative.cli.Console;
+import org.mule.weave.dwnative.cli.DefaultConsole$;
+import org.mule.weave.dwnative.cli.commands.VerifyWeaveCommand;
+import org.mule.weave.dwnative.cli.commands.WeaveModule;
+import org.mule.weave.dwnative.cli.commands.WeaveVerifyConfig;
+import org.mule.weave.v2.io.FileHelper;
+import org.mule.weave.v2.parser.ast.variables.NameIdentifier;
+import org.mule.weave.v2.utils.DataWeaveVersion;
+import picocli.CommandLine;
+import scala.Option;
+
+import java.io.File;
+import java.util.concurrent.Callable;
+
+import static org.mule.weave.cli.pico.AbstractPicoExecCommand.calculateRuntimeVersion;
+import static org.mule.weave.cli.pico.AbstractPicoExecCommand.fileToString;
+
+@CommandLine.Command(
+        name = "validate",
+        description = "Validate if a script is valid or not."
+)
+public class PicoValidateScript implements Callable<Integer> {
+
+    Console console;
+
+    @CommandLine.Option(names = {"--language-level"}, description = {"The version of DW to be supported."})
+    protected String languageLevel = null;
+
+    @CommandLine.Parameters(
+            index = "0",
+            arity = "0..1",
+            description = "The DW script to be used",
+            paramLabel = "SCRIPT"
+    )
+    String script = null;
+
+    @CommandLine.Option(names = {"--file", "-f"}, description = "The Path to the dw file to run.")
+    File dwFile = null;
+
+    @CommandLine.Option(names = {"--input", "-i"}, description = "The name of an in implicit input.")
+    String[] inputs = new String[0];
+
+    @CommandLine.Spec
+    CommandLine.Model.CommandSpec spec = null;
+
+
+    public PicoValidateScript() {
+        this.console = DefaultConsole$.MODULE$;
+    }
+
+    public PicoValidateScript(Console console) {
+        this.console = console;
+    }
+
+    @Override
+    public Integer call() {
+        if ((script == null && dwFile == null) || (script != null && dwFile != null)) {
+            String msg = "The script and file parameters are mutually exclusive, but one is required.";
+            throw new CommandLine.ParameterException(spec.commandLine(), msg);
+        }
+        Option<DataWeaveVersion> dataWeaveVersionOption = calculateRuntimeVersion(languageLevel, spec);
+
+        WeaveVerifyConfig config = new WeaveVerifyConfig(
+
+                ((nr) -> {
+                    if (script != null) {
+                        return new WeaveModule(script, NameIdentifier.ANONYMOUS_NAME().name());
+                    } else if (dwFile != null) {
+                        return new WeaveModule(fileToString(dwFile), FileHelper.baseName(dwFile));
+                    } else {
+                        throw new RuntimeException("Missing dw script or main file");
+                    }
+                }),
+                dataWeaveVersionOption,
+                inputs
+
+        );
+        final VerifyWeaveCommand command = new VerifyWeaveCommand(config, console);
+        return command.exec();
+    }
+
+
+}
diff --git a/native-cli/src/main/java/org/mule/weave/cli/pico/PicoWizard.java b/native-cli/src/main/java/org/mule/weave/cli/pico/PicoWizard.java
new file mode 100644
index 0000000..ad869c9
--- /dev/null
+++ b/native-cli/src/main/java/org/mule/weave/cli/pico/PicoWizard.java
@@ -0,0 +1,21 @@
+package org.mule.weave.cli.pico;
+
+import picocli.CommandLine;
+
+@CommandLine.Command(
+        name = "wizard",
+        description = "Wizard actions.",
+        subcommands = {
+                PicoAddWizard.class
+        }
+)
+public class PicoWizard implements Runnable {
+
+    @CommandLine.Spec
+    CommandLine.Model.CommandSpec spec;
+
+    @Override
+    public void run() {
+        spec.commandLine().usage(System.out);
+    }
+}
diff --git a/native-cli/src/main/scala/org/mule/weave/dwnative/NativeRuntime.scala b/native-cli/src/main/scala/org/mule/weave/dwnative/NativeRuntime.scala
index 4742432..ef98306 100644
--- a/native-cli/src/main/scala/org/mule/weave/dwnative/NativeRuntime.scala
+++ b/native-cli/src/main/scala/org/mule/weave/dwnative/NativeRuntime.scala
@@ -101,7 +101,7 @@ class NativeRuntime(libDir: File, path: Array[File], console: Console, maybeLang
     }
   }
 
-  private def compileScript(script: String, inputs: ScriptingBindings, nameIdentifier: NameIdentifier, defaultOutputMimeType: String) = {
+  def compileScript(script: String, inputs: ScriptingBindings, nameIdentifier: NameIdentifier, defaultOutputMimeType: String): DataWeaveScript = {
     var config = weaveScriptingEngine.newConfig()
       .withScript(script)
       .withInputs(inputs.entries().map(wi => new InputType(wi, None)).toArray)
diff --git a/native-cli/src/main/scala/org/mule/weave/dwnative/cli/commands/VerifyWeaveCommand.scala b/native-cli/src/main/scala/org/mule/weave/dwnative/cli/commands/VerifyWeaveCommand.scala
new file mode 100644
index 0000000..591da53
--- /dev/null
+++ b/native-cli/src/main/scala/org/mule/weave/dwnative/cli/commands/VerifyWeaveCommand.scala
@@ -0,0 +1,45 @@
+package org.mule.weave.dwnative.cli.commands
+
+import org.mule.weave.dwnative.NativeRuntime
+import org.mule.weave.dwnative.cli.Console
+import org.mule.weave.dwnative.utils.DataWeaveUtils
+import org.mule.weave.v2.parser.ast.variables.NameIdentifier
+import org.mule.weave.v2.parser.phase.CompilationException
+import org.mule.weave.v2.runtime.ScriptingBindings
+import org.mule.weave.v2.utils.DataWeaveVersion
+
+class VerifyWeaveCommand(val config: WeaveVerifyConfig, console: Console) extends WeaveCommand {
+  val weaveUtils = new DataWeaveUtils(console)
+
+  def exec(): Int = {
+    var exitCode: Int = ExitCodes.SUCCESS
+    val nativeRuntime: NativeRuntime = new NativeRuntime(weaveUtils.getLibPathHome(), Array(), console, config.maybeLanguageLevel)
+    val weaveModule = config.scriptToRun(nativeRuntime)
+    try {
+      console.info(s"Compiling `${weaveModule.nameIdentifier}`...")
+      val bindings = ScriptingBindings()
+      config.inputs.foreach((input) => {
+        bindings.addBinding(input, "")
+      })
+      nativeRuntime.compileScript(weaveModule.content, bindings, NameIdentifier(weaveModule.nameIdentifier), "")
+      console.info(s"No errors found.")
+    } catch {
+      case ce: CompilationException => {
+        val ee = ce.getErrorMessages()
+        ee.foreach((em) => {
+          console.error(em)
+        })
+        console.info(s"${ee.length} errors found")
+        exitCode = ExitCodes.FAILURE
+      }
+    }
+    exitCode
+  }
+}
+
+case class WeaveVerifyConfig(
+                              scriptToRun: NativeRuntime => WeaveModule,
+                              maybeLanguageLevel: Option[DataWeaveVersion],
+                              inputs: Array[String]
+                            )
+