mirror of
https://github.com/koalaman/shellcheck.git
synced 2025-09-30 16:59:20 +08:00
Compare commits
19 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
cb57b4a74f | ||
|
e3f0243c0e | ||
|
66b5f13c6f | ||
|
a7a404a5a8 | ||
|
0761f5c923 | ||
|
b55149b22d | ||
|
4097bb5154 | ||
|
1b207b3d43 | ||
|
135b4aa485 | ||
|
cb76951ad2 | ||
|
705e476e4c | ||
|
e705552c97 | ||
|
198aa4fc3d | ||
|
f4044fbcc7 | ||
|
2827b35696 | ||
|
de95c376ea | ||
|
5e1b1e010a | ||
|
620c9c2023 | ||
|
359b1467a2 |
14
.snapsquid.conf
Normal file
14
.snapsquid.conf
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# In 2015, cabal-install had a http bug triggered when proxies didn't keep
|
||||||
|
# the connection open. This version made it into Ubuntu Xenial as used by
|
||||||
|
# Snapcraft. In June 2018, Snapcraft's proxy started triggering this bug.
|
||||||
|
#
|
||||||
|
# https://bugs.launchpad.net/launchpad-buildd/+bug/1797809
|
||||||
|
#
|
||||||
|
# Workaround: add more proxy
|
||||||
|
|
||||||
|
visible_hostname localhost
|
||||||
|
http_port 8888
|
||||||
|
cache_peer 10.10.10.1 parent 8222 0 no-query default
|
||||||
|
cache_peer_domain localhost !.internal
|
||||||
|
http_access allow all
|
||||||
|
|
13
CHANGELOG.md
13
CHANGELOG.md
@@ -1,17 +1,26 @@
|
|||||||
## ???
|
## v0.6.0 - 2018-12-02
|
||||||
### Added
|
### Added
|
||||||
- Command line option --severity/-S for filtering by minimum severity
|
- Command line option --severity/-S for filtering by minimum severity
|
||||||
- Command line option --wiki-link-count/-W for showing wiki links
|
- Command line option --wiki-link-count/-W for showing wiki links
|
||||||
|
- SC2152/SC2151: Warn about bad `exit` values like `1234` and `"foo"`
|
||||||
- SC2236/SC2237: Suggest -n/-z instead of ! -z/-n
|
- SC2236/SC2237: Suggest -n/-z instead of ! -z/-n
|
||||||
- SC2238: Warn when redirecting to a known command name, e.g. ls > rm
|
- SC2238: Warn when redirecting to a known command name, e.g. ls > rm
|
||||||
- SC2239: Warn if the shebang is not an absolute path, e.g. #!bin/sh
|
- SC2239: Warn if the shebang is not an absolute path, e.g. #!bin/sh
|
||||||
|
- SC2240: Warn when passing additional arguments to dot (.) in sh/dash
|
||||||
- SC1133: Better diagnostics when starting a line with |/||/&&
|
- SC1133: Better diagnostics when starting a line with |/||/&&
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- Most warnings now have useful end positions
|
- Most warnings now have useful end positions
|
||||||
- SC1117 about unknown double-quoted escape sequences has been retired
|
- SC1117 about unknown double-quoted escape sequences has been retired
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- SC2021 no longer triggers for equivalence classes like '[=e=]'
|
- SC2021 no longer triggers for equivalence classes like `[=e=]`
|
||||||
- SC2221/SC2222 no longer mistriggers on fall-through case branches
|
- SC2221/SC2222 no longer mistriggers on fall-through case branches
|
||||||
|
- SC2081 about glob matches in `[ .. ]` now also triggers for `!=`
|
||||||
|
- SC2086 no longer warns about spaces in `$#`
|
||||||
|
- SC2164 no longer suggests subshells for `cd ..; cmd; cd ..`
|
||||||
|
- `read -a` is now correctly considered an array assignment
|
||||||
|
- SC2039 no longer warns about LINENO now that it's POSIX
|
||||||
|
|
||||||
## v0.5.0 - 2018-05-31
|
## v0.5.0 - 2018-05-31
|
||||||
### Added
|
### Added
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
# Build-only image
|
# Build-only image
|
||||||
FROM ubuntu:17.10 AS build
|
FROM ubuntu:18.04 AS build
|
||||||
USER root
|
USER root
|
||||||
WORKDIR /opt/shellCheck
|
WORKDIR /opt/shellCheck
|
||||||
|
|
||||||
|
@@ -133,6 +133,10 @@ On OS X with homebrew:
|
|||||||
|
|
||||||
brew install shellcheck
|
brew install shellcheck
|
||||||
|
|
||||||
|
On OpenBSD:
|
||||||
|
|
||||||
|
pkg_add shellcheck
|
||||||
|
|
||||||
On openSUSE
|
On openSUSE
|
||||||
|
|
||||||
zypper in ShellCheck
|
zypper in ShellCheck
|
||||||
@@ -168,6 +172,11 @@ Alternatively, you can download pre-compiled binaries for the latest release her
|
|||||||
|
|
||||||
or see the [storage bucket listing](https://shellcheck.storage.googleapis.com/index.html) for checksums, older versions and the latest daily builds.
|
or see the [storage bucket listing](https://shellcheck.storage.googleapis.com/index.html) for checksums, older versions and the latest daily builds.
|
||||||
|
|
||||||
|
Distro packages already come with a `man` page. If you are building from source, it can be installed with:
|
||||||
|
|
||||||
|
pandoc -s -t man shellcheck.1.md -o shellcheck.1
|
||||||
|
sudo mv shellcheck.1 /usr/share/man/man1
|
||||||
|
|
||||||
## Travis CI
|
## Travis CI
|
||||||
|
|
||||||
Travis CI has now integrated ShellCheck by default, so you don't need to manually install it.
|
Travis CI has now integrated ShellCheck by default, so you don't need to manually install it.
|
||||||
|
2
Setup.hs
2
Setup.hs
@@ -33,4 +33,4 @@ myPreSDist _ _ = do
|
|||||||
putStrLn $ "pandoc exited with " ++ show result
|
putStrLn $ "pandoc exited with " ++ show result
|
||||||
return emptyHookedBuildInfo
|
return emptyHookedBuildInfo
|
||||||
where
|
where
|
||||||
pandoc_cmd = "pandoc -s -t man shellcheck.1.md -o shellcheck.1"
|
pandoc_cmd = "pandoc -s -f markdown-smart -t man shellcheck.1.md -o shellcheck.1"
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
Name: ShellCheck
|
Name: ShellCheck
|
||||||
Version: 0.5.0
|
Version: 0.6.0
|
||||||
Synopsis: Shell script analysis tool
|
Synopsis: Shell script analysis tool
|
||||||
License: GPL-3
|
License: GPL-3
|
||||||
License-file: LICENSE
|
License-file: LICENSE
|
||||||
@@ -28,6 +28,8 @@ Extra-Source-Files:
|
|||||||
shellcheck.1.md
|
shellcheck.1.md
|
||||||
-- built with a cabal sdist hook
|
-- built with a cabal sdist hook
|
||||||
shellcheck.1
|
shellcheck.1
|
||||||
|
-- convenience script for stripping tests
|
||||||
|
striptests
|
||||||
-- tests
|
-- tests
|
||||||
test/shellcheck.hs
|
test/shellcheck.hs
|
||||||
|
|
||||||
@@ -53,7 +55,6 @@ library
|
|||||||
base > 4.6.0.1 && < 5,
|
base > 4.6.0.1 && < 5,
|
||||||
bytestring,
|
bytestring,
|
||||||
containers >= 0.5,
|
containers >= 0.5,
|
||||||
deepseq >= 1.4.0.0,
|
|
||||||
directory,
|
directory,
|
||||||
mtl >= 2.2.1,
|
mtl >= 2.2.1,
|
||||||
parsec,
|
parsec,
|
||||||
@@ -90,7 +91,6 @@ executable shellcheck
|
|||||||
aeson,
|
aeson,
|
||||||
base >= 4 && < 5,
|
base >= 4 && < 5,
|
||||||
bytestring,
|
bytestring,
|
||||||
deepseq >= 1.4.0.0,
|
|
||||||
ShellCheck,
|
ShellCheck,
|
||||||
containers,
|
containers,
|
||||||
directory,
|
directory,
|
||||||
@@ -106,7 +106,6 @@ test-suite test-shellcheck
|
|||||||
aeson,
|
aeson,
|
||||||
base >= 4 && < 5,
|
base >= 4 && < 5,
|
||||||
bytestring,
|
bytestring,
|
||||||
deepseq >= 1.4.0.0,
|
|
||||||
ShellCheck,
|
ShellCheck,
|
||||||
containers,
|
containers,
|
||||||
directory,
|
directory,
|
||||||
|
@@ -37,9 +37,16 @@ parts:
|
|||||||
source: ./
|
source: ./
|
||||||
build-packages:
|
build-packages:
|
||||||
- cabal-install
|
- cabal-install
|
||||||
|
- squid3
|
||||||
build: |
|
build: |
|
||||||
|
# See comments in .snapsquid.conf
|
||||||
|
[ "$http_proxy" ] && {
|
||||||
|
squid3 -f .snapsquid.conf
|
||||||
|
export http_proxy="http://localhost:8888"
|
||||||
|
sleep 3
|
||||||
|
}
|
||||||
cabal sandbox init
|
cabal sandbox init
|
||||||
cabal update
|
cabal update || cat /var/log/squid/*
|
||||||
cabal install -j
|
cabal install -j
|
||||||
install: |
|
install: |
|
||||||
install -d $SNAPCRAFT_PART_INSTALL/usr/bin
|
install -d $SNAPCRAFT_PART_INSTALL/usr/bin
|
||||||
|
@@ -17,17 +17,14 @@
|
|||||||
You should have received a copy of the GNU General Public License
|
You should have received a copy of the GNU General Public License
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
-}
|
-}
|
||||||
{-# LANGUAGE DeriveGeneric, DeriveAnyClass #-}
|
|
||||||
module ShellCheck.AST where
|
module ShellCheck.AST where
|
||||||
|
|
||||||
import GHC.Generics (Generic)
|
|
||||||
import Control.Monad.Identity
|
import Control.Monad.Identity
|
||||||
import Control.DeepSeq
|
|
||||||
import Text.Parsec
|
import Text.Parsec
|
||||||
import qualified ShellCheck.Regex as Re
|
import qualified ShellCheck.Regex as Re
|
||||||
import Prelude hiding (id)
|
import Prelude hiding (id)
|
||||||
|
|
||||||
newtype Id = Id Int deriving (Show, Eq, Ord, Generic, NFData)
|
newtype Id = Id Int deriving (Show, Eq, Ord)
|
||||||
|
|
||||||
data Quoted = Quoted | Unquoted deriving (Show, Eq)
|
data Quoted = Quoted | Unquoted deriving (Show, Eq)
|
||||||
data Dashed = Dashed | Undashed deriving (Show, Eq)
|
data Dashed = Dashed | Undashed deriving (Show, Eq)
|
||||||
|
@@ -241,39 +241,6 @@ isCondition (child:parent:rest) =
|
|||||||
T_UntilExpression id c l -> take 1 . reverse $ c
|
T_UntilExpression id c l -> take 1 . reverse $ c
|
||||||
_ -> []
|
_ -> []
|
||||||
|
|
||||||
-- helpers to build replacements
|
|
||||||
replaceStart id params n r =
|
|
||||||
let tp = tokenPositions params
|
|
||||||
(start, _) = tp Map.! id
|
|
||||||
new_end = start {
|
|
||||||
posColumn = posColumn start + n
|
|
||||||
}
|
|
||||||
in
|
|
||||||
newReplacement {
|
|
||||||
repStartPos = start,
|
|
||||||
repEndPos = new_end,
|
|
||||||
repString = r
|
|
||||||
}
|
|
||||||
replaceEnd id params n r =
|
|
||||||
-- because of the way we count columns 1-based
|
|
||||||
-- we have to offset end columns by 1
|
|
||||||
let tp = tokenPositions params
|
|
||||||
(_, end) = tp Map.! id
|
|
||||||
new_start = end {
|
|
||||||
posColumn = posColumn end - n + 1
|
|
||||||
}
|
|
||||||
new_end = end {
|
|
||||||
posColumn = posColumn end + 1
|
|
||||||
}
|
|
||||||
in
|
|
||||||
newReplacement {
|
|
||||||
repStartPos = new_start,
|
|
||||||
repEndPos = new_end,
|
|
||||||
repString = r
|
|
||||||
}
|
|
||||||
surroundWidth id params s = fixWith [replaceStart id params 0 s, replaceEnd id params 0 s]
|
|
||||||
fixWith fixes = newFix { fixReplacements = fixes }
|
|
||||||
|
|
||||||
prop_checkEchoWc3 = verify checkEchoWc "n=$(echo $foo | wc -c)"
|
prop_checkEchoWc3 = verify checkEchoWc "n=$(echo $foo | wc -c)"
|
||||||
checkEchoWc _ (T_Pipeline id _ [a, b]) =
|
checkEchoWc _ (T_Pipeline id _ [a, b]) =
|
||||||
when (acmd == ["echo", "${VAR}"]) $
|
when (acmd == ["echo", "${VAR}"]) $
|
||||||
@@ -1230,11 +1197,12 @@ prop_checkComparisonAgainstGlob2 = verifyNot checkComparisonAgainstGlob "[[ $cow
|
|||||||
prop_checkComparisonAgainstGlob3 = verify checkComparisonAgainstGlob "[ $cow = *foo* ]"
|
prop_checkComparisonAgainstGlob3 = verify checkComparisonAgainstGlob "[ $cow = *foo* ]"
|
||||||
prop_checkComparisonAgainstGlob4 = verifyNot checkComparisonAgainstGlob "[ $cow = foo ]"
|
prop_checkComparisonAgainstGlob4 = verifyNot checkComparisonAgainstGlob "[ $cow = foo ]"
|
||||||
prop_checkComparisonAgainstGlob5 = verify checkComparisonAgainstGlob "[[ $cow != $bar ]]"
|
prop_checkComparisonAgainstGlob5 = verify checkComparisonAgainstGlob "[[ $cow != $bar ]]"
|
||||||
|
prop_checkComparisonAgainstGlob6 = verify checkComparisonAgainstGlob "[ $f != /* ]"
|
||||||
checkComparisonAgainstGlob _ (TC_Binary _ DoubleBracket op _ (T_NormalWord id [T_DollarBraced _ _]))
|
checkComparisonAgainstGlob _ (TC_Binary _ DoubleBracket op _ (T_NormalWord id [T_DollarBraced _ _]))
|
||||||
| op `elem` ["=", "==", "!="] =
|
| op `elem` ["=", "==", "!="] =
|
||||||
warn id 2053 $ "Quote the right-hand side of " ++ op ++ " in [[ ]] to prevent glob matching."
|
warn id 2053 $ "Quote the right-hand side of " ++ op ++ " in [[ ]] to prevent glob matching."
|
||||||
checkComparisonAgainstGlob _ (TC_Binary _ SingleBracket op _ word)
|
checkComparisonAgainstGlob _ (TC_Binary _ SingleBracket op _ word)
|
||||||
| (op == "=" || op == "==") && isGlob word =
|
| op `elem` ["=", "==", "!="] && isGlob word =
|
||||||
err (getId word) 2081 "[ .. ] can't match globs. Use [[ .. ]] or case statement."
|
err (getId word) 2081 "[ .. ] can't match globs. Use [[ .. ]] or case statement."
|
||||||
checkComparisonAgainstGlob _ _ = return ()
|
checkComparisonAgainstGlob _ _ = return ()
|
||||||
|
|
||||||
@@ -1367,10 +1335,8 @@ checkPS1Assignments _ _ = return ()
|
|||||||
prop_checkBackticks1 = verify checkBackticks "echo `foo`"
|
prop_checkBackticks1 = verify checkBackticks "echo `foo`"
|
||||||
prop_checkBackticks2 = verifyNot checkBackticks "echo $(foo)"
|
prop_checkBackticks2 = verifyNot checkBackticks "echo $(foo)"
|
||||||
prop_checkBackticks3 = verifyNot checkBackticks "echo `#inlined comment` foo"
|
prop_checkBackticks3 = verifyNot checkBackticks "echo `#inlined comment` foo"
|
||||||
checkBackticks params (T_Backticked id list) | not (null list) =
|
checkBackticks _ (T_Backticked id list) | not (null list) =
|
||||||
addComment $
|
style id 2006 "Use $(...) notation instead of legacy backticked `...`."
|
||||||
makeCommentWithFix StyleC id 2006 "Use $(...) notation instead of legacy backticked `...`."
|
|
||||||
(fixWith [replaceStart id params 1 "$(", replaceEnd id params 1 ")"])
|
|
||||||
checkBackticks _ _ = return ()
|
checkBackticks _ _ = return ()
|
||||||
|
|
||||||
prop_checkIndirectExpansion1 = verify checkIndirectExpansion "${foo$n}"
|
prop_checkIndirectExpansion1 = verify checkIndirectExpansion "${foo$n}"
|
||||||
@@ -1674,10 +1640,8 @@ checkSpacefulness params t =
|
|||||||
makeComment InfoC (getId token) 2223
|
makeComment InfoC (getId token) 2223
|
||||||
"This default assignment may cause DoS due to globbing. Quote it."
|
"This default assignment may cause DoS due to globbing. Quote it."
|
||||||
else
|
else
|
||||||
makeCommentWithFix InfoC (getId token) 2086
|
makeComment InfoC (getId token) 2086
|
||||||
"Double quote to prevent globbing and word splitting." (surroundWidth (getId token) params "\"")
|
"Double quote to prevent globbing and word splitting."
|
||||||
-- makeComment InfoC (getId token) 2086
|
|
||||||
-- "Double quote to prevent globbing and word splitting."
|
|
||||||
|
|
||||||
writeF _ _ name (DataString SourceExternal) = setSpaces name True >> return []
|
writeF _ _ name (DataString SourceExternal) = setSpaces name True >> return []
|
||||||
writeF _ _ name (DataString SourceInteger) = setSpaces name False >> return []
|
writeF _ _ name (DataString SourceInteger) = setSpaces name False >> return []
|
||||||
@@ -2574,8 +2538,7 @@ checkUncheckedCdPushdPopd params root =
|
|||||||
&& not (isSafeDir t)
|
&& not (isSafeDir t)
|
||||||
&& not (name t `elem` ["pushd", "popd"] && ("n" `elem` map snd (getAllFlags t)))
|
&& not (name t `elem` ["pushd", "popd"] && ("n" `elem` map snd (getAllFlags t)))
|
||||||
&& not (isCondition $ getPath (parentMap params) t)) $
|
&& not (isCondition $ getPath (parentMap params) t)) $
|
||||||
warnWithFix (getId t) 2164 "Use 'cd ... || exit' or 'cd ... || return' in case cd fails."
|
warn (getId t) 2164 "Use 'cd ... || exit' or 'cd ... || return' in case cd fails."
|
||||||
(fixWith [replaceEnd (getId t) params 0 " || exit"])
|
|
||||||
checkElement _ = return ()
|
checkElement _ = return ()
|
||||||
name t = fromMaybe "" $ getCommandName t
|
name t = fromMaybe "" $ getCommandName t
|
||||||
isSafeDir t = case oversimplify t of
|
isSafeDir t = case oversimplify t of
|
||||||
@@ -2732,7 +2695,7 @@ checkArrayAssignmentIndices params root =
|
|||||||
T_Literal id str -> [(id,str)]
|
T_Literal id str -> [(id,str)]
|
||||||
_ -> []
|
_ -> []
|
||||||
guard $ '=' `elem` str
|
guard $ '=' `elem` str
|
||||||
return $ warnWithFix id 2191 "The = here is literal. To assign by index, use ( [index]=value ) with no spaces. To keep as literal, quote it." (surroundWidth id params "\"")
|
return $ warn id 2191 "The = here is literal. To assign by index, use ( [index]=value ) with no spaces. To keep as literal, quote it."
|
||||||
in
|
in
|
||||||
if null literalEquals && isAssociative
|
if null literalEquals && isAssociative
|
||||||
then warn (getId t) 2190 "Elements in associative arrays need index, e.g. array=( [index]=value ) ."
|
then warn (getId t) 2190 "Elements in associative arrays need index, e.g. array=( [index]=value ) ."
|
||||||
|
@@ -20,7 +20,6 @@
|
|||||||
{-# LANGUAGE FlexibleContexts #-}
|
{-# LANGUAGE FlexibleContexts #-}
|
||||||
{-# LANGUAGE TemplateHaskell #-}
|
{-# LANGUAGE TemplateHaskell #-}
|
||||||
module ShellCheck.AnalyzerLib where
|
module ShellCheck.AnalyzerLib where
|
||||||
|
|
||||||
import ShellCheck.AST
|
import ShellCheck.AST
|
||||||
import ShellCheck.ASTLib
|
import ShellCheck.ASTLib
|
||||||
import ShellCheck.Data
|
import ShellCheck.Data
|
||||||
@@ -29,16 +28,15 @@ import ShellCheck.Parser
|
|||||||
import ShellCheck.Regex
|
import ShellCheck.Regex
|
||||||
|
|
||||||
import Control.Arrow (first)
|
import Control.Arrow (first)
|
||||||
import Control.DeepSeq
|
|
||||||
import Control.Monad.Identity
|
import Control.Monad.Identity
|
||||||
import Control.Monad.RWS
|
import Control.Monad.RWS
|
||||||
import Control.Monad.State
|
import Control.Monad.State
|
||||||
import Control.Monad.Writer
|
import Control.Monad.Writer
|
||||||
import Data.Char
|
import Data.Char
|
||||||
import Data.List
|
import Data.List
|
||||||
|
import qualified Data.Map as Map
|
||||||
import Data.Maybe
|
import Data.Maybe
|
||||||
import Data.Semigroup
|
import Data.Semigroup
|
||||||
import qualified Data.Map as Map
|
|
||||||
|
|
||||||
import Test.QuickCheck.All (forAllProperties)
|
import Test.QuickCheck.All (forAllProperties)
|
||||||
import Test.QuickCheck.Test (maxSuccess, quickCheckWithResult, stdArgs)
|
import Test.QuickCheck.Test (maxSuccess, quickCheckWithResult, stdArgs)
|
||||||
@@ -83,8 +81,7 @@ data Parameters = Parameters {
|
|||||||
parentMap :: Map.Map Id Token, -- A map from Id to parent Token
|
parentMap :: Map.Map Id Token, -- A map from Id to parent Token
|
||||||
shellType :: Shell, -- The shell type, such as Bash or Ksh
|
shellType :: Shell, -- The shell type, such as Bash or Ksh
|
||||||
shellTypeSpecified :: Bool, -- True if shell type was forced via flags
|
shellTypeSpecified :: Bool, -- True if shell type was forced via flags
|
||||||
rootNode :: Token, -- The root node of the AST
|
rootNode :: Token -- The root node of the AST
|
||||||
tokenPositions :: Map.Map Id (Position, Position) -- map from token id to start and end position
|
|
||||||
}
|
}
|
||||||
|
|
||||||
-- TODO: Cache results of common AST ops here
|
-- TODO: Cache results of common AST ops here
|
||||||
@@ -145,7 +142,7 @@ makeComment severity id code note =
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
addComment note = note `deepseq` tell [note]
|
addComment note = tell [note]
|
||||||
|
|
||||||
warn :: MonadWriter [TokenComment] m => Id -> Code -> String -> m ()
|
warn :: MonadWriter [TokenComment] m => Id -> Code -> String -> m ()
|
||||||
warn id code str = addComment $ makeComment WarningC id code str
|
warn id code str = addComment $ makeComment WarningC id code str
|
||||||
@@ -153,20 +150,6 @@ err id code str = addComment $ makeComment ErrorC id code str
|
|||||||
info id code str = addComment $ makeComment InfoC id code str
|
info id code str = addComment $ makeComment InfoC id code str
|
||||||
style id code str = addComment $ makeComment StyleC id code str
|
style id code str = addComment $ makeComment StyleC id code str
|
||||||
|
|
||||||
warnWithFix id code str fix = addComment $
|
|
||||||
let comment = makeComment WarningC id code str in
|
|
||||||
comment {
|
|
||||||
tcFix = Just fix
|
|
||||||
}
|
|
||||||
|
|
||||||
makeCommentWithFix :: Severity -> Id -> Code -> String -> Fix -> TokenComment
|
|
||||||
makeCommentWithFix severity id code str fix =
|
|
||||||
let comment = makeComment severity id code str
|
|
||||||
withFix = comment {
|
|
||||||
tcFix = Just fix
|
|
||||||
}
|
|
||||||
in withFix `deepseq` withFix
|
|
||||||
|
|
||||||
makeParameters spec =
|
makeParameters spec =
|
||||||
let params = Parameters {
|
let params = Parameters {
|
||||||
rootNode = root,
|
rootNode = root,
|
||||||
@@ -181,8 +164,7 @@ makeParameters spec =
|
|||||||
|
|
||||||
shellTypeSpecified = isJust $ asShellType spec,
|
shellTypeSpecified = isJust $ asShellType spec,
|
||||||
parentMap = getParentTree root,
|
parentMap = getParentTree root,
|
||||||
variableFlow = getVariableFlow params root,
|
variableFlow = getVariableFlow params root
|
||||||
tokenPositions = asTokenPositions spec
|
|
||||||
} in params
|
} in params
|
||||||
where root = asScript spec
|
where root = asScript spec
|
||||||
|
|
||||||
|
@@ -42,8 +42,7 @@ tokenToPosition startMap t = fromMaybe fail $ do
|
|||||||
return $ newPositionedComment {
|
return $ newPositionedComment {
|
||||||
pcStartPos = fst span,
|
pcStartPos = fst span,
|
||||||
pcEndPos = snd span,
|
pcEndPos = snd span,
|
||||||
pcComment = tcComment t,
|
pcComment = tcComment t
|
||||||
pcFix = tcFix t
|
|
||||||
}
|
}
|
||||||
where
|
where
|
||||||
fail = error "Internal shellcheck error: id doesn't exist. Please report!"
|
fail = error "Internal shellcheck error: id doesn't exist. Please report!"
|
||||||
@@ -64,20 +63,11 @@ checkScript sys spec = do
|
|||||||
psShellTypeOverride = csShellTypeOverride spec
|
psShellTypeOverride = csShellTypeOverride spec
|
||||||
}
|
}
|
||||||
let parseMessages = prComments result
|
let parseMessages = prComments result
|
||||||
let tokenPositions = prTokenPositions result
|
|
||||||
let analysisSpec root =
|
|
||||||
as {
|
|
||||||
asScript = root,
|
|
||||||
asShellType = csShellTypeOverride spec,
|
|
||||||
asCheckSourced = csCheckSourced spec,
|
|
||||||
asExecutionMode = Executed,
|
|
||||||
asTokenPositions = tokenPositions
|
|
||||||
} where as = newAnalysisSpec root
|
|
||||||
let analysisMessages =
|
let analysisMessages =
|
||||||
fromMaybe [] $
|
fromMaybe [] $
|
||||||
(arComments . analyzeScript . analysisSpec)
|
(arComments . analyzeScript . analysisSpec)
|
||||||
<$> prRoot result
|
<$> prRoot result
|
||||||
let translator = tokenToPosition tokenPositions
|
let translator = tokenToPosition (prTokenPositions result)
|
||||||
return . nub . sortMessages . filter shouldInclude $
|
return . nub . sortMessages . filter shouldInclude $
|
||||||
(parseMessages ++ map translator analysisMessages)
|
(parseMessages ++ map translator analysisMessages)
|
||||||
|
|
||||||
@@ -100,6 +90,13 @@ checkScript sys spec = do
|
|||||||
cMessage comment)
|
cMessage comment)
|
||||||
getPosition = pcStartPos
|
getPosition = pcStartPos
|
||||||
|
|
||||||
|
analysisSpec root =
|
||||||
|
as {
|
||||||
|
asScript = root,
|
||||||
|
asShellType = csShellTypeOverride spec,
|
||||||
|
asCheckSourced = csCheckSourced spec,
|
||||||
|
asExecutionMode = Executed
|
||||||
|
} where as = newAnalysisSpec root
|
||||||
|
|
||||||
getErrors sys spec =
|
getErrors sys spec =
|
||||||
sort . map getCode . crComments $
|
sort . map getCode . crComments $
|
||||||
|
@@ -61,6 +61,7 @@ commandChecks = [
|
|||||||
,checkGrepRe
|
,checkGrepRe
|
||||||
,checkTrapQuotes
|
,checkTrapQuotes
|
||||||
,checkReturn
|
,checkReturn
|
||||||
|
,checkExit
|
||||||
,checkFindExecWithSingleArgument
|
,checkFindExecWithSingleArgument
|
||||||
,checkUnusedEchoEscapes
|
,checkUnusedEchoEscapes
|
||||||
,checkInjectableFindSh
|
,checkInjectableFindSh
|
||||||
@@ -92,6 +93,7 @@ commandChecks = [
|
|||||||
,checkWhich
|
,checkWhich
|
||||||
,checkSudoRedirect
|
,checkSudoRedirect
|
||||||
,checkSudoArgs
|
,checkSudoArgs
|
||||||
|
,checkSourceArgs
|
||||||
]
|
]
|
||||||
|
|
||||||
buildCommandMap :: [CommandCheck] -> Map.Map CommandName (Token -> Analysis)
|
buildCommandMap :: [CommandCheck] -> Map.Map CommandName (Token -> Analysis)
|
||||||
@@ -280,15 +282,28 @@ prop_checkReturn4 = verifyNot checkReturn "return $((a|b))"
|
|||||||
prop_checkReturn5 = verify checkReturn "return -1"
|
prop_checkReturn5 = verify checkReturn "return -1"
|
||||||
prop_checkReturn6 = verify checkReturn "return 1000"
|
prop_checkReturn6 = verify checkReturn "return 1000"
|
||||||
prop_checkReturn7 = verify checkReturn "return 'hello world'"
|
prop_checkReturn7 = verify checkReturn "return 'hello world'"
|
||||||
checkReturn = CommandCheck (Exactly "return") (f . arguments)
|
checkReturn = CommandCheck (Exactly "return") (returnOrExit
|
||||||
|
(\c -> err c 2151 "Only one integer 0-255 can be returned. Use stdout for other data.")
|
||||||
|
(\c -> err c 2152 "Can only return 0-255. Other data should be written to stdout."))
|
||||||
|
|
||||||
|
prop_checkExit1 = verifyNot checkExit "exit"
|
||||||
|
prop_checkExit2 = verifyNot checkExit "exit 1"
|
||||||
|
prop_checkExit3 = verifyNot checkExit "exit $var"
|
||||||
|
prop_checkExit4 = verifyNot checkExit "exit $((a|b))"
|
||||||
|
prop_checkExit5 = verify checkExit "exit -1"
|
||||||
|
prop_checkExit6 = verify checkExit "exit 1000"
|
||||||
|
prop_checkExit7 = verify checkExit "exit 'hello world'"
|
||||||
|
checkExit = CommandCheck (Exactly "exit") (returnOrExit
|
||||||
|
(\c -> err c 2241 "The exit status can only be one integer 0-255. Use stdout for other data.")
|
||||||
|
(\c -> err c 2242 "Can only exit with status 0-255. Other data should be written to stdout/stderr."))
|
||||||
|
|
||||||
|
returnOrExit multi invalid = (f . arguments)
|
||||||
where
|
where
|
||||||
f (first:second:_) =
|
f (first:second:_) =
|
||||||
err (getId second) 2151
|
multi (getId first)
|
||||||
"Only one integer 0-255 can be returned. Use stdout for other data."
|
|
||||||
f [value] =
|
f [value] =
|
||||||
when (isInvalid $ literal value) $
|
when (isInvalid $ literal value) $
|
||||||
err (getId value) 2152
|
invalid (getId value)
|
||||||
"Can only return 0-255. Other data should be written to stdout."
|
|
||||||
f _ = return ()
|
f _ = return ()
|
||||||
|
|
||||||
isInvalid s = s == "" || any (not . isDigit) s || length s > 5
|
isInvalid s = s == "" || any (not . isDigit) s || length s > 5
|
||||||
@@ -1008,5 +1023,16 @@ checkSudoArgs = CommandCheck (Basename "sudo") f
|
|||||||
-- This mess is why ShellCheck prefers not to know.
|
-- This mess is why ShellCheck prefers not to know.
|
||||||
parseOpts = getBsdOpts "vAknSbEHPa:g:h:p:u:c:T:r:"
|
parseOpts = getBsdOpts "vAknSbEHPa:g:h:p:u:c:T:r:"
|
||||||
|
|
||||||
|
prop_checkSourceArgs1 = verify checkSourceArgs "#!/bin/sh\n. script arg"
|
||||||
|
prop_checkSourceArgs2 = verifyNot checkSourceArgs "#!/bin/sh\n. script"
|
||||||
|
prop_checkSourceArgs3 = verifyNot checkSourceArgs "#!/bin/bash\n. script arg"
|
||||||
|
checkSourceArgs = CommandCheck (Exactly ".") f
|
||||||
|
where
|
||||||
|
f t = whenShell [Sh, Dash] $
|
||||||
|
case arguments t of
|
||||||
|
(file:arg1:_) -> warn (getId arg1) 2240 $
|
||||||
|
"The dot command does not support arguments in sh/dash. Set them as variables."
|
||||||
|
_ -> return ()
|
||||||
|
|
||||||
return []
|
return []
|
||||||
runTests = $( [| $(forAllProperties) (quickCheckWithResult (stdArgs { maxSuccess = 1 }) ) |])
|
runTests = $( [| $(forAllProperties) (quickCheckWithResult (stdArgs { maxSuccess = 1 }) ) |])
|
||||||
|
@@ -39,20 +39,7 @@ format = do
|
|||||||
footer = finish ref
|
footer = finish ref
|
||||||
}
|
}
|
||||||
|
|
||||||
instance ToJSON Replacement where
|
instance ToJSON (PositionedComment) where
|
||||||
toJSON replacement =
|
|
||||||
let start = repStartPos replacement
|
|
||||||
end = repEndPos replacement
|
|
||||||
str = repString replacement in
|
|
||||||
object [
|
|
||||||
"line" .= posLine start,
|
|
||||||
"endLine" .= posLine end,
|
|
||||||
"column" .= posColumn start,
|
|
||||||
"endColumn" .= posColumn end,
|
|
||||||
"replaceWith" .= str
|
|
||||||
]
|
|
||||||
|
|
||||||
instance ToJSON PositionedComment where
|
|
||||||
toJSON comment =
|
toJSON comment =
|
||||||
let start = pcStartPos comment
|
let start = pcStartPos comment
|
||||||
end = pcEndPos comment
|
end = pcEndPos comment
|
||||||
@@ -65,8 +52,7 @@ instance ToJSON PositionedComment where
|
|||||||
"endColumn" .= posColumn end,
|
"endColumn" .= posColumn end,
|
||||||
"level" .= severityText comment,
|
"level" .= severityText comment,
|
||||||
"code" .= cCode c,
|
"code" .= cCode c,
|
||||||
"message" .= cMessage c,
|
"message" .= cMessage c
|
||||||
"fix" .= pcFix comment
|
|
||||||
]
|
]
|
||||||
|
|
||||||
toEncoding comment =
|
toEncoding comment =
|
||||||
@@ -82,14 +68,8 @@ instance ToJSON PositionedComment where
|
|||||||
<> "level" .= severityText comment
|
<> "level" .= severityText comment
|
||||||
<> "code" .= cCode c
|
<> "code" .= cCode c
|
||||||
<> "message" .= cMessage c
|
<> "message" .= cMessage c
|
||||||
<> "fix" .= pcFix comment
|
|
||||||
)
|
)
|
||||||
|
|
||||||
instance ToJSON Fix where
|
|
||||||
toJSON fix = object [
|
|
||||||
"replacements" .= fixReplacements fix
|
|
||||||
]
|
|
||||||
|
|
||||||
outputError file msg = hPutStrLn stderr $ file ++ ": " ++ msg
|
outputError file msg = hPutStrLn stderr $ file ++ ": " ++ msg
|
||||||
collectResult ref result _ =
|
collectResult ref result _ =
|
||||||
modifyIORef ref (\x -> crComments result ++ x)
|
modifyIORef ref (\x -> crComments result ++ x)
|
||||||
@@ -97,3 +77,4 @@ collectResult ref result _ =
|
|||||||
finish ref = do
|
finish ref = do
|
||||||
list <- readIORef ref
|
list <- readIORef ref
|
||||||
BL.putStrLn $ encode list
|
BL.putStrLn $ encode list
|
||||||
|
|
||||||
|
@@ -25,7 +25,6 @@ import ShellCheck.Formatter.Format
|
|||||||
import Control.Monad
|
import Control.Monad
|
||||||
import Data.IORef
|
import Data.IORef
|
||||||
import Data.List
|
import Data.List
|
||||||
import Data.Maybe
|
|
||||||
import GHC.Exts
|
import GHC.Exts
|
||||||
import System.IO
|
import System.IO
|
||||||
import System.Info
|
import System.Info
|
||||||
@@ -95,7 +94,7 @@ outputWiki errRef = do
|
|||||||
where
|
where
|
||||||
showErr (_, code, msg) =
|
showErr (_, code, msg) =
|
||||||
putStrLn $ " " ++ wikiLink ++ "SC" ++ show code ++ " -- " ++ shorten msg
|
putStrLn $ " " ++ wikiLink ++ "SC" ++ show code ++ " -- " ++ shorten msg
|
||||||
limit = 40
|
limit = 36
|
||||||
shorten msg =
|
shorten msg =
|
||||||
if length msg < limit
|
if length msg < limit
|
||||||
then msg
|
then msg
|
||||||
@@ -119,8 +118,8 @@ outputForFile color sys comments = do
|
|||||||
let fileLines = lines contents
|
let fileLines = lines contents
|
||||||
let lineCount = fromIntegral $ length fileLines
|
let lineCount = fromIntegral $ length fileLines
|
||||||
let groups = groupWith lineNo comments
|
let groups = groupWith lineNo comments
|
||||||
mapM_ (\commentsForLine -> do
|
mapM_ (\x -> do
|
||||||
let lineNum = lineNo (head commentsForLine)
|
let lineNum = lineNo (head x)
|
||||||
let line = if lineNum < 1 || lineNum > lineCount
|
let line = if lineNum < 1 || lineNum > lineCount
|
||||||
then ""
|
then ""
|
||||||
else fileLines !! fromIntegral (lineNum - 1)
|
else fileLines !! fromIntegral (lineNum - 1)
|
||||||
@@ -128,62 +127,10 @@ outputForFile color sys comments = do
|
|||||||
putStrLn $ color "message" $
|
putStrLn $ color "message" $
|
||||||
"In " ++ fileName ++" line " ++ show lineNum ++ ":"
|
"In " ++ fileName ++" line " ++ show lineNum ++ ":"
|
||||||
putStrLn (color "source" line)
|
putStrLn (color "source" line)
|
||||||
mapM_ (\c -> putStrLn (color (severityText c) $ cuteIndent c)) commentsForLine
|
mapM_ (\c -> putStrLn (color (severityText c) $ cuteIndent c)) x
|
||||||
putStrLn ""
|
putStrLn ""
|
||||||
-- FIXME: Enable when reasonably stable
|
|
||||||
-- showFixedString color comments lineNum line
|
|
||||||
) groups
|
) groups
|
||||||
|
|
||||||
hasApplicableFix lineNum comment = fromMaybe False $ do
|
|
||||||
replacements <- fixReplacements <$> pcFix comment
|
|
||||||
guard $ all (\c -> onSameLine (repStartPos c) && onSameLine (repEndPos c)) replacements
|
|
||||||
return True
|
|
||||||
where
|
|
||||||
onSameLine pos = posLine pos == lineNum
|
|
||||||
|
|
||||||
-- FIXME: Work correctly with multiple replacements
|
|
||||||
showFixedString color comments lineNum line =
|
|
||||||
case filter (hasApplicableFix lineNum) comments of
|
|
||||||
(first:_) -> do
|
|
||||||
-- in the spirit of error prone
|
|
||||||
putStrLn $ color "message" "Did you mean: "
|
|
||||||
putStrLn $ fixedString first line
|
|
||||||
putStrLn ""
|
|
||||||
_ -> return ()
|
|
||||||
|
|
||||||
-- need to do something smart about sorting by end index
|
|
||||||
fixedString :: PositionedComment -> String -> String
|
|
||||||
fixedString comment line =
|
|
||||||
case (pcFix comment) of
|
|
||||||
Nothing -> ""
|
|
||||||
Just rs ->
|
|
||||||
applyReplacement (fixReplacements rs) line 0
|
|
||||||
where
|
|
||||||
applyReplacement [] s _ = s
|
|
||||||
applyReplacement (rep:xs) s offset =
|
|
||||||
let replacementString = repString rep
|
|
||||||
start = (posColumn . repStartPos) rep
|
|
||||||
end = (posColumn . repEndPos) rep
|
|
||||||
z = doReplace start end s replacementString
|
|
||||||
len_r = (fromIntegral . length) replacementString in
|
|
||||||
applyReplacement xs z (offset + (end - start) + len_r)
|
|
||||||
|
|
||||||
-- FIXME: Work correctly with tabs
|
|
||||||
-- start and end comes from pos, which is 1 based
|
|
||||||
-- doReplace 0 0 "1234" "A" -> "A1234" -- technically not valid
|
|
||||||
-- doReplace 1 1 "1234" "A" -> "A1234"
|
|
||||||
-- doReplace 1 2 "1234" "A" -> "A234"
|
|
||||||
-- doReplace 3 3 "1234" "A" -> "12A34"
|
|
||||||
-- doReplace 4 4 "1234" "A" -> "123A4"
|
|
||||||
-- doReplace 5 5 "1234" "A" -> "1234A"
|
|
||||||
doReplace start end o r =
|
|
||||||
let si = fromIntegral (start-1)
|
|
||||||
ei = fromIntegral (end-1)
|
|
||||||
(x, xs) = splitAt si o
|
|
||||||
(y, z) = splitAt (ei - si) xs
|
|
||||||
in
|
|
||||||
x ++ r ++ z
|
|
||||||
|
|
||||||
cuteIndent :: PositionedComment -> String
|
cuteIndent :: PositionedComment -> String
|
||||||
cuteIndent comment =
|
cuteIndent comment =
|
||||||
replicate (fromIntegral $ colNo comment - 1) ' ' ++
|
replicate (fromIntegral $ colNo comment - 1) ' ' ++
|
||||||
|
@@ -17,7 +17,6 @@
|
|||||||
You should have received a copy of the GNU General Public License
|
You should have received a copy of the GNU General Public License
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
-}
|
-}
|
||||||
{-# LANGUAGE DeriveGeneric, DeriveAnyClass #-}
|
|
||||||
module ShellCheck.Interface
|
module ShellCheck.Interface
|
||||||
(
|
(
|
||||||
SystemInterface(..)
|
SystemInterface(..)
|
||||||
@@ -25,7 +24,7 @@ module ShellCheck.Interface
|
|||||||
, CheckResult(crFilename, crComments)
|
, CheckResult(crFilename, crComments)
|
||||||
, ParseSpec(psFilename, psScript, psCheckSourced, psShellTypeOverride)
|
, ParseSpec(psFilename, psScript, psCheckSourced, psShellTypeOverride)
|
||||||
, ParseResult(prComments, prTokenPositions, prRoot)
|
, ParseResult(prComments, prTokenPositions, prRoot)
|
||||||
, AnalysisSpec(asScript, asShellType, asExecutionMode, asCheckSourced, asTokenPositions)
|
, AnalysisSpec(asScript, asShellType, asExecutionMode, asCheckSourced)
|
||||||
, AnalysisResult(arComments)
|
, AnalysisResult(arComments)
|
||||||
, FormatterOptions(foColorOption, foWikiLinkCount)
|
, FormatterOptions(foColorOption, foWikiLinkCount)
|
||||||
, Shell(Ksh, Sh, Bash, Dash)
|
, Shell(Ksh, Sh, Bash, Dash)
|
||||||
@@ -35,9 +34,9 @@ module ShellCheck.Interface
|
|||||||
, Severity(ErrorC, WarningC, InfoC, StyleC)
|
, Severity(ErrorC, WarningC, InfoC, StyleC)
|
||||||
, Position(posFile, posLine, posColumn)
|
, Position(posFile, posLine, posColumn)
|
||||||
, Comment(cSeverity, cCode, cMessage)
|
, Comment(cSeverity, cCode, cMessage)
|
||||||
, PositionedComment(pcStartPos , pcEndPos , pcComment, pcFix)
|
, PositionedComment(pcStartPos , pcEndPos , pcComment)
|
||||||
, ColorOption(ColorAuto, ColorAlways, ColorNever)
|
, ColorOption(ColorAuto, ColorAlways, ColorNever)
|
||||||
, TokenComment(tcId, tcComment, tcFix)
|
, TokenComment(tcId, tcComment)
|
||||||
, emptyCheckResult
|
, emptyCheckResult
|
||||||
, newParseResult
|
, newParseResult
|
||||||
, newAnalysisSpec
|
, newAnalysisSpec
|
||||||
@@ -50,18 +49,10 @@ module ShellCheck.Interface
|
|||||||
, emptyCheckSpec
|
, emptyCheckSpec
|
||||||
, newPositionedComment
|
, newPositionedComment
|
||||||
, newComment
|
, newComment
|
||||||
, Fix(fixReplacements)
|
|
||||||
, newFix
|
|
||||||
, Replacement(repStartPos, repEndPos, repString)
|
|
||||||
, newReplacement
|
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import ShellCheck.AST
|
import ShellCheck.AST
|
||||||
|
|
||||||
import Control.DeepSeq
|
|
||||||
import Control.Monad.Identity
|
import Control.Monad.Identity
|
||||||
import Data.Monoid
|
|
||||||
import GHC.Generics (Generic)
|
|
||||||
import qualified Data.Map as Map
|
import qualified Data.Map as Map
|
||||||
|
|
||||||
|
|
||||||
@@ -135,16 +126,14 @@ data AnalysisSpec = AnalysisSpec {
|
|||||||
asScript :: Token,
|
asScript :: Token,
|
||||||
asShellType :: Maybe Shell,
|
asShellType :: Maybe Shell,
|
||||||
asExecutionMode :: ExecutionMode,
|
asExecutionMode :: ExecutionMode,
|
||||||
asCheckSourced :: Bool,
|
asCheckSourced :: Bool
|
||||||
asTokenPositions :: Map.Map Id (Position, Position)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
newAnalysisSpec token = AnalysisSpec {
|
newAnalysisSpec token = AnalysisSpec {
|
||||||
asScript = token,
|
asScript = token,
|
||||||
asShellType = Nothing,
|
asShellType = Nothing,
|
||||||
asExecutionMode = Executed,
|
asExecutionMode = Executed,
|
||||||
asCheckSourced = False,
|
asCheckSourced = False
|
||||||
asTokenPositions = Map.empty
|
|
||||||
}
|
}
|
||||||
|
|
||||||
newtype AnalysisResult = AnalysisResult {
|
newtype AnalysisResult = AnalysisResult {
|
||||||
@@ -174,13 +163,12 @@ data ExecutionMode = Executed | Sourced deriving (Show, Eq)
|
|||||||
type ErrorMessage = String
|
type ErrorMessage = String
|
||||||
type Code = Integer
|
type Code = Integer
|
||||||
|
|
||||||
data Severity = ErrorC | WarningC | InfoC | StyleC
|
data Severity = ErrorC | WarningC | InfoC | StyleC deriving (Show, Eq, Ord)
|
||||||
deriving (Show, Eq, Ord, Generic, NFData)
|
|
||||||
data Position = Position {
|
data Position = Position {
|
||||||
posFile :: String, -- Filename
|
posFile :: String, -- Filename
|
||||||
posLine :: Integer, -- 1 based source line
|
posLine :: Integer, -- 1 based source line
|
||||||
posColumn :: Integer -- 1 based source column, where tabs are 8
|
posColumn :: Integer -- 1 based source column, where tabs are 8
|
||||||
} deriving (Show, Eq, Generic, NFData)
|
} deriving (Show, Eq)
|
||||||
|
|
||||||
newPosition :: Position
|
newPosition :: Position
|
||||||
newPosition = Position {
|
newPosition = Position {
|
||||||
@@ -193,7 +181,7 @@ data Comment = Comment {
|
|||||||
cSeverity :: Severity,
|
cSeverity :: Severity,
|
||||||
cCode :: Code,
|
cCode :: Code,
|
||||||
cMessage :: String
|
cMessage :: String
|
||||||
} deriving (Show, Eq, Generic, NFData)
|
} deriving (Show, Eq)
|
||||||
|
|
||||||
newComment :: Comment
|
newComment :: Comment
|
||||||
newComment = Comment {
|
newComment = Comment {
|
||||||
@@ -202,52 +190,27 @@ newComment = Comment {
|
|||||||
cMessage = ""
|
cMessage = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
-- only support single line for now
|
|
||||||
data Replacement = Replacement {
|
|
||||||
repStartPos :: Position,
|
|
||||||
repEndPos :: Position,
|
|
||||||
repString :: String
|
|
||||||
} deriving (Show, Eq, Generic, NFData)
|
|
||||||
|
|
||||||
newReplacement = Replacement {
|
|
||||||
repStartPos = newPosition,
|
|
||||||
repEndPos = newPosition,
|
|
||||||
repString = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
data Fix = Fix {
|
|
||||||
fixReplacements :: [Replacement]
|
|
||||||
} deriving (Show, Eq, Generic, NFData)
|
|
||||||
|
|
||||||
newFix = Fix {
|
|
||||||
fixReplacements = []
|
|
||||||
}
|
|
||||||
|
|
||||||
data PositionedComment = PositionedComment {
|
data PositionedComment = PositionedComment {
|
||||||
pcStartPos :: Position,
|
pcStartPos :: Position,
|
||||||
pcEndPos :: Position,
|
pcEndPos :: Position,
|
||||||
pcComment :: Comment,
|
pcComment :: Comment
|
||||||
pcFix :: Maybe Fix
|
} deriving (Show, Eq)
|
||||||
} deriving (Show, Eq, Generic, NFData)
|
|
||||||
|
|
||||||
newPositionedComment :: PositionedComment
|
newPositionedComment :: PositionedComment
|
||||||
newPositionedComment = PositionedComment {
|
newPositionedComment = PositionedComment {
|
||||||
pcStartPos = newPosition,
|
pcStartPos = newPosition,
|
||||||
pcEndPos = newPosition,
|
pcEndPos = newPosition,
|
||||||
pcComment = newComment,
|
pcComment = newComment
|
||||||
pcFix = Nothing
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data TokenComment = TokenComment {
|
data TokenComment = TokenComment {
|
||||||
tcId :: Id,
|
tcId :: Id,
|
||||||
tcComment :: Comment,
|
tcComment :: Comment
|
||||||
tcFix :: Maybe Fix
|
} deriving (Show, Eq)
|
||||||
} deriving (Show, Eq, Generic, NFData)
|
|
||||||
|
|
||||||
newTokenComment = TokenComment {
|
newTokenComment = TokenComment {
|
||||||
tcId = Id 0,
|
tcId = Id 0,
|
||||||
tcComment = newComment,
|
tcComment = newComment
|
||||||
tcFix = Nothing
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data ColorOption =
|
data ColorOption =
|
||||||
|
@@ -1916,8 +1916,9 @@ readNewlineList =
|
|||||||
where
|
where
|
||||||
checkBadBreak = optional $ do
|
checkBadBreak = optional $ do
|
||||||
pos <- getPosition
|
pos <- getPosition
|
||||||
try $ lookAhead (oneOf "|&") -- |, || or &&
|
try $ lookAhead (oneOf "|&") -- See if the next thing could be |, || or &&
|
||||||
parseProblemAt pos ErrorC 1133 "Unexpected start of line. If breaking lines, |/||/&& should be at the end of the previous one."
|
parseProblemAt pos ErrorC 1133
|
||||||
|
"Unexpected start of line. If breaking lines, |/||/&& should be at the end of the previous one."
|
||||||
readLineBreak = optional readNewlineList
|
readLineBreak = optional readNewlineList
|
||||||
|
|
||||||
prop_readSeparator1 = isWarning readScript "a &; b"
|
prop_readSeparator1 = isWarning readScript "a &; b"
|
||||||
|
@@ -16,10 +16,14 @@ and is still highly experimental.
|
|||||||
|
|
||||||
Make sure you're plugged in and have screen/tmux in place,
|
Make sure you're plugged in and have screen/tmux in place,
|
||||||
then re-run with $0 --run to continue.
|
then re-run with $0 --run to continue.
|
||||||
|
|
||||||
|
Also note that 'dist' will be deleted.
|
||||||
EOF
|
EOF
|
||||||
exit 0
|
exit 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
echo "Deleting 'dist'..."
|
||||||
|
rm -rf dist
|
||||||
|
|
||||||
log=$(mktemp) || die "Can't create temp file"
|
log=$(mktemp) || die "Can't create temp file"
|
||||||
date >> "$log" || die "Can't write to log"
|
date >> "$log" || die "Can't write to log"
|
||||||
@@ -63,7 +67,8 @@ opensuse:latest zypper install -y cabal-install ghc
|
|||||||
ubuntu:18.04 apt-get update && apt-get install -y cabal-install
|
ubuntu:18.04 apt-get update && apt-get install -y cabal-install
|
||||||
ubuntu:17.10 apt-get update && apt-get install -y cabal-install
|
ubuntu:17.10 apt-get update && apt-get install -y cabal-install
|
||||||
|
|
||||||
# Misc
|
# Misc Haskell including current and latest Stack build
|
||||||
|
ubuntu:18.10 set -e; apt-get update && apt-get install -y curl && curl -sSL https://get.haskellstack.org/ | sh -s - -f && cd /mnt && exec test/stacktest
|
||||||
haskell:latest true
|
haskell:latest true
|
||||||
|
|
||||||
# Known to currently fail
|
# Known to currently fail
|
||||||
|
27
test/stacktest
Executable file
27
test/stacktest
Executable file
@@ -0,0 +1,27 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# This script builds ShellCheck through `stack` using
|
||||||
|
# various resolvers. It's run via distrotest.
|
||||||
|
|
||||||
|
resolvers=(
|
||||||
|
nightly-"$(date -d "3 days ago" +"%Y-%m-%d")"
|
||||||
|
)
|
||||||
|
|
||||||
|
die() { echo "$*" >&2; exit 1; }
|
||||||
|
|
||||||
|
[ -e "ShellCheck.cabal" ] ||
|
||||||
|
die "ShellCheck.cabal not in current dir"
|
||||||
|
[ -e "stack.yaml" ] ||
|
||||||
|
die "stack.yaml not in current dir"
|
||||||
|
command -v stack ||
|
||||||
|
die "stack is missing"
|
||||||
|
|
||||||
|
stack setup || die "Failed to setup with default resolver"
|
||||||
|
stack build --test || die "Failed to build/test with default resolver"
|
||||||
|
|
||||||
|
for resolver in "${resolvers[@]}"
|
||||||
|
do
|
||||||
|
stack --resolver="$resolver" setup || die "Failed to setup $resolver"
|
||||||
|
stack --resolver="$resolver" build --test || die "Failed build/test with $resolver!"
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "Success"
|
Reference in New Issue
Block a user