mirror of
https://github.com/koalaman/shellcheck.git
synced 2025-09-29 16:29:18 +08:00
Compare commits
80 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
b10d31c8b7 | ||
|
133c779701 | ||
|
d830a36bc8 | ||
|
1af23fd131 | ||
|
d21b3362b2 | ||
|
6cd454e88b | ||
|
0b5f6b9762 | ||
|
3824e9cfc2 | ||
|
fdce0116da | ||
|
b069f7ed27 | ||
|
c4181d45d2 | ||
|
680f838c63 | ||
|
e6d81ca7b7 | ||
|
fd909eeca0 | ||
|
deab146fab | ||
|
f9aeabc245 | ||
|
558d8ffc6c | ||
|
e96c4c3ffa | ||
|
c566efd442 | ||
|
47c220d59c | ||
|
4bd902c5c4 | ||
|
033ce6d941 | ||
|
6ad3f557fe | ||
|
d0bad6c057 | ||
|
58c362f97c | ||
|
5f568dd207 | ||
|
2c1e414ac5 | ||
|
6699109ab8 | ||
|
423ca82296 | ||
|
0a263579e0 | ||
|
d63406abe4 | ||
|
81956d324d | ||
|
f549aad809 | ||
|
f9f965693d | ||
|
727d940e10 | ||
|
c26c2b8536 | ||
|
d8878ed852 | ||
|
c3cc5f649f | ||
|
8bd4365cdb | ||
|
a00a6fb53b | ||
|
3332eba9a0 | ||
|
ad08bb64aa | ||
|
f01e6e1a99 | ||
|
de0145fb29 | ||
|
0d4ae95e1d | ||
|
50db49e2fb | ||
|
60aafae21d | ||
|
902cb9c303 | ||
|
4f1fd43360 | ||
|
ca5af5c55a | ||
|
503cac3bb3 | ||
|
2a9c9ae0ad | ||
|
def4551991 | ||
|
67f4a0d6eb | ||
|
f92f934688 | ||
|
d4059c30b7 | ||
|
b68de7f42b | ||
|
7dacb62d36 | ||
|
3423cde931 | ||
|
b2d1aa01f7 | ||
|
19e1bdf11f | ||
|
75d51087c8 | ||
|
ed524fb77f | ||
|
97045c4af1 | ||
|
1b806f6c9f | ||
|
632c1614a1 | ||
|
00d9ef12e7 | ||
|
d07294810b | ||
|
948b750754 | ||
|
41ae95116d | ||
|
bf3c942294 | ||
|
055b40462d | ||
|
b087b7efb1 | ||
|
5d8d57cf07 | ||
|
661091a9da | ||
|
2ec60c2627 | ||
|
8b4909b238 | ||
|
95a3be6546 | ||
|
968e34e002 | ||
|
197b3e3f20 |
6
Makefile
6
Makefile
@@ -1,6 +1,7 @@
|
||||
# TODO: Phase out Makefile in favor of Cabal
|
||||
|
||||
GHCFLAGS=-O9
|
||||
GHCFLAGS_STATIC=$(GHCFLAGS) -optl-static -optl-pthread
|
||||
|
||||
all: shellcheck .tests shellcheck.1
|
||||
: Done
|
||||
@@ -14,6 +15,7 @@ shellcheck: regardless
|
||||
./test/runQuack && touch .tests
|
||||
|
||||
shellcheck.1: shellcheck.1.md
|
||||
: Formatting man page
|
||||
pandoc -s -t man $< -o $@
|
||||
|
||||
clean:
|
||||
@@ -21,4 +23,8 @@ clean:
|
||||
rm -f *.hi *.o ShellCheck/*.hi ShellCheck/*.o
|
||||
rm -rf dist
|
||||
|
||||
shellcheck-static: regardless
|
||||
: Conditionally compiling a statically linked shellcheck-static
|
||||
ghc $(GHCFLAGS_STATIC) --make shellcheck -o shellcheck-static
|
||||
|
||||
regardless:
|
||||
|
44
README
44
README
@@ -1,44 +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 Fedora, these can be installed with:
|
||||
yum install cabal-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 cabal-install
|
||||
|
||||
For older releases, you may have to use:
|
||||
apt-get install ghc6 libghc6-parsec3-dev libghc6-quickcheck2-dev libghc6-json-dev libghc-regex-compat-dev cabal-install
|
||||
|
||||
On Mac OS X with homebrew (http://brew.sh/), use:
|
||||
brew install cabal-install
|
||||
|
||||
On Mac OS X with MacPorts (http://www.macports.org/), use:
|
||||
port install hs-cabal-install
|
||||
|
||||
Executables can be built with cabal. Tests currently still rely on a Makefile.
|
||||
|
||||
Install:
|
||||
cabal install
|
||||
|
||||
which shellcheck
|
||||
~/.cabal/bin/shellcheck
|
||||
|
||||
Happy ShellChecking!
|
83
README.md
Normal file
83
README.md
Normal file
@@ -0,0 +1,83 @@
|
||||
# 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
|
||||
|
||||
Let cabal update itself, in case your distro version is outdated:
|
||||
|
||||
$ cabal update
|
||||
$ cabal install cabal-install
|
||||
|
||||
With cabal installed, cd to the ShellCheck source directory and:
|
||||
|
||||
$ cabal install
|
||||
|
||||
This will install ShellCheck to your ~/.cabal/bin directory.
|
||||
|
||||
Add the directory to your PATH (for bash, add this to your ~/.bashrc file):
|
||||
|
||||
export PATH=$HOME/.cabal/bin:$PATH
|
||||
|
||||
Verify that your PATH is set up correctly:
|
||||
|
||||
$ 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 pandoc
|
||||
|
||||
On Ubuntu and similar, use:
|
||||
|
||||
apt-get install ghc libghc-parsec3-dev libghc-json-dev \
|
||||
libghc-regex-compat-dev libghc-quickcheck2-dev pandoc
|
||||
|
||||
To build and run the tests, cd to the shellcheck source directory and:
|
||||
|
||||
$ make
|
||||
|
||||
If you want to distribute the binary and/or run it on other distros, you
|
||||
can `make shellcheck-static` to build a statically linked executable without
|
||||
library dependencies.
|
||||
|
||||
Happy ShellChecking!
|
@@ -1,6 +1,6 @@
|
||||
Name: ShellCheck
|
||||
-- Must also be updated in ShellCheck/Data.hs :
|
||||
Version: 0.3.1
|
||||
Version: 0.3.3
|
||||
Synopsis: Shell script analysis tool
|
||||
License: OtherLicense
|
||||
License-file: LICENSE
|
||||
@@ -9,7 +9,7 @@ Author: Vidar Holen
|
||||
Maintainer: vidar@vidarholen.net
|
||||
Homepage: http://www.shellcheck.net/
|
||||
Build-Type: Simple
|
||||
Cabal-Version: >= 1.6
|
||||
Cabal-Version: >= 1.8
|
||||
Bug-reports: https://github.com/koalaman/shellcheck/issues
|
||||
Description:
|
||||
The goals of ShellCheck are:
|
||||
@@ -28,8 +28,29 @@ source-repository head
|
||||
location: git://github.com/koalaman/shellcheck.git
|
||||
|
||||
library
|
||||
build-depends: base >= 4, base < 5, parsec, containers, regex-compat, mtl, directory, json
|
||||
exposed-modules: ShellCheck.AST, ShellCheck.Data, 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
|
||||
|
@@ -28,6 +28,7 @@ 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 ForInType = NormalForIn | ShortForIn deriving (Show, Eq)
|
||||
|
||||
data Token =
|
||||
TA_Base Id String Token
|
||||
@@ -81,7 +82,7 @@ data Token =
|
||||
| T_Fi Id
|
||||
| T_For Id
|
||||
| T_ForArithmetic Id Token Token Token [Token]
|
||||
| T_ForIn Id String [Token] [Token]
|
||||
| T_ForIn Id ForInType [String] [Token] [Token]
|
||||
| T_Function Id FunctionKeyword FunctionParentheses String Token
|
||||
| T_GREATAND Id
|
||||
| T_Glob Id String
|
||||
@@ -102,7 +103,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
|
||||
@@ -120,6 +121,7 @@ data 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)
|
||||
@@ -128,12 +130,12 @@ 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
|
||||
@@ -182,7 +184,7 @@ analyze f g i t =
|
||||
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
|
||||
@@ -201,7 +203,7 @@ analyze f g i t =
|
||||
delve (T_BraceGroup id l) = dl l $ T_BraceGroup id
|
||||
delve (T_WhileExpression id c l) = dll c l $ T_WhileExpression id
|
||||
delve (T_UntilExpression id c l) = dll c l $ T_UntilExpression id
|
||||
delve (T_ForIn id v w l) = dll w l $ T_ForIn id v
|
||||
delve (T_ForIn id t v w l) = dll w l $ T_ForIn id t v
|
||||
delve (T_SelectIn id v w l) = dll w l $ T_SelectIn id v
|
||||
delve (T_CaseExpression id word cases) = do
|
||||
newWord <- round word
|
||||
@@ -297,7 +299,7 @@ getId t = case t of
|
||||
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
|
||||
@@ -307,7 +309,7 @@ getId t = case t of
|
||||
T_BraceGroup id _ -> id
|
||||
T_WhileExpression id _ _ -> id
|
||||
T_UntilExpression id _ _ -> id
|
||||
T_ForIn id _ _ _ -> id
|
||||
T_ForIn id _ _ _ _ -> id
|
||||
T_SelectIn id _ _ _ -> id
|
||||
T_CaseExpression id _ _ -> id
|
||||
T_Function id _ _ _ _ -> id
|
||||
@@ -337,17 +339,18 @@ getId t = case t of
|
||||
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
|
||||
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
@@ -1,6 +1,6 @@
|
||||
module ShellCheck.Data where
|
||||
|
||||
shellcheckVersion = "0.3.1" -- Must also be updated in ShellCheck.cabal
|
||||
shellcheckVersion = "0.3.3" -- Must also be updated in ShellCheck.cabal
|
||||
|
||||
internalVariables = [
|
||||
-- Generic
|
||||
@@ -74,3 +74,11 @@ commonCommands = [
|
||||
"val", "vi", "wait", "wc", "what", "who", "write", "xargs", "yacc",
|
||||
"zcat"
|
||||
]
|
||||
|
||||
sampleWords = [
|
||||
"alpha", "bravo", "charlie", "delta", "echo", "foxtrot",
|
||||
"golf", "hotel", "india", "juliett", "kilo", "lima", "mike",
|
||||
"november", "oscar", "papa", "quebec", "romeo", "sierra",
|
||||
"tango", "uniform", "victor", "whiskey", "xray", "yankee",
|
||||
"zulu"
|
||||
]
|
||||
|
@@ -46,20 +46,29 @@ tokenDelimiter = oneOf "&|;<> \t\n\r" <|> nbsp
|
||||
quotableChars = "|&;<>()\\ '\t\n\r\xA0" ++ doubleQuotableChars
|
||||
quotable = nbsp <|> unicodeDoubleQuote <|> oneOf quotableChars
|
||||
bracedQuotable = oneOf "}\"$`'"
|
||||
doubleQuotableChars = "\"$`\x201C\x201D"
|
||||
doubleQuotableChars = "\"$`" ++ unicodeDoubleQuoteChars
|
||||
doubleQuotable = unicodeDoubleQuote <|> oneOf doubleQuotableChars
|
||||
whitespace = oneOf " \t\n" <|> carriageReturn <|> nbsp
|
||||
linewhitespace = oneOf " \t" <|> nbsp
|
||||
|
||||
suspectCharAfterQuotes = variableChars <|> char '%'
|
||||
|
||||
extglobStartChars = "?*@!+"
|
||||
extglobStart = oneOf extglobStartChars
|
||||
|
||||
unicodeDoubleQuoteChars = "\x201C\x201D\x2033\x2036"
|
||||
|
||||
prop_spacing = isOk spacing " \\\n # Comment"
|
||||
spacing = do
|
||||
x <- many (many1 linewhitespace <|> (try $ string "\\\n"))
|
||||
optional readComment
|
||||
return $ concat x
|
||||
|
||||
spacing1 = do
|
||||
spacing <- spacing
|
||||
when (null spacing) $ fail "no spacing"
|
||||
return spacing
|
||||
|
||||
prop_allspacing = isOk allspacing "#foo"
|
||||
prop_allspacing2 = isOk allspacing " #foo\n # bar\n#baz\n"
|
||||
prop_allspacing3 = isOk allspacing "#foo\n#bar\n#baz\n"
|
||||
@@ -78,7 +87,7 @@ allspacingOrFail = do
|
||||
|
||||
unicodeDoubleQuote = do
|
||||
pos <- getPosition
|
||||
char '\x201C' <|> char '\x201D'
|
||||
oneOf unicodeDoubleQuoteChars
|
||||
parseProblemAt pos WarningC 1015 "This is a unicode double quote. Delete and retype it."
|
||||
return '"'
|
||||
|
||||
@@ -313,9 +322,11 @@ readConditionContents single = do
|
||||
when (endedWith "]" x) $ do
|
||||
parseProblemAt pos ErrorC 1020 $
|
||||
"You need a space before the " ++ (if single then "]" else "]]") ++ "."
|
||||
fail "Missing space before ]"
|
||||
when (single && endedWith ")" x) $ do
|
||||
parseProblemAt pos ErrorC 1021 $
|
||||
"You need a space before the \\)"
|
||||
fail "Missing space before )"
|
||||
disregard spacing
|
||||
return x
|
||||
where endedWith str (T_NormalWord id s@(_:_)) =
|
||||
@@ -327,6 +338,7 @@ readConditionContents single = do
|
||||
id <- getNextId
|
||||
x <- try (string "&&" <|> string "-a")
|
||||
softCondSpacing
|
||||
skipLineFeeds
|
||||
return $ TC_And id typ x
|
||||
|
||||
readCondOrOp = do
|
||||
@@ -334,6 +346,7 @@ readConditionContents single = do
|
||||
id <- getNextId
|
||||
x <- try (string "||" <|> string "-o")
|
||||
softCondSpacing
|
||||
skipLineFeeds
|
||||
return $ TC_Or id typ x
|
||||
|
||||
readCondNoaryOrBinary = do
|
||||
@@ -395,7 +408,7 @@ readConditionContents single = do
|
||||
where
|
||||
readGlobLiteral = do
|
||||
id <- getNextId
|
||||
s <- many1 (extglobStart <|> oneOf "[]$")
|
||||
s <- many1 (extglobStart <|> oneOf "{}[]$")
|
||||
return $ T_Literal id s
|
||||
readGroup = called "regex grouping" $ do
|
||||
id <- getNextId
|
||||
@@ -412,7 +425,17 @@ readConditionContents single = do
|
||||
str <- string "|"
|
||||
return $ T_Literal id str
|
||||
|
||||
readCondTerm = readCondNot <|> readCondExpr
|
||||
skipLineFeeds = do
|
||||
pos <- getPosition
|
||||
spacing <- allspacing
|
||||
when (single && '\n' `elem` spacing) $
|
||||
parseProblemAt pos ErrorC 1080 "In [ ] you need \\ before line feeds."
|
||||
|
||||
readCondTerm = do
|
||||
term <- readCondNot <|> readCondExpr
|
||||
skipLineFeeds
|
||||
return term
|
||||
|
||||
readCondNot = do
|
||||
id <- getNextId
|
||||
char '!'
|
||||
@@ -611,6 +634,10 @@ prop_readCondition6 = isOk readCondition "[[ $c =~ ^[yY]$ ]]"
|
||||
prop_readCondition7 = isOk readCondition "[[ ${line} =~ ^[[:space:]]*# ]]"
|
||||
prop_readCondition8 = isOk readCondition "[[ $l =~ ogg|flac ]]"
|
||||
prop_readCondition9 = isOk readCondition "[ foo -a -f bar ]"
|
||||
prop_readCondition10= isOk readCondition "[[ a == b \n || c == d ]]"
|
||||
prop_readCondition11= isOk readCondition "[[ a == b || \n c == d ]]"
|
||||
prop_readCondition12= isWarning readCondition "[ a == b \n -o c == d ]"
|
||||
prop_readCondition13= isOk readCondition "[[ foo =~ ^fo{1,3}$ ]]"
|
||||
readCondition = called "test expression" $ do
|
||||
opos <- getPosition
|
||||
id <- getNextId
|
||||
@@ -679,6 +706,8 @@ prop_readNormalWord = isOk readNormalWord "'foo'\"bar\"{1..3}baz$(lol)"
|
||||
prop_readNormalWord2 = isOk readNormalWord "foo**(foo)!!!(@@(bar))"
|
||||
prop_readNormalWord3 = isOk readNormalWord "foo#"
|
||||
prop_readNormalWord4 = isOk readNormalWord "$\"foo\"$'foo\nbar'"
|
||||
prop_readNormalWord5 = isWarning readNormalWord "${foo}}"
|
||||
prop_readNormalWord6 = isOk readNormalWord "foo/{}"
|
||||
readNormalWord = readNormalishWord ""
|
||||
|
||||
readNormalishWord end = do
|
||||
@@ -689,14 +718,24 @@ readNormalishWord end = do
|
||||
return $ T_NormalWord id x
|
||||
|
||||
checkPossibleTermination pos [T_Literal _ x] =
|
||||
if x `elem` ["do", "done", "then", "fi", "esac", "}"]
|
||||
if x `elem` ["do", "done", "then", "fi", "esac"]
|
||||
then parseProblemAt pos WarningC 1010 $ "Use semicolon or linefeed before '" ++ x ++ "' (or quote to make it literal)."
|
||||
else return ()
|
||||
checkPossibleTermination _ _ = return ()
|
||||
|
||||
readNormalWordPart end = do
|
||||
checkForParenthesis
|
||||
readSingleQuoted <|> readDoubleQuoted <|> readGlob <|> readNormalDollar <|> readBraced <|> readBackTicked <|> readProcSub <|> (readNormalLiteral end)
|
||||
choice [
|
||||
readSingleQuoted,
|
||||
readDoubleQuoted,
|
||||
readGlob,
|
||||
readNormalDollar,
|
||||
readBraced,
|
||||
readBackTicked,
|
||||
readProcSub,
|
||||
readNormalLiteral end,
|
||||
readLiteralCurlyBraces
|
||||
]
|
||||
where
|
||||
checkForParenthesis = do
|
||||
return () `attempting` do
|
||||
@@ -704,6 +743,19 @@ readNormalWordPart end = do
|
||||
lookAhead $ char '('
|
||||
parseProblemAt pos ErrorC 1036 "'(' is invalid here. Did you forget to escape it?"
|
||||
|
||||
readLiteralCurlyBraces = do
|
||||
id <- getNextId
|
||||
str <- findParam <|> literalBraces
|
||||
return $ T_Literal id str
|
||||
|
||||
findParam = try $ string "{}"
|
||||
literalBraces = do
|
||||
pos <- getPosition
|
||||
c <- oneOf "{}"
|
||||
parseProblemAt pos WarningC 1083 $
|
||||
"This " ++ [c] ++ " is literal. Check expression (missing ;/\\n?) or quote it."
|
||||
return [c]
|
||||
|
||||
|
||||
readSpacePart = do
|
||||
id <- getNextId
|
||||
@@ -724,10 +776,11 @@ readDollarBracedLiteral = do
|
||||
|
||||
prop_readProcSub1 = isOk readProcSub "<(echo test | wc -l)"
|
||||
prop_readProcSub2 = isOk readProcSub "<( if true; then true; fi )"
|
||||
prop_readProcSub3 = isOk readProcSub "=(ls)"
|
||||
readProcSub = called "process substitution" $ do
|
||||
id <- getNextId
|
||||
dir <- try $ do
|
||||
x <- oneOf "<>"
|
||||
x <- oneOf "<>="
|
||||
char '('
|
||||
return [x]
|
||||
allspacing
|
||||
@@ -739,17 +792,29 @@ readProcSub = called "process substitution" $ do
|
||||
prop_readSingleQuoted = isOk readSingleQuoted "'foo bar'"
|
||||
prop_readSingleQuoted2 = isWarning readSingleQuoted "'foo bar\\'"
|
||||
prop_readsingleQuoted3 = isWarning readSingleQuoted "\x2018hello\x2019"
|
||||
prop_readSingleQuoted4 = isWarning readNormalWord "'it's"
|
||||
prop_readSingleQuoted5 = isWarning readSimpleCommand "foo='bar\ncow 'arg"
|
||||
prop_readSingleQuoted6 = isOk readSimpleCommand "foo='bar cow 'arg"
|
||||
readSingleQuoted = called "single quoted string" $ do
|
||||
id <- getNextId
|
||||
startPos <- getPosition
|
||||
singleQuote
|
||||
s <- readSingleQuotedPart `reluctantlyTill` singleQuote
|
||||
pos <- getPosition
|
||||
let string = concat s
|
||||
endPos <- getPosition
|
||||
singleQuote <?> "end of single quoted string"
|
||||
|
||||
let string = concat s
|
||||
return (T_SingleQuoted id string) `attempting` do
|
||||
x <- lookAhead anyChar
|
||||
when (isAlpha x && not (null string) && isAlpha (last string)) $ parseProblemAt pos WarningC 1011 "This apostrophe terminated the single quoted string!"
|
||||
optional $ do
|
||||
c <- try . lookAhead $ suspectCharAfterQuotes <|> oneOf "'"
|
||||
if (not (null string) && isAlpha c && isAlpha (last string))
|
||||
then
|
||||
parseProblemAt endPos WarningC 1011 $
|
||||
"This apostrophe terminated the single quoted string!"
|
||||
else
|
||||
when ('\n' `elem` string && not ("\n" `isPrefixOf` string)) $
|
||||
suggestForgotClosingQuote startPos endPos "single quoted string"
|
||||
|
||||
return (T_SingleQuoted id string)
|
||||
|
||||
readSingleQuotedLiteral = do
|
||||
singleQuote
|
||||
@@ -763,13 +828,24 @@ readSingleQuotedPart =
|
||||
|
||||
prop_readBackTicked = isOk readBackTicked "`ls *.mp3`"
|
||||
prop_readBackTicked2 = isOk readBackTicked "`grep \"\\\"\"`"
|
||||
prop_readBackTicked3 = isWarning readBackTicked "´grep \"\\\"\"´"
|
||||
prop_readBackTicked4 = isOk readBackTicked "`echo foo\necho bar`"
|
||||
prop_readBackTicked5 = isOk readSimpleCommand "echo `foo`bar"
|
||||
prop_readBackTicked6 = isWarning readSimpleCommand "echo `foo\necho `bar"
|
||||
readBackTicked = called "backtick expansion" $ do
|
||||
id <- getNextId
|
||||
pos <- getPosition
|
||||
char '`'
|
||||
startPos <- getPosition
|
||||
backtick
|
||||
subStart <- getPosition
|
||||
subString <- readGenericLiteral "`"
|
||||
char '`'
|
||||
subString <- readGenericLiteral "`´"
|
||||
endPos <- getPosition
|
||||
backtick
|
||||
|
||||
optional $ do
|
||||
c <- try . lookAhead $ suspectCharAfterQuotes
|
||||
when ('\n' `elem` subString && not ("\n" `isPrefixOf` subString)) $ do
|
||||
suggestForgotClosingQuote startPos endPos "backtick expansion"
|
||||
|
||||
-- Result positions may be off due to escapes
|
||||
result <- subParse subStart readCompoundList (unEscape subString)
|
||||
return $ T_Backticked id result
|
||||
@@ -778,6 +854,12 @@ readBackTicked = called "backtick expansion" $ do
|
||||
unEscape ('\\':x:rest) | x `elem` "$`\\" = x : unEscape rest
|
||||
unEscape ('\\':'\n':rest) = unEscape rest
|
||||
unEscape (c:rest) = c : unEscape rest
|
||||
backtick =
|
||||
disregard (char '`') <|> do
|
||||
pos <- getPosition
|
||||
char '´'
|
||||
parseProblemAt pos ErrorC 1077 $
|
||||
"For command expansion, the tick should slant left (` vs ´)."
|
||||
|
||||
subParse pos parser input = do
|
||||
lastPosition <- getPosition
|
||||
@@ -792,12 +874,31 @@ subParse pos parser input = do
|
||||
prop_readDoubleQuoted = isOk readDoubleQuoted "\"Hello $FOO\""
|
||||
prop_readDoubleQuoted2 = isOk readDoubleQuoted "\"$'\""
|
||||
prop_readDoubleQuoted3 = isWarning readDoubleQuoted "\x201Chello\x201D"
|
||||
prop_readDoubleQuoted4 = isWarning readSimpleCommand "\"foo\nbar\"foo"
|
||||
prop_readDoubleQuoted5 = isOk readSimpleCommand "lol \"foo\nbar\" etc"
|
||||
readDoubleQuoted = called "double quoted string" $ do
|
||||
id <- getNextId
|
||||
startPos <- getPosition
|
||||
doubleQuote
|
||||
x <- many doubleQuotedPart
|
||||
endPos <- getPosition
|
||||
doubleQuote <?> "end of double quoted string"
|
||||
optional $ do
|
||||
try . lookAhead $ suspectCharAfterQuotes <|> oneOf "$\""
|
||||
when (any hasLineFeed x && not (startsWithLineFeed x)) $
|
||||
suggestForgotClosingQuote startPos endPos "double quoted string"
|
||||
return $ T_DoubleQuoted id x
|
||||
where
|
||||
startsWithLineFeed ((T_Literal _ ('\n':_)):_) = True
|
||||
startsWithLineFeed _ = False
|
||||
hasLineFeed (T_Literal _ str) | '\n' `elem` str = True
|
||||
hasLineFeed _ = False
|
||||
|
||||
suggestForgotClosingQuote startPos endPos name = do
|
||||
parseProblemAt startPos WarningC 1078 $
|
||||
"Did you forget to close this " ++ name ++ "?"
|
||||
parseProblemAt endPos InfoC 1079 $
|
||||
"This is actually an end quote, but due to next char it looks suspect."
|
||||
|
||||
doubleQuotedPart = readDoubleLiteral <|> readDoubleQuotedDollar <|> readBackTicked
|
||||
|
||||
@@ -853,7 +954,7 @@ readGlob = readExtglob <|> readSimple <|> readClass <|> readGlobbyLiteral
|
||||
return $ T_Literal id [c]
|
||||
|
||||
readNormalLiteralPart end = do
|
||||
readNormalEscaped <|> (many1 $ noneOf (end ++ quotableChars ++ extglobStartChars ++ "["))
|
||||
readNormalEscaped <|> (many1 $ noneOf (end ++ quotableChars ++ extglobStartChars ++ "[{}"))
|
||||
|
||||
readNormalEscaped = called "escaped char" $ do
|
||||
pos <- getPosition
|
||||
@@ -865,10 +966,12 @@ readNormalEscaped = called "escaped char" $ do
|
||||
do
|
||||
next <- anyChar
|
||||
case escapedChar next of
|
||||
Just name -> parseNoteAt pos WarningC 1012 $ "\\" ++ [next] ++ " is just literal '" ++ [next] ++ "' here. For " ++ name ++ ", use \"$(printf \"\\" ++ [next] ++ "\")\"."
|
||||
Just name -> parseNoteAt pos WarningC 1012 $ "\\" ++ [next] ++ " is just literal '" ++ [next] ++ "' here. For " ++ name ++ ", use " ++ (alternative next) ++ " instead."
|
||||
Nothing -> parseNoteAt pos InfoC 1001 $ "This \\" ++ [next] ++ " will be a regular '" ++ [next] ++ "' in this context."
|
||||
return [next]
|
||||
where
|
||||
alternative 'n' = "a quoted, literal line feed"
|
||||
alternative t = "\"$(printf \"\\" ++ [t] ++ "\")\""
|
||||
escapedChar 'n' = Just "line feed"
|
||||
escapedChar 't' = Just "tab"
|
||||
escapedChar 'r' = Just "carriage return"
|
||||
@@ -958,7 +1061,10 @@ readBraced = try $ do
|
||||
char '{'
|
||||
str <- many1 ((readDoubleQuotedLiteral >>= (strip)) <|> readGenericLiteral1 (oneOf "}\"" <|> whitespace))
|
||||
char '}'
|
||||
return $ T_BraceExpansion id $ concat str
|
||||
let result = concat str
|
||||
unless (',' `elem` result || ".." `isInfixOf` result) $
|
||||
fail "Not a brace expression"
|
||||
return $ T_BraceExpansion id $ result
|
||||
|
||||
readNormalDollar = readDollarExpression <|> readDollarDoubleQuote <|> readDollarSingleQuote <|> readDollarLonely
|
||||
readDoubleQuotedDollar = readDollarExpression <|> readDollarLonely
|
||||
@@ -1008,7 +1114,7 @@ readArithmeticExpression = called "((..)) command" $ do
|
||||
|
||||
prop_readDollarBraced1 = isOk readDollarBraced "${foo//bar/baz}"
|
||||
prop_readDollarBraced2 = isOk readDollarBraced "${foo/'{cow}'}"
|
||||
prop_readDollarBraced3 = isOk readDollarBraced "${foo%%$(echo cow})}"
|
||||
prop_readDollarBraced3 = isOk readDollarBraced "${foo%%$(echo cow\\})}"
|
||||
prop_readDollarBraced4 = isOk readDollarBraced "${foo#\\}}"
|
||||
readDollarBraced = called "parameter expansion" $ do
|
||||
id <- getNextId
|
||||
@@ -1062,7 +1168,6 @@ readDollarLonely = do
|
||||
pos <- getPosition
|
||||
char '$'
|
||||
n <- lookAhead (anyChar <|> (eof >> return '_'))
|
||||
when (n /= '\'') $ parseNoteAt pos StyleC 1000 "$ is not used specially and should therefore be escaped."
|
||||
return $ T_Literal id "$"
|
||||
|
||||
prop_readHereDoc = isOk readHereDoc "<< foo\nlol\ncow\nfoo"
|
||||
@@ -1197,8 +1302,8 @@ readHereString = called "here string" $ do
|
||||
readNewlineList = many1 ((newline <|> carriageReturn) `thenSkip` spacing)
|
||||
readLineBreak = optional readNewlineList
|
||||
|
||||
prop_roflol = isWarning readScript "a &; b"
|
||||
prop_roflol2 = isOk readScript "a & b"
|
||||
prop_readSeparator1 = isWarning readScript "a &; b"
|
||||
prop_readSeparator2 = isOk readScript "a & b"
|
||||
readSeparatorOp = do
|
||||
notFollowedBy2 (g_AND_IF <|> g_DSEMI)
|
||||
notFollowedBy2 (string "&>")
|
||||
@@ -1207,6 +1312,8 @@ readSeparatorOp = do
|
||||
spacing
|
||||
pos <- getPosition
|
||||
char ';'
|
||||
-- In case statements we might have foo & ;;
|
||||
notFollowedBy2 $ char ';'
|
||||
parseProblemAt pos ErrorC 1045 "It's not 'foo &; bar', just 'foo & bar'."
|
||||
return '&'
|
||||
) <|> char ';' <|> char '&'
|
||||
@@ -1248,6 +1355,7 @@ prop_readSimpleCommand3 = isOk readSimpleCommand "export foo=(bar baz)"
|
||||
prop_readSimpleCommand4 = isOk readSimpleCommand "typeset -a foo=(lol)"
|
||||
prop_readSimpleCommand5 = isOk readSimpleCommand "time if true; then echo foo; fi"
|
||||
prop_readSimpleCommand6 = isOk readSimpleCommand "time -p ( ls -l; )"
|
||||
prop_readSimpleCommand7 = isOk readSimpleCommand "cat =(ls)"
|
||||
readSimpleCommand = called "simple command" $ do
|
||||
id1 <- getNextId
|
||||
id2 <- getNextId
|
||||
@@ -1325,13 +1433,25 @@ transformWithSeparator i _ = id
|
||||
|
||||
readPipeSequence = do
|
||||
id <- getNextId
|
||||
list <- readCommand `sepBy1` (readPipe `thenSkip` (spacing >> readLineBreak))
|
||||
(cmds, pipes) <- sepBy1WithSeparators readCommand
|
||||
(readPipe `thenSkip` (spacing >> readLineBreak))
|
||||
spacing
|
||||
return $ T_Pipeline id list
|
||||
return $ T_Pipeline id pipes cmds
|
||||
where
|
||||
sepBy1WithSeparators p s = do
|
||||
let elems = p >>= \x -> return ([x], [])
|
||||
let seps = do
|
||||
separator <- s
|
||||
return $ \(a,b) (c,d) -> (a++c, b ++ d ++ [separator])
|
||||
elems `chainl1` seps
|
||||
|
||||
readPipe = do
|
||||
notFollowedBy2 g_OR_IF
|
||||
char '|' `thenSkip` spacing
|
||||
id <- getNextId
|
||||
char '|'
|
||||
qualifier <- string "&" <|> return ""
|
||||
spacing
|
||||
return $ T_Pipe id ('|':qualifier)
|
||||
|
||||
readCommand = (readCompoundCommand <|> readSimpleCommand)
|
||||
|
||||
@@ -1487,6 +1607,7 @@ prop_readForClause7 = isOk readForClause "for ((;;)) do echo $i\ndone"
|
||||
prop_readForClause8 = isOk readForClause "for ((;;)) ; do echo $i\ndone"
|
||||
prop_readForClause9 = isOk readForClause "for i do true; done"
|
||||
prop_readForClause10= isOk readForClause "for ((;;)) { true; }"
|
||||
prop_readForClause11= isOk readForClause "for a b in *; do echo $a $b; done"
|
||||
readForClause = called "for loop" $ do
|
||||
pos <- getPosition
|
||||
(T_For id) <- g_For
|
||||
@@ -1512,11 +1633,25 @@ readForClause = called "for loop" $ do
|
||||
return list
|
||||
|
||||
readRegular id pos = do
|
||||
name <- readVariableName
|
||||
spacing
|
||||
values <- readInClause <|> (optional readSequentialSep >> return [])
|
||||
group <- readDoGroup pos
|
||||
return $ T_ForIn id name values group
|
||||
names <- readNames
|
||||
readShort names <|> readLong names
|
||||
where
|
||||
readLong names = do
|
||||
values <- readInClause <|> (optional readSequentialSep >> return [])
|
||||
group <- readDoGroup pos
|
||||
return $ T_ForIn id NormalForIn names values group
|
||||
readShort names = do
|
||||
char '('
|
||||
allspacing
|
||||
words <- many (readNormalWord `thenSkip` allspacing)
|
||||
char ')'
|
||||
allspacing
|
||||
command <- readAndOr
|
||||
return $ T_ForIn id ShortForIn names words [command]
|
||||
|
||||
readNames =
|
||||
reluctantlyTill1 (readVariableName `thenSkip` spacing) $
|
||||
disregard g_Do <|> disregard readInClause <|> disregard readSequentialSep
|
||||
|
||||
prop_readSelectClause1 = isOk readSelectClause "select foo in *; do echo $foo; done"
|
||||
prop_readSelectClause2 = isOk readSelectClause "select foo; do echo $foo; done"
|
||||
@@ -1551,6 +1686,7 @@ readInClause = do
|
||||
|
||||
prop_readCaseClause = isOk readCaseClause "case foo in a ) lol; cow;; b|d) fooo; esac"
|
||||
prop_readCaseClause2 = isOk readCaseClause "case foo\n in * ) echo bar;; esac"
|
||||
prop_readCaseClause3 = isOk readCaseClause "case foo\n in * ) echo bar & ;; esac"
|
||||
readCaseClause = called "case expression" $ do
|
||||
id <- getNextId
|
||||
g_Case
|
||||
@@ -1624,7 +1760,7 @@ readFunctionDefinition = called "function" $ do
|
||||
g_Rparen
|
||||
return ()
|
||||
|
||||
readFunctionName = many1 functionChars
|
||||
readFunctionName = many functionChars
|
||||
|
||||
readPattern = (readNormalWord `thenSkip` spacing) `sepBy1` (char '|' `thenSkip` spacing)
|
||||
|
||||
@@ -1672,6 +1808,9 @@ readAssignmentWord = try $ do
|
||||
pos <- getPosition
|
||||
optional (char '$' >> parseNote ErrorC 1066 "Don't use $ on the left side of assignments.")
|
||||
variable <- readVariableName
|
||||
notFollowedBy2 $ do -- Special case for zsh =(..) syntax
|
||||
spacing1
|
||||
string "=("
|
||||
optional (readNormalDollar >> parseNoteAt pos ErrorC
|
||||
1067 "For indirection, use (associative) arrays or 'read \"var$n\" <<< \"value\"'")
|
||||
index <- optionMaybe readArrayIndex
|
||||
@@ -1682,7 +1821,8 @@ readAssignmentWord = try $ do
|
||||
if space == "" && space2 /= ""
|
||||
then do
|
||||
when (variable /= "IFS") $
|
||||
parseNoteAt pos InfoC 1007 $ "Note that 'var= value' (with space after equals sign) is similar to 'var=\"\"; value'."
|
||||
parseNoteAt pos WarningC 1007
|
||||
"Remove space after = if trying to assign a value (for empty string, use var='' ... )."
|
||||
value <- readEmptyLiteral
|
||||
return $ T_Assignment id op variable index value
|
||||
else do
|
||||
@@ -1729,16 +1869,24 @@ redirToken c t = try $ do
|
||||
notFollowedBy2 $ char '('
|
||||
return $ t id
|
||||
|
||||
tryWordToken s t = tryParseWordToken (string s) t `thenSkip` spacing
|
||||
tryParseWordToken parser t = try $ do
|
||||
tryWordToken s t = tryParseWordToken s t `thenSkip` spacing
|
||||
tryParseWordToken keyword t = try $ do
|
||||
id <- getNextId
|
||||
parser
|
||||
str <- anycaseString keyword
|
||||
optional (do
|
||||
try . lookAhead $ char '['
|
||||
parseProblem ErrorC 1069 "You need a space before the [.")
|
||||
try $ lookAhead (keywordSeparator)
|
||||
when (str /= keyword) $
|
||||
parseProblem ErrorC 1081 $
|
||||
"Scripts are case sensitive. Use '" ++ keyword ++ "', not '" ++ str ++ "'."
|
||||
return $ t id
|
||||
|
||||
anycaseString str =
|
||||
mapM anycaseChar str
|
||||
where
|
||||
anycaseChar c = char (toLower c) <|> char (toUpper c)
|
||||
|
||||
g_AND_IF = tryToken "&&" T_AND_IF
|
||||
g_OR_IF = tryToken "||" T_OR_IF
|
||||
g_DSEMI = tryToken ";;" T_DSEMI
|
||||
@@ -1803,6 +1951,10 @@ prop_readScript4 = isWarning readScript "#!/usr/bin/perl\nfoo=("
|
||||
readScript = do
|
||||
id <- getNextId
|
||||
pos <- getPosition
|
||||
optional $ do
|
||||
readUtf8Bom
|
||||
parseProblem ErrorC 1082 $
|
||||
"This file has a UTF-8 BOM. Remove it with: LC_CTYPE=C sed '1s/^...//' < yourscript ."
|
||||
sb <- option "" readShebang
|
||||
verifyShell pos (getShell sb)
|
||||
if (isValidShell $ getShell sb) /= Just False
|
||||
@@ -1862,6 +2014,8 @@ readScript = do
|
||||
"tcsh"
|
||||
]
|
||||
|
||||
readUtf8Bom = called "Byte Order Mark" $ string "\xFEFF"
|
||||
|
||||
rp p filename contents = Ms.runState (runParserT p initialState filename contents) ([], [])
|
||||
|
||||
isWarning p s = (fst cs) && (not . null . snd $ cs) where cs = checkString p s
|
||||
|
@@ -69,7 +69,7 @@ parseArguments argv =
|
||||
return $ Just (opts, files)
|
||||
|
||||
(_, _, errors) -> do
|
||||
printErr $ (concat errors) ++ "\n" ++ usageInfo header options
|
||||
printErr $ concat errors ++ "\n" ++ usageInfo header options
|
||||
exitWith syntaxFailure
|
||||
|
||||
formats = Map.fromList [
|
||||
@@ -84,7 +84,7 @@ forTty options files = do
|
||||
return $ and output
|
||||
where
|
||||
clear = ansi 0
|
||||
ansi n = "\x1B[" ++ (show n) ++ "m"
|
||||
ansi n = "\x1B[" ++ show n ++ "m"
|
||||
|
||||
colorForLevel "error" = 31 -- red
|
||||
colorForLevel "warning" = 33 -- yellow
|
||||
@@ -94,7 +94,8 @@ forTty options files = do
|
||||
colorForLevel "source" = 0 -- none
|
||||
colorForLevel _ = 0 -- none
|
||||
|
||||
colorComment level comment = (ansi $ colorForLevel level) ++ comment ++ clear
|
||||
colorComment level comment =
|
||||
ansi (colorForLevel level) ++ comment ++ clear
|
||||
|
||||
doFile path = do
|
||||
contents <- readContents path
|
||||
@@ -112,15 +113,17 @@ forTty options files = do
|
||||
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
|
||||
return $ null comments
|
||||
|
||||
cuteIndent comment =
|
||||
(replicate ((scColumn comment) - 1) ' ') ++ "^-- " ++ (code $ scCode comment) ++ ": " ++ (scMessage comment)
|
||||
replicate (scColumn comment - 1) ' ' ++
|
||||
"^-- " ++ code (scCode comment) ++ ": " ++ scMessage comment
|
||||
|
||||
code code = "SC" ++ (show code)
|
||||
|
||||
@@ -131,7 +134,7 @@ forTty options files = do
|
||||
-- This totally ignores the filenames. Fixme?
|
||||
forJson options files = do
|
||||
comments <- liftM concat $ mapM (commentsFor options) files
|
||||
putStrLn $ encodeStrict $ comments
|
||||
putStrLn $ encodeStrict comments
|
||||
return . null $ comments
|
||||
|
||||
-- Mimic GCC "file:line:col: (error|warning|note): message" format
|
||||
@@ -178,8 +181,8 @@ forCheckstyle options files = do
|
||||
severity "warning" = "warning"
|
||||
severity _ = "info"
|
||||
attr s v = concat [ s, "='", escape v, "' " ]
|
||||
escape msg = concatMap escape' msg
|
||||
escape' c = if isOk c then [c] else "&#" ++ (show $ ord c) ++ ";"
|
||||
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 [
|
||||
@@ -226,7 +229,7 @@ makeNonVirtual comments contents =
|
||||
real (_:rest) r v target = real rest (r+1) (v+1) target
|
||||
|
||||
getOption [] _ = Nothing
|
||||
getOption ((Flag var val):_) name | name == var = return val
|
||||
getOption (Flag var val:_) name | name == var = return val
|
||||
getOption (_:rest) flag = getOption rest flag
|
||||
|
||||
getOptions options name =
|
||||
@@ -247,8 +250,8 @@ getExclusions options =
|
||||
in
|
||||
map (Prelude.read . clean) elements :: [Int]
|
||||
|
||||
excludeCodes codes comments =
|
||||
filter (not . hasCode) comments
|
||||
excludeCodes codes =
|
||||
filter (not . hasCode)
|
||||
where
|
||||
hasCode c = scCode c `elem` codes
|
||||
|
||||
@@ -265,7 +268,7 @@ main = do
|
||||
exitWith code
|
||||
|
||||
process Nothing = return False
|
||||
process (Just (options, files)) = do
|
||||
process (Just (options, files)) =
|
||||
let format = fromMaybe "tty" $ getOption options "format" in
|
||||
case Map.lookup format formats of
|
||||
Nothing -> do
|
||||
@@ -281,11 +284,9 @@ verifyOptions opts files = do
|
||||
when (isJust $ getOption opts "version") printVersionAndExit
|
||||
|
||||
let shell = getOption opts "shell" in
|
||||
if isNothing shell
|
||||
then return ()
|
||||
else when (isNothing $ shell >>= shellForExecutable) $ do
|
||||
printErr $ "Unknown shell: " ++ (fromJust shell)
|
||||
exitWith supportFailure
|
||||
when (isJust shell && isNothing (shell >>= shellForExecutable)) $ do
|
||||
printErr $ "Unknown shell: " ++ (fromJust shell)
|
||||
exitWith supportFailure
|
||||
|
||||
when (null files) $ do
|
||||
printErr "No files specified.\n"
|
||||
|
Reference in New Issue
Block a user