147 Commits

Author SHA1 Message Date
Vidar Holen
de0145fb29 Stable version 0.3.2
This release is dedicated to knirch, mcandre, Dridi,
ptman, pihentagy, Riviera, and everyone else who keeps
submitting bug reports and feature suggestions!
2014-03-22 11:05:27 -07:00
Vidar Holen
0d4ae95e1d Recognize declare -x as exporting variables 2014-03-22 10:43:56 -07:00
Vidar Holen
50db49e2fb Rename Unquotable to QuoteFree 2014-03-22 10:27:59 -07:00
Vidar Holen
60aafae21d Count array indexes as references, even without $ 2014-03-22 10:22:34 -07:00
Vidar Holen
902cb9c303 Fixed up README 2014-03-19 09:57:01 -07:00
Vidar Holen
4f1fd43360 Don't suggest removing $ in (( 10#$n )) 2014-03-16 15:06:18 -07:00
Vidar Holen
ca5af5c55a Don't warn about decimals in (( )) for zsh/ksh 2014-03-16 14:56:23 -07:00
Vidar Holen
503cac3bb3 Merge branch 'master' of github.com:koalaman/shellcheck 2014-03-16 14:53:18 -07:00
Vidar Holen
2a9c9ae0ad Warn about using <=/>=, and don't warn about -gt 1.2 in ksh/zsh 2014-03-16 14:51:46 -07:00
koalaman
def4551991 Merge pull request #117 from mcandre/master
readme: markdown for link rendering
2014-03-15 16:17:07 -07:00
Vidar Holen
67f4a0d6eb Accept and warn about capitalization in keywords. 2014-03-15 16:08:33 -07:00
Andrew Pennebaker
f92f934688 readme: added compiler memory note 2014-03-15 16:49:05 -04:00
Andrew Pennebaker
d4059c30b7 readme: markdown for link rendering 2014-03-14 16:37:34 -04:00
Vidar Holen
b68de7f42b Don't warn about for s in "${!var}", it could be an array 2014-03-13 20:54:10 -07:00
Vidar Holen
7dacb62d36 Fixed determining shell for shebangs with flags 2014-03-09 17:24:05 -07:00
Vidar Holen
3423cde931 Check attempts to set variables with 'set' 2014-03-01 16:52:53 -08:00
Vidar Holen
b2d1aa01f7 Don't warn about commas when quoted in a=("a,b") 2014-03-01 15:30:51 -08:00
Vidar Holen
19e1bdf11f Warn about array assignments for /bin/sh 2014-03-01 15:16:31 -08:00
Vidar Holen
75d51087c8 Warn about functions using parameters that are never passed 2014-03-01 14:42:00 -08:00
Vidar Holen
ed524fb77f Don't warn about decimals when comparing with = 2014-03-01 10:24:22 -08:00
Vidar Holen
97045c4af1 Fixed x[0] not recognized as reference of x in arithmetics 2014-03-01 10:14:17 -08:00
Vidar Holen
1b806f6c9f Merge branch 'master' of github.com:koalaman/shellcheck 2014-03-01 10:06:03 -08:00
Vidar Holen
632c1614a1 Added support for |& 2014-03-01 10:05:43 -08:00
koalaman
00d9ef12e7 Merge pull request #105 from Dridi/dynamic_link
Dynamic linking to libShellCheck
2014-03-01 09:00:47 -08:00
Vidar Holen
d07294810b Allow \n before and after ||/&& in [[ ]] 2014-02-28 18:46:10 -08:00
Dridi Boukelmoune
948b750754 Make the executable depend on the library
It needed a bump to 1.8 for the minimum `Cabal-Version'. One downside is
that the executable also build-depends on the same libraries.
Alphabetical order is kept, except for the dependency to the ShellCheck
library itself.
2014-02-28 19:55:04 +01:00
Dridi Boukelmoune
41ae95116d Reformat ShellCheck.cabal for readability
Uses one line per `build-depends' or `exposed-modules'. Also got them
sorted by name. Folded `base' dependencies into a single one.
2014-02-28 19:44:09 +01:00
Vidar Holen
bf3c942294 Warn about using 'su foo' to continue as foo 2014-02-16 18:51:30 -08:00
Vidar Holen
055b40462d Improved $(echo ..) warnings 2014-02-16 13:26:50 -08:00
Vidar Holen
b087b7efb1 Some hlint fixes.
Ironically, this is the first time the linter has been linted.
2014-02-16 12:57:34 -08:00
Vidar Holen
5d8d57cf07 Suggest useless use of echo for $(echo $var) 2014-02-12 19:20:39 -08:00
Vidar Holen
661091a9da Added better message for SC1007, for 'var= value' 2014-02-12 18:26:41 -08:00
Vidar Holen
2ec60c2627 Added double prime to list of unicode quotes. 2014-02-08 14:15:04 -08:00
Vidar Holen
8b4909b238 Improve warnings for missing quotes. 2014-02-08 14:10:45 -08:00
Vidar Holen
95a3be6546 README: Updated URL, reformatted long lines 2014-02-08 09:58:11 -08:00
Vidar Holen
968e34e002 Parse forward ticks (acute accents) just like backticks and warn. 2014-02-08 09:50:20 -08:00
Vidar Holen
197b3e3f20 Some checks for accidental rm -r 2014-02-04 19:43:16 -08:00
Vidar Holen
0e464ea476 Stable version 0.3.1
This release is dedicated to the Flycheck team,
even though ShellCheck is written entirely in Vim.
2014-02-03 20:22:30 -08:00
Vidar Holen
811df6f0da Fixed cabal file 2014-02-03 20:21:26 -08:00
Vidar Holen
4e5d32b05a Added --version flag 2014-02-03 20:06:59 -08:00
Vidar Holen
c5141b77bf Fixed parser not accepting `` in arithmetic contex 2014-02-03 16:45:48 -08:00
Vidar Holen
9dfeb6b42a Added -s to the man page 2014-02-02 21:56:04 -08:00
Vidar Holen
77916d2645 Fixed compilation error on GHC7 2014-02-02 21:47:02 -08:00
Vidar Holen
4968e7d9ff Added -s flag to override dialect, e.g. -s ksh 2014-02-02 19:28:09 -08:00
Vidar Holen
075d58ee90 Replaced parser error for 'function' with shell-aware check. 2014-02-02 13:39:44 -08:00
Vidar Holen
6a4a5a815e Don't consider last stage of pipeline a subshell for Ksh/Zsh
Also fixes the problem where pipelines were considered a single subshell.
2014-02-02 13:03:26 -08:00
Vidar Holen
76a39f254b Refactoring, 25% speedup.
* Checks now use Writer monad instead of State

* Parser no longer emits notes unrelated to parsing.

* All checks are now passed a parameter value, containing shell type,
  map from notes to parents and such. This eliminates recalculation
  and removes the need for a special group of parent examining checks.
2014-02-02 04:59:17 -08:00
Vidar Holen
8ec9fa43fd Warn about break/continue in subshells and outside loops 2014-02-01 23:45:26 -08:00
Vidar Holen
e8634a3c27 Removed duplicate check for [[ a == b + 1 ]] 2014-02-01 20:45:44 -08:00
Vidar Holen
9ae776530b Check for [[ i + 1 = 2 ]] 2014-01-27 22:47:48 -08:00
Vidar Holen
0ec62390d5 Merge branch 'master' of github.com:koalaman/shellcheck 2014-01-27 22:13:04 -08:00
Vidar Holen
82328cd86e Warn about literal "\ " just like literal quotes.
Also, do it recursively.
2014-01-27 22:11:46 -08:00
koalaman
5b58da7249 Merge pull request #75 from michaelsanford/master
Added MacPorts cabal install information to README
2014-01-27 12:11:19 -08:00
michaelsanford
8676517270 Macports install info thanks to @myint
Capital P on MacPorts
2014-01-27 14:41:40 -05:00
Vidar Holen
4262c4b1bf Allow {} in arithmetic for loops 2014-01-26 12:44:51 -08:00
Vidar Holen
7ad0110443 Don't warn about sed '$d' or '$p' 2014-01-25 14:54:05 -08:00
Vidar Holen
e9bba2f75a Don't warn about comma separation in for f in {a,b} 2014-01-25 14:30:25 -08:00
Vidar Holen
74ea5eaeec Parse but warn about "else if" 2014-01-25 14:12:31 -08:00
koalaman
b7ee5f4410 Merge pull request #67 from michaelsanford/master
Added cabal setup instructions for Mac OS X to README
2014-01-24 21:15:10 -08:00
Michael Sanford
e294db171e Added Mac OS instructions with brew (relates #11)
Un-Markdown text
2014-01-24 15:59:10 -05:00
Vidar Holen
8c3d8d7cfa Stable version 0.3.0 2014-01-19 13:04:17 -08:00
Vidar Holen
380d6c3317 Cleaned up cabal file to make 'cabal check' pass. 2014-01-19 13:03:05 -08:00
Vidar Holen
16bd52333a man page: Added Directives section 2014-01-19 12:36:48 -08:00
Vidar Holen
cfb44b3fe2 Warn about 'cd $f; foo; cd ..' loop antipattern. 2014-01-17 09:02:14 -08:00
Vidar Holen
43ed5e748d Allow '# shellcheck disable=SC1234' to ignore by code. 2014-01-16 23:08:56 -08:00
Vidar Holen
4dca88aade Don't warn about quotes in variables for eval 2014-01-16 19:41:09 -08:00
Vidar Holen
1d2c7a8551 Warn about abused char classes, such as [10-15] and [:digit:] 2014-01-14 21:20:21 -08:00
koalaman
ba080e7e34 Merge pull request #61 from cheecheeo/master
expanded cabal file
2014-01-14 10:40:27 -08:00
John Chee
fc716738eb expanded cabal file 2014-01-13 19:14:18 -08:00
Vidar Holen
659709d529 Fixed ${!var} not counting as reference of var 2013-12-30 10:00:05 -08:00
Vidar Holen
5b4729d940 Parser: allow subshell function definitions 2013-12-30 09:50:14 -08:00
Vidar Holen
b936f28763 Merge branch 'manpage' 2013-12-15 19:43:02 -08:00
Vidar Holen
78d9a7ad97 Tweaked man page format 2013-12-15 19:40:05 -08:00
Vidar Holen
d540a98d33 Merge branch 'man_page' of https://github.com/Dridi/shellcheck into manpage 2013-12-15 18:44:49 -08:00
Vidar Holen
8c00850134 Minor performance tweaks (~10% improvement) 2013-12-15 18:43:34 -08:00
Vidar Holen
d1990e3396 Warn about 'i=i+1' and 'i=i + 1' 2013-12-15 16:11:17 -08:00
Dridi Boukelmoune
91fc4a046c Added a shellcheck(1) man page
The manual is mainly a copy/paste from the current documentation. It is
builded with pandoc, and written in markdown.

closes #50
2013-12-16 00:04:31 +01:00
Vidar Holen
95ebe1cd07 Fixed parser error for '..; done| ..' 2013-12-15 14:39:47 -08:00
Vidar Holen
27822a1f56 Warn about 'foo=bar echo $foo' 2013-12-14 16:06:19 -08:00
Vidar Holen
eb06b06475 Don't warn about single quoted vars for ssh 2013-12-14 15:28:58 -08:00
Vidar Holen
5d72432046 If printf string contains %, don't warn about using vars 2013-12-14 15:27:11 -08:00
Vidar Holen
da51b14789 Parser: accept here doc token strings more liberally 2013-12-14 15:20:15 -08:00
Vidar Holen
7be8485b8b Fixed parsing of 'time' in front of compound commands 2013-12-14 14:44:47 -08:00
Vidar Holen
a4d36ba0d2 Warn about while read f; do ssh "$f"; done 2013-11-29 23:05:41 -08:00
Vidar Holen
d4bc0f6e10 Don't require separator before do in 'for i do ..' 2013-11-29 16:22:37 -08:00
Vidar Holen
1011ae7b3c Fixed incorrect parsing of [ foo -a -f bar ] 2013-11-24 18:49:49 -08:00
Vidar Holen
d603ee1e89 Don't warn for A&&B||C if C is echo/exit/assignment 2013-11-24 16:15:10 -08:00
Vidar Holen
4fc518c877 Cleaned up command matching code 2013-11-24 15:48:07 -08:00
Vidar Holen
7fda86d6e2 Fixed warning for 'exec foo; exit 3' 2013-11-24 14:48:12 -08:00
Vidar Holen
6905373b6c Fix incorrect warning for 'foo > bar 2> bar' 2013-11-24 14:20:24 -08:00
Vidar Holen
1d8401d583 Assume variables have spaces by default 2013-11-17 16:16:58 -08:00
Vidar Holen
a89aee1a34 Assume variables have spaces/globs by default 2013-11-17 16:13:45 -08:00
Vidar Holen
4853dce3fe Fixed wrong quoting warning for foo=" "; foo 2013-11-17 15:00:07 -08:00
Vidar Holen
a793e09bab Added hint to suggest missing ;; in case 2013-11-17 14:41:55 -08:00
Vidar Holen
fbd85e93ee Added --exclude to filter out warnings 2013-11-17 13:41:42 -08:00
Vidar Holen
77f754fa32 Replace Prelude.catch with Control.Exception.catch 2013-11-15 17:31:55 -08:00
Vidar Holen
01d557abe6 More discriminating error codes 2013-11-14 22:46:09 -08:00
Vidar Holen
68cc00b6e8 Exit with 1 on comments and 2 on failure 2013-11-14 22:35:38 -08:00
Vidar Holen
8b7c0be06f gcc output mode now uses a tab width of 1 2013-11-14 22:23:55 -08:00
Vidar Holen
473bb666d8 Support for checkstyle compatible xml output 2013-11-13 22:39:35 -08:00
Vidar Holen
376d407ea1 Added gcc compatible output for editor integrations 2013-11-13 17:28:08 -08:00
Vidar Holen
2e13cedc4b Removed jsoncheck. Use 'shellcheck -f json -' instead 2013-11-12 21:39:41 -08:00
Vidar Holen
17515ad706 Added proper command line parsing 2013-11-12 21:22:52 -08:00
Vidar Holen
d8b5d6393a \" should not be treated specially in backticks 2013-11-12 18:27:18 -08:00
Vidar Holen
d404bc703d Show expose error code in shellcheck frontends 2013-11-10 12:22:33 -08:00
Vidar Holen
e5e08df1d9 Numbered messages 2013-11-10 10:55:46 -08:00
koalaman
1988cba147 Merge pull request #33 from daniellawrence/master
updated debian/ubuntu required packages
2013-11-07 10:21:48 -08:00
Daniel Lawrence
4cee7fd27f updated debian/ubuntu required packages 2013-11-04 16:00:47 +11:00
Vidar Holen
b75fe02aac Merge branch 'master' of github.com:koalaman/shellcheck 2013-11-03 13:58:33 -08:00
Vidar Holen
83c3dd3418 Accept ./? in function names 2013-11-03 13:58:06 -08:00
koalaman
020850dbbb Merge pull request #16 from carenas/master
gitignore and makefile for building with cabal and make
2013-11-03 13:51:13 -08:00
Vidar Holen
8d265aa25e Don't warn about expr if using : operator 2013-11-03 13:47:04 -08:00
Vidar Holen
c343217fd2 Added Fedora prerequisites in README 2013-11-03 13:27:10 -08:00
Vidar Holen
71bc26aefa Fixed parsing of | outside of groups in =~ regex 2013-11-03 13:13:24 -08:00
Vidar Holen
8a3d259ae6 Don't warn about single quotes in args to perl 2013-11-03 12:47:44 -08:00
Vidar Holen
3a9ae0ebf1 Accept dir-/basename due to edge cases like "/" and "foo" 2013-11-03 12:42:11 -08:00
Carlo Marcelo Arenas Belon
d6b903e6cc gitignore and makefile for building with cabal and make
makefile should also delete cabal generated files on cleanup so
they could be used interchangably.

ensure that all generated files are ignored as recommended by
bese practices.
2013-10-28 02:56:29 -07:00
Vidar Holen
b9f7f82e29 Stable version 0.2.0 2013-10-27 16:04:33 -07:00
Vidar Holen
6d0bfcf37a Fixed parser accepting spaces after here doc token 2013-10-27 16:02:27 -07:00
Vidar Holen
e0bbb89d00 Fixed parser bug where }> wasn't recognized as Rbrace 2013-10-27 15:36:47 -07:00
Vidar Holen
a0a58d432a Merge branch 'master' of github.com:koalaman/shellcheck 2013-10-20 15:04:45 -07:00
Vidar Holen
206900fb64 Use exit status and stderr properly in terminal tool 2013-10-20 15:03:14 -07:00
koalaman
794a5523d1 Merge pull request #12 from mcandre/master
README: clarify build step
2013-10-20 13:47:15 -07:00
Andrew Pennebaker
389c7b670c README: clarify build step
Help those unfamiliar with cabal to install shellcheck.
2013-10-20 16:33:00 -04:00
Vidar Holen
b1af7bb8f2 Fixed parser error for 'for ((;;)) ; do' 2013-10-11 20:03:55 -07:00
Vidar Holen
157fea73da Suggest ./* instead of * for command arguments 2013-10-06 15:54:31 -07:00
Vidar Holen
b439f02b8e Improved assignment parsing: track indices and += vs = 2013-10-06 14:44:43 -07:00
Vidar Holen
710a28c572 Revert "Fixed parsing for 'eval var=(values)'", because it fails for 'eval $x=foo'
This reverts commit 34e69556b1.
2013-09-29 21:35:20 -07:00
Vidar Holen
702d57b655 Warn about unicode quotes 2013-09-29 21:22:42 -07:00
Vidar Holen
34e69556b1 Fixed parsing for 'eval var=(values)' 2013-09-29 21:00:29 -07:00
Vidar Holen
7c411b39ac Fixed warnings for read -p "Name: " 2013-09-29 20:55:26 -07:00
Vidar Holen
5a959bc340 Fix parser errors for unclosed $( in here docs 2013-09-21 16:27:17 -07:00
Vidar Holen
fb5f72951d Don't warn about unused variables for read '' 2013-09-12 19:11:42 -07:00
Vidar Holen
7630136d6c Move command and variable lists to ShellCheck.Data. 2013-09-12 18:50:33 -07:00
Vidar Holen
dacb8c597f Fixed a series of extglob parsing oddities and edge cases 2013-09-04 13:02:30 -07:00
Vidar Holen
d99aaaf8dc Merge branch 'master' of github.com:koalaman/shellcheck 2013-09-03 14:09:50 -07:00
Vidar Holen
876831b419 Fixed up readme 2013-09-03 14:08:30 -07:00
Vidar Holen
24580609b8 Merge branch 'master' of https://github.com/carenas/shellcheck into carenas-master 2013-09-03 14:06:38 -07:00
koalaman
5828abe324 Merge pull request #7 from carenas/master
Include jsoncheck in 'make clean' target and update required ubuntu package list.
2013-09-03 14:04:49 -07:00
Carlo Marcelo Arenas Belon
c229d3929a build: update recommended Ubuntu package list
last still supported version to use ghc6 was lucid, so it is
better to refer instead to ghc 7 as used since precise
2013-09-01 21:46:32 -07:00
Carlo Marcelo Arenas Belon
31907ca51d build: include all binaries for clean 2013-09-01 21:37:30 -07:00
Vidar Holen
58b8e0ab70 Suggest pgrep when grepping ps 2013-08-31 18:32:07 -07:00
Vidar Holen
9586a46c9c Warn about assigned but unreferenced variables 2013-08-31 18:26:20 -07:00
Vidar Holen
bb49cf8e65 Parse the contents of unquoted here documents 2013-08-31 17:03:15 -07:00
Vidar Holen
de1fa61560 Warn about client side expansion in ssh strings/heredocs. 2013-08-03 21:19:32 -07:00
Vidar Holen
07b1fd6f44 Allow :+- in function names. :(){ :|:;};:, anyone? 2013-08-03 20:22:32 -07:00
Vidar Holen
d0caa1e1df Don't warn for 'find .. | xargs -0' or 'ls -N | ..' 2013-07-25 19:58:53 -07:00
14 changed files with 2552 additions and 887 deletions

7
.gitignore vendored Normal file
View File

@@ -0,0 +1,7 @@
*.hi
*.o
.tests
jsoncheck
shellcheck
shellcheck.1
dist

View File

@@ -2,23 +2,23 @@
GHCFLAGS=-O9
all: shellcheck jsoncheck .tests
all: shellcheck .tests shellcheck.1
: Done
shellcheck: regardless
: Conditionally compiling shellcheck
ghc $(GHCFLAGS) --make shellcheck
jsoncheck: regardless
: Conditionally compiling shellcheck
ghc $(GHCFLAGS) --make jsoncheck
.tests: *.hs */*.hs
: Running unit tests
./test/runQuack && touch .tests
shellcheck.1: shellcheck.1.md
pandoc -s -t man $< -o $@
clean:
rm -f .tests shellcheck *.hi *.o ShellCheck/*.hi ShellCheck/*.o
rm -f .tests shellcheck shellcheck.1
rm -f *.hi *.o ShellCheck/*.hi ShellCheck/*.o
rm -rf dist
regardless:

26
README
View File

@@ -1,26 +0,0 @@
ShellCheck - A shell script static analysis tool
http://www.vidarholen.net/contents/shellcheck
Copyright 2012, Vidar 'koala_man' Holen
Licensed under the GNU Affero General Public License, v3
The goals of ShellCheck are:
- To point out and clarify typical beginner's syntax issues,
that causes a shell to give cryptic error messages.
- To point out and clarify typical intermediate level semantic problems,
that causes a shell to behave strangely and counter-intuitively.
- To point out subtle caveats, corner cases and pitfalls, that may cause an
advanced user's otherwise working script to fail under future circumstances.
ShellCheck is written in Haskell, and requires GHC, Parsec3 and Text.Regex.
To build the JSON interface and run the unit tests, it also requires QuickCheck2 and JSON.
On Ubuntu and similar, these are called:
ghc6 libghc6-parsec3-dev libghc6-quickcheck2-dev libghc6-json-dev libghc-regex-compat-dev
Executables can be built with cabal. Tests currently still rely on a Makefile.
Happy ShellChecking!

68
README.md Normal file
View File

@@ -0,0 +1,68 @@
# ShellCheck - A shell script static analysis tool
http://www.shellcheck.net
Copyright 2012-2014, Vidar 'koala_man' Holen
Licensed under the GNU Affero General Public License, v3
The goals of ShellCheck are:
- To point out and clarify typical beginner's syntax issues,
that causes a shell to give cryptic error messages.
- To point out and clarify typical intermediate level semantic problems,
that causes a shell to behave strangely and counter-intuitively.
- To point out subtle caveats, corner cases and pitfalls, that may cause an
advanced user's otherwise working script to fail under future circumstances.
ShellCheck requires at least 1 GB of RAM to compile. Executables can be built with cabal. Tests currently still rely on a Makefile.
## Building with Cabal
Make sure cabal is installed. On Debian based distros:
apt-get install cabal-install
On Fedora:
yum install cabal-install
On Mac OS X with homebrew (http://brew.sh/):
brew install cabal-install
On Mac OS X with MacPorts (http://www.macports.org/):
port install hs-cabal-install
With cabal installed, cd to the shellcheck source directory and:
$ cabal install
...
$ which shellcheck
~/.cabal/bin/shellcheck
## Building with Make
ShellCheck is written in Haskell, and requires GHC, Parsec3, JSON and
Text.Regex. To run the unit tests, it also requires QuickCheck2.
On Fedora, these can be installed with:
yum install ghc ghc-parsec-devel ghc-QuickCheck-devel \
ghc-json-devel ghc-regex-compat-devel
On Ubuntu and similar, use:
apt-get install ghc libghc-parsec3-dev libghc-json-dev \
libghc-regex-compat-dev libghc-quickcheck2-dev
To build and run the tests, cd to the shellcheck source directory and:
$ make
Happy ShellChecking!

2
Setup.hs Normal file
View File

@@ -0,0 +1,2 @@
import Distribution.Simple
main = defaultMain

View File

@@ -1,20 +1,56 @@
Name: ShellCheck
Version: 0.1.0
Description: Shell script analysis tool
-- Must also be updated in ShellCheck/Data.hs :
Version: 0.3.2
Synopsis: Shell script analysis tool
License: OtherLicense
License-file: LICENSE
Category: Static Analysis
Author: Vidar Holen
Maintainer: vidar@vidarholen.net
Homepage: http://www.shellcheck.net/
Build-Type: Simple
Cabal-Version: >= 1.2
Cabal-Version: >= 1.8
Bug-reports: https://github.com/koalaman/shellcheck/issues
Description:
The goals of ShellCheck are:
.
* To point out and clarify typical beginner's syntax issues,
that causes a shell to give cryptic error messages.
.
* To point out and clarify typical intermediate level semantic problems,
that causes a shell to behave strangely and counter-intuitively.
.
* To point out subtle caveats, corner cases and pitfalls, that may cause an
advanced user's otherwise working script to fail under future circumstances.
source-repository head
type: git
location: git://github.com/koalaman/shellcheck.git
library
build-depends: base >= 4, parsec, containers, regex-compat, mtl, directory
exposed-modules: ShellCheck.AST, ShellCheck.Parser, ShellCheck.Analytics, ShellCheck.Simple
build-depends:
base >= 4 && < 5,
containers,
directory,
json,
mtl,
parsec,
regex-compat
exposed-modules:
ShellCheck.Analytics
ShellCheck.AST
ShellCheck.Data
ShellCheck.Parser
ShellCheck.Simple
executable shellcheck
build-depends:
ShellCheck,
base >= 4 && < 5,
containers,
directory,
json,
mtl,
parsec,
regex-compat
main-is: shellcheck.hs
executable jsoncheck
build-depends: json
main-is: jsoncheck.hs

View File

@@ -23,6 +23,12 @@ import qualified Text.Regex as Re
data Id = Id Int deriving (Show, Eq, Ord)
data Quoted = Quoted | Unquoted deriving (Show, Eq)
data Dashed = Dashed | Undashed deriving (Show, Eq)
data AssignmentMode = Assign | Append deriving (Show, Eq)
data FunctionKeyword = FunctionKeyword Bool deriving (Show, Eq)
data FunctionParentheses = FunctionParentheses Bool deriving (Show, Eq)
data Token =
TA_Base Id String Token
| TA_Binary Id String Token Token
@@ -42,7 +48,7 @@ data Token =
| T_AndIf Id (Token) (Token)
| T_Arithmetic Id Token
| T_Array Id [Token]
| T_Assignment Id String Token
| T_Assignment Id AssignmentMode String (Maybe Token) Token
| T_Backgrounded Id Token
| T_Backticked Id [Token]
| T_Bang Id
@@ -76,11 +82,11 @@ data Token =
| T_For Id
| T_ForArithmetic Id Token Token Token [Token]
| T_ForIn Id String [Token] [Token]
| T_Function Id String Token
| T_Function Id FunctionKeyword FunctionParentheses String Token
| T_GREATAND Id
| T_Glob Id String
| T_Greater Id
| T_HereDoc Id Bool Bool String
| T_HereDoc Id Dashed Quoted String [Token]
| T_HereString Id Token
| T_If Id
| T_IfExpression Id [([Token],[Token])] [Token]
@@ -96,7 +102,7 @@ data Token =
| T_NormalWord Id [Token]
| T_OR_IF Id
| T_OrIf Id (Token) (Token)
| T_Pipeline Id [Token]
| T_Pipeline Id [Token] [Token] -- [Pipe separators] [Commands]
| T_ProcSub Id String [Token]
| T_Rbrace Id
| T_Redirecting Id [Token] Token
@@ -113,19 +119,22 @@ data Token =
| T_UntilExpression Id [Token] [Token]
| T_While Id
| T_WhileExpression Id [Token] [Token]
| T_Annotation Id [Annotation] Token
| T_Pipe Id String
deriving (Show)
data Annotation = DisableComment Integer deriving (Show, Eq)
data ConditionType = DoubleBracket | SingleBracket deriving (Show, Eq)
-- I apologize for nothing!
lolHax s = Re.subRegex (Re.mkRegex "(Id [0-9]+)") (show s) "(Id 0)"
instance Eq Token where
(==) a b = (lolHax a) == (lolHax b)
(==) a b = lolHax a == lolHax b
analyze :: Monad m => (Token -> m ()) -> (Token -> m ()) -> (Token -> Token) -> Token -> m Token
analyze f g i t =
round t
analyze f g i =
round
where
round t = do
f t
@@ -134,6 +143,11 @@ analyze f g i t =
return . i $ newT
roundAll = mapM round
roundMaybe Nothing = return Nothing
roundMaybe (Just v) = do
s <- round v
return (Just s)
dl l v = do
x <- roundAll l
return $ v x
@@ -159,14 +173,17 @@ analyze f g i t =
delve (T_IoFile id op file) = d2 op file $ T_IoFile id
delve (T_HereString id word) = d1 word $ T_HereString id
delve (T_FdRedirect id v t) = d1 t $ T_FdRedirect id v
delve (T_Assignment id v t) = d1 t $ T_Assignment id v
delve (T_Assignment id mode var index value) = do
a <- roundMaybe index
b <- round value
return $ T_Assignment id mode var a b
delve (T_Array id t) = dl t $ T_Array id
delve (T_Redirecting id redirs cmd) = do
a <- roundAll redirs
b <- round cmd
return $ T_Redirecting id a b
delve (T_SimpleCommand id vars cmds) = dll vars cmds $ T_SimpleCommand id
delve (T_Pipeline id l) = dl l $ T_Pipeline id
delve (T_Pipeline id l1 l2) = dll l1 l2 $ T_Pipeline id
delve (T_Banged id l) = d1 l $ T_Banged id
delve (T_AndIf id t u) = d2 t u $ T_AndIf id
delve (T_OrIf id t u) = d2 t u $ T_OrIf id
@@ -204,10 +221,11 @@ analyze f g i t =
return $ T_ForArithmetic id x y z list
delve (T_Script id s l) = dl l $ T_Script id s
delve (T_Function id name body) = d1 body $ T_Function id name
delve (T_Function id a b name body) = d1 body $ T_Function id a b name
delve (T_Condition id typ token) = d1 token $ T_Condition id typ
delve (T_Extglob id str l) = dl l $ T_Extglob id str
delve (T_DollarBraced id op) = d1 op $ T_DollarBraced id
delve (T_HereDoc id d q str l) = dl l $ T_HereDoc id d q str
delve (TC_And id typ str t1 t2) = d2 t1 t2 $ TC_And id typ str
delve (TC_Or id typ str t1 t2) = d2 t1 t2 $ TC_Or id typ str
@@ -226,6 +244,7 @@ analyze f g i t =
return $ TA_Trinary id a b c
delve (TA_Expansion id t) = d1 t $ TA_Expansion id
delve (TA_Base id b t) = d1 t $ TA_Base id b
delve (T_Annotation id anns t) = d1 t $ T_Annotation id anns
delve t = return t
getId t = case t of
@@ -272,14 +291,14 @@ getId t = case t of
T_DollarArithmetic id _ -> id
T_BraceExpansion id _ -> id
T_IoFile id _ _ -> id
T_HereDoc id _ _ _ -> id
T_HereDoc id _ _ _ _ -> id
T_HereString id _ -> id
T_FdRedirect id _ _ -> id
T_Assignment id _ _ -> id
T_Assignment id _ _ _ _ -> id
T_Array id _ -> id
T_Redirecting id _ _ -> id
T_SimpleCommand id _ _ -> id
T_Pipeline id _ -> id
T_Pipeline id _ _ -> id
T_Banged id _ -> id
T_AndIf id _ _ -> id
T_OrIf id _ _ -> id
@@ -292,7 +311,7 @@ getId t = case t of
T_ForIn id _ _ _ -> id
T_SelectIn id _ _ _ -> id
T_CaseExpression id _ _ -> id
T_Function id _ _ -> id
T_Function id _ _ _ _ -> id
T_Arithmetic id _ -> id
T_Script id _ _ -> id
T_Condition id _ _ -> id
@@ -318,10 +337,19 @@ getId t = case t of
T_DollarSingleQuoted id _ -> id
T_DollarDoubleQuoted id _ -> id
T_DollarBracket id _ -> id
T_Annotation id _ _ -> id
T_Pipe id _ -> id
blank :: Monad m => Token -> m ()
blank = const $ return ()
doAnalysis f t = analyze f blank id t
doStackAnalysis startToken endToken t = analyze startToken endToken id t
doTransform i t = runIdentity $ analyze blank blank i t
doAnalysis f = analyze f blank id
doStackAnalysis startToken endToken = analyze startToken endToken id
doTransform i = runIdentity . analyze blank blank i
isLoop t = case t of
T_WhileExpression {} -> True
T_UntilExpression {} -> True
T_ForIn {} -> True
T_ForArithmetic {} -> True
T_SelectIn {} -> True
_ -> False

File diff suppressed because it is too large Load Diff

76
ShellCheck/Data.hs Normal file
View File

@@ -0,0 +1,76 @@
module ShellCheck.Data where
shellcheckVersion = "0.3.2" -- Must also be updated in ShellCheck.cabal
internalVariables = [
-- Generic
"", "_", "rest", "REST",
-- Bash
"BASH", "BASHOPTS", "BASHPID", "BASH_ALIASES", "BASH_ARGC",
"BASH_ARGV", "BASH_CMDS", "BASH_COMMAND", "BASH_EXECUTION_STRING",
"BASH_LINENO", "BASH_REMATCH", "BASH_SOURCE", "BASH_SUBSHELL",
"BASH_VERSINFO", "BASH_VERSION", "COMP_CWORD", "COMP_KEY",
"COMP_LINE", "COMP_POINT", "COMP_TYPE", "COMP_WORDBREAKS",
"COMP_WORDS", "COPROC", "DIRSTACK", "EUID", "FUNCNAME", "GROUPS",
"HISTCMD", "HOSTNAME", "HOSTTYPE", "LINENO", "MACHTYPE", "MAPFILE",
"OLDPWD", "OPTARG", "OPTIND", "OSTYPE", "PIPESTATUS", "PPID", "PWD",
"RANDOM", "READLINE_LINE", "READLINE_POINT", "REPLY", "SECONDS",
"SHELLOPTS", "SHLVL", "UID", "BASH_ENV", "BASH_XTRACEFD", "CDPATH",
"COLUMNS", "COMPREPLY", "EMACS", "ENV", "FCEDIT", "FIGNORE",
"FUNCNEST", "GLOBIGNORE", "HISTCONTROL", "HISTFILE", "HISTFILESIZE",
"HISTIGNORE", "HISTSIZE", "HISTTIMEFORMAT", "HOME", "HOSTFILE", "IFS",
"IGNOREEOF", "INPUTRC", "LANG", "LC_ALL", "LC_COLLATE", "LC_CTYPE",
"LC_MESSAGES", "LC_NUMERIC", "LINES", "MAIL", "MAILCHECK", "MAILPATH",
"OPTERR", "PATH", "POSIXLY_CORRECT", "PROMPT_COMMAND",
"PROMPT_DIRTRIM", "PS1", "PS2", "PS3", "PS4", "SHELL", "TIMEFORMAT",
"TMOUT", "TMPDIR", "auto_resume", "histchars",
-- Zsh
"ARGV0", "BAUD", "cdpath", "COLUMNS", "CORRECT_IGNORE",
"DIRSTACKSIZE", "ENV", "FCEDIT", "fignore", "fpath", "histchars",
"HISTCHARS", "HISTFILE", "HISTSIZE", "HOME", "IFS", "KEYBOARD_HACK",
"KEYTIMEOUT", "LANG", "LC_ALL", "LC_COLLATE", "LC_CTYPE",
"LC_MESSAGES", "LC_NUMERIC", "LC_TIME", "LINES", "LISTMAX",
"LOGCHECK", "MAIL", "MAILCHECK", "mailpath", "manpath", "module_path",
"NULLCMD", "path", "POSTEDIT", "PROMPT", "PROMPT2", "PROMPT3",
"PROMPT4", "prompt", "PROMPT_EOL_MARK", "PS1", "PS2", "PS3", "PS4",
"psvar", "READNULLCMD", "REPORTTIME", "REPLY", "reply", "RPROMPT",
"RPS1", "RPROMPT2", "RPS2", "SAVEHIST", "SPROMPT", "STTY", "TERM",
"TERMINFO", "TIMEFMT", "TMOUT", "TMPPREFIX", "watch", "WATCHFMT",
"WORDCHARS", "ZBEEP", "ZDOTDIR", "ZLE_LINE_ABORTED",
"ZLE_REMOVE_SUFFIX_CHARS", "ZLE_SPACE_SUFFIX_CHARS"
]
variablesWithoutSpaces = [
"$", "-", "?", "!",
"BASHPID", "BASH_ARGC", "BASH_LINENO", "BASH_SUBSHELL", "EUID", "LINENO",
"OPTIND", "PPID", "RANDOM", "SECONDS", "SHELLOPTS", "SHLVL", "UID",
"COLUMNS", "HISTFILESIZE", "HISTSIZE", "LINES"
]
commonCommands = [
"admin", "alias", "ar", "asa", "at", "awk", "basename", "batch",
"bc", "bg", "break", "c99", "cal", "cat", "cd", "cflow", "chgrp",
"chmod", "chown", "cksum", "cmp", "colon", "comm", "command",
"compress", "continue", "cp", "crontab", "csplit", "ctags", "cut",
"cxref", "date", "dd", "delta", "df", "diff", "dirname", "dot",
"du", "echo", "ed", "env", "eval", "ex", "exec", "exit", "expand",
"export", "expr", "fc", "fg", "file", "find", "fold", "fort77",
"fuser", "gencat", "get", "getconf", "getopts", "grep", "hash",
"head", "iconv", "ipcrm", "ipcs", "jobs", "join", "kill", "lex",
"link", "ln", "locale", "localedef", "logger", "logname", "lp",
"ls", "m4", "mailx", "make", "man", "mesg", "mkdir", "mkfifo",
"more", "mv", "newgrp", "nice", "nl", "nm", "nohup", "od", "paste",
"patch", "pathchk", "pax", "pr", "printf", "prs", "ps", "pwd",
"qalter", "qdel", "qhold", "qmove", "qmsg", "qrerun", "qrls",
"qselect", "qsig", "qstat", "qsub", "read", "readonly", "renice",
"return", "rm", "rmdel", "rmdir", "sact", "sccs", "sed", "set",
"sh", "shift", "sleep", "sort", "split", "strings", "strip", "stty",
"tabs", "tail", "talk", "tee", "test", "time", "times", "touch",
"tput", "tr", "trap", "tsort", "tty", "type", "ulimit", "umask",
"unalias", "uname", "uncompress", "unexpand", "unget", "uniq",
"unlink", "unset", "uucp", "uudecode", "uuencode", "uustat", "uux",
"val", "vi", "wait", "wc", "what", "who", "write", "xargs", "yacc",
"zcat"
]

File diff suppressed because it is too large Load Diff

View File

@@ -15,7 +15,7 @@
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
-}
module ShellCheck.Simple (shellCheck, ShellCheckComment, scLine, scColumn, scSeverity, scMessage) where
module ShellCheck.Simple (shellCheck, ShellCheckComment, scLine, scColumn, scSeverity, scCode, scMessage) where
import ShellCheck.Parser
import ShellCheck.Analytics
@@ -23,21 +23,38 @@ import Data.Maybe
import Text.Parsec.Pos
import Data.List
shellCheck :: String -> [ShellCheckComment]
shellCheck script =
prop_findsParseIssue =
let comments = shellCheck "echo \"$12\"" [] in
(length comments) == 1 && (scCode $ head comments) == 1037
prop_commentDisablesParseIssue1 =
null $ shellCheck "#shellcheck disable=SC1037\necho \"$12\"" []
prop_commentDisablesParseIssue2 =
null $ shellCheck "#shellcheck disable=SC1037\n#lol\necho \"$12\"" []
prop_findsAnalysisIssue =
let comments = shellCheck "echo $1" [] in
(length comments) == 1 && (scCode $ head comments) == 2086
prop_commentDisablesAnalysisIssue1 =
null $ shellCheck "#shellcheck disable=SC2086\necho $1" []
prop_commentDisablesAnalysisIssue2 =
null $ shellCheck "#shellcheck disable=SC2086\n#lol\necho $1" []
shellCheck :: String -> [AnalysisOption] -> [ShellCheckComment]
shellCheck script options =
let (ParseResult result notes) = parseShell "-" script in
let allNotes = notes ++ (concat $ maybeToList $ do
(tree, map) <- result
let newMap = runAllAnalytics tree map
return $ notesFromMap newMap
(tree, posMap) <- result
let list = runAnalytics options tree
return $ map (noteToParseNote posMap) $ filterByAnnotation tree list
)
in
map formatNote $ nub $ sortNotes allNotes
data ShellCheckComment = ShellCheckComment { scLine :: Int, scColumn :: Int, scSeverity :: String, scMessage :: String }
data ShellCheckComment = ShellCheckComment { scLine :: Int, scColumn :: Int, scSeverity :: String, scCode :: Int, scMessage :: String }
instance Show ShellCheckComment where
show c = concat ["(", show $ scLine c, ",", show $ scColumn c, ") ", scSeverity c, ": ", scMessage c]
show c = concat ["(", show $ scLine c, ",", show $ scColumn c, ") ", scSeverity c, ": ", show (scCode c), " ", scMessage c]
severityToString s =
case s of
@@ -46,4 +63,5 @@ severityToString s =
InfoC -> "info"
StyleC -> "style"
formatNote (ParseNote pos severity text) = ShellCheckComment (sourceLine pos) (sourceColumn pos) (severityToString severity) text
formatNote (ParseNote pos severity code text) =
ShellCheckComment (sourceLine pos) (sourceColumn pos) (severityToString severity) (fromIntegral code) text

View File

@@ -1,32 +0,0 @@
{-
This file is part of ShellCheck.
http://www.vidarholen.net/contents/shellcheck
ShellCheck is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
ShellCheck is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
-}
import ShellCheck.Simple
import Text.JSON
instance JSON ShellCheckComment where
showJSON c = makeObj [
("line", showJSON $ scLine c),
("column", showJSON $ scColumn c),
("level", showJSON $ scSeverity c),
("message", showJSON $ scMessage c)
]
readJSON = undefined
main = do
script <- getContents
putStrLn $ encodeStrict $ shellCheck script

121
shellcheck.1.md Normal file
View File

@@ -0,0 +1,121 @@
% SHELLCHECK(1) Shell script analysis tool
# NAME
shellcheck - Shell script analysis tool
# SYNOPSIS
**shellcheck** [*OPTIONS*...] *FILES*...
# DESCRIPTION
ShellCheck is a static analysis and linting tool for sh/bash scripts. It's
mainly focused on handling typical beginner and intermediate level syntax
errors and pitfalls where the shell just gives a cryptic error message or
strange behavior, but it also reports on a few more advanced issues where
corner cases can cause delayed failures.
# OPTIONS
**-f** *FORMAT*, **--format=***FORMAT*
: Specify the output format of shellcheck, which prints its results in the
standard output. Subsequent **-f** options are ignored, see **FORMATS**
below for more information.
**-e**\ *CODE1*[,*CODE2*...],\ **--exclude=***CODE1*[,*CODE2*...]
: Explicitly exclude the specified codes from the report. Subsequent **-e**
options are cumulative, but all the codes can be specified at once,
comma-separated as a single argument.
**-s**\ *shell*,\ **--shell=***shell*
: Specify Bourne shell dialect. Valid values are *sh*, *bash*, *ksh* and
*zsh*. The default is to use the file's shebang, or *bash* if the target
shell can't be determined.
# FORMATS
**tty**
: Plain text, human readable output. This is the default.
**gcc**
: GCC compatible output. Useful for editors that support compiling and
showing syntax errors.
For example, in Vim, `:set makeprg=shellcheck\ -f\ gcc\ %` will allow
using `:make` to check the script, and `:cnext` to jump to the next error.
<file>:<line>:<column>: <type>: <message>
**checkstyle**
: Checkstyle compatible XML output. Supported directly or through plugins
by many IDEs and build monitoring systems.
<?xml version='1.0' encoding='UTF-8'?>
<checkstyle version='4.3'>
<file name='file'>
<error
line='line'
column='column'
severity='severity'
message='message'
source='ShellCheck.SC####' />
...
</file>
...
</checkstyle>
**json**
: Json is a popular serialization format that is more suitable for web
applications. ShellCheck's json is compact and contains only the bare
minimum.
[
{
"line": line,
"column": column,
"level": level,
"code": ####,
"message": message
},
...
]
# DIRECTIVES
ShellCheck directives can be specified as comments in the shell script
before a command or block:
# shellcheck key=value key=value
command-or-structure
For example, to suppress SC2035 about using `./*.jpg`:
# shellcheck disable=SC2035
echo "Files: " *.jpg
Valid keys are:
**disable**
: Disables a comma separated list of error codes for the following command.
The command can be a simple command like `echo foo`, or a compound command
like a function definition, subshell block or loop.
# AUTHOR
ShellCheck is written and maintained by Vidar Holen.
# REPORTING BUGS
Bugs and issues can be reported on GitHub:
https://github.com/koalaman/shellcheck/issues
# SEE ALSO
sh(1) bash(1)

View File

@@ -15,72 +15,287 @@
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
-}
import Control.Exception
import Control.Monad
import Data.Char
import Data.Maybe
import GHC.Exts
import GHC.IO.Device
import Prelude hiding (catch)
import ShellCheck.Data
import ShellCheck.Simple
import ShellCheck.Analytics
import System.Console.GetOpt
import System.Directory
import System.Environment
import System.Exit
import System.IO
import Text.JSON
import qualified Data.Map as Map
data Flag = Flag String String
clear = ansi 0
ansi n = "\x1B[" ++ (show n) ++ "m"
header = "Usage: shellcheck [OPTIONS...] FILES..."
options = [
Option ['f'] ["format"]
(ReqArg (Flag "format") "FORMAT") "output format",
Option ['e'] ["exclude"]
(ReqArg (Flag "exclude") "CODE1,CODE2..") "exclude types of warnings",
Option ['s'] ["shell"]
(ReqArg (Flag "shell") "SHELLNAME") "Specify dialect (bash,sh,ksh,zsh)",
Option ['V'] ["version"]
(NoArg $ Flag "version" "true") "Print version information"
]
colorForLevel "error" = 31 -- red
colorForLevel "warning" = 33 -- yellow
colorForLevel "info" = 32 -- green
colorForLevel "style" = 32 -- green
colorForLevel "message" = 1 -- bold
colorForLevel "source" = 0 -- none
colorForLevel _ = 0 -- none
printErr = hPutStrLn stderr
colorComment level comment = (ansi $ colorForLevel level) ++ comment ++ clear
syntaxFailure = ExitFailure 3
supportFailure = ExitFailure 4
doFile path colorFunc = do
let actualPath = if path == "-" then "/dev/stdin" else path
exists <- doesFileExist actualPath
if exists then do
contents <- readFile actualPath
doInput path contents colorFunc
else do
putStrLn (colorFunc "error" $ "No such file: " ++ actualPath)
instance JSON ShellCheckComment where
showJSON c = makeObj [
("line", showJSON $ scLine c),
("column", showJSON $ scColumn c),
("level", showJSON $ scSeverity c),
("code", showJSON $ scCode c),
("message", showJSON $ scMessage c)
]
readJSON = undefined
doInput filename contents colorFunc = do
let fileLines = lines contents
let lineCount = length fileLines
let comments = shellCheck contents
let groups = groupWith scLine comments
if not $ null comments then do
parseArguments argv =
case getOpt Permute options argv of
(opts, files, []) -> do
verifyOptions opts files
return $ Just (opts, files)
(_, _, errors) -> do
printErr $ concat errors ++ "\n" ++ usageInfo header options
exitWith syntaxFailure
formats = Map.fromList [
("json", forJson),
("gcc", forGcc),
("checkstyle", forCheckstyle),
("tty", forTty)
]
forTty options files = do
output <- mapM doFile files
return $ and output
where
clear = ansi 0
ansi n = "\x1B[" ++ show n ++ "m"
colorForLevel "error" = 31 -- red
colorForLevel "warning" = 33 -- yellow
colorForLevel "info" = 32 -- green
colorForLevel "style" = 32 -- green
colorForLevel "message" = 1 -- bold
colorForLevel "source" = 0 -- none
colorForLevel _ = 0 -- none
colorComment level comment =
ansi (colorForLevel level) ++ comment ++ clear
doFile path = do
contents <- readContents path
doInput path contents
doInput filename contents = do
let fileLines = lines contents
let lineCount = length fileLines
let comments = getComments options contents
let groups = groupWith scLine comments
colorFunc <- getColorFunc
mapM_ (\x -> do
let lineNum = scLine (head x)
let line = if lineNum < 1 || lineNum > lineCount
then ""
else fileLines !! (lineNum - 1)
putStrLn ""
putStrLn $ colorFunc "message" ("In " ++ filename ++" line " ++ (show $ lineNum) ++ ":")
putStrLn $ colorFunc "message"
("In " ++ filename ++" line " ++ show lineNum ++ ":")
putStrLn (colorFunc "source" line)
mapM (\c -> putStrLn (colorFunc (scSeverity c) $ cuteIndent c)) x
mapM_ (\c -> putStrLn (colorFunc (scSeverity c) $ cuteIndent c)) x
putStrLn ""
) groups
else do
putStrLn ("No comments for " ++ filename)
return $ null comments
cuteIndent comment =
(replicate ((scColumn comment) - 1) ' ') ++ "^-- " ++ (scMessage comment)
cuteIndent comment =
replicate (scColumn comment - 1) ' ' ++
"^-- " ++ code (scCode comment) ++ ": " ++ scMessage comment
getColorFunc = do
term <- hIsTerminalDevice stdout
return $ if term then colorComment else const id
code code = "SC" ++ (show code)
getColorFunc = do
term <- hIsTerminalDevice stdout
return $ if term then colorComment else const id
-- This totally ignores the filenames. Fixme?
forJson options files = do
comments <- liftM concat $ mapM (commentsFor options) files
putStrLn $ encodeStrict comments
return . null $ comments
-- Mimic GCC "file:line:col: (error|warning|note): message" format
forGcc options files = do
files <- mapM process files
return $ and files
where
process file = do
contents <- readContents file
let comments = makeNonVirtual (getComments options contents) contents
mapM_ (putStrLn . format file) comments
return $ null comments
format filename c = concat [
filename, ":",
show $ scLine c, ":",
show $ scColumn c, ": ",
case scSeverity c of
"error" -> "error"
"warning" -> "warning"
_ -> "note",
": ",
concat . lines $ scMessage c,
" [SC", show $ scCode c, "]"
]
-- Checkstyle compatible output. A bit of a hack to avoid XML dependencies
forCheckstyle options files = do
putStrLn "<?xml version='1.0' encoding='UTF-8'?>"
putStrLn "<checkstyle version='4.3'>"
statuses <- mapM (\x -> process x `catch` report) files
putStrLn "</checkstyle>"
return $ and statuses
where
process file = do
comments <- commentsFor options file
putStrLn (formatFile file comments)
return $ null comments
report error = do
printErr $ show (error :: SomeException)
return False
severity "error" = "error"
severity "warning" = "warning"
severity _ = "info"
attr s v = concat [ s, "='", escape v, "' " ]
escape = concatMap escape'
escape' c = if isOk c then [c] else "&#" ++ show (ord c) ++ ";"
isOk x = any ($x) [isAsciiUpper, isAsciiLower, isDigit, (`elem` " ./")]
formatFile name comments = concat [
"<file ", attr "name" name, ">\n",
concatMap format comments,
"</file>"
]
format c = concat [
"<error ",
attr "line" $ show . scLine $ c,
attr "column" $ show . scColumn $ c,
attr "severity" $ severity . scSeverity $ c,
attr "message" $ scMessage c,
attr "source" $ "ShellCheck.SC" ++ (show $ scCode c),
"/>\n"
]
commentsFor options file =
liftM (getComments options) $ readContents file
getComments options contents =
excludeCodes (getExclusions options) $ shellCheck contents analysisOptions
where
analysisOptions = catMaybes [ shellOption ]
shellOption = do
option <- getOption options "shell"
sh <- shellForExecutable option
return $ ForceShell sh
readContents file = if file == "-" then getContents else readFile file
-- Realign comments from a tabstop of 8 to 1
makeNonVirtual comments contents =
map fix comments
where
ls = lines contents
fix c = c { scColumn = real (ls !! (scLine c - 1)) 0 0 (scColumn c) }
real _ r v target | target <= v = r
real [] r v _ = r -- should never happen
real ('\t':rest) r v target =
real rest (r+1) (v + 8 - (v `mod` 8)) target
real (_:rest) r v target = real rest (r+1) (v+1) target
getOption [] _ = Nothing
getOption (Flag var val:_) name | name == var = return val
getOption (_:rest) flag = getOption rest flag
getOptions options name =
map (\(Flag _ val) -> val) . filter (\(Flag var _) -> var == name) $ options
split char str =
split' str []
where
split' (a:rest) element =
if a == char
then (reverse element) : split' rest []
else split' rest (a:element)
split' [] element = [reverse element]
getExclusions options =
let elements = concatMap (split ',') $ getOptions options "exclude"
clean = dropWhile (not . isDigit)
in
map (Prelude.read . clean) elements :: [Int]
excludeCodes codes =
filter (not . hasCode)
where
hasCode c = scCode c `elem` codes
main = do
args <- getArgs
colors <- getColorFunc
if null args then do
hPutStrLn stderr "shellcheck -- bash/sh script static analysis tool"
hPutStrLn stderr "Usage: shellcheck filenames..."
exitFailure
else
mapM (\f -> doFile f colors) args
parsedArgs <- parseArguments args
code <- do
status <- process parsedArgs
return $ if status then ExitSuccess else ExitFailure 1
`catch` return
`catch` \err -> do
printErr $ show (err :: SomeException)
return $ ExitFailure 2
exitWith code
process Nothing = return False
process (Just (options, files)) =
let format = fromMaybe "tty" $ getOption options "format" in
case Map.lookup format formats of
Nothing -> do
printErr $ "Unknown format " ++ format
printErr $ "Supported formats:"
mapM_ (printErr . write) $ Map.keys formats
exitWith supportFailure
where write s = " " ++ s
Just f -> do
f options files
verifyOptions opts files = do
when (isJust $ getOption opts "version") printVersionAndExit
let shell = getOption opts "shell" in
when (isJust shell && isNothing (shell >>= shellForExecutable)) $ do
printErr $ "Unknown shell: " ++ (fromJust shell)
exitWith supportFailure
when (null files) $ do
printErr "No files specified.\n"
printErr $ usageInfo header options
exitWith syntaxFailure
printVersionAndExit = do
putStrLn $ "ShellCheck - shell script analysis tool"
putStrLn $ "version: " ++ shellcheckVersion
putStrLn $ "license: GNU Affero General Public License, version 3"
putStrLn $ "website: http://www.shellcheck.net"
exitWith ExitSuccess