From 81388cefd29e032ececf69b0a7ea124bccf07998 Mon Sep 17 00:00:00 2001 From: Vidar Holen Date: Mon, 10 Jul 2017 22:53:26 -0700 Subject: [PATCH] Warn when calling functions before defining them. --- ShellCheck/ASTLib.hs | 2 ++ ShellCheck/Analytics.hs | 34 +++++++++++++++++++++++++++++++++- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/ShellCheck/ASTLib.hs b/ShellCheck/ASTLib.hs index c883fd6..80fad09 100644 --- a/ShellCheck/ASTLib.hs +++ b/ShellCheck/ASTLib.hs @@ -308,6 +308,7 @@ isBraceExpansion t = case t of T_BraceExpansion {} -> True; _ -> False -- Get the lists of commands from tokens that contain them, such as -- the body of while loops or branches of if statements. +getCommandSequences :: Token -> [[Token]] getCommandSequences t = case t of T_Script _ _ cmds -> [cmds] @@ -318,6 +319,7 @@ getCommandSequences t = T_ForIn _ _ _ cmds -> [cmds] T_ForArithmetic _ _ _ _ cmds -> [cmds] T_IfExpression _ thens elses -> map snd thens ++ [elses] + T_Annotation _ _ t -> getCommandSequences t _ -> [] -- Get a list of names of associative arrays diff --git a/ShellCheck/Analytics.hs b/ShellCheck/Analytics.hs index 909e60f..d153d2b 100644 --- a/ShellCheck/Analytics.hs +++ b/ShellCheck/Analytics.hs @@ -63,6 +63,7 @@ treeChecks = [ ,checkUnassignedReferences ,checkUncheckedCdPushdPopd ,checkArrayAssignmentIndices + ,checkUseBeforeDefinition ] runAnalytics :: AnalysisSpec -> [TokenComment] @@ -1522,7 +1523,7 @@ prop_subshellAssignmentCheck19 = verifyNotTree subshellAssignmentCheck "#!/bin/b subshellAssignmentCheck params t = let flow = variableFlow params check = findSubshelled flow [("oops",[])] Map.empty - in snd $ runWriter check + in execWriter check findSubshelled [] _ _ = return () @@ -2832,5 +2833,36 @@ checkPipeToNowhere _ t = T_FdRedirect _ _ T_HereString {} -> True _ -> False +prop_checkUseBeforeDefinition1 = verifyTree checkUseBeforeDefinition "f; f() { true; }" +prop_checkUseBeforeDefinition2 = verifyNotTree checkUseBeforeDefinition "f() { true; }; f" +prop_checkUseBeforeDefinition3 = verifyNotTree checkUseBeforeDefinition "if ! mycmd --version; then mycmd() { true; }; fi" +prop_checkUseBeforeDefinition4 = verifyNotTree checkUseBeforeDefinition "mycmd || mycmd() { f; }" +checkUseBeforeDefinition _ t = + execWriter $ evalStateT (mapM_ examine $ revCommands) Map.empty + where + examine t = case t of + T_Pipeline _ _ [T_Redirecting _ _ (T_Function _ _ _ name _)] -> + modify $ Map.insert name t + T_Annotation _ _ w -> examine w + T_Pipeline _ _ cmds -> do + m <- get + unless (Map.null m) $ + mapM_ (checkUsage m) $ concatMap recursiveSequences cmds + _ -> return () + + checkUsage map cmd = potentially $ do + name <- getCommandName cmd + def <- Map.lookup name map + return $ + err (getId cmd) 2218 + "This function is only defined later. Move the definition up." + + revCommands = reverse $ concat $ getCommandSequences t + recursiveSequences x = + let list = concat $ getCommandSequences x in + if null list + then [x] + else concatMap recursiveSequences list + return [] runTests = $( [| $(forAllProperties) (quickCheckWithResult (stdArgs { maxSuccess = 1 }) ) |])