Code Monkey home page Code Monkey logo

joker's Introduction

CircleCI

Joker is a small Clojure interpreter, linter and formatter written in Go.

Installation

On macOS, the easiest way to install Joker is via Homebrew:

brew install candid82/brew/joker

The same command can be used on Linux if you use Linuxbrew.

If you use Arch Linux, there is AUR package.

If you use Nix, then you can install Joker with

nix-env -i joker

On other platforms (or if you prefer manual installation), download a precompiled binary for your platform and put it on your PATH.

You can also build Joker from the source code.

Usage

joker - launch REPL. Exit via (exit), EOF (such as Ctrl-D), or SIGINT (such as Ctrl-C).

Hint: In the REPL typing ( adds a pair of matched parentheses. Use the delete key to remove individual parenthesis ignoring parenthesis matching. Ctrl-D works as a delete key on some systems. If you find the default REPL editing behavior annoying (e.g., automatic parenthesis matching, backspace doesn't delete individual parenthesis), try joker --no-readline or rlwrap joker --no-readline if you have rlwrap installed.

joker <filename> - execute a script. Joker uses .joke filename extension. For example: joker foo.joke. Normally exits after executing the script, unless --exit-to-repl is specified before --file <filename> in which case drops into the REPL after the script is (successfully) executed. (Note use of --file in this case, to ensure <filename> is not treated as a <socket> specification for the repl.)

joker --eval <expression> - execute an expression. For example: joker -e '(println "Hello, world!")'. Normally exits after executing the script, unless --exit-to-repl is specified before --eval, in which case drops into the REPL after the expression is (successfully) executed.

joker - - execute a script on standard input (os.Stdin).

joker --lint <filename> - lint a source file. See Linter mode for more details.

joker --lint --working-dir <dirname> - recursively lint all Clojure files in a directory.

joker --format <filename> - format a source file and write the result to standard output. See Format mode for more details.

joker --format - - read Clojure source code from standard input, format it and print the result to standard output.

Documentation

Standard library reference

Dash docset: dash-feed://https%3A%2F%2Fraw.githubusercontent.com%2Fcandid82%2Fjoker%2Fmaster%2Fdocs%2Fjoker.xml

(either copy and paste this link to your browser's url bar or open it in a terminal with open command)

Joker slack channel

Organizing libraries (namespaces)

Developer notes

Project goals

These are high level goals of the project that guide design and implementation decisions.

  • Be suitable for scripting (lightweight, fast startup). This is something that Clojure is not good at and my personal itch I am trying to scratch.
  • Be user friendly. Good error messages and stack traces are absolutely critical for programmer's happiness and productivity.
  • Provide some tooling for Clojure and its dialects. Joker has linter mode which can be used for linting Joker, Clojure and ClojureScript code. It catches some basic errors. Joker can also format (pretty print) Clojure code (see format mode) or EDN data structures. For example, the following command can be used to pretty print EDN data structure (read from stdin):
joker --hashmap-threshold -1 -e "(pprint (read))"

There is Sublime Text plugin that uses Joker for pretty printing EDN files. Here you can find the description of --hashmap-threshold parameter, if curious.

  • Be as close (syntactically and semantically) to Clojure as possible. Joker should truly be a dialect of Clojure, not a language inspired by Clojure. That said, there is a lot of Clojure features that Joker doesn't and will never have. Being close to Clojure only applies to features that Joker does have.

Project Non-goals

  • Performance. If you need it, use Clojure. Joker is a naive implementation of an interpreter that evaluates unoptimized AST directly. I may be interested in doing some basic optimizations but this is definitely not a priority.
  • Have all Clojure features. Some features are impossible to implement due to a different host language (Go vs Java), others I don't find that important for the use cases I have in mind for Joker. But generally Clojure is a pretty large language at this point and it is simply unfeasible to reach feature parity with it, even with naive implementation.

Differences with Clojure

  1. Primitive types are different due to a different host language and desire to simplify things. Scripting doesn't normally require all the integer and float types, for example. Here is a list of Joker's primitive types:
Joker type Corresponding Go type
BigFloat big.Float (see below)
BigInt big.Int
Boolean bool
Char rune
Double float64
Int int
Keyword n/a
Nil n/a
Ratio big.Rat
Regex regexp.Regexp
String string
Symbol n/a
Time time.Time

See Floating-point Constants and the BigFloat Type for more on BigFloat (M-suffixed) constants.

Note that Nil is a type that has one value: nil.

  1. The set of persistent data structures is much smaller:
Joker type Corresponding Clojure type
ArrayMap PersistentArrayMap
MapSet PersistentHashSet (or hypothetical PersistentArraySet, depending on which kind of underlying map is used)
HashMap PersistentHashMap
List PersistentList
Vector PersistentVector
  1. Joker doesn't have the same level of interoperability with the host language (Go) as Clojure does with Java or ClojureScript does with JavaScript. It doesn't have access to arbitrary Go types and functions. There is only a small fixed set of built-in types and interfaces. Dot notation for calling methods is not supported (as there are no methods). All Java/JVM specific functionality of Clojure is not implemented for obvious reasons.
  2. Joker is single-threaded with no support for parallelism. Therefore no refs, agents, futures, promises, locks, volatiles, transactions, p* functions that use multiple threads. Vars always have just one "root" binding. Joker does have core.async style support for concurrency. See go macro documentation for details.
  3. The following features are not implemented: protocols, records, structmaps, chunked seqs, transients, tagged literals, unchecked arithmetics, primitive arrays, custom data readers, transducers, validators and watch functions for vars and atoms, hierarchies, sorted maps and sets.
  4. Unrelated to the features listed above, the following function from clojure.core namespace are not currently implemented but will probably be implemented in some form in the future: subseq, iterator-seq, reduced?, reduced, mix-collection-hash, definline, re-groups, hash-ordered-coll, enumeration-seq, compare-and-set!, rationalize, load-reader, find-keyword, comparator, resultset-seq, file-seq, sorted?, ensure-reduced, rsubseq, pr-on, seque, alter-var-root, hash-unordered-coll, re-matcher, unreduced.
  5. Built-in namespaces have joker prefix. The core namespace is called joker.core. Other built-in namespaces include joker.string, joker.json, joker.os, joker.base64 etc. See standard library reference for details.
  6. Joker doesn't support AOT compilation and (-main) entry point as Clojure does. It simply reads s-expressions from the file and executes them sequentially. If you want some code to be executed only if the file it's in is passed as joker argument but not if it's loaded from other files, use (when (= *main-file* *file*) ...) idiom. See #277 for details.
  7. Miscellaneous:
  • case is just a syntactic sugar on top of condp and doesn't require options to be constants. It scans all the options sequentially.
  • slurp only takes one argument - a filename (string). No options are supported.
  • ifn? is called callable?
  • Map entry is represented as a two-element vector.
  • resolving unbound var returns nil, not the value Unbound. You can still check if the var is bound with bound? function.

Linter mode

To run Joker in linter mode pass --lint --dialect <dialect> flag, where <dialect> can be clj, cljs, joker or edn. If --dialect <dialect> is omitted, it will be set based on file extension. For example, joker --lint foo.clj will run linter for the file foo.clj using Clojure (as opposed to ClojureScript or Joker) dialect. joker --lint --dialect cljs - will run linter for standard input using ClojureScript dialect. Linter will read and parse all forms in the provided file (or read them from standard input) and output errors and warnings (if any) to standard output (for edn dialect it will only run read phase and won't parse anything). Let's say you have file test.clj with the following content:

(let [a 1])

Executing the following command joker --lint test.clj will produce the following output:

test.clj:1:1: Parse warning: let form with empty body

The output format is as follows: <filename>:<line>:<column>: <issue type>: <message>, where <issue type> can be Read error, Parse error, Parse warning or Exception.

Integration with editors

Here are some examples of errors and warnings that the linter can output.

Reducing false positives

Joker lints the code in one file at a time and doesn't try to resolve symbols from external namespaces. Because of that and since it's missing some Clojure(Script) features it doesn't always provide accurate linting. In general it tries to be unobtrusive and error on the side of false negatives rather than false positives. One common scenario that can lead to false positives is resolving symbols inside a macro. Consider the example below:

(ns foo (:require [bar :refer [def-something]]))

(def-something baz ...)

Symbol baz is introduced inside def-something macro. The code is totally valid. However, the linter will output the following error: Parse error: Unable to resolve symbol: baz. This is because by default the linter assumes external vars (bar/def-something in this case) to hold functions, not macros. The good news is that you can tell Joker that bar/def-something is a macro and thus suppress the error message. To do that you need to add bar/def-something to the list of known macros in Joker configuration file. The configuration file is called .joker and should be in the same directory as the target file, or in its parent directory, or in its parent's parent directory etc up to the root directory. When reading from stdin Joker will look for a .joker file in the current working directory. The --working-dir <path/to/file> flag can be used to override the working directory that Joker starts looking in. Joker will also look for a .joker file in your home directory if it cannot find it in the above directories. The file should contain a single map with :known-macros key:

{:known-macros [bar/def-something foo/another-macro ...]}

Please note that the symbols are namespace qualified and unquoted. Also, Joker knows about some commonly used macros (outside of clojure.core namespace) like clojure.test/deftest or clojure.core.async/go-loop, so you won't have to add those to your config file.

Joker also allows you to specify symbols that are introduced by a macro:

{:known-macros [[riemann.streams/where [service event]]]}

So each element in :known-macros vector can be either a symbol (as in the previous example) or a vector with two elements: macro's name and a list of symbols introduced by this macro. This allows to avoid symbol resolution warnings in macros that intern specific symbols implicitly.

Additionally, if you want Joker to ignore some unused namespaces (for example, if they are required for their side effects) you can add the :ignored-unused-namespaces key to your .joker file:

{:ignored-unused-namespaces [foo.bar.baz]}

Sometimes your code may refer to a namespace that is not explicitly required in the same file. This is rarely needed, but if you face such situation you can add that namespace to :known-namespaces list to avoid "No namespace found" or "Unable to resolve symbol" warnings:

{:known-namespaces [clojure.spec.gen.test]}

If your code uses tagged literals that Joker doesn't know about, add them to :known-tags list:

{:known-tags [db/fn]}

If you use :refer :all Joker won't be able to properly resolve symbols because it doesn't know what vars are declared in the required namespace (i.e. clojure.test). There are generally three options here:

  1. Refer specific symbols. For example: [clojure.test :refer [deftest testing is are]]. This is usually not too tedious, and you only need to do it once per file.
  2. Use alias and qualified symbols:
(:require [clojure.test :as t])
(t/deftest ...)
  1. "Teach" Joker declarations from referred namespace. Joker executes the following files (if they exist) before linting your file: .jokerd/linter.cljc (for both Clojure and ClojureScript), .jokerd/linter.clj (Clojure only), .jokerd/linter.cljs (ClojureScript only), or .jokerd/linter.joke (Joker only). The rules for locating .jokerd directory are the same as for locating .joker file.

    • ⚠️ Joker can be made aware of any additional declarations (like deftest and is) by providing them in .jokerd/linter.clj[s|c] files. However, this means Joker cannot check that the symbols really are declared in your namespace, so this feature should be used sparingly.
    • If you really want some symbols to be considered declared in any namespace no matter what, you can add (in-ns 'joker.core) to your linter.clj[s|c] and then declare those symbols. (see issues 52 and 50 for discussion).

I generally prefer first option for clojure.test namespace.

Linting directories

To recursively lint all files in a directory pass --working-dir <dirname> parameter. Please note that if you also pass file argument (or --file parameter) Joker will lint that single file and will only use --working-dir to locate .joker config file. That is,

joker --lint --working-dir my-project

lints all Clojure files in my-project directory, whereas

joker --lint --working-dir my-project foo.clj

lints single file foo.clj but uses .joker config file from my-project directory.

When linting directories Joker lints all files with the extension corresponding to the selected dialect (*.clj, *.cljs, *.joke, or *.edn). To exclude certain files specify regex patterns in :ignored-file-regexes vector in .joker file, e.g. :ignored-file-regexes [#".*user\.clj" #".*/dev/profiling\.clj"].

When linting directories Joker can report globally unused namespaces and public vars. This is turned off by default but can be enabled with --report-globally-unused flag, e.g. joker --lint --working-dir my-project --report-globally-unused. This is useful for finding "dead" code. Some namespaces or vars are intended to be used by external systems (e.g. public API of a library or main function of a program). To exclude such namespaces and vars from being reported as globally unused list them in :entry-points vector in .joker file, which may contain the names of namespaces or fully qualified names of vars. For example:

{:entry-points [my-project.public-api
                my-project.core/-main]}

Optional rules

Joker supports a few configurable linting rules. To turn them on or off set their values to true or false in :rules map in .joker file. For example:

{:rules {:if-without-else true
         :no-forms-threading false}}

Below is the list of all configurable rules.

Rule Description Default value
if-without-else warn on if without the else branch false
no-forms-threading warn on threading macros with no forms, i.e. (-> a) true
unused-as warn on unused :as binding true
unused-keys warn on unused :keys, :strs, and :syms bindings true
unused-fn-parameters warn on unused fn parameters false
fn-with-empty-body warn on fn form with empty body true

Note that unused binding and unused parameter warnings are suppressed for names starting with underscore.

Valid Identifiers

Symbols and keywords (collectively referred to herein as "identifiers") can be comprised of nearly any encodable character ("rune" in Go), especially when composed from a String via e.g. (symbol "arbitrary-string").

Unlike most popular programming languages, Clojure allows "extreme flexibility" (as does Joker) in choosing characters for identifiers in source code, permitting many control and other invisible characters, even as the first character. In short, any character not specifically allocated to another purpose (another lexeme) by the Clojure language defaults to starting or continuing an identifier lexeme: (def ^@ "test"), where ^@ denotes the ASCII NUL (0x00) character, works.

When linting an identifier (versus composing one at runtime), Joker ensures its characters are members of a more "reasonable" set, aligned with those used by the core libraries of Clojure (as well as Joker).

This "core set" of characters, as a Regex, is #"[a-zA-Z0-9*+!?<=>&_.'-]". It represents the intersection of a limited set of letters, digits, symbols, and punctuation within the (7-bit) ASCII encoding range. The letters are the ASCII-range members of Unicode category L, while the digits are the ASCII-range members of category Nd.

Thus, Joker will warn about using an em dash (instead of an ASCII hyphen-minus (0x2D)), a non-breaking space (&nbsp; in HTML), an accented letter (e.g. é), or a control character (even NUL), in an identifier.

The .joker file may specify key/value pairs that change this default:

Key Value Meaning
:character-set :core #"[*+!?<=>&_.'\-$:#%]" plus categories L and Nd
:symbol :core plus symbols (category S)
:visible :symbol plus punctuation (P) and marks (M)
:any any category
:encoding-range :ascii only 7-bit ASCII (<= unicode.MaxASCII)
:unicode only Unicode (<= unicode.MaxRune)
:any any encodable character

The intersection of these specifications governs how identifiers are linted; any character outside the resulting set yields a linter warning.

If :valid-ident is not fully specified, the defaults are the core character set in the ASCII range, as if .joker contained:

{:valid-ident {:character-set :core
               :encoding-range :ascii}}

Changing :core to :symbol would allow, for example, | in identifiers; whereas changing :ascii to :unicode would allow é.

Format mode

To run Joker in format mode pass --format flag. For example:

joker --format <filename> - format a source file and write the result to standard output.

joker --format - - read Clojure source code from standard input, format it and print the result to standard output.

You might also want to try cljf. Its formatting algorithm is similar to Joker's, but it runs much faster.

Integration with editors

Building

Joker requires Go v1.13 or later. Below commands should get you up and running.

go get -d github.com/candid82/joker
cd $GOPATH/src/github.com/candid82/joker
./run.sh --version && go install

Cross-platform Builds

After building the native version (to autogenerate appropriate files, "vet" the source code, etc.), set the appropriate environment variables and invoke go build. E.g.:

$ GOOS=linux GOARCH=arm GOARM=6 go build

The run.sh script does not support cross-platform building directly, but can be used in conjunction with build-arm.sh to cross-build from a Linux amd64 or 386 system to a Linux arm system via:

$ ./run.sh --version && ./build-arm.sh

Note that cross-building from 64-bit to 32-bit machines (and vice versa) is problematic due to the gen_code step of building Joker. This step builds a faster-startup version of Joker than was built in earlier versions, prior to the introduction of gen_code. It does this by building much of Joker code into gen_code itself, running the (expensive) dynamic-initialization code to build up core namespaces from (nearly) scratch, then using reflection to discover the resulting data structures and output their contents as Go code that (mostly) statically recreates them when built into the Joker executable itself. (See DEVELOPER.md for more information on gen_code.)

As types such as int are 32-bit on 32-bit machines, and 64-bit on 64-bit machines, the final Joker executable must be built with code generated by a same-word-size build of gen_code. Otherwise, compile-time errors might well result due to too-large integers; or, run-time errors might result due to too-small integers.

Since Linux (on amd64) supports building and running 32-bit (386) executables, it's a good candidate for cross-building to 32-bit architectures such as arm.

Coding Guidelines

  • Dashes (-) in namespaces are not converted to underscores (_) by Joker, so (unlike with Clojure) there's no need to name .joke files accordingly.
  • Avoid :refer :all and the use function, as that reduces the effectiveness of linting.

Developer Notes

See DEVELOPER.md for information on Joker internals, such as adding new namespaces to the Joker executable.

License

Copyright (c) Roman Bataev. All rights reserved.
The use and distribution terms for this software are covered by the
Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
which can be found in the LICENSE file.

Joker contains parts of Clojure source code (from clojure.core namespace). Clojure is licensed as follows:

Copyright (c) Rich Hickey. All rights reserved.
The use and distribution terms for this software are covered by the
Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
which can be found in the file epl-v10.html at the root of this distribution.
By using this software in any fashion, you are agreeing to be bound by
the terms of this license.
You must not remove this notice, or any other, from this software.

joker's People

Contributors

bcarrell avatar benalbrecht avatar blak3mill3r avatar bmwiedemann avatar candid82 avatar charlesg3 avatar conormcd avatar dbohdan avatar dependabot[bot] avatar djwhitt avatar dpritchett avatar eko234 avatar gsawko avatar harold avatar hlship avatar jcburley avatar manicolosi avatar martinklepsch avatar mrichards42 avatar nicwest avatar prochac avatar rcarmo avatar romaindoumenc avatar rwstauner avatar temochka avatar testwill avatar traviswsims avatar wnh avatar youngnh avatar znley avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

joker's Issues

False positives with clojure.spec.alpha/fdef

It is common practice to specify a function prior its implementation.. But when I do this with joker-linting enabled, it says "Unable to resolve symbol: add".

For example:

(require '[clojure.spec.alpha :as s])

(s/fdef add
        :args (s/cat :x int? :y int?)
        :ret int?)

(defn add
  "Insecure add. Not the best idea."
  [x y] (+ x y))

That is just a small thing, just a nice-to-have feature to remove this false-positive. I am usually using joker in combination with flycheck (I even created a small spacemacs-layer for it) and I have always this warning in my editor ;-)

Thank you!

Stdin via pipe?

I'm probably just missing something but the README mentions stdin support, shouldn't that mean the following should work?

cat boot/core/src/boot/cli.clj | joker --clojure --lint

Thanks! :)

"undefined" errors

Upon running:

$ go get github.com/candid82/joker

I get:

github.com/candid82/joker/core
go/src/github.com/candid82/joker/core/procs.go:1594:28: undefined: linter_allData
go/src/github.com/candid82/joker/core/procs.go:1600:27: undefined: linter_cljxData
go/src/github.com/candid82/joker/core/procs.go:1604:28: undefined: linter_cljData
go/src/github.com/candid82/joker/core/procs.go:1606:28: undefined: linter_cljsData
go/src/github.com/candid82/joker/core/procs.go:1805:14: undefined: coreData
go/src/github.com/candid82/joker/core/procs.go:1806:14: undefined: timeData
go/src/github.com/candid82/joker/core/procs.go:1807:14: undefined: mathData

Also I was able to install the binary from the download page successfully and installed the Emacs library flycheck-joker and it works awesomely! Just wanted to thank you for writing such a cool project.

Another question: does this library suffer from flycheck-clojure's issue:

Do not use squiggly-clojure unless your code can be reloaded without side effects, as the linters will be reloading it repeatedly.

Problems building v0.8.5

First of all, thank you for all the work you are putting into this. Using flycheck-joker for linting in emacs became part of my daily coding.

For this reason, I was trying to package joker for the nix package manager which I've been using a lot lately, but I ran into a problem as currently, I am unable to build v0.8.5 from source, seeing as I get this error:

building
../std/generate-std.sh: line 4: joker: command not found

I am able to build the master branch and if I understood correctly the issue I'm having was fixed with the commit "Commit joker generated files". Would there be a plan for releasing a new version with the latest fixes soon? I'd love to be able to finish packaging it for use in nix, whenever a new version is out (Hope this sounds good to you!).

Hangs when running --lint on Windows?

Downloaded and installed the 0.8.3 Windows binary. I can run "joker" and get a prompt, but running "joker --lint foo.clj" just burns CPU for several minutes, exits with no obvious output.

Thanks!

Not an issue per se, but just wanted to say thanks for writing such a useful tool! I use it everyday at work, and it helps me tremendously 💯

:require-macros with same namespace alias causes panic

In ClojureScript, it's possible to alias macros under the same alias as non-macros. Joker panics in this situation. In core async, since the macros are in a different namespace than the functions it's desirable to alias them like this:

(ns example.macros
  (:require-macros [cljs.core.async.macros :as async])
  (:require [cljs.core.async :as async]))
$ joker --lint test.cljs
test.cljs:1:1: Eval error: Alias async already exists in namespace user, aliasing cljs.core.async.macros
Stacktrace:
  global test.cljs:1:1
  joker.core/require <joker.core>:3323:3
  joker.core/apply <joker.core>:509:4
  joker.core/apply__ <joker.core>:3243:9
  joker.core/apply <joker.core>:509:4
  joker.core/apply__ <joker.core>:3202:5
  joker.core/with-bindings* <joker.core>:1382:7
  joker.core/apply <joker.core>:507:4
  joker.core/apply__ <joker.core>:3217:9
  joker.core/alias <joker.core>:2372:3

Clojure 1.9 namespaced destructuring

Clojure 1.9 allows the following:

(require '[com.acme.foo :as foo])
(let [{::foo/keys [bar]} {:com.acme.foo/bar :YUP}]
  bar)
;; => :YUP

But joker signals an Unable to resolve symbol: bar error.

Lint errors when using new CLJS :npm-deps

A recent version of ClojureScript introduced the :npm-deps compiler flag (blog post). The example used in the blog post is:

;; src/example/core.cljs
(ns example.core
  (:require left-pad))

(enable-console-print!)

;; pad the number 42 with five zeros
(println (left-pad 42 5 0))

In joker's lint mode, this currently throws two errors: One for the unused namespace and one at the site of the function call (unable to resolve symbol).

P.S. I'm still learning my way around ClojureScript and I've integrated joker's linter into my editor and it's helping quite a bit. Thank you!

Support ClojureScript-specific interop

http://cljs.github.io/api/syntax/dot

This allows doing things otherwise not allowed in Clojure. Examples are e.target.value (accessing a property of a variable), Math.PI (accessing a property of an imported object), (XHR.send ...) (calling a property belonging to an imported object). I've taken a brief look at the code, but haven't found CLJS-specific code for interop, so I suspect both Clojure and ClojureScript are treated the same.

Strange lint warning in `case` forms

(case val
  ("F" "f") false    ;; Parse warning: F is not a function
  true)

(case val
  ("F" "f") "false"  ;; ok
  true)

(case val
  ("T" "t") true     ;; ok
  false)

v0.8.9. I think this appeared in one of the recent versions, not sure in which.

Looks like github's syntax hl has the same problem 😄

Provide column start and end

Currently joker provides the column where an error/warning "starts" but not where it ends. When an undeclared var is used or similar it would be useful to know start and end columns so the issue can be properly highlighted in editors.

nice to have: warn on unused vars

I've found that using the joker linter is great to catch typos and syntax errors that I usually would miss until executing the code, so it's a huge time saver.

Comparing it to linters I use in other languages, I think it could benefit from showing a warning when a required or declared var is never actually used. This can help to catch stuff like you forgot some step in your logic, or you're not referencing the var you actually meant, or simply you didn't do the proper cleanup after a refactor.

Thanks for the good work!

Clojure macro introduces name -- Joker thinks it is not declared

I have a macro that introduces a local symbol and it's used like this:

  (api/with-validated-params [::api-spec/reset-new-stats nil]
    (act/visit (:user-id-int params) (:pagename params))
    (resp/response {})))

The macro takes (:params req), applies the conforming clojure.spec stuff, and introduces a local binding of params. Unfortunately, Joker flags the use of params on the next line as invalid.

I verified that :known-macros is working correctly here: if I add foo immediately after with-validated-params in that code, Joker doesn't flag that -- so it's assuming that a macro is invoked with a symbol and that is defining something new.

I guess it doesn't allow for a deliberately unhygienic macro that introduces a new local binding?

linter doesn't mark missing "joker" symbols when the dialect isn't joker

I'm not sure if this expected behaviour but when I have a clojure file like this:

;foo.clj
(ns foobar.core)

(joker.string/split "ha-ha-ha-ha" #"-")

and I run the linter I get no errors:

$ joker --lint foo.clj
$ joker --lintclj foo.clj

If I introduce an error by removing the pattern from the original example:

;foo.clj
(ns foobar.core)

(joker.string/split "ha-ha-ha-ha")

you get joker errors

$ joker --lint foo.clj
foo.clj:4:1: Parse warning: Wrong number of args (1) passed to #'joker.string/split
$ joker --lintclj foo.clj
foo.clj:4:1: Parse warning: Wrong number of args (1) passed to #'joker.string/split

I'm expecting something along the lines of this when I lint this clojure file as the joker dialect:

;foo.clj
(ns foo.core)

(clojure.string/split "ha-ha-ha-ha" #"-")
$ joker --lintjoker input.clj
input.clj:4:2: Parse error: Unable to resolve symbol: clojure.string/split

Strings in :require

I'm playing quite a bit with shadow-cljs and the way it works with npm is that instead of using symbols in :require it uses strings.

Example:

(ns app.fb-auth
  (:require ["firebase" :as firebase]
            [clojure.string :as string]
            [app.state :as state]))

which gives following error
screen shot 2018-03-02 at 04 27 13

is there anything I could do about this?

Thank you

#'joker.core/get conflict

In (ns some.place) I have a function

(defn get [options] 
   (do-something))

I get the following warning (in NeoVim):

WARNING: get already refers to: #'joker.core/get in namespace user, being replaced by: #'user/get

Does anyone know if there's a workaround for this?

How do i build this.?

I am trying to build it using go get . and then go buildbut it fails at go get itself.

Unable to resolve symbol: double?

I'm getting this error on line:

>>> 15 (s/def ::original_total (s/and double? pos?))

am I miss something, or joker doesn't support double? function?

joker --lint failure in cljc require with reader conditionals, exception stops the process

this might actually be two issues, the first is with one-armed reader contionals

(ns user (:require [clojure.set :as set] #?(:cljs foo.bar)))

which throws

Read error: Unexpected )

The other valid syntax for contional require where the clj platform has no matching requirement is

(ns user (:require [clojure.set :as set] #?@(:clj [] :cljs [foo.bar])))

which throws

cljc:1:1: Eval error: No value supplied for key true

my system:

joker --version v0.8.3
ls ~/.joker No such file or directory

Mention #joker Slack channel?

Just created a #joker channel on Clojurians Slack where we (hopefully) can discuss useful things to do with Joker. Maybe it's nice to mention it in the README?

Allow linter to receive code text over stdin?

I would love to be able to provide linter messages to the user while they're typing (before the file has been saved). I can provide both the current edited unsaved text and the file path, but I think I would need a way to pass the code text via the command line.

This is kind of a "nice-to-have", so I understand if it's out of scope, but it would be cool if this were possible.

Allow reader conditionals in cljc files

I'm using the linter for cljc files and it works good except for reader conditionals, which give the warning No reader function for tag ?. Also ClojureScript expressions inside those conditionals may show up as errors:

screen shot 2017-02-17 at 9 19 04 am

Allowing the #? and maybe ignoring whatever's inside of them would be great.

Thanks for this project!

Don't complain about escape character

Joker complains about the \033 escape sequence. Using \e is also problematic. These are commonly needed for ansi escape terminal sequences, like color printing.

(defn foo []
  (println "\033[31mFOO\033[0m"))  ; print FOO in red

% joker --lint src/foo/core.clj
src/foo/core.clj:151:17: Read error: Unsupported escape character: \0

Unused bindings via :as

I may be alone in this request, but I often like to use :as in destructuring to name a map for descriptive purposes, even though I don't use the binding. Is it possible to ignore measurement here?

e.g.

(defn my-fn [{:keys [value] :as measurement}]
   value)

Thank you for this tool. It has already improved my life massively when used via vim.

Macros required via :refer-macros not known to joker

I am not quite sure if this is too specific, but there is an issue with referred macros in ClojureScript and om.next. I'll show you an example:

$ joker -v
0.8.4

image

The tooltip says "Unable to resolve symbol: HelloWorld" etc.

Console output:

$ joker --lint hello.cljs
hello.cljs:9:8: Parse error: Unable to resolve symbol: HelloWorld
hello.cljs:13:3: Parse error: Unable to resolve symbol: Object
hello.cljs:14:4: Parse error: Unable to resolve symbol: render

This is not really a big thing, but I wanted to add this for completeness, because I really like your project and there will be many people developing ClojureScript and seeing this false-positive.

Thanks for your work! I'd help you with this, but I can't program Go...

Supporting `.joker` in project root path

The file is called .joker and should be in your home directory. It should contain a single map with :known-macros key

Why can't we use .joker in root directory of the project?
We just want to make same linter options for whole team. It would be great if along with ~/.joker, it will look at project root directory.
Thanks!

Build errors following instructions on Debian Jessie with go `1.8.3`

go version
go version go1.8.3 linux/amd64

go build
# github.com/candid82/joker/std/time
std/time/a_time.go:127: time.Duration(d).Round undefined (type time.Duration has no field or method Round)
std/time/a_time.go:219: time.Duration(d).Truncate undefined (type time.Duration has no field or method Truncate)

Linter: warn on if with only one branch

I'd like Joker linter to give a warning on the following:

(if true 1)

Often this happens when I mistakenly forget to handle the else case.

The warning could be: "if with only one branch, use when".

Linter error on -> and ->> without forms.

-> and ->> with one arg could have it's use cases. For ex. mapping "to a structure":

(map #(-> [%]) [1 2 3]) => ([1] [2] [3])
(map #(-> {:foo %}) [1 2 3]) => ({:foo 1} {:foo 2} {:foo 3})
(map #(-> `(~%)) [1 2 3]) => ((1) (2) (3))

which cause "No forms in ->". This has to be written like

(map #(vec [%]) [1 2 3])          ;; i put vec in your ver so you get vec...
(map #(assoc {} :foo %) [1 2 3])  ;; almost like m = {}; for i in ...
(map #(list %) [1 2 3])           ;; ok, this probably is a tie

I assume the linter wants to guard against mistakes like

(let [val ...]
  (-> val)
  (do-something-with-val))  ;; should be in ->

but I can't think of any examples where this could go unnoticed and not cause problems immediately.

Can't run `joker` command in console

I downloaded the Windows binary (v0.7.1), and ran it as described - however, the program itself is just the interpreter. I exited out of the program and try to run joker, to no avail:

'joker' is not recognized as an internal or external command,
operable program or batch file.

How do I install joker on Windows?

Cannot get :ignore-unused-namespaces to work

Suppose I have test.clj:

(ns foo.core
  (:require [bar.baz]))

This gives me a warning, "unused namespace bar.baz". I'm assuming the following configuration in .joker would inhibit it, but no luck:

{:ignore-unused-namespaces [bar.baz]}

Other configuration options like known-macros work as expected. It makes no difference whether I'm linting Clojure or ClojureScript files. I'm using v0.9.1 on Arch Linux.

limitations of only being able to pass a single flag to the linter

I recently wrote a linter for https://github.com/w0rp/ale (a vim plugin for async linting). Joker works really really well for this due to it's fast startup speed and nice outputs.

However having spent a couple of days using it I started running into an issue where joker didn't seem to detect my .joker file. Looking deeper into the issue, I found it was because ale was writing the contents of my file to a /tmp/xzy.clj file then linting that, loosing the context of the original sources directory and thus its .joker file.

Swapping the ale linter to pipe the files contents to stdin worked... until I changed directory and again lost the context of my working directory. finally frustrated I swapped the linter to just read the file off the disk and now it only works on saving the file.

Ideally I would like to add a flag to the linter to parse in the config directly:

joker --lint --config='{:known-macros [bar/def-something foo/another-macro ...]}' <filename/stdin>

or

joker --lint --config='/path/to/file/.joker' <filename/stdin>

or

joker --lint --working-dir='/path/to/file/' <filename/stdin>

however the current main doesn't leave much leeway in terms of adding additional flags.

Instead of parsing os.Args manually something along the lines of the flag lib would give more flexibility. I had a mess about last night converting it use pflag just to see if it would work and it appears functional (if a bit ugly).

Is this something that seems reasonable? I don't want to break anything in the repl/interpreter.

Allow Joker to accept file path when reading via stdin

When linting in my editor, Joker doesn't seem to respect the .joker file in my project root, and I think this is because when Joker is receiving input over stdin, it doesn't know where the file it's linting is located, and so doesn't know where to start looking for the .joker file.

Would it be a good idea to be able to pass the file path in addition to the unsaved text over stdin?

Support `:known-tags` for tagged literals

Datomic's #db/fn tagged literal is current flagged with an error "No reader function for tag db/fn". Perhaps a :known-tags feature would mitigate this? This particular case also has the possibility of introducting local symbols in the :params attribute, would be nice-to-have to avoid that, but it's a pretty limited use-case.

Possibility of project level context for unused vars?

I'm loving the unused vars linter but in my current project I'm finding that many public vars and some namespaces are simply never called or required elsewhere in the app.

It would be great if joker could somehow lint these as well. What are your feelings on this idea?

I see two possible ways to accomplish this. The first would be to simply take a list of source-dirs and write an index of used ns/vars to file, then subsequent​ lints of single files could read that index and call out missing vars while updating the index for it's calls.

The other way, a user provide a main fn and joker could statically trace calls, and produce a call graph with anything outside of the graph producing a warning. This graph could be useful for other cursive/intellij like analyses.

joker doesn't recognize `use` + `:as`

Example:

(ns dre.integration.frontend.core
  (:use [etaoin.api :as eta :exclude [chrome]])
  (:require [etaoin.keys :as k]))

(def chrome (eta/chrome)) ;; Unable to resolve symbol eta/chrome

Different color for duplicate require and obsolete require

I tend to remove namespace requires that have squiggles in Emacs, but sometimes this is a duplicate require instead of an obsolete require. Then I will remove some refers that were needed, but should be combined in the single require.

Is it possible to get a different color in the squiggle for duplicate requires and unused requires?

"Unable to resolve symbol: deftest" when using `:refer :all`

I'm using flycheck-joker in spacemacs and I'm very happy with what it provides.
However, one thing that bothers me quite a lot is that joker reports deftest and is as unresolved symbols.
Here's the minimal repro for me:

(ns myapp.util-test
  (:require [myapp.util :as u]
                 [clojure.test :refer :all]))

(deftest namespaced-map
  (testing "keys are namespaced"
    (let [unqualified-keys {:name "Juraj" :age 32}
          qualified-keys #:user{:name "Juraj" :age 32}]
      (is (= qualified-keys unqualified-keys)))))

Here's the screenshot:
image

I realized that this might be because deftest and is are macros so I tried to add following to ~/.joker (inspired by https://github.com/candid82/joker#reducing-false-positives):

{:known-macros [clojure.test/deftest clojure.test/testing clojure.test/is clojure.test/are]}

No success, however.

Any ideas why that happens?
Is it something that should work out of the box?

Option to accept specific unqualified symbols?

I understand that joker doesn't try to resolve symbols from external namespaces, and it does show an error if I use a symbol that was referred with a :refer :all in the require form.

I do not use :refer :all much, but there are a few cases (like com.rpl.specter) where I make an exception, and I would be fine with configuring joker to know about those few exceptions.

Would it be easy to add an option I could put in the .joker config file :known-unqualified-syms [ALL collect collect-one MAP-KEYS ... ] and then not get false positives from joker when using those symbols unqualified? (or, is there another way to silence these warnings that I missed?)

If someone can point me in the right direction, I might be able to do it myself. I just discovered joker today (and it's awesome! 🚀 ).

Support map namespace syntax

Clojure 1.9 introduces map namespace syntax, which allows to write

#:person{:first "Han" :last "Solo"}}

which is read as

{:person/first "Han" :person/last "Solo"}

Currently joker complains about this:

vlaaad@ubuntu ~/P/transportal> joker --lint src/common/clj/transportal/seeder.clj
src/common/clj/transportal/seeder.clj:237:7: Read error: Reader tag must be a symbol

It would be great if joker supported such syntax

Interpret all def* forms as known macros

Probably not ideal as a default but given that def* macros is a very common thing it could be nice.

Just a thought, just close if you think this is a bad idea :)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.