@@ -29,6 +29,7 @@ import ShellCheck.AnalyzerLib hiding (producesComments)
2929import ShellCheck.CFG
3030import qualified ShellCheck.CFGAnalysis as CF
3131import ShellCheck.Data
32+ import ShellCheck.Fixer (applyFix )
3233import ShellCheck.Parser
3334import ShellCheck.Prelude
3435import ShellCheck.Interface
@@ -40,6 +41,7 @@ import Control.Monad.Identity
4041import Control.Monad.State
4142import Control.Monad.Writer hiding ((<>) )
4243import Control.Monad.Reader
44+ import Data.Array (listArray )
4345import Data.Char
4446import Data.Functor
4547import Data.Function (on )
@@ -310,6 +312,17 @@ verifyTree f s = producesComments f s == Just True
310312verifyNotTree :: (Parameters -> Token -> [TokenComment ]) -> String -> Bool
311313verifyNotTree f s = producesComments f s == Just False
312314
315+ verifyTreeFix :: (Parameters -> Token -> [TokenComment ]) -> String -> String -> Bool
316+ verifyTreeFix f input expected =
317+ case runAndGetComments f input of
318+ Just comments ->
319+ let fixes = mapMaybe tcFix comments
320+ inputLines = lines input
321+ in
322+ not (null fixes)
323+ && applyFix (mconcat fixes) (listArray (1 , length inputLines) inputLines) == lines expected
324+ Nothing -> False
325+
313326checkCommand str f t@ (T_SimpleCommand id _ (cmd: rest))
314327 | t `isCommand` str = f cmd rest
315328checkCommand _ _ _ = return ()
@@ -332,7 +345,7 @@ producesComments f s = not . null <$> runAndGetComments f s
332345runAndGetComments f s = do
333346 let pr = pScript s
334347 root <- prRoot pr
335- let spec = defaultSpec pr
348+ let spec = ( defaultSpec pr) { asSourceText = s }
336349 let params = makeParameters spec
337350 return $
338351 filterByAnnotation spec params $
@@ -4689,6 +4702,11 @@ prop_checkRequireDoubleBracket1 = verifyTree checkRequireDoubleBracket "[ -x foo
46894702prop_checkRequireDoubleBracket2 = verifyTree checkRequireDoubleBracket " [ foo -o bar ]"
46904703prop_checkRequireDoubleBracket3 = verifyNotTree checkRequireDoubleBracket " #!/bin/sh\n [ -x foo ]"
46914704prop_checkRequireDoubleBracket4 = verifyNotTree checkRequireDoubleBracket " [[ -x foo ]]"
4705+ prop_checkRequireDoubleBracket5 = verifyTreeFix checkRequireDoubleBracket " #!/bin/bash\n [ -n \" $witness_me\" ]" " #!/bin/bash\n [[ -n \" $witness_me\" ]]"
4706+ prop_checkRequireDoubleBracket6 = verifyTreeFix checkRequireDoubleBracket " #!/bin/bash\n [ -n \" $witness_me\" ]]" " #!/bin/bash\n [[ -n \" $witness_me\" ]]"
4707+ prop_checkRequireDoubleBracket7 = verifyTreeFix checkRequireDoubleBracket " #!/bin/bash\n [ -n \" $witness_me\" ]" " #!/bin/bash\n [[ -n \" $witness_me\" ]]"
4708+ prop_checkRequireDoubleBracket8 = verifyTreeFix checkRequireDoubleBracket " #!/bin/bash\n [\n -n \" $witness_me\"\n ]]" " #!/bin/bash\n [[\n -n \" $witness_me\"\n ]]"
4709+ prop_checkRequireDoubleBracket9 = verifyTreeFix checkRequireDoubleBracket " #!/bin/bash\n [\t -n \" $witness_me\"\t ]]" " #!/bin/bash\n [[\t -n \" $witness_me\"\t ]]"
46924710checkRequireDoubleBracket params =
46934711 if (shellType params) `elem` [Bash , Ksh , BusyboxSh ]
46944712 then nodeChecksToTreeCheck [check] params
@@ -4704,10 +4722,73 @@ checkRequireDoubleBracket params =
47044722 then
47054723 [
47064724 replaceStart (getId t) params 0 " [" ,
4707- replaceEnd (getId t) params 0 " ] "
4725+ replaceConditionEnd t
47084726 ]
47094727 else []
47104728
4729+ replaceConditionEnd t@ (T_Condition id _ s) =
4730+ let (_, conditionEnd) = tokenPositions params Map. ! id
4731+ (_, innerEnd) = simpleConditionRange s
4732+ suffix = sourceSpan innerEnd conditionEnd
4733+ leadingWhitespace = takeWhile isSpace suffix
4734+ in
4735+ if dropWhile isSpace suffix == " ]]"
4736+ then replaceSpan id innerEnd conditionEnd (leadingWhitespace ++ " ]]" )
4737+ else replaceEnd id params 0 " ]"
4738+
4739+ simpleConditionRange s =
4740+ case s of
4741+ TC_Binary id _ _ lhs rhs -> foldl1 mergeRanges [tokenRange id , simpleConditionRange lhs, simpleConditionRange rhs]
4742+ TC_Nullary id _ t -> mergeRanges (tokenRange id ) (tokenRange (getId t))
4743+ TC_Unary id _ _ t -> mergeRanges (tokenRange id ) (tokenRange (getId t))
4744+ _ -> tokenRange (getId s)
4745+
4746+ tokenRange id = tokenPositions params Map. ! id
4747+ mergeRanges (start1, end1) (start2, end2) = (min start1 start2, max end1 end2)
4748+
4749+ sourceSpan start end
4750+ | otherwise =
4751+ let sourceLines = lines (sourceText params)
4752+ startLine = fromIntegral (posLine start)
4753+ endLine = fromIntegral (posLine end)
4754+ lineAt n
4755+ | n >= 1 && n <= length sourceLines = Just (sourceLines !! (n - 1 ))
4756+ | otherwise = Nothing
4757+ in
4758+ case (lineAt startLine, lineAt endLine) of
4759+ (Just startText, Just endText) ->
4760+ let startIndex = columnToIndex startText (fromIntegral $ posColumn start)
4761+ endIndex = columnToIndex endText (fromIntegral $ posColumn end)
4762+ in
4763+ if startLine == endLine
4764+ then
4765+ take (endIndex - startIndex) . drop startIndex $ startText
4766+ else
4767+ intercalate " \n " $
4768+ [drop startIndex startText]
4769+ ++ [sourceLines !! (n - 1 ) | n <- [startLine + 1 .. endLine - 1 ]]
4770+ ++ [take endIndex endText]
4771+ _ -> " "
4772+
4773+ columnToIndex line target = go line 0 1
4774+ where
4775+ go rest raw visual
4776+ | target <= visual = raw
4777+ go [] raw _ = raw
4778+ go (' \t ' : rest) raw visual = go rest (raw + 1 ) (visual + 8 - ((visual - 1 ) `mod` 8 ))
4779+ go (_: rest) raw visual = go rest (raw + 1 ) (visual + 1 )
4780+
4781+ replaceSpan id start end r =
4782+ let depth = length $ getPath (parentMap params) (T_EOF id )
4783+ in
4784+ newReplacement {
4785+ repStartPos = start,
4786+ repEndPos = end,
4787+ repString = r,
4788+ repPrecedence = depth,
4789+ repInsertionPoint = InsertBefore
4790+ }
4791+
47114792 -- We don't tag operators like < and -o well enough to replace them,
47124793 -- so just handle the simple cases.
47134794 isSimple t = case t of
0 commit comments