From 970fd0a891e947cfae14ddd0af1832575ce88274 Mon Sep 17 00:00:00 2001 From: Profpatsch Date: Sat, 8 Apr 2023 15:08:52 +0200 Subject: feat(users/Profpatsch): Actually add the mentioned .hlint file Change-Id: I6d08379835160dfb941fe45e708cfc2f942acfbf Reviewed-on: https://cl.tvl.fyi/c/depot/+/8471 Autosubmit: Profpatsch Tested-by: BuildkiteCI Reviewed-by: Profpatsch --- users/Profpatsch/.hlint.yaml | 355 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 355 insertions(+) create mode 100644 users/Profpatsch/.hlint.yaml (limited to 'users') diff --git a/users/Profpatsch/.hlint.yaml b/users/Profpatsch/.hlint.yaml new file mode 100644 index 000000000000..09e6f2c95401 --- /dev/null +++ b/users/Profpatsch/.hlint.yaml @@ -0,0 +1,355 @@ +# HLint configuration file +# https://github.com/ndmitchell/hlint +# Run `hlint --default` to see the example configuration file. +########################## + +# WARNING: These need to be synced with the default-extensions field +# in the cabal file. +- arguments: [-XGHC2021, -XOverloadedRecordDot] + +# Ignore some builtin hints + +# often functions are more readable with explicit arguments +- ignore: { name: Eta reduce } + +# these redundancy warnings are just completely irrelevant +- ignore: { name: Redundant bracket } +- ignore: { name: Move brackets to avoid $ } +- ignore: { name: Redundant $ } +- ignore: { name: Redundant do } +- ignore: { name: Redundant multi-way if } + +# allow case-matching on bool, because why not +- ignore: { name: Use if } + +# hlint cannot distinguish actual newtypes from data types +# that accidentally have only one field +# (but might have more in the future). +# Since it’s a mostly irrelevant runtime optimization, we don’t care. +- ignore: { name: Use newtype instead of data } + +# these lead to harder-to-read/more implicit code +- ignore: { name: Use fmap } +- ignore: { name: Use <$> } +- ignore: { name: Use tuple-section } +- ignore: { name: Use forM_ } +# fst and snd are usually a code smell and should be explicit matches, _naming the ignored side. +- ignore: { name: Use fst } +- ignore: { name: Use snd } +- ignore: { name: Use fromMaybe } +- ignore: { name: Use const } +- ignore: { name: Replace case with maybe } +- ignore: { name: Replace case with fromMaybe } +- ignore: { name: Avoid lambda } +- ignore: { name: Avoid lambda using `infix` } +- ignore: { name: Use curry } +- ignore: { name: Use uncurry } +- ignore: { name: Use first } +- ignore: { name: Redundant first } +- ignore: { name: Use second } +- ignore: { name: Use bimap } +# just use `not x` +- ignore: { name: Use unless } +- ignore: { name: Redundant <&> } + +# list comprehensions are a seldomly used part of the Haskell language +# and they introduce syntactic overhead that is usually not worth the conciseness +- ignore: { name: Use list comprehension } + +# Seems to be buggy in cases +- ignore: { name: Use section } + +# multiple maps in a row are usually used for clarity, +# and the compiler will optimize them away, thank you very much. +- ignore: { name: Use map once } +- ignore: { name: Fuse foldr/map } +- ignore: { name: Fuse traverse/map } +- ignore: { name: Fuse traverse_/map } + +# this is silly, why would I use a special function if I can just (heh) `== Nothing` +- ignore: { name: Use isNothing } + +# The duplication heuristic is not very smart +# and more annoying than helpful. +# see https://github.com/ndmitchell/hlint/issues/1009 +- ignore: { name: Reduce duplication } + +# Stops the pattern match trick +- ignore: { name: Use record patterns } +- ignore: { name: Use null } +- ignore: { name: Use uncurry } + +# we don’t want void, see below +- ignore: { name: Use void } + +- functions: + # disallow Enum instance functions, they are partial + - name: Prelude.succ + within: [Relude.Extra.Enum] + message: "Dangerous, will fail for highest element" + - name: Prelude.pred + within: [Relude.Extra.Enum] + message: "Dangerous, will fail for lowest element" + - name: Prelude.toEnum + within: [] + message: "Extremely partial" + - name: Prelude.fromEnum + within: [] + message: "Dangerous for most uses" + - name: Prelude.enumFrom + within: [] + - name: Prelude.enumFromThen + within: [] + - name: Prelude.enumFromThenTo + within: [] + - name: Prelude.oundedEnumFrom + within: [] + - name: Prelude.boundedEnumFromThen + within: [] + + - name: Text.Read.readMaybe + within: + # The BSON ObjectId depends on Read for parsing + - Milkmap.Milkmap + - Milkmap.FieldData.Value + message: "`readMaybe` is probably not what you want for parsing values, please use the `FieldParser` module." + + # `void` discards its argument and is polymorphic, + # thus making it brittle in the face of code changes. + # (see https://tech.freckle.com/2020/09/23/void-is-a-smell/) + # Use an explicit `_ <- …` instead. + - name: Data.Functor.void + within: [] + message: "`void` leads to bugs. Use an explicit `_ <- …` instead" + + - name: Data.Foldable.length + within: [] + message: "`Data.Foldable.length` is dangerous to use, because it also works on types you wouldn’t expect, like `length (3,4) == 1` and `length (Just 2) == 1`. Use the `length` function for your specific type instead, for example `List.length` or `Map.length`." + + - name: Prelude.length + within: [] + message: "`Prelude.length` is dangerous to use, because it also works on types you wouldn’t expect, like `length (3,4) == 1` and `length (Just 2) == 1`. Use the `length` function for your specific type instead, for example `List.length` or `Map.length`." + + # Using an explicit lambda with its argument “underscored” + # is more clear in every case. + # e.g. `const True` => `\_request -> True` + # shows the reader that the ignored argument was a request. + - name: Prelude.const + within: [] + message: "Replace `const` with an explicit lambda with type annotation for code clarity and type safety, e.g.: `const True` => `\\(_ :: Request) -> True`. If you really don’t want to spell out the type (which might lead to bugs!), you can also use something like `\_request -> True`." + + - name: Data.List.nub + within: [] + message: "O(n²), use `Data.Containers.ListUtils.nubOrd" + + - name: Prelude.maximum + within: [] + message: "`maximum` crashes on empty list; use non-empty lists and `maximum1`" + + - name: Data.List.maximum + within: [] + message: "`maximum` crashes on empty list; use non-empty lists and `maximum1`" + + - name: Prelude.minimum + within: [] + message: "`minimum` crashes on empty list; use non-empty lists and `minimum1`" + + - name: Data.List.minimum + within: [] + message: "`minimum` crashes on empty list; use non-empty lists and `minimum1`" + + - name: Data.Foldable.maximum + within: [] + message: "`maximum` crashes on empty foldable stucture; use Foldable1 and `maximum1`." + + - name: Data.Foldable.minimum + within: [] + message: "`minimum` crashes on empty foldable stucture; use Foldable1 and `minimum1`." + + # Using prelude functions instead of stdlib functions + + - name: "Data.Text.Encoding.encodeUtf8" + within: ["MyPrelude"] + message: "Use `textToBytesUtf8`" + + - name: "Data.Text.Lazy.Encoding.encodeUtf8" + within: ["MyPrelude"] + message: "Use `textToBytesUtf8Lazy`" + + - name: "Data.Text.Encoding.decodeUtf8'" + within: ["MyPrelude"] + message: "Use `bytesToTextUtf8`" + + - name: "Data.Text.Encoding.Lazy.decodeUtf8'" + within: ["MyPrelude"] + message: "Use `bytesToTextUtf8Lazy`" + + - name: "Data.Text.Encoding.decodeUtf8" + within: ["MyPrelude"] + message: "Either check for errors with `bytesToTextUtf8`, decode leniently with unicode replacement characters with `bytesToTextUtf8Lenient` or use the crashing version `bytesToTextUtf8Unsafe` (discouraged)." + + - name: "Data.Text.Encoding.Lazy.decodeUtf8" + within: ["MyPrelude"] + message: "Either check for errors with `bytesToTextUtf8Lazy`, decode leniently with unicode replacement characters with `bytesToTextUtf8LenientLazy` or use the crashing version `bytesToTextUtf8UnsafeLazy` (discouraged)." + + - name: "Data.Text.Lazy.toStrict" + within: ["MyPrelude"] + message: "Use `toStrict`" + + - name: "Data.Text.Lazy.fromStrict" + within: ["MyPrelude"] + message: "Use `toLazy`" + + - name: "Data.ByteString.Lazy.toStrict" + within: ["MyPrelude"] + message: "Use `toStrictBytes`" + + - name: "Data.ByteString.Lazy.fromStrict" + within: ["MyPrelude"] + message: "Use `toLazyBytes`" + + - name: "Data.Text.unpack" + within: ["MyPrelude"] + message: "Use `textToString`" + + - name: "Data.Text.pack" + within: ["MyPrelude"] + message: "Use `stringToText`" + + - name: "Data.Maybe.listToMaybe" + within: [] + message: | + `listToMaybe`` throws away everything but the first element of a list (it is essentially `safeHead`). + If that is what you want, please use a pattern match like + + ``` + case xs of + [] -> … + (x:_) -> … + ``` + + - name: "Data.List.head" + within: [] + message: | + `List.head` fails on an empty list. I didn’t think I have to say this, but please use a pattern match on the list, like: + + ``` + case xs of + [] -> … error handling … + (x:_) -> … + ``` + + Also think about why the rest of the list should be ignored. + + - name: "Prelude.head" + within: [] + message: | + `List.head` fails on an empty list. I didn’t think I have to say this, but please use a pattern match on the list, like. + + ``` + case xs of + [] -> … error handling … + (x:_) -> … + ``` + + Also think about why the rest of the list should be ignored. + + - name: "Data.Maybe.fromJust" + within: [] + message: | + `Maybe.fromJust` is obviously partial. Please use a pattern match. + + In case you actually want to throw an error on an empty list, + please add an error message, like so: + + ``` + myMaybe & annotate "my error message" & unwrapError + ``` + + If you are in `IO`, use `unwrapIOError` instead, + or throw a monad-specific error. + + - name: "Data.Either.fromLeft" + within: [] + message: | + `Either.fromLeft` is obviously partial. Please use a pattern match. + + - name: "Data.Either.fromRight" + within: [] + message: | + `Either.fromRight` is obviously partial. Please use a pattern match. + +# Make restricted functions into an error if found +- error: { name: "Avoid restricted function, see comment in .hlint.yaml" } + +# Some functions that have (more modern) aliases. +# They are not dangerous per se, +# but we want to make it easier to read our code so we should +# make sure we don’t use too many things that are renames. + +- hint: + lhs: "undefined" + rhs: "todo" + note: "`undefined` is a silent error, `todo` will display a warning as long as it exists in the code." + +- hint: + lhs: "return" + rhs: "pure" + note: "Use `pure` from `Applicative` instead, it’s the exact same function." + +- hint: + lhs: "mapM" + rhs: "traverse" + note: "Use `traverse` from `Traversable` instead. It’s the exact same function." + +- hint: + lhs: "mapM_" + rhs: "traverse_" + note: "Use `traverse_` from `Traversable` instead. It’s the exact same function." + +- hint: + lhs: "forM" + rhs: "for" + note: "Use `for` from `Traversable` instead. It’s the exact same function." + +- hint: + lhs: "forM_" + rhs: "for_" + note: "Use `for_` from `Traversable` instead. It’s the exact same function." + +- hint: + lhs: "stringToText (show x)" + rhs: "showToText x" + +- hint: + lhs: "Data.Set.toList (Data.Set.fromList x)" + rhs: "List.nubOrd x" + note: "`nubOrd` removes duplicate elements from a list." + +- modules: + # Disallowed Modules + - name: Data.Map + within: [] + message: "Lazy maps leak space, use `import Data.Map.Strict as Map` instead" + - name: Control.Monad.Writer + within: [] + message: "Lazy writers leak space, use `Control.Monad.Trans.Writer.CPS` instead" + - name: Control.Monad.Trans.Writer.Lazy + within: [] + message: "Lazy writers leak space, use `Control.Monad.Trans.Writer.CPS` instead" + - name: Control.Monad.Trans.Writer.Strict + within: [] + message: "Even strict writers leak space, use `Control.Monad.Trans.Writer.CPS` instead" + + # Qualified module imports + - { name: Data.Map.Strict, as: Map } + - { name: Data.HashMap.Strict, as: HashMap } + - { name: Data.Set, as: Set } + - { name: Data.ByteString.Char8, as: Char8 } + - { name: Data.ByteString.Lazy.Char8, as: Char8.Lazy } + - { name: Data.Text, as: Text } + - { name: Data.Vector, as: Vector } + - { name: Data.Vault.Lazy, as: Vault } + - { name: Data.Aeson, as: Json } + - { name: Data.Aeson.Types, as: Json } + - { name: Data.Aeson.BetterErrors as Json } -- cgit 1.4.1