Skip to content

Commit c24d79a

Browse files
committed
Add rules object.
This should prevent GitHub from using an entire message as an issue title.
1 parent b1415a7 commit c24d79a

File tree

5 files changed

+344
-5
lines changed

5 files changed

+344
-5
lines changed

package.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ library:
5353
- aeson
5454
- base64
5555
- bytestring
56+
- containers
5657
- filepath
5758
- github-rest
5859
- process
@@ -94,6 +95,7 @@ tests:
9495
- QuickCheck
9596
- aeson
9697
- base64
98+
- bytestring
9799
- github-rest
98100
- quickcheck-instances
99101
- text

src/Format.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ limitations under the License.
1515
-}
1616

1717
-- |
18-
-- Description: Format messages for GitHub
18+
-- Description: Format messages for GitHub.
1919
-- Copyright: Copyright 2023 Google LLC
2020
-- License: Apache-2.0
2121
-- Maintainer: [email protected]

src/Rules.hs

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
{-
2+
Copyright 2023 Google LLC
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
https://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
-}
16+
17+
-- |
18+
-- Description: Add rules for GitHub.
19+
-- Copyright: Copyright 2023 Google LLC
20+
-- License: Apache-2.0
21+
-- Maintainer: [email protected]
22+
--
23+
-- At the time of writing, HLint does not add a separate @rules@ object
24+
-- because there is not much that it would add that is not already in the results.
25+
-- However, GitHub would use it for the title of a code scanning issue if available,
26+
-- which is frequently better than using the entire message when it is not available.
27+
--
28+
-- This module adds a @rules@ object to SARIF output if it does not already exist.
29+
-- It will use the rule ID as the name of the rule,
30+
-- which GitHub will use as the title of an issue.
31+
--
32+
-- It would have been nice to include the notes in full descriptions.
33+
-- However, we should not because there can be multiple hints
34+
-- with the same name but different notes.
35+
module Rules (add) where
36+
37+
import Data.Aeson
38+
import Data.Aeson.KeyMap hiding (map)
39+
import Data.Set qualified as Set
40+
import Data.Text (Text)
41+
import Data.Vector qualified as Vector
42+
import Prelude hiding (lookup)
43+
44+
-- | If a @sarifLog@ object does not have a @rules@ object
45+
-- in a @driver@ object inside a @tool@ object, add one based on the results.
46+
-- This will make the code scanning issue titles in GitHub more tidy.
47+
add :: Value -> Value
48+
add (Object v) = Object $ mapWithKey addRulesToRuns v
49+
where
50+
addRulesToRuns "runs" (Array us) = Array $ fmap addRulesToRun us
51+
addRulesToRuns _ u = u
52+
53+
addRulesToRun (Object u)
54+
| member "tool" u = Object $ mapWithKey addRulesToTool u
55+
| otherwise =
56+
Object $
57+
insert
58+
"tool"
59+
( Object . singleton "driver" $
60+
Object . singleton "rules" $
61+
rulesArray
62+
)
63+
u
64+
addRulesToRun u = u
65+
66+
addRulesToTool "tool" (Object u)
67+
| member "driver" u = Object $ mapWithKey addRulesToDriver u
68+
| otherwise = Object $ insert "driver" (Object $ singleton "rules" rulesArray) u
69+
addRulesToTool _ u = u
70+
71+
addRulesToDriver "driver" o@(Object u)
72+
| member "rules" u = o
73+
| otherwise = Object $ insert "rules" rulesArray u
74+
addRulesToDriver _ u = u
75+
76+
rules = Set.unions $ mapWithKey getRulesFromRuns v
77+
rulesArray = Array $ Vector.fromList $ map formatRule $ Set.toList rules
78+
79+
getRulesFromRuns "runs" (Array us) = Set.unions $ fmap getRulesFromRun us
80+
getRulesFromRuns _ _ = Set.empty
81+
82+
getRulesFromRun (Object u) = Set.unions $ mapWithKey getRulesFromResults u
83+
getRulesFromRun _ = Set.empty
84+
85+
getRulesFromResults "results" (Array us) = Set.unions $ fmap getRulesFromResult us
86+
getRulesFromResults _ _ = Set.empty
87+
88+
getRulesFromResult (Object u)
89+
| Just (String r) <- lookup "ruleId" u = Set.singleton r
90+
| otherwise = Set.empty
91+
getRulesFromResult _ = Set.empty
92+
add v = v
93+
94+
-- | Formats a object which will be placed in a @rules@ object.
95+
-- Basically the minimum that GitHub requires,
96+
-- although we cannot do much more than use the hint name everywhere.
97+
formatRule :: Text -> Value
98+
formatRule ruleId =
99+
Object . fromList $
100+
[ ("id", String ruleId),
101+
("name", String ruleId),
102+
("shortDescription", Object $ singleton "text" $ String ruleId),
103+
("fullDescription", Object $ singleton "text" $ String ruleId)
104+
]

src/Scan.hs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ import FilePath qualified
4343
import Fingerprint qualified
4444
import Format (formatMessages)
4545
import GitHub.REST
46+
import Rules qualified
4647
import System.Environment (getEnvironment)
4748
import System.Exit (ExitCode (ExitSuccess), die, exitWith)
4849
import System.Process (proc, readCreateProcessWithExitCode)
@@ -103,15 +104,20 @@ invoke args = do
103104
annotate :: Context -> ByteString -> IO ()
104105
annotate context output = do
105106
env <- getEnvironment
106-
let annotated = AutomationDetails.add env (category context) <$> value
107-
let annotated' = formatMessages . FilePath.normalize . Fingerprint.fill <$> annotated
107+
let annotated =
108+
formatMessages
109+
. FilePath.normalize
110+
. Fingerprint.fill
111+
. Rules.add
112+
. AutomationDetails.add env (category context)
113+
<$> value
108114

109115
when (runnerDebug context) $ do
110116
putStrLn "rewritten output:"
111-
print annotated'
117+
print annotated
112118
putStrLn ""
113119

114-
case annotated' of
120+
case annotated of
115121
Nothing -> die $ "invalid encoding\n" <> show output <> "\n"
116122
Just output' -> send context $ encode output'
117123
where

test/RulesSpec.hs

Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
{-
2+
Copyright 2023 Google LLC
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
https://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
-}
16+
17+
-- |
18+
-- Description: Tests for the "Rules" module.
19+
-- Copyright: Copyright 2023 Google LLC
20+
-- License: Apache-2.0
21+
-- Maintainer: [email protected]
22+
module RulesSpec (spec) where
23+
24+
import Data.Aeson
25+
import Data.Aeson.KeyMap qualified as KeyMap
26+
import Data.Text qualified as Text
27+
import Data.Vector qualified as Vector
28+
import Rules
29+
import Test.Hspec
30+
31+
spec :: Spec
32+
spec = do
33+
describe "add" $ do
34+
it "adds rules when there was no tool object" $
35+
add
36+
( Object . KeyMap.singleton "runs" $
37+
Array . Vector.fromList $
38+
[ Object . KeyMap.singleton "results" . Array . Vector.fromList $
39+
[ Object . KeyMap.fromList $
40+
[ ("ruleId", String "use 1")
41+
],
42+
Object . KeyMap.fromList $
43+
[ ("ruleId", String "use 2"),
44+
( "message",
45+
Object . KeyMap.singleton "text" . String $
46+
Text.unlines
47+
[ "Some warning",
48+
"Found:",
49+
" not (x-y < 0 || x-y > 0)",
50+
"Perhaps:",
51+
" x == y",
52+
"Note: this is just better"
53+
]
54+
)
55+
]
56+
]
57+
]
58+
)
59+
`shouldBe` Object
60+
( KeyMap.singleton "runs" $
61+
Array . Vector.singleton . Object . KeyMap.fromList $
62+
[ ( "tool",
63+
Object . KeyMap.singleton "driver" $
64+
Object . KeyMap.singleton "rules" $
65+
Array . Vector.fromList $
66+
[ Object . KeyMap.fromList $
67+
[ ("id", String "use 1"),
68+
("name", String "use 1"),
69+
( "shortDescription",
70+
Object $ KeyMap.singleton "text" $ String "use 1"
71+
),
72+
( "fullDescription",
73+
Object $ KeyMap.singleton "text" $ String "use 1"
74+
)
75+
],
76+
Object . KeyMap.fromList $
77+
[ ("id", String "use 2"),
78+
("name", String "use 2"),
79+
( "shortDescription",
80+
Object . KeyMap.singleton "text" $ String "use 2"
81+
),
82+
( "fullDescription",
83+
Object . KeyMap.singleton "text" $ String "use 2"
84+
)
85+
]
86+
]
87+
),
88+
( "results",
89+
Array . Vector.fromList $
90+
[ Object . KeyMap.fromList $
91+
[ ("ruleId", String "use 1")
92+
],
93+
Object . KeyMap.fromList $
94+
[ ("ruleId", String "use 2"),
95+
( "message",
96+
Object . KeyMap.singleton "text" . String $
97+
Text.unlines
98+
[ "Some warning",
99+
"Found:",
100+
" not (x-y < 0 || x-y > 0)",
101+
"Perhaps:",
102+
" x == y",
103+
"Note: this is just better"
104+
]
105+
)
106+
]
107+
]
108+
)
109+
]
110+
)
111+
112+
it "adds rules when there was no driver object" $
113+
add
114+
( Object . KeyMap.singleton "runs" $
115+
Array . Vector.fromList $
116+
[ Object . KeyMap.fromList $
117+
[ ( "tool",
118+
Object . KeyMap.singleton "extensions" . Array . Vector.singleton $
119+
Object . KeyMap.singleton "name" $
120+
String "random extension"
121+
),
122+
( "results",
123+
Array . Vector.fromList $
124+
[ Object . KeyMap.fromList $
125+
[ ("ruleId", String "use 1")
126+
]
127+
]
128+
)
129+
]
130+
]
131+
)
132+
`shouldBe` Object
133+
( KeyMap.singleton "runs" $
134+
Array . Vector.singleton . Object . KeyMap.fromList $
135+
[ ( "tool",
136+
Object . KeyMap.fromList $
137+
[ ( "driver",
138+
Object . KeyMap.singleton "rules" $
139+
Array . Vector.fromList $
140+
[ Object . KeyMap.fromList $
141+
[ ("id", String "use 1"),
142+
("name", String "use 1"),
143+
( "shortDescription",
144+
Object $ KeyMap.singleton "text" $ String "use 1"
145+
),
146+
( "fullDescription",
147+
Object $ KeyMap.singleton "text" $ String "use 1"
148+
)
149+
]
150+
]
151+
),
152+
( "extensions",
153+
Array . Vector.singleton $
154+
Object . KeyMap.singleton "name" $
155+
String "random extension"
156+
)
157+
]
158+
),
159+
( "results",
160+
Array . Vector.fromList $
161+
[ Object . KeyMap.fromList $
162+
[("ruleId", String "use 1")]
163+
]
164+
)
165+
]
166+
)
167+
168+
it "does not overwrite existing rules" $
169+
add
170+
( Object . KeyMap.singleton "runs" $
171+
Array . Vector.fromList $
172+
[ Object . KeyMap.fromList $
173+
[ ( "tool",
174+
Object . KeyMap.singleton "driver" . Object $
175+
KeyMap.singleton "rules" . Array . Vector.fromList $
176+
[ Object . KeyMap.fromList $
177+
[ ("id", String "use 1"),
178+
("name", String "The first rule"),
179+
( "shortDescription",
180+
Object $ KeyMap.singleton "text" $ String "See rule 1"
181+
),
182+
( "fullDescription",
183+
Object $ KeyMap.singleton "text" $ String "Really see rule 1"
184+
)
185+
]
186+
]
187+
),
188+
( "results",
189+
Array . Vector.fromList $
190+
[ Object . KeyMap.fromList $
191+
[ ("ruleId", String "use 1")
192+
]
193+
]
194+
)
195+
]
196+
]
197+
)
198+
`shouldBe` Object
199+
( KeyMap.singleton "runs" $
200+
Array . Vector.singleton . Object . KeyMap.fromList $
201+
[ ( "tool",
202+
Object . KeyMap.fromList $
203+
[ ( "driver",
204+
Object . KeyMap.singleton "rules" $
205+
Array . Vector.fromList $
206+
[ Object . KeyMap.fromList $
207+
[ ("id", String "use 1"),
208+
("name", String "The first rule"),
209+
( "shortDescription",
210+
Object $ KeyMap.singleton "text" $ String "See rule 1"
211+
),
212+
( "fullDescription",
213+
Object $ KeyMap.singleton "text" $ String "Really see rule 1"
214+
)
215+
]
216+
]
217+
)
218+
]
219+
),
220+
( "results",
221+
Array . Vector.fromList $
222+
[ Object . KeyMap.fromList $
223+
[("ruleId", String "use 1")]
224+
]
225+
)
226+
]
227+
)

0 commit comments

Comments
 (0)