diff --git a/circle.yml b/circle.yml index b927d8d74..0d517e067 100644 --- a/circle.yml +++ b/circle.yml @@ -10,3 +10,4 @@ dependencies: - go generate -v ./... - go build -v - ./linter-tests.sh + - ./flag-tests.sh diff --git a/core/procs.go b/core/procs.go index c1a54c95d..45c99c056 100644 --- a/core/procs.go +++ b/core/procs.go @@ -1400,12 +1400,21 @@ func processData(data []byte) { GLOBAL_ENV.ns.Value = currentNamespace } -func findConfigFile(filename string) string { +func findConfigFile(filename string, workingDir string) string { filename, err := filepath.Abs(filename) if err != nil { fmt.Fprintln(os.Stderr, "Error reading config file "+filename+": ", err) return "" } + + if workingDir != "" { + workingDir, err = filepath.Abs(workingDir) + if err != nil { + fmt.Fprintln(os.Stderr, "Error resolving working directory"+workingDir+": ", err) + return "" + } + filename = filepath.Join(workingDir, ".joker") + } for { oldFilename := filename filename = filepath.Dir(filename) @@ -1455,10 +1464,10 @@ func knownMacrosToMap(km Object) (Map, error) { return res, nil } -func ReadConfig(filename string) { +func ReadConfig(filename string, workingDir string) { LINTER_CONFIG = GLOBAL_ENV.CoreNamespace.Intern(MakeSymbol("*linter-config*")) LINTER_CONFIG.Value = EmptyArrayMap() - configFileName := findConfigFile(filename) + configFileName := findConfigFile(filename, workingDir) if configFileName == "" { return } diff --git a/flag-tests.sh b/flag-tests.sh new file mode 100755 index 000000000..0c3666fa9 --- /dev/null +++ b/flag-tests.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +./joker tests/run-flag-tests.joke diff --git a/main.go b/main.go index f57f3e0a2..8c28a0448 100644 --- a/main.go +++ b/main.go @@ -183,12 +183,12 @@ func detectDialect(filename string) Dialect { return CLJ } -func lintFile(filename string, dialect Dialect) { +func lintFile(filename string, dialect Dialect, workingDir string) { phase := PARSE if dialect == EDN { phase = READ } - ReadConfig(filename) + ReadConfig(filename, workingDir) configureLinterMode(dialect) if processFile(filename, phase) == nil { WarnOnUnusedNamespaces() @@ -202,6 +202,7 @@ var parseFlag = pflag.Bool("parse", false, "parse the file") var lintFlag = pflag.Bool("lint", false, "lint the file") var lintDialect = pflag.String("dialect", "", "dialect to lint as. Valid options are clj, cljs, joker and edn") +var lintWorkingDir = pflag.String("working-dir", "", "override the working directory for the linter") // these flags are here to support the preexisting `--lint` flags: var lintCljFlag = pflag.Bool("lintclj", false, "lint as clojure") @@ -231,7 +232,7 @@ func init() { case *lintJokerFlag: *lintFlag = true *lintDialect = "joker" - case *lintJokerFlag: + case *lintEDNFlag: *lintFlag = true *lintDialect = "edn" } @@ -242,8 +243,6 @@ func main() { switch { case *versionFlag: println(VERSION) - case len(pflag.Args()) == 0: - repl(EVAL) case *readFlag: processFile(pflag.Arg(0), READ) case *parseFlag: @@ -262,8 +261,14 @@ func main() { default: dialect = detectDialect(pflag.Arg(0)) } - lintFile(pflag.Arg(0), dialect) - default: + filename := pflag.Arg(0) + if filename == "" { + filename = "--" + } + lintFile(filename, dialect, *lintWorkingDir) + case len(pflag.Args()) > 0: processFile(pflag.Arg(0), EVAL) + default: + repl(EVAL) } } diff --git a/tests/flags/config/.joker b/tests/flags/config/.joker new file mode 100644 index 000000000..b71946342 --- /dev/null +++ b/tests/flags/config/.joker @@ -0,0 +1 @@ +{:known-macros [foobar.macros/defthing]} diff --git a/tests/flags/input-warning.clj b/tests/flags/input-warning.clj new file mode 100644 index 000000000..e327b8976 --- /dev/null +++ b/tests/flags/input-warning.clj @@ -0,0 +1 @@ +(let [a 1] "foo") diff --git a/tests/flags/input.clj b/tests/flags/input.clj new file mode 100644 index 000000000..849217d96 --- /dev/null +++ b/tests/flags/input.clj @@ -0,0 +1 @@ +(clojure.string/split "foobar") diff --git a/tests/flags/input.cljs b/tests/flags/input.cljs new file mode 100644 index 000000000..7f62761ab --- /dev/null +++ b/tests/flags/input.cljs @@ -0,0 +1 @@ +(.log js/console) diff --git a/tests/flags/input.edn b/tests/flags/input.edn new file mode 100644 index 000000000..34c1de47c --- /dev/null +++ b/tests/flags/input.edn @@ -0,0 +1 @@ +{:foobar #foo/bar "something something"} diff --git a/tests/flags/input.joke b/tests/flags/input.joke new file mode 100644 index 000000000..5a245b3b1 --- /dev/null +++ b/tests/flags/input.joke @@ -0,0 +1 @@ +(joker.string/split "ha-ha-ha-ha" #"-") diff --git a/tests/flags/macro.clj b/tests/flags/macro.clj new file mode 100644 index 000000000..4e8e8b595 --- /dev/null +++ b/tests/flags/macro.clj @@ -0,0 +1,6 @@ +(ns test.foo + (:require [foobar.macros :refer [defthing]])) + +(defthing something + "pewpew") + diff --git a/tests/run-flag-tests.joke b/tests/run-flag-tests.joke new file mode 100644 index 000000000..5f0d79b35 --- /dev/null +++ b/tests/run-flag-tests.joke @@ -0,0 +1,111 @@ +(def exit-code 0) + +(defn clean + [output] + (let [output (if (nil? output) "" output)] + (->> output + (joker.string/split-lines) + (remove #(joker.string/starts-with? % " ")) + (remove #(= % "")) + (joker.string/join "\n")))) + +(defn test-flags + [description flags expected] + (let [pwd (get (joker.os/env) "PWD") + flag-parts (joker.string/split flags #" ") + stdin? (some #(= "<" %) flag-parts) + cmd (str pwd "/joker") + output (if stdin? + (:err (joker.os/sh "sh" "-c" (str cmd " " flags))) + (:err (apply (partial joker.os/sh cmd) flag-parts))) + output (clean output)] + (when-not (= output expected) + (println "FAILED: testing" description "(" flags ")") + (println "EXPECTED") + (println expected) + (println "ACTUAL") + (println output) + (println "") + (var-set #'exit-code 1)))) + +(defn testing + [description & tests] + (let [tests (partition 2 tests)] + (doall (map (fn [[flags expected]] + (test-flags description flags expected)) tests)))) + +(testing "auto detect dialect from filename" + "--lint tests/flags/input.clj" + "" + "--lint tests/flags/input-warning.clj" + "tests/flags/input-warning.clj:1:7: Parse warning: unused binding: a") + +(testing "specify joker dialect" + "--lintjoker tests/flags/input.joke" + "" + + "--lint --dialect joker tests/flags/input.joke" + "" + + "--lintjoker tests/flags/input.clj" + "tests/flags/input.clj:1:2: Parse error: Unable to resolve symbol: clojure.string/split" + + "--lint --dialect joker tests/flags/input.cljs" + "tests/flags/input.cljs:1:7: Parse error: Unable to resolve symbol: js/console") + +(testing "specify clj dialect" + "--lintclj tests/flags/input.clj" + "" + + "--lint --dialect clj tests/flags/input.clj" + "" + + "--lintclj tests/flags/input.edn" + "tests/flags/input.edn:1:17: Read warning: No reader function for tag foo/bar" + + "--lint --dialect clj tests/flags/input.cljs" + "tests/flags/input.cljs:1:7: Parse error: Unable to resolve symbol: js/console") + +(testing "specify cljs dialect" + "--lintcljs tests/flags/input.cljs" + "" + + "--lint --dialect cljs tests/flags/input.cljs" + "" + + "--lintcljs tests/flags/input.edn" + "tests/flags/input.edn:1:17: Read warning: No reader function for tag foo/bar" + + "--lintcljs tests/flags/input.clj" + "tests/flags/input.clj:1:2: Parse error: Unable to resolve symbol: clojure.string/split") + +(testing "reading from stdin" + "--lint --dialect edn < tests/flags/input.edn" + "" + + "--lint --dialect clj < tests/flags/input.edn" + ":1:17: Read warning: No reader function for tag foo/bar" + + "--lint --dialect clj < tests/flags/input.clj" + "" + + "--lint --dialect cljs < tests/flags/input.clj" + ":1:2: Parse error: Unable to resolve symbol: clojure.string/split" + + "--lint --dialect cljs < tests/flags/input.cljs" + "" + + "--lint --dialect joker < tests/flags/input.cljs" + ":1:7: Parse error: Unable to resolve symbol: js/console" + + "--lint --dialect joker < tests/flags/input.joke" + "") + +(testing "working direction override" + "--lint --dialect clj < tests/flags/macro.clj" + ":4:11: Parse error: Unable to resolve symbol: something" + + "--lint --dialect clj --working-dir tests/flags/config < tests/flags/macro.clj" + "") + +(joker.os/exit exit-code)