Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
Annlix
ShellCheck
提交
f31c8bd3
S
ShellCheck
项目概览
Annlix
/
ShellCheck
与 Fork 源项目一致
Fork自
镜像 / koalaman / ShellCheck
通知
1
Star
0
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
DevOps
流水线
流水线任务
计划
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
S
ShellCheck
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
DevOps
DevOps
流水线
流水线任务
计划
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
流水线任务
提交
Issue看板
前往新版Gitcode,体验更适合开发者的 AI 搜索 >>
提交
f31c8bd3
编写于
8月 16, 2015
作者:
V
Vidar Holen
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
Preliminary support for sourced files.
上级
0dd61b65
变更
5
隐藏空白更改
内联
并排
Showing
5 changed file
with
204 addition
and
29 deletion
+204
-29
ShellCheck/AST.hs
ShellCheck/AST.hs
+3
-0
ShellCheck/Analytics.hs
ShellCheck/Analytics.hs
+1
-0
ShellCheck/Checker.hs
ShellCheck/Checker.hs
+41
-1
ShellCheck/Parser.hs
ShellCheck/Parser.hs
+98
-9
shellcheck.hs
shellcheck.hs
+61
-19
未找到文件。
ShellCheck/AST.hs
浏览文件 @
f31c8bd3
...
...
@@ -125,6 +125,7 @@ data Token =
|
T_Pipe
Id
String
|
T_CoProc
Id
(
Maybe
String
)
Token
|
T_CoProcBody
Id
Token
|
T_Include
Id
Token
Token
-- . & source: SimpleCommand T_Script
deriving
(
Show
)
data
Annotation
=
DisableComment
Integer
deriving
(
Show
,
Eq
)
...
...
@@ -255,6 +256,7 @@ analyze f g i =
delve
(
T_Annotation
id
anns
t
)
=
d1
t
$
T_Annotation
id
anns
delve
(
T_CoProc
id
var
body
)
=
d1
body
$
T_CoProc
id
var
delve
(
T_CoProcBody
id
t
)
=
d1
t
$
T_CoProcBody
id
delve
(
T_Include
id
includer
script
)
=
d2
includer
script
$
T_Include
id
delve
t
=
return
t
getId
t
=
case
t
of
...
...
@@ -350,6 +352,7 @@ getId t = case t of
T_Pipe
id
_
->
id
T_CoProc
id
_
_
->
id
T_CoProcBody
id
_
->
id
T_Include
id
_
_
->
id
blank
::
Monad
m
=>
Token
->
m
()
blank
=
const
$
return
()
...
...
ShellCheck/Analytics.hs
浏览文件 @
f31c8bd3
...
...
@@ -224,6 +224,7 @@ filterByAnnotation token =
any
hasNum
anns
where
hasNum
(
DisableComment
ts
)
=
num
==
ts
shouldIgnoreFor
_
(
T_Include
{})
=
True
-- Ignore included files
shouldIgnoreFor
_
_
=
False
parents
=
getParentTree
token
...
...
ShellCheck/Checker.hs
浏览文件 @
f31c8bd3
...
...
@@ -81,7 +81,7 @@ checkScript sys spec = do
}
getErrors
sys
spec
=
map
getCode
.
crComments
$
sort
.
map
getCode
.
crComments
$
runIdentity
(
checkScript
sys
spec
)
where
getCode
(
PositionedComment
_
(
Comment
_
code
_
))
=
code
...
...
@@ -124,5 +124,45 @@ prop_optionDisablesIssue2 =
csExcludedWarnings
=
[
2148
,
1037
]
}
prop_failsWhenNotSourcing
=
[
1091
,
2154
]
==
getErrors
(
mockedSystemInterface
[]
)
emptyCheckSpec
{
csScript
=
"source lob; echo
\"
$bar
\"
"
,
csExcludedWarnings
=
[
2148
]
}
prop_worksWhenSourcing
=
null
$
getErrors
(
mockedSystemInterface
[(
"lib"
,
"bar=1"
)])
emptyCheckSpec
{
csScript
=
"source lib; echo
\"
$bar
\"
"
,
csExcludedWarnings
=
[
2148
]
}
prop_worksWhenDotting
=
null
$
getErrors
(
mockedSystemInterface
[(
"lib"
,
"bar=1"
)])
emptyCheckSpec
{
csScript
=
". lib; echo
\"
$bar
\"
"
,
csExcludedWarnings
=
[
2148
]
}
prop_noInfiniteSourcing
=
[]
==
getErrors
(
mockedSystemInterface
[(
"lib"
,
"source lib"
)])
emptyCheckSpec
{
csScript
=
"source lib"
,
csExcludedWarnings
=
[
2148
]
}
prop_canSourceBadSyntax
=
[
1094
,
2086
]
==
getErrors
(
mockedSystemInterface
[(
"lib"
,
"for f; do"
)])
emptyCheckSpec
{
csScript
=
"source lib; echo $1"
,
csExcludedWarnings
=
[
2148
]
}
return
[]
runTests
=
$
quickCheckAll
ShellCheck/Parser.hs
浏览文件 @
f31c8bd3
...
...
@@ -21,23 +21,28 @@
module
ShellCheck.Parser
(
parseScript
,
runTests
)
where
import
ShellCheck.AST
import
ShellCheck.ASTLib
import
ShellCheck.Data
import
ShellCheck.Interface
import
Text.Parsec
hiding
(
runParser
)
import
Debug.Trace
import
Control.Monad
import
Control.Monad.Identity
import
Control.Monad.Trans
import
Data.Char
import
Data.Functor
import
Data.List
(
isPrefixOf
,
isInfixOf
,
isSuffixOf
,
partition
,
sortBy
,
intercalate
,
nub
)
import
qualified
Data.Map
as
Map
import
qualified
Control.Monad.State
as
Ms
import
qualified
Control.Monad.Reader
as
Mr
import
Data.Maybe
import
Debug.Trace
import
GHC.Exts
(
sortWith
)
import
Prelude
hiding
(
readList
)
import
System.IO
import
Text.Parsec
hiding
(
runParser
)
import
Text.Parsec.Error
import
GHC.Exts
(
sortWith
)
import
Text.Parsec.Pos
import
qualified
Control.Monad.Reader
as
Mr
import
qualified
Control.Monad.State
as
Ms
import
qualified
Data.Map
as
Map
import
Test.QuickCheck.All
(
quickCheckAll
)
type
SCBase
m
=
Mr
.
ReaderT
(
SystemInterface
m
)
(
Ms
.
StateT
SystemState
m
)
...
...
@@ -125,7 +130,11 @@ almostSpace =
--------- Message/position annotation on top of user state
data
Note
=
Note
Id
Severity
Code
String
deriving
(
Show
,
Eq
)
data
ParseNote
=
ParseNote
SourcePos
Severity
Code
String
deriving
(
Show
,
Eq
)
data
Context
=
ContextName
SourcePos
String
|
ContextAnnotation
[
Annotation
]
deriving
(
Show
)
data
Context
=
ContextName
SourcePos
String
|
ContextAnnotation
[
Annotation
]
|
ContextSource
String
deriving
(
Show
)
data
UserState
=
UserState
{
lastId
::
Id
,
...
...
@@ -179,9 +188,27 @@ shouldIgnoreCode code = do
where
disabling
(
ContextAnnotation
list
)
=
any
disabling'
list
disabling
(
ContextSource
_
)
=
True
-- Don't add messages for sourced files
disabling
_
=
False
disabling'
(
DisableComment
n
)
=
code
==
n
shouldFollow
file
=
do
context
<-
getCurrentContexts
if
any
isThisFile
context
then
return
False
else
if
length
(
filter
isSource
context
)
>=
100
then
do
parseProblem
ErrorC
1092
"Stopping at 100 'source' frames :O"
return
False
else
return
True
where
isSource
(
ContextSource
_
)
=
True
isSource
_
=
False
isThisFile
(
ContextSource
name
)
|
name
==
file
=
True
isThisFile
_
=
False
-- Store potential parse problems outside of parsec
data
SystemState
=
SystemState
{
...
...
@@ -900,6 +927,18 @@ subParse pos parser input = do
setPosition
lastPosition
return
result
inSeparateContext
parser
=
do
context
<-
Ms
.
get
success
context
<|>
failure
context
where
success
c
=
do
res
<-
try
parser
Ms
.
put
c
return
res
failure
c
=
do
Ms
.
put
c
fail
""
prop_readDoubleQuoted
=
isOk
readDoubleQuoted
"
\"
Hello $FOO
\"
"
prop_readDoubleQuoted2
=
isOk
readDoubleQuoted
"
\"
$'
\"
"
prop_readDoubleQuoted3
=
isWarning
readDoubleQuoted
"
\x201C
hello
\x201D
"
...
...
@@ -1403,7 +1442,6 @@ makeSimpleCommand id1 id2 prefix cmd suffix =
redirection
(
T_FdRedirect
{})
=
True
redirection
_
=
False
prop_readSimpleCommand
=
isOk
readSimpleCommand
"echo test > file"
prop_readSimpleCommand2
=
isOk
readSimpleCommand
"cmd &> file"
prop_readSimpleCommand3
=
isOk
readSimpleCommand
"export foo=(bar baz)"
...
...
@@ -1411,6 +1449,7 @@ 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; )"
readSimpleCommand
=
called
"simple command"
$
do
pos
<-
getPosition
id1
<-
getNextId
id2
<-
getNextId
prefix
<-
option
[]
readCmdPrefix
...
...
@@ -1424,7 +1463,11 @@ readSimpleCommand = called "simple command" $ do
([
"time"
],
readTimeSuffix
),
([
"let"
],
readLetSuffix
)
]
return
$
makeSimpleCommand
id1
id2
prefix
[
cmd
]
suffix
let
result
=
makeSimpleCommand
id1
id2
prefix
[
cmd
]
suffix
if
isCommand
[
"source"
,
"."
]
cmd
then
readSource
pos
result
else
return
result
where
isCommand
strings
(
T_NormalWord
_
[
T_Literal
_
s
])
=
s
`
elem
`
strings
isCommand
_
_
=
False
...
...
@@ -1434,6 +1477,51 @@ readSimpleCommand = called "simple command" $ do
then
action
else
getParser
def
cmd
rest
readSource
::
Monad
m
=>
SourcePos
->
Token
->
SCParser
m
Token
readSource
pos
t
@
(
T_Redirecting
_
_
(
T_SimpleCommand
_
_
(
cmd
:
file
:
_
)))
=
do
let
literalFile
=
getLiteralString
file
case
literalFile
of
Nothing
->
do
parseNoteAt
pos
InfoC
1090
"This source will be skipped since it's not constant."
return
t
Just
filename
->
do
proceed
<-
shouldFollow
filename
if
not
proceed
then
do
parseNoteAt
pos
InfoC
1093
"This file appears to be recursively sourced. Ignoring."
return
t
else
do
sys
<-
Mr
.
ask
input
<-
system
$
siReadFile
sys
filename
case
input
of
Left
err
->
do
parseNoteAt
pos
InfoC
1091
$
"Not following: "
++
err
return
t
Right
script
->
do
id
<-
getNextIdAt
pos
let
included
=
do
src
<-
subRead
filename
script
return
$
T_Include
id
t
src
let
failed
=
do
parseNoteAt
pos
WarningC
1094
"Parsing of sourced file failed. Ignoring it."
return
t
included
<|>
failed
where
subRead
name
script
=
withContext
(
ContextSource
name
)
$
inSeparateContext
$
subParse
(
initialPos
name
)
readScript
script
readSource
_
t
=
return
t
prop_readPipeline
=
isOk
readPipeline
"! cat /etc/issue | grep -i ubuntu"
prop_readPipeline2
=
isWarning
readPipeline
"!cat /etc/issue | grep -i ubuntu"
prop_readPipeline3
=
isOk
readPipeline
"for f; do :; done|cat"
...
...
@@ -2222,6 +2310,7 @@ runParser sys p filename contents =
(
runParserT
p
initialUserState
filename
contents
)
sys
)
initialSystemState
system
=
lift
.
lift
.
lift
parseShell
sys
name
contents
=
do
(
result
,
state
)
<-
runParser
sys
(
parseWithNotes
readScript
)
name
contents
...
...
shellcheck.hs
浏览文件 @
f31c8bd3
...
...
@@ -38,6 +38,7 @@ import Data.Maybe
import
Data.Monoid
import
Prelude
hiding
(
catch
)
import
System.Console.GetOpt
import
System.Directory
import
System.Environment
import
System.Exit
import
System.IO
...
...
@@ -56,6 +57,16 @@ instance Monoid Status where
mempty
=
NoProblems
mappend
=
max
data
Options
=
Options
{
checkSpec
::
CheckSpec
,
externalSources
::
Bool
}
defaultOptions
=
Options
{
checkSpec
=
emptyCheckSpec
,
externalSources
=
False
}
usageHeader
=
"Usage: shellcheck [OPTIONS...] FILES..."
options
=
[
Option
"e"
[
"exclude"
]
...
...
@@ -64,6 +75,8 @@ options = [
(
ReqArg
(
Flag
"format"
)
"FORMAT"
)
"output format"
,
Option
"s"
[
"shell"
]
(
ReqArg
(
Flag
"shell"
)
"SHELLNAME"
)
"Specify dialect (bash,sh,ksh)"
,
Option
"x"
[
"external-sources"
]
(
NoArg
$
Flag
"externals"
"true"
)
"Allow 'source' outside of FILES."
,
Option
"V"
[
"version"
]
(
NoArg
$
Flag
"version"
"true"
)
"Print version information"
]
...
...
@@ -81,9 +94,9 @@ parseArguments argv =
formats
::
Map
.
Map
String
(
IO
Formatter
)
formats
=
Map
.
fromList
[
(
"checkstyle"
,
ShellCheck
.
Formatter
.
CheckStyle
.
format
),
(
"gcc"
,
ShellCheck
.
Formatter
.
GCC
.
format
),
(
"gcc"
,
ShellCheck
.
Formatter
.
GCC
.
format
),
(
"json"
,
ShellCheck
.
Formatter
.
JSON
.
format
),
(
"tty"
,
ShellCheck
.
Formatter
.
TTY
.
format
)
(
"tty"
,
ShellCheck
.
Formatter
.
TTY
.
format
)
]
getOption
[]
_
=
Nothing
...
...
@@ -128,7 +141,7 @@ statusToCode status =
process
::
[
Flag
]
->
[
FilePath
]
->
ExceptT
Status
IO
Status
process
flags
files
=
do
options
<-
foldM
(
flip
parseOption
)
emptyCheckSpec
flags
options
<-
foldM
(
flip
parseOption
)
defaultOptions
flags
verifyFiles
files
let
format
=
fromMaybe
"tty"
$
getOption
flags
"format"
formatter
<-
...
...
@@ -140,12 +153,12 @@ process flags files = do
throwError
SupportFailure
where
write
s
=
" "
++
s
Just
f
->
ExceptT
$
fmap
Right
f
let
sys
=
ioInterface
(
const
False
)
sys
<-
lift
$
ioInterface
options
files
lift
$
runFormatter
sys
formatter
options
files
runFormatter
::
SystemInterface
IO
->
Formatter
->
CheckSpec
->
[
FilePath
]
runFormatter
::
SystemInterface
IO
->
Formatter
->
Options
->
[
FilePath
]
->
IO
Status
runFormatter
sys
format
spec
files
=
do
runFormatter
sys
format
options
files
=
do
header
format
result
<-
foldM
f
NoProblems
files
footer
format
...
...
@@ -163,7 +176,7 @@ runFormatter sys format spec files = do
process
::
FilePath
->
IO
Status
process
filename
=
do
contents
<-
inputFile
filename
let
checkspec
=
spec
{
let
checkspec
=
(
checkSpec
options
)
{
csFilename
=
filename
,
csScript
=
contents
}
...
...
@@ -179,17 +192,30 @@ parseOption flag options =
Flag
"shell"
str
->
fromMaybe
(
die
$
"Unknown shell: "
++
str
)
$
do
shell
<-
shellForExecutable
str
return
$
return
options
{
csShellTypeOverride
=
Just
shell
}
return
$
return
options
{
checkSpec
=
(
checkSpec
options
)
{
csShellTypeOverride
=
Just
shell
}
}
Flag
"exclude"
str
->
do
new
<-
mapM
parseNum
$
split
','
str
let
old
=
csExcludedWarnings
options
return
options
{
csExcludedWarnings
=
new
++
old
}
let
old
=
csExcludedWarnings
.
checkSpec
$
options
return
options
{
checkSpec
=
(
checkSpec
options
)
{
csExcludedWarnings
=
new
++
old
}
}
Flag
"version"
_
->
do
liftIO
printVersion
throwError
NoProblems
Flag
"externals"
_
->
do
return
options
{
externalSources
=
True
}
_
->
return
options
where
die
s
=
do
...
...
@@ -202,18 +228,34 @@ parseOption flag options =
throwError
SyntaxFailure
return
(
Prelude
.
read
num
::
Integer
)
ioInterface
filter
=
SystemInterface
{
siReadFile
=
get
ioInterface
options
files
=
do
inputs
<-
mapM
normalize
files
return
SystemInterface
{
siReadFile
=
get
inputs
}
where
get
file
=
if
filter
file
get
inputs
file
=
do
ok
<-
allowable
inputs
file
if
ok
then
(
Right
<$>
inputFile
file
)
`
catch
`
handler
else
return
$
Left
(
file
++
" was not specified as input."
)
handler
::
IOException
->
IO
(
Either
ErrorMessage
String
)
handler
ex
=
return
.
Left
$
show
ex
else
return
$
Left
(
file
++
" was not specified as input (see shellcheck -x)."
)
where
handler
::
IOException
->
IO
(
Either
ErrorMessage
String
)
handler
ex
=
return
.
Left
$
show
ex
allowable
inputs
x
=
if
externalSources
options
then
return
True
else
do
path
<-
normalize
x
return
$
path
`
elem
`
inputs
normalize
x
=
canonicalizePath
x
`
catch
`
fallback
x
where
fallback
::
FilePath
->
IOException
->
IO
FilePath
fallback
path
_
=
return
path
inputFile
file
=
do
contents
<-
...
...
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录