diff --git a/.github/workflows/elm_tests.yml b/.github/workflows/elm_tests.yml index 06f618d..28370c0 100644 --- a/.github/workflows/elm_tests.yml +++ b/.github/workflows/elm_tests.yml @@ -16,7 +16,7 @@ jobs: # Re-use node_modules between runs until package.json or package-lock.json changes. - name: Cache node_modules id: cache-node_modules - uses: actions/cache@9b0c1fce7a93df8e3bb8926b0d6e9d89e92f20a7 + uses: actions/cache@7de21022a7b6824c106a9847befcbd8154b45b6a with: path: node_modules key: node_modules-${{ hashFiles('package.json', 'package-lock.json') }} @@ -28,7 +28,7 @@ jobs: # review/elm.json changes. The Elm compiler saves downloaded Elm packages # to ~/.elm, and elm-tooling saves downloaded tool executables there. - name: Cache ~/.elm - uses: actions/cache@9b0c1fce7a93df8e3bb8926b0d6e9d89e92f20a7 + uses: actions/cache@7de21022a7b6824c106a9847befcbd8154b45b6a with: path: ~/.elm key: elm-${{ hashFiles('elm-tooling.json', 'elm.json', 'review/elm.json') }} @@ -83,7 +83,7 @@ jobs: - name: Cache node_modules id: cache-node_modules - uses: actions/cache@9b0c1fce7a93df8e3bb8926b0d6e9d89e92f20a7 + uses: actions/cache@7de21022a7b6824c106a9847befcbd8154b45b6a with: path: node_modules key: node_modules-${{ hashFiles('package.json', 'package-lock.json') }} @@ -92,7 +92,7 @@ jobs: node_modules- - name: Cache ~/.elm - uses: actions/cache@9b0c1fce7a93df8e3bb8926b0d6e9d89e92f20a7 + uses: actions/cache@7de21022a7b6824c106a9847befcbd8154b45b6a with: path: ~/.elm key: elm-${{ hashFiles('elm-tooling.json', 'elm.json', 'review/elm.json') }} diff --git a/src/Exercise/BirdCount.elm b/src/Exercise/BirdCount.elm new file mode 100644 index 0000000..c00fe73 --- /dev/null +++ b/src/Exercise/BirdCount.elm @@ -0,0 +1,26 @@ +module Exercise.BirdCount exposing (doNotUseListModule, ruleConfig) + +import Analyzer exposing (CalledExpression(..), CalledFrom(..), Find(..)) +import Comment exposing (Comment, CommentType(..)) +import Dict +import Review.Rule exposing (Rule) +import RuleConfig exposing (AnalyzerRule(..), RuleConfig) + + +ruleConfig : RuleConfig +ruleConfig = + { restrictToFiles = Just [ "src/BirdCount.elm" ] + , rules = + [ CustomRule doNotUseListModule + (Comment "elm.bird-count.do_not_use_list" Essential Dict.empty) + ] + } + + +doNotUseListModule : Comment -> Rule +doNotUseListModule = + Analyzer.functionCalls + { calledFrom = Anywhere + , findExpressions = [ AnyFromExternalModule [ "List" ] ] + , find = None + } diff --git a/src/ReviewConfig.elm b/src/ReviewConfig.elm index ab9a680..bd2dd0b 100644 --- a/src/ReviewConfig.elm +++ b/src/ReviewConfig.elm @@ -7,6 +7,7 @@ import Common.UseCamelCase import Exercise.AnnalynsInfiltration import Exercise.Bandwagoner import Exercise.BettysBikeShop +import Exercise.BirdCount import Exercise.BlorkemonCards import Exercise.CustomSet import Exercise.GottaSnatchEmAll @@ -56,6 +57,7 @@ ruleConfigs = , Exercise.GottaSnatchEmAll.ruleConfig , Exercise.AnnalynsInfiltration.ruleConfig , Exercise.LuciansLusciousLasagna.ruleConfig + , Exercise.BirdCount.ruleConfig -- Practice Exercises , Exercise.Strain.ruleConfig diff --git a/tests/Exercise/BirdCountTest.elm b/tests/Exercise/BirdCountTest.elm new file mode 100644 index 0000000..2fbc5c9 --- /dev/null +++ b/tests/Exercise/BirdCountTest.elm @@ -0,0 +1,170 @@ +module Exercise.BirdCountTest exposing (tests) + +import Comment exposing (Comment, CommentType(..)) +import Dict +import Exercise.BirdCount as BirdCount +import Review.Rule exposing (Rule) +import Review.Test +import RuleConfig +import Test exposing (Test, describe, test) +import TestHelper + + +tests : Test +tests = + describe "BirdCountTest" + [ exemplar + , usingList + ] + + +rules : List Rule +rules = + BirdCount.ruleConfig |> .rules |> List.map RuleConfig.analyzerRuleToRule + + +exemplar : Test +exemplar = + test "should not report anything for the example solution" <| + \() -> + TestHelper.expectNoErrorsForRules rules + """ +module BirdCount exposing (busyDays, hasDayWithoutBirds, incrementDayCount, today, total) + + +today : List Int -> Maybe Int +today counts = + case counts of + [] -> + Nothing + + head :: _ -> + Just head + + +incrementDayCount : List Int -> List Int +incrementDayCount counts = + case counts of + [] -> + [ 1 ] + + head :: tail -> + (head + 1) :: tail + + +hasDayWithoutBirds : List Int -> Bool +hasDayWithoutBirds counts = + case counts of + [] -> + False + + 0 :: _ -> + True + + _ :: tail -> + hasDayWithoutBirds tail + + +total : List Int -> Int +total counts = + case counts of + [] -> + 0 + + head :: tail -> + head + total tail + + +busyDays : List Int -> Int +busyDays counts = + case counts of + [] -> + 0 + + head :: tail -> + if head >= 5 then + 1 + busyDays tail + + else + busyDays tail +""" + + +usingList : Test +usingList = + let + comment = + Comment "elm.bird-count.do_not_use_list" Essential Dict.empty + in + describe "solutions that use the List function" <| + [ test "using List.head for today" <| + \() -> + """ +module BirdCount exposing (today) + +today : List Int -> Maybe Int +today = List.head +""" + |> Review.Test.run (BirdCount.doNotUseListModule comment) + |> Review.Test.expectErrors + [ TestHelper.createExpectedErrorUnder comment "List.head" ] + , test "using List.any for hasDayWithoutBirds" <| + \() -> + """ +module BirdCount exposing (hasDayWithoutBirds) + +hasDayWithoutBirds : List Int -> Bool +hasDayWithoutBirds = List.any ((==) 0) +""" + |> Review.Test.run (BirdCount.doNotUseListModule comment) + |> Review.Test.expectErrors + [ TestHelper.createExpectedErrorUnder comment "List.any" ] + , test "using List.sum for total" <| + \() -> + """ +module BirdCount exposing (total) + +total : List Int -> Int +total = List.sum +""" + |> Review.Test.run (BirdCount.doNotUseListModule comment) + |> Review.Test.expectErrors + [ TestHelper.createExpectedErrorUnder comment "List.sum" ] + , test "using List.filter and List.length for busyDays" <| + \() -> + """ +module BirdCount exposing (busyDays) + +busyDays : List Int -> Int +busyDays = List.filter ((>=) 5) >> List.length +""" + |> Review.Test.run (BirdCount.doNotUseListModule comment) + |> Review.Test.expectErrors + [ TestHelper.createExpectedErrorUnder comment "List.filter" ] + , test "using List with an alias" <| + \() -> + """ +module BirdCount exposing (today) +import List as L + +today : List Int -> Maybe Int +today = L.head +""" + |> Review.Test.run (BirdCount.doNotUseListModule comment) + |> Review.Test.expectErrors + [ TestHelper.createExpectedErrorUnder comment "L.head" ] + , test "using List with exposing" <| + \() -> + """ +module BirdCount exposing (today) +import List exposing (head) + +today : List Int -> Maybe Int +today = head +""" + |> Review.Test.run (BirdCount.doNotUseListModule comment) + |> Review.Test.expectErrors + [ TestHelper.createExpectedErrorUnder comment "head" + |> Review.Test.atExactly { start = { row = 6, column = 9 }, end = { row = 6, column = 13 } } + ] + ]