hlint-3.1.6/0000755000000000000000000000000013674744766011055 5ustar0000000000000000hlint-3.1.6/Setup.hs0000644000000000000000000000005612455035033012462 0ustar0000000000000000import Distribution.Simple main = defaultMain hlint-3.1.6/README.md0000644000000000000000000006420313671470061012316 0ustar0000000000000000# HLint [![Hackage version](https://img.shields.io/hackage/v/hlint.svg?label=Hackage)](https://hackage.haskell.org/package/hlint) [![Stackage version](https://www.stackage.org/package/hlint/badge/nightly?label=Stackage)](https://www.stackage.org/package/hlint) [![Linux build status](https://img.shields.io/travis/ndmitchell/hlint/master.svg?label=Linux%20build)](https://travis-ci.org/ndmitchell/hlint) [![Windows build status](https://img.shields.io/appveyor/ci/ndmitchell/hlint/master.svg?label=Windows%20build)](https://ci.appveyor.com/project/ndmitchell/hlint) HLint is a tool for suggesting possible improvements to Haskell code. These suggestions include ideas such as using alternative functions, simplifying code and spotting redundancies. This document is structured as follows: * [Installing and running HLint](#installing-and-running-hlint) * [FAQ](#faq) * [Customizing the hints](#customizing-the-hints) * [Hacking HLint](#hacking-hlint) ### Bugs and limitations Bugs can be reported [on the bug tracker](https://github.com/ndmitchell/hlint/issues). There are some issues that I do not intend to fix: * HLint operates on each module at a time in isolation, as a result HLint does not know about types or which names are in scope. * The presence of `seq` may cause some hints (i.e. eta-reduction) to change the semantics of a program. * Some transformed programs may require additional type signatures, particularly if the transformations trigger the monomorphism restriction or involve rank-2 types. * Sometimes HLint will change the code in a way that causes values to default to different types, which may change the behaviour. * HLint assumes duplicate identical expressions within in a single expression are used at the same type. * The `RebindableSyntax` extension can cause HLint to suggest incorrect changes. * HLint can be configured with knowledge of C Pre Processor flags, but it can only see one conditional set of code at a time. * HLint turns on many language extensions so it can parse more documents, occasionally some break otherwise legal syntax - e.g. `{-#INLINE foo#-}` doesn't work with `MagicHash`, `foo $bar` means something different with `TemplateHaskell`. These extensions can be disabled with `-XNoMagicHash` or `-XNoTemplateHaskell` etc. * HLint doesn't run any custom preprocessors, e.g. [markdown-unlit](https://hackage.haskell.org/package/markdown-unlit) or [record-dot-preprocessor](https://hackage.haskell.org/package/record-dot-preprocessor), so code making use of them will usually fail to parse. ## Installing and running HLint Installation follows the standard pattern of any Haskell library or program: type `cabal update` to update your local hackage database, then `cabal install hlint` to install HLint. Once HLint is installed, run `hlint source` where `source` is either a Haskell file, or a directory containing Haskell files. A directory will be searched recursively for any files ending with `.hs` or `.lhs`. For example, running HLint over darcs would give: ```console $ hlint darcs-2.1.2 darcs-2.1.2\src\CommandLine.lhs:94:1: Warning: Use concatMap Found: concat $ map escapeC s Perhaps: concatMap escapeC s darcs-2.1.2\src\CommandLine.lhs:103:1: Suggestion: Use fewer brackets Found: ftable ++ (map (\ (c, x) -> (toUpper c, urlEncode x)) ftable) Perhaps: ftable ++ map (\ (c, x) -> (toUpper c, urlEncode x)) ftable darcs-2.1.2\src\Darcs\Patch\Test.lhs:306:1: Warning: Use a more efficient monadic variant Found: mapM (delete_line (fn2fp f) line) old Perhaps: mapM_ (delete_line (fn2fp f) line) old ... lots more hints ... ``` Each hint says which file/line the hint relates to, how serious an issue it is, a description of the hint, what it found, and what you might want to replace it with. In the case of the first hint, it has suggested that instead of applying `concat` and `map` separately, it would be better to use the combination function `concatMap`. The first hint is marked as an warning, because using `concatMap` in preference to the two separate functions is always desirable. In contrast, the removal of brackets is probably a good idea, but not always. Reasons that a hint might be a suggestion include requiring an additional import, something not everyone agrees on, and functions only available in more recent versions of the base library. **Bug reports:** The suggested replacement should be equivalent - please report all incorrect suggestions not mentioned as known limitations. ### Suggested usage HLint usage tends to proceed in three distinct phases: 1. Initially, run `hlint . --report` to generate `report.html` containing a list of all issues HLint has found. Fix those you think are worth fixing and keep repeating. 1. Once you are happy, run `hlint . --default > .hlint.yaml`, which will generate a settings file ignoring all the hints currently outstanding. Over time you may wish to edit the list. 1. For larger projects, add [custom hints or rules](#customizing-the-hints). Most hints are intended to be a good idea in most circumstances, but not universally - judgement is required. When contributing to someone else's project, HLint can identify pieces of code to look at, but only make changes you consider improvements - not merely to adhere to HLint rules. ### Running with Continuous Integration On CI you might wish to run `hlint .` (or `hlint src` if you only want to check the `src` directory). To avoid the cost of compilation you may wish to fetch the [latest HLint binary release](https://github.com/ndmitchell/hlint/releases/latest). For the CI systems [Travis](https://travis-ci.org/), [Appveyor](https://www.appveyor.com/) and [Azure Pipelines](https://azure.microsoft.com/en-gb/services/devops/pipelines/) add the line: ```sh curl -sSL https://raw.github.com/ndmitchell/hlint/master/misc/run.sh | sh -s . ``` The arguments after `-s` are passed to `hlint`, so modify the final `.` if you want other arguments. This command works on Windows, Mac and Linux. ### Integrations HLint is integrated into lots of places: * Lots of editors have HLint plugins (quite a few have more than one HLint plugin). * HLint is part of the multiple editor plugins [ghc-mod](https://hackage.haskell.org/package/ghc-mod) and [Intero](https://github.com/commercialhaskell/intero). * [HLint Source Plugin](https://github.com/ocharles/hlint-source-plugin) makes HLint available as a GHC plugin. * [Splint](https://github.com/tfausak/splint) is another source plugin that doesn't require reparsing the GHC source if you are on the latest GHC version. * [Code Climate](https://docs.codeclimate.com/v1.0/docs/hlint) is a CI for analysis which integrates HLint. * [Danger](http://allocinit.io/haskell/danger-and-hlint/) can be used to automatically comment on pull requests with HLint suggestions. * [Restyled](https://restyled.io) includes an HLint Restyler to automatically run `hlint --refactor` on files changed in GitHub Pull Requests. * [lpaste](http://lpaste.net/) integrates with HLint - suggestions are shown at the bottom. * [hlint-test](https://hackage.haskell.org/package/hlint-test) helps you write a small test runner with HLint. * [hint-man](https://github.com/apps/hint-man) automatically submits reviews to opened pull requests in your repositories with inline hints. ### Automatically Applying Hints HLint can automatically apply some suggestions using the `--refactor` flag. If passed, instead of printing out the hints, HLint will output the refactored file on stdout. For `--refactor` to work it is necessary to have the `refactor` executable from the [`apply-refact`](https://github.com/mpickering/apply-refact) package on your `$PATH`. HLint uses that tool to perform the refactoring. When using `--refactor` you can pass additional options to the `refactor` binary using `--refactor-options` flag. Some useful flags include `-i` (which replaces the original file) and `-s` (which asks for confirmation before performing a hint). The `--with-refactor` flag can be used to specify an alternative location for the `refactor` binary. Simple bindings for [Vim](https://github.com/mpickering/hlint-refactor-vim), [Emacs](https://github.com/mpickering/hlint-refactor-mode) and [Atom](https://github.com/mpickering/hlint-refactor-atom) are available. While the `--refactor` flag is useful, it is not complete or at the same level of quality as the rest of HLint: * Some hints don't generate refactorings. Examples include excess duplication, renaming hints and eta reduction hints. * There are bugs in the underlying `refactor` tool which cause the resultant file to be incorrect. For example, `[1,2..3]` comes out as `[12..3]` ([#389](https://github.com/ndmitchell/hlint/issues/389)), even if there isn't a hint that touches it. ### Reports HLint can generate a lot of information, making it difficult to search for particular types of errors. The `--report` flag will cause HLint to generate a report file in HTML, which can be viewed interactively. Reports are recommended when there are more than a handful of hints. ### Language Extensions HLint enables most Haskell extensions, disabling only those which steal too much syntax (e.g. Arrows, TransformListComp and TypeApplications). Individual extensions can be enabled or disabled with, for instance, `-XArrows`, or `-XNoMagicHash`. The flag `-XHaskell2010` selects Haskell 2010 compatibility. You can also pass them via `.hlint.yaml` file. For example: `- arguments: [-XArrows]`. ### Emacs Integration Emacs integration has been provided by [Alex Ott](http://xtalk.msk.su/~ott/). The integration is similar to compilation-mode, allowing navigation between errors. The script is at [hs-lint.el](https://raw.githubusercontent.com/ndmitchell/hlint/master/data/hs-lint.el), and a copy is installed locally in the data directory. To use, add the following code to the Emacs init file: ```guile (require 'hs-lint) (defun my-haskell-mode-hook () (local-set-key "\C-cl" 'hs-lint)) (add-hook 'haskell-mode-hook 'my-haskell-mode-hook) ``` ### GHCi Integration GHCi integration has been provided by Gwern Branwen. The integration allows running `:hlint` from the GHCi prompt. The script is at [hlint.ghci](https://raw.githubusercontent.com/ndmitchell/hlint/master/data/hlint.ghci), and a copy is installed locally in the data directory. To use, add the contents to your [GHCi startup file](https://www.haskell.org/ghc/docs/latest/html/users_guide/ghci.html#the-ghci-and-haskeline-files). ### Parallel Operation To run HLint on 4 processors append the flags `-j4`. HLint will usually perform fastest if n is equal to the number of physical processors, which can be done with `-j` alone. If your version of GHC does not support the GHC threaded runtime then install with the command: `cabal install --flags="-threaded"` ### C preprocessor support HLint runs the [cpphs C preprocessor](http://hackage.haskell.org/package/cpphs) over all input files, by default using the current directory as the include path with no defined macros. These settings can be modified using the flags `--cpp-include` and `--cpp-define`. To disable the C preprocessor use the flag `-XNoCPP`. There are a number of limitations to the C preprocessor support: * HLint will only check one branch of an `#if`, based on which macros have been defined. * Any missing `#include` files will produce a warning on the console, but no information in the reports. ## FAQ ### Why are hints not applied recursively? Consider: ```haskell foo xs = concat (map op xs) ``` This will suggest eta reduction to `concat . map op`, and then after making that change and running HLint again, will suggest use of `concatMap`. Many people wonder why HLint doesn't directly suggest `concatMap op`. There are a number of reasons: * HLint aims to both improve code, and to teach the author better style. Doing modifications individually helps this process. * Sometimes the steps are reasonably complex, by automatically composing them the user may become confused. * Sometimes HLint gets transformations wrong. If suggestions are applied recursively, one error will cascade. * Some people only make use of some of the suggestions. In the above example using concatMap is a good idea, but sometimes eta reduction isn't. By suggesting them separately, people can pick and choose. * Sometimes a transformed expression will be large, and a further hint will apply to some small part of the result, which appears confusing. * Consider `f $ (a b)`. There are two valid hints, either remove the $ or remove the brackets, but only one can be applied. ### Why doesn't the compiler automatically apply the optimisations? HLint doesn't suggest optimisations, it suggests code improvements - the intention is to make the code simpler, rather than making the code perform faster. The [GHC compiler](http://haskell.org/ghc/) automatically applies many of the rules suggested by HLint, so HLint suggestions will rarely improve performance. ### Why doesn't HLint know the fixity for my custom !@%$ operator? HLint knows the fixities for all the operators in the base library, but no others. HLint works on a single file at a time, and does not resolve imports, so cannot see fixity declarations from imported modules. You can tell HLint about fixities by putting them in a hint file named `.hlint.yaml` with the syntax `- fixity: "infixr 5 !@%$"`. You can also use `--find` to automatically produce a list of fixity declarations in a file. ### Which hints are used? HLint uses the `hlint.yaml` file it ships with by default (containing things like the `concatMap` hint above), along with the first `.hlint.yaml` file it finds in the current directory or any parent thereof. To include other hints, pass `--hint=filename.yaml`. If you pass any `--with` hint you will need to explicitly add any `--hint` flags required. ### Why do I sometimes get a "Note" with my hint? Most hints are perfect substitutions, and these are displayed without any notes. However, some hints change the semantics of your program - typically in irrelevant ways - but HLint shows a warning note. HLint does not warn when assuming typeclass laws (such as `==` being symmetric). Some notes you may see include: * __Increases laziness__ - for example `foldl (&&) True` suggests `and` including this note. The new code will work on infinite lists, while the old code would not. Increasing laziness is usually a good idea. * __Decreases laziness__ - for example `(fst a, snd a)` suggests `a` including this note. On evaluation the new code will raise an error if a is an error, while the old code would produce a pair containing two error values. Only a small number of hints decrease laziness, and anyone relying on the laziness of the original code would be advised to include a comment. * __Removes error__ - for example `foldr1 (&&)` suggests `and` including the note `Removes error on []`. The new code will produce `True` on the empty list, while the old code would raise an error. Unless you are relying on the exception thrown by the empty list, this hint is safe - and if you do rely on the exception, you would be advised to add a comment. ### What is the difference between error/warning/suggestion? Every hint has a severity level: * __Error__ - by default only used for parse errors. * __Warning__ - for example `concat (map f x)` suggests `concatMap f x` as a "warning" severity hint. From a style point of view, you should always replace a combination of `concat` and `map` with `concatMap`. * __Suggestion__ - for example `x !! 0` suggests `head x` as a "suggestion" severity hint. Typically `head` is a simpler way of expressing the first element of a list, especially if you are treating the list inductively. However, in the expression `f (x !! 4) (x !! 0) (x !! 7)`, replacing the middle argument with `head` makes it harder to follow the pattern, and is probably a bad idea. Suggestion hints are often worthwhile, but should not be applied blindly. The difference between warning and suggestion is one of personal taste, typically my personal taste. If you already have a well developed sense of Haskell style, you should ignore the difference. If you are a beginner Haskell programmer you may wish to focus on warning hints before suggestion hints. ### Is it possible to use pragma annotations in code that is read by `ghci` (conflicts with `OverloadedStrings`)? Short answer: yes, it is! If the language extension `OverloadedStrings` is enabled, `ghci` may however report error messages such as: ```console Ambiguous type variable ‘t0’ arising from an annotation prevents the constraint ‘(Data.Data.Data t0)’ from being solved. ``` In this case, a solution is to add the `:: String` type annotation. For example: ```haskell {-# ANN someFunc ("HLint: ignore Use fmap" :: String) #-} ``` See discussion in [issue #372](https://github.com/ndmitchell/hlint/issues/372). ### Why do I get a parse error? HLint enables/disables a set of extensions designed to allow as many files to parse as possible, but sometimes you'll need to enable an additional extension (e.g. Arrows), or disable some (e.g. MagicHash) to enable your code to parse. ## Customizing the hints To customize the hints given by HLint, create a file `.hlint.yaml` in the root of your project. For a suitable default run: ```console hlint --default > .hlint.yaml ``` This default configuration contains lots of examples, including: * Adding command line arguments to all runs, e.g. `--color` or `-XNoMagicHash`. * Ignoring certain hints, perhaps within certain modules/functions. * Restricting use of GHC flags/extensions/functions, e.g. banning `Arrows` and `unsafePerformIO`. * Adding additional project-specific hints. You can see the output of `--default` [here](https://github.com/ndmitchell/hlint/blob/master/data/default.yaml). If you wish to use the [Dhall configuration language](https://github.com/dhall-lang/dhall-lang) to customize HLint, there [is an example](https://kowainik.github.io/posts/2018-09-09-dhall-to-hlint) and [type definition](https://github.com/kowainik/relude/blob/master/hlint/Rule.dhall). ### Ignoring hints Some of the hints are subjective, and some users believe they should be ignored. Some hints are applicable usually, but occasionally don't always make sense. The ignoring mechanism provides features for suppressing certain hints. Ignore directives can either be written as pragmas in the file being analysed, or in the hint files. Examples of pragmas are: * `{-# ANN module "HLint: ignore" #-}` or `{-# HLINT ignore #-}` or `{- HLINT ignore -}` - ignore all hints in this module (use `module` literally, not the name of the module). * `{-# ANN module "HLint: ignore Eta reduce" #-}` or `{-# HLINT ignore "Eta reduce" #-}` or `{- HLINT ignore "Eta reduce" -}` - ignore all eta reduction suggestions in this module. * `{-# ANN myFunction "HLint: ignore" #-}` or `{-# HLINT ignore myFunction #-}` or `{- HLINT ignore myFunction -}` - don't give any hints in the function `myFunction`. * `{-# ANN myFunction "HLint: error" #-}` or `{-# HLINT error myFunction #-}` or `{- HLINT error myFunction -}` - any hint in the function `myFunction` is an error. * `{-# ANN module "HLint: error Use concatMap" #-}` or `{-# HLINT error "Use concatMap" #-}` or `{- HLINT error "Use concatMap" -}` - the hint to use `concatMap` is an error (you may also use `warn` or `suggest` in place of `error` for other severity levels). For `ANN` pragmas it is important to put them _after_ any `import` statements. If you have the `OverloadedStrings` extension enabled you will need to give an explicit type to the annotation, e.g. `{-# ANN myFunction ("HLint: ignore" :: String) #-}`. The `ANN` pragmas can also increase compile times or cause more recompilation than otherwise required, since they are evaluated by `TemplateHaskell`. For `{-# HLINT #-}` pragmas GHC may give a warning about an unrecognised pragma, which can be suppressed with `-Wno-unrecognised-pragmas`. For `{- HLINT -}` comments they are likely to be treated as comments in syntax highlighting, which can lead to them being overlooked. Ignore directives can also be written in the hint files: * `- ignore: {name: Eta reduce}` - suppress all eta reduction suggestions. * `- ignore: {name: Eta reduce, within: [MyModule1, MyModule2]}` - suppress eta reduction hints in the `MyModule1` and `MyModule2` modules. * `- ignore: {within: MyModule.myFunction}` - don't give any hints in the function `MyModule.myFunction`. * `- error: {within: MyModule.myFunction}` - any hint in the function `MyModule.myFunction` is an error. * `- error: {name: Use concatMap}` - the hint to use `concatMap` is an error (you may also use `warn` or `suggest` in place of `error` for other severity levels). These directives are applied in the order they are given, with later hints overriding earlier ones. You can choose to ignore all hints with `- ignore: {}` then selectively enable the ones you want (e.g. `- warn: {name: Use const}`), but it isn't a totally smooth experience (see [#747](https://github.com/ndmitchell/hlint/issues/747) and [#748](https://github.com/ndmitchell/hlint/issues/748)). Finally, `hlint` defines the `__HLINT__` preprocessor definition (with value `1`), so problematic definitions (including those that don't parse) can be hidden with: ```haskell #ifndef __HLINT__ foo = ( -- HLint would fail to parse this #endif ``` ### Adding hints The hint suggesting `concatMap` can be defined as: ```yaml - warn: {lhs: concat (map f x), rhs: concatMap f x} ``` This line can be read as replace `concat (map f x)` with `concatMap f x`. All single-letter variables are treated as substitution parameters. For examples of more complex hints see the supplied `hlint.yaml` file in the data directory. This hint will automatically match `concat . map f` and `concat $ map f x`, so there is no need to give eta-reduced variants of the hints. Hints may tagged with `error`, `warn` or `suggest` to denote how severe they are by default. In addition, `hint` is a synonym for `suggest`. If you come up with interesting hints, please submit them for inclusion. You can search for possible hints to add from a source file with the `--find` flag, for example: ```console $ hlint --find=src/Utils.hs -- hints found in src/Util.hs - warn: {lhs: "null (intersect a b)", rhs: "disjoint a b"} - warn: {lhs: "dropWhile isSpace", rhs: "trimStart"} - fixity: "infixr 5 !:" ``` These hints are suitable for inclusion in a custom hint file. You can also include Haskell fixity declarations in a hint file, and these will also be extracted. If you pass only `--find` flags then the hints will be written out, if you also pass files/folders to check, then the found hints will be automatically used when checking. Hints can specify more advanced aspects, with names and side conditions. To see examples and descriptions of these features look at [the default hint file](https://github.com/ndmitchell/hlint/blob/master/data/hlint.yaml) and [the hint interpretation module comments](https://github.com/ndmitchell/hlint/blob/master/src/Hint/Match.hs). ### Restricting items HLint can restrict what Haskell code is allowed, which is particularly useful for larger projects which wish to enforce coding standards - there is a short example in the [HLint repo itself](https://github.com/ndmitchell/hlint/blob/master/.hlint.yaml#L10-L32). As an example of restricting extensions: ```yaml - extensions: - default: false - name: [DeriveDataTypeable, GeneralizedNewtypeDeriving] - {name: CPP, within: CompatLayer} ``` The above block declares that GHC extensions are not allowed by default, apart from `DeriveDataTypeable` and `GeneralizedNewtypeDeriving` which are available everywhere. The `CPP` extension is only allowed in the module `CompatLayer`. Much like `extensions`, you can use `flags` to limit the `GHC_OPTIONS` flags that are allowed to occur. You can also ban certain functions: ```yaml - functions: - {name: nub, within: []} - {name: unsafePerformIO, within: CompatLayer} ``` This declares that the `nub` function can't be used in any modules, and thus is banned from the code. That's probably a good idea, as most people should use an alternative that isn't _O(n^2)_ (e.g. [`nubOrd`](https://hackage.haskell.org/package/extra/docs/Data-List-Extra.html#v:nubOrd)). We also whitelist where `unsafePerformIO` can occur, ensuring that there can be a centrally reviewed location to declare all such instances. Finally, we can restrict the use of modules with: ```yaml - modules: - {name: [Data.Set, Data.HashSet], as: Set} - {name: Control.Arrow, within: []} - {name: Control.Monad.State, badidents: [modify, get, put], message: "Use Control.Monad.State.Class instead"} ``` This fragment requires that all imports of `Set` must be `qualified Data.Set as Set`, enforcing consistency. It also ensures the module `Control.Arrow` can't be used anywhere. It also prevents explicit imports of the `modify` identifier from `Control.Monad.State` (this is meant to allow you to prevent people from importing reexported identifiers). You can customize the `Note:` for restricted modules, functions and extensions, by providing a `message` field (default: `may break the code`). ## Hacking HLint Contributions to HLint are most welcome, following [my standard contribution guidelines](https://github.com/ndmitchell/neil/blob/master/README.md#contributions). You can run the tests either from within a `ghci` session by typing `:test` or by running the standalone binary's tests via `cabal run hlint test` or `stack init && stack run hlint test`. New tests for individual hints can be added directly to source and hint files by adding annotations bracketed in `` code comment blocks. As some examples: ```haskell {- Tests to check the zipFrom hint works zip [1..length x] x -- zipFrom 1 x zip [1..length y] x zip [1..length x] x -- ??? @Warning -} ``` The general syntax is `lhs -- rhs` with `lhs` being the expression you expect to be rewritten as `rhs`. The absence of `rhs` means you expect no hints to fire. In addition `???` lets you assert a warning without a particular suggestion, while `@` tags require a specific severity -- both these features are used less commonly. ### Acknowledgements Many improvements to this program have been made by [Niklas Broberg](http://www.nbroberg.se) in response to feature requests. Additionally, many people have provided help and patches, including Lennart Augustsson, Malcolm Wallace, Henk-Jan van Tuyl, Gwern Branwen, Alex Ott, Andy Stewart, Roman Leshchinskiy, Johannes Lippmann, Iustin Pop, Steve Purcell, Mitchell Rosen and others. hlint-3.1.6/LICENSE0000644000000000000000000000276413605323506012046 0ustar0000000000000000Copyright Neil Mitchell 2006-2020. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Neil Mitchell nor the names of other contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. hlint-3.1.6/hlint.cabal0000644000000000000000000000762713674744603013161 0ustar0000000000000000cabal-version: >= 1.18 build-type: Simple name: hlint version: 3.1.6 license: BSD3 license-file: LICENSE category: Development author: Neil Mitchell maintainer: Neil Mitchell copyright: Neil Mitchell 2006-2020 synopsis: Source code suggestions description: HLint gives suggestions on how to improve your source code. homepage: https://github.com/ndmitchell/hlint#readme bug-reports: https://github.com/ndmitchell/hlint/issues data-dir: data data-files: hlint.yaml default.yaml Test.hs report_template.html hs-lint.el hlint.1 hlint.ghci HLint_QuickCheck.hs HLint_TypeCheck.hs extra-doc-files: README.md CHANGES.txt tested-with: GHC==8.10.1, GHC==8.8.3, GHC==8.6.5 source-repository head type: git location: https://github.com/ndmitchell/hlint.git flag threaded default: True manual: True description: Build with support for multithreaded execution flag gpl default: True manual: True description: Use GPL libraries, specifically hscolour flag ghc-lib default: False manual: True description: Force dependency on ghc-lib-parser even if GHC API in the ghc package is supported flag hsyaml default: False manual: True description: Use HsYAML instead of yaml library default-language: Haskell2010 build-depends: base == 4.*, process, filepath, directory, containers, unordered-containers, vector, text, bytestring, transformers, file-embed, utf8-string, data-default >= 0.3, cpphs >= 1.20.1, cmdargs >= 0.10, uniplate >= 1.5, ansi-terminal >= 0.6.2, extra >= 1.7.3, refact >= 0.3, aeson >= 1.1.2.0, filepattern >= 0.1.1 if !flag(ghc-lib) && impl(ghc >= 8.10.0) && impl(ghc < 8.11.0) build-depends: ghc == 8.10.*, ghc-boot-th, ghc-boot else build-depends: ghc-lib-parser == 8.10.* build-depends: ghc-lib-parser-ex >= 8.10.0.14 && < 8.10.1 if flag(gpl) build-depends: hscolour >= 1.21 else cpp-options: -DGPL_SCARES_ME if flag(hsyaml) build-depends: HsYAML >= 0.2, HsYAML-aeson >= 0.2 cpp-options: -DHS_YAML else build-depends: yaml >= 0.5.0 hs-source-dirs: src exposed-modules: Language.Haskell.HLint other-modules: Paths_hlint Apply CmdLine Grep Extension Fixity HLint HsColour Idea Report Util Parallel Refact Timing CC EmbedData Config.Compute Config.Haskell Config.Read Config.Type Config.Yaml GHC.All GHC.Util GHC.Util.ApiAnnotation GHC.Util.View GHC.Util.Brackets GHC.Util.DynFlags GHC.Util.FreeVars GHC.Util.HsDecl GHC.Util.HsExpr GHC.Util.SrcLoc GHC.Util.Scope GHC.Util.Unify Hint.All Hint.Bracket Hint.Comment Hint.Duplicate Hint.Export Hint.Extensions Hint.Import Hint.Lambda Hint.List Hint.ListRec Hint.Match Hint.Monad Hint.Naming Hint.NewType Hint.Pattern Hint.Pragma Hint.Restrict Hint.Smell Hint.Type Hint.Unsafe Test.All Test.Annotations Test.InputOutput Test.Proof Test.Summary Test.Translate Test.Util executable hlint default-language: Haskell2010 build-depends: base, hlint main-is: src/Main.hs ghc-options: -rtsopts if flag(threaded) ghc-options: -threaded hlint-3.1.6/CHANGES.txt0000644000000000000000000013016213674744552012662 0ustar0000000000000000Changelog for HLint (* = breaking change) 3.1.6, released 2020-06-24 #1062, make sure matching inserts brackets if required #1058, weaken the self-definition check to match more things #1060, suggest [] ++ x and [] ++ x to x 3.1.5, released 2020-06-19 #1049, suggest or/and from True `elem` xs and False `notElem` xs #1055, avoid incorrect hints with nested (.)'s #1054, make isLitString work again #1038, make -XNoTemplateHaskell imply -XNoTemplateHaskellQuotes #970, require an arg to suggest fromMaybe True ==> (Just True ==) #1047, suggest pushing take outside zip #1041, fix language/pragma ordering in refactor #1040, fix refactoring for "Avoid lambda" #1042, fix redundant lambda refactoring #1039, don't suggest move map inside list comp repeatedly #1035, fix refactoring for "Use fewer imports" in some cases #1036, disable refactoring for Use camelCase #766, match quasi quotes properly in rules Ignore [Char] to String hints by default #1034, remove suggestions to use heirarchical module names #1032, fix refactoring for "Use :" #1028, add hints around sequenceA/traverse #1027, pass enabled and disabled extensions to apply-refact #1024, make the redundant bracket hints cover just the bracket #1024, make redundant $ display more context on the command line Suggest removing OverloadedLabels if there are no labels #367, suggest removing OverloadedLists if there are no lists #1023, speed up checking on large files (up to 12%) 3.1.4, released 2020-05-31 #1018, stop --cross being quadratic #1019, more rules suggesting even/odd 3.1.3, released 2020-05-25 #1016, check scopes of restricted functions 3.1.2, released 2020-05-24 #1014, don't error on empty do blocks #1008, make redundant do ignored by default #1012, add CodeWorld hints around pictures #1003, enable refactoring for (v1 . v2) <$> v3 #1002, warn on unused NumericUnderscores 3.1.1, released 2020-05-13 #993, deal with infix declarations in the module they occur #993, make createModuleEx use the default HLint fixities #995, add unpackSrcSpan to the API 3.1, released 2020-05-07 #979, suggest removing flip only for simple final variables #978, do is not redundant with non-decreasing indentation #969, wrong redundant bracket suggestion with BlockArguments #970, detect redundant sections, (a +) b ==> a + b * #974, split ParseFlags.extensions into enabled/disabled #971, add support for -XNoFoo command line flags #976, run refactor even if no hints #971, add support for NoFoo language pragmas 3.0.4, released 2020-05-03 #968, fail on all parse errors #967, enable TypeApplications by default 3.0.3, released 2020-05-03 #965, fix incorrect avoid lambda suggestion 3.0.2, released 2020-05-03 #963, don't generate use-section hints for tuples #745, fix up free variables for A{x}, fixes list comp hints 3.0.1, released 2020-05-02 #961, don't crash on non-extension LANGUAGE pragmas, e.g. Safe 3.0, released 2020-05-02 Be more permissive with names'with'quotes'in #953, fix incorrect suggestions with free variables and \case #955, make --find generate fixity: not infix: #952, improve refactorings with qualified imports #945, suggest Map.fromList [] ==> Map.empty #949, warn about redundant fmaps with binds #950, reduce the span of "Redundant $" to only cover the "$" #944, reduce the span of "Use let" to only cover the "let" line #669, don't suggest replacing reverse . sort (it's quite fast) #939, reduce the span of "Redundant where" to only cover the "where" Remove support for GHC 8.4 Remove support for _eval, #926, fix refactoring when the hint contains _noParen_ #933, improve the output for Redundant do hints * Merge ParseMode into ParseFlags * Rename Language.Haskell.HLint3,HLint4 to Language.Haskell.HLint * Delete the old Language.Haskell.HLint #881, add a monomorphic group of hints #837, don't suggest redundant do if its being used for brackets #923, don't suggest eta reducing infix definitions #931, disable StaticPointers extension by default #924, remove dependency on haskell-src-exts #922, reduce the span of "Redundant do" to only cover the "do" #919, more specific names for foldMap fusion rules #918, warn on unused TypeOperators #916, warn on unused InstanceSigs Improve parse error context messages * Most parse errors are fixed #881, disable hints about maybe that are sometimes wrong #909, be more careful about redundant bracket warnings #905, match hints even if there is composition to the left #904, suggest map/fromMaybe[] becomes maybe [] map * Remove the hse command line argument, to parse a file with HSE #901, warn on unused MultiWayIf Don't raise a parse error if haskell-src-exts can't parse code #899, warn on unused PatternSynonyms #898, don't suggest removing NamedFieldPuns with record updates * Make any --hint flag disable implicit .hlint.yaml search * Delete the --with flag * Haskell hint definitions are no longer supported (use YAML) * Report hints with src-span information, e.g. file:1:1-10 * Delete resolveHints (it was the identity) * Change to GHC types in the API Add --with-group=future to add return ==> pure hint #888, suggest foldr from (.) to ($) in some cases #884, add more >=> operator hints #875, fix the extension implication information Add --with-group=extra to give extra library hints #873, add more Applicative hints #872, fix refactoring in hints to use lists #871, warn when fmapping the result of gets or asks #869, improve hints for maybe/fromMaybe on Bool 2.2.11, released 2020-02-09 #868, fix some brackets in refactoring suggestions #865, suggest biList if generalise-for-conciseness is turned on #859, suggest regular if instead of a simple multi-way if #860, improve the sortBy/sortOn hints #862, only suggest TupleSections for 2-tuples once #854, add more generalise-for-conciseness hints for Either/Maybe #852, change maybe to fromMaybe, when the function is duplicated #851, add a rule for maybe Nothing Just 2.2.10, released 2020-02-02 #846, add splitAt warnings #774, don't warn about 'Redundant compare' in == and /= 2.2.9, released 2020-01-27 Add any/map and all/map fusion hints #837, don't warn about redundant do for BlockArguments #842, fix parsing of <% operators in hlint.yaml files #839, match hints inside instances #833, UnboxedTuples can be necessary from newtype deriving #817, add the ability to blacklist identifiers from a module #834, move not out of any and all 2.2.8, released 2020-01-22 #802, suggest lambda instead of lambda-case for single alts #811, add some foldMap/map hints #822, generalise the map/zipWith hint #824, embed HLint data files using TemplateHaskell #826, remove curry/uncurry on lambdas #820, make some hints work in more situations Reenable PackageImport unused extension detectection #821, warn on unless/not #821, avoid curry/uncurry and vice versa #819, fix a lot of bifunctor hints #812, add some rules for generalised and/or/any/all 2.2.7, released 2020-01-11 #818, fix incorrect unused LANGUAGE BangPatterns hint 2.2.6, released 2020-01-09 #813, remove any/all with const predicates Allow haskell-src-exts-1.23 #814, suggest find instead of listToMaybe/filter Allow ghc-lib-parser-8.8.* 2.2.5, released 2019-12-06 #803, allow newer ghc-lib-8.8.1 #792, note that reverse/sort changes sort stability #793, don't incorrectly suggest foldr 2.2.4, released 2019-11-02 Allow haskell-src-exts-1.22 #788, give less redundant context on unused variable capture #334, add --ignore=glob flag 2.2.3, released 2019-09-29 #766, turn on more extensions when parsing config files #255, don't match variables with type application Switch to ghc-parser-8.8.1 Slightly restrict the replace case with fromMaybe hint #701, add hints for replacing case with maybe #724, suggest Data.Bifunctor in some places #725, allow custom message for restricted items 2.2.2, released 2019-07-23 #716, upgrade to ghc-lib-parser 8.8.0.20190723 2.2.1, released 2019-07-22 #713, make sure -XNoPatternSynonyms works (fix regression) #700, add some Monoid and Alternative hints Add createModuleEx to the API #698, don't suggest a replacement for DerivingStrategies 2.2, released 2019-06-26 * Remove functions and make some things abstract in HLint3 API 2.1.26, released 2019-06-26 Make sure unknown extensions don't cause errors 2.1.25, released 2019-06-26 #681, fix for extensions on the command line not being used #686, suggest head (drop n x) ==> x !! max 0 n #683, add Use DerivingStrategies hint, ignored by default #685, skip running refactoring tool if there are no hints #675, warn about redundant fmaps on Eithers and Maybes Add back two $ hints removed in error 2.1.24, released 2019-06-10 Add Language.Haskell.HLint4 #658, ignore the previously undocumented {- LINT -} comments #658, force parsing of all pragmas and comments eagerly #665, make different fromMaybe hints have different names #664, better name for the Use uncurry hint #659, make hints with brackets at the root work 2.1.23, released 2019-06-09 Make it an error if your code does not parse with GHC #662, don't warn on ($x), since it might not really be TH #660, suggest tuple sections for \y -> (x,y) and similar #667, warn on return x >> m and similar #653, add symmetric versions of some == hints #650, add a group of teaching hints #651, warn on unused NamedFieldPuns #646, switch to an HTML doctype 2.1.22, released 2019-05-25 #634, suggest modifyIORef ==> writeIORef when applicable #642, suggest null in more places #640, reenable GHC parsing 2.1.21, released 2019-05-19 #637, temporary workaround for GHC parser segfaults 2.1.20, released 2019-05-15 * Fix a dumb break in the API on parseModuleEx 2.1.19, released 2019-05-14 * Revert PVP breakage 2.1.18, released 2019-05-13 * Change parseModuleEx/ParseError by accident #633, don't suggest changes inside RULES #631, suggest typeOf ==> typeRep Add matching on type variables #627, restrict to GHC 8.4 and above 2.1.17, released 2019-04-17 #626, add operator wildcards with ?, ??, ??? etc #625, fix an rnf/rhs typo #562, make test --verbose show a list of matched hints 2.1.16, released 2019-04-15 Make `seq` and `rem` hints apply to prefix functions #604, suggest rnf x `seq` () ==> rnf x #619, require haskell-src-exts-util-0.2.5 #619, fix move guards forward with record puns #618, add pure x <*> y ==> x <$> y #611, add == and subst for more advanced match conditions #612, add: Suggest f =<< instead of maybe Nothing f #609, add code smells #614, adds refactorings for camelCase and some list suggestions #605, make command line arguments override the .yaml file #603, QuasiQuotes can programatically use any extension 2.1.15, released 2019-02-27 #593, reorder guards in list comps where possible #597, suggest pushing a map over a list comp inside Say redundant pure, when the thing you are removing is pure #554, add more verbosity Don't test with GHC 7.4 to 7.8 #590, say which extensions should be deleted #591, be careful about encoding on stdin 2.1.14, released 2019-01-28 #587, fix extensions implied by ImplicitParams #588, suggest optional from attoparsec 2.1.13, released 2019-01-23 #583, suggest left sections to avoid lambdas #580, remove redundant LANGUAGE pragmas which are implied by others #575, add fixities for lattice #564, fix hint around withFile with AppendMode 2.1.12, released 2018-12-10 Require haskell-src-exts-1.21 2.1.11, released 2018-12-02 #553, define __HLINT__=1 for the C preprocessor #546, suggest `x $> y` for `const x <$> y`, `pure x <$> y`, and `return x <$> y` #546, suggest `x <$ y` for `x <&> const y`, `x <&> pure y`, and `x <&> return y` #556, disable a few incorrect lens hints #545, don't suggest turning type applications into sections #466, avoid false positives for Esqueleto #535, more lens hints Allow {-# HLINT #-} and {- HLINT -} pragmas #532, generate requested report files even if there are no hints #524, don't suggest newtype for existentials #521, add a hint for f x@_ = ... ==> f x = ... 2.1.10, released 2018-08-16 #516, don't require a .hlint.yaml when running tests Prefer .hlint.yaml to HLint.hs for settings #513, add section links in the HTML report 2.1.9, released 2018-08-08 Add QuickCheck fixities Warn on redundant EmptyCase extension 2.1.8, released 2018-07-06 #509, remove incorrect suggestions around sequence/pure 2.1.7, released 2018-07-03 #483, don't break quasi quotes when suggesting const #404, remove the "Unnecessary hiding" hint introduced in #338 #162, make avoiding lambda with `infix` give a different name #507, rename id x ==> x to redundant id #286, improve the duplicate pragma message #399, suggest (& f) ==> f #136, don't suggest eta-reducing runST #345, add catMaybes/fmap ==> mapMaybe #345, add foldMap id ==> fold #364, suggest >> instead of >>= \_ -> #502, DeriveTraversable implies DeriveFoldable and DeriveFunctor Add hints about fusing traverse/map Better names for mapM/map fusion hints and others #498, change the output to say "Perhaps:" rather than "Why not:" 2.1.6, released 2018-06-16 Match on explicit brackets at the root of a match expression #470, suggest TupleSections #496, suggest sequence/fmap ==> mapM #473, warn on redundant void, _ <- and return () Make use of <$> more general, but in simpler cases Warn about returns in the middle of do blocks #471, suggest readTVarIO #468, suggest using sortOn/Down #458, document the restriction feature #494, don't suggest newtype for unboxed tuples #488, avoid warning about more test prefixes 2.1.5, released 2018-05-05 #478, take account of deriving strategies for extension use #477, don't warn about unit_ as tasty-discover recommends it 2.1.4, released 2018-05-01 Don't warn about redundant $ for a $ b{c=d} 2.1.3, released 2018-04-18 Improve the performance of the camelCase hint Don't suggest camelCase for record fields Add a --timing flag to detect what is slow 2.1.2, released 2018-04-16 #407, don't error on unknown extensions on the command line Require extra-1.6.6 #464, add more hints for concatMap #462, ignore home directory when it isn't present 2.1.1, released 2018-03-24 #457, suggest turning on LambdaCase if necessary #457, add RequiresExtension note #454, add fixities for the HSpec `should*` functions #455, add some more sequence hints #453, allow pure in a few Monad hints as well as return #451, add --with-group command line option #424, suggest Foldable.forM_ in a few more places #445, add suggestions for reader/state monad #443, suggest join (x <$> y) ==> x =<< y 2.1, released 2018-02-07 * #433, make ideas span multiple modules/declarations #433, allow ignoring statement-level duplication hint #439, add more fixities for new base operators #437, --json output should be finite #425, avoid misparsing use of Gtk2Hs `on` function #353, detect unused results from for/traverse/sequenceA #428, add a few rules for the lens package #429, spot restricted functions in infix operators #427, don't eta reduce variables in the presence of quasi-quotes Improve the HTML slightly #416, add lens package fixities 2.0.15, released 2018-01-18 #426, don't suggest removing brackets for "x . (x +? x . x)" #426, better results with haskell-src-exts-util-0.2.2 2.0.14, released 2018-01-14 #376, apply the "use fmap" hint in fewer places #421, binaries available for OS X 2.0.13, released 2018-01-12 #376, suggest <$> instead of x <- foo; return $ f x #401, suggest removing brackets for (f . g) <$> x Add Semigroup instances 2.0.12, released 2017-12-12 Don't suggest Control.Arrow Upgrade to haskell-src-exts-1.20 2.0.11, released 2017-11-30 #411, parse the YAML file with lots of HSE extensions turned on #408, use the same config file logic in argsSettings as in hlint Don't use unexported type synonyms in the public API #405, fix false positives on MagicHash due to unboxed literals 2.0.10, released 2017-11-03 #377, suggest lambda case Add CodeClimate support #378, suggest map for degenerate foldr #395, suggest x $> y from x *> pure y and x *> return y #395, suggest x <$ y from pure x <* y and return x <* y #393, suggest f <$> m from m >>= pure . f and pure . f =<< #366, avoid the github API for prebuilt hlint, is rate limited #352, suggest maybe for fromMaybe d (f <$> x) #338, warn about things imported hidden but not used #337, add --git flag to additionally check files in git #353, suggest _ <- mapM to mapM_ #357, warn on unnecessary use of MagicHash 2.0.9, released 2017-06-13 #346, don't suggest explicit export lists #344, fix the API so it works with hlint.yaml by default 2.0.8, released 2017-05-21 #342, add back support for - to mean stdin 2.0.7, released 2017-05-16 #340, fix for directory arguments in the .hlint.yaml 2.0.6, released 2017-05-08 Do statements are not redundant if they change an operator parse #333, simplify labels on Parse error, makes it easier to ignore 2.0.5, released 2017-04-19 If the datadir is missing use data/ relative to the executable Fix test mode to obey --datadir 2.0.4, released 2017-04-17 --default adds ignores for any warnings it finds 2.0.3, released 2017-04-12 #312, suggest removing the DeriveAnyClass extension Suggest removing the DeriveLift extension Remove redundant parts from list comprehensions, e.g. [a | True] #326, fix up the bounds on the eta-reduce hint 2.0.2, released 2017-04-10 #323, try and avoid malformatted JSON #324, use `backticks` in notes #324, remove double escaping in note for --json #322, fix the YAML syntax in default.yaml 2.0.1, released 2017-04-07 #320, still read ./HLint.hs if it exists 2.0, released 2017-04-06 #319, add a hint \x -> f <$> g x ==> fmap f . g Don't say how many hints were ignored Add a --default flag #314, allow arguments in YAML configuration files Add maybe False (== x) ==> (== Just x) hint, plus for /= Remove the ability to pass the file on stdin using - as the file Remove encoding from ParseFlags Remove the --encoding/--utf8 options, always use UTF8 #311, suggest writeFile instead of withFile/hPutStr #288, add configurable restrictions Suggest using an export list on modules Look for nearby .hlint.yaml files to supply configuration Support YAML configuration files #308, don't suggest newtype for unboxed types Remove the import "hint" feature for hint inclusion Builtin hints do not need to be imported, can only be ignored Delete HLint2 API #290, add hints suggesting traverse/traverse_ #303, detect unused OverloadedStrings extension #302, detect unused TupleSections extension 1.9.41, released 2017-02-09 #299, warn in some cases when NumDecimals extension is unused #300, warn when LambdaCase extension is unused #301, when suggesting newtype remove strictness annotations #297, better testing that there isn't a performance regression #167, add -j flag for number of threads #292, add fst/snd . unzip ==> map fst/snd Don't suggest module export trick, breaks Haddock 1.9.40, released 2017-01-22 #293, fix the JSON format of the output 1.9.39, released 2016-12-04 #287, don't incorrectly suggest newtype 1.9.38, released 2016-11-24 #279, suggest newtype instead of data #262, add rules to detect redundant castPtr calls Detect unused TypeApplications extension #277, don't enable TypeApplications extension by default Allow haskell-src-exts-1.19 #276, remove multiple redundant parens in one go #160, add a --only CLI option #237, fix incorrect quasi quotes extension warning #257, better bang pattern hints 1.9.37, released 2016-08-08 #255, don't suggest id @Int ==> @Int #252, avoid clashes with GHCJS in the interim 1.9.36, released 2016-07-25 Require haskell-src-exts-1.18 #249, suggest avoiding elem on singletons 1.9.35, released 2016-06-10 #245, fix parse error reports #243, update hlint.ghci to work with modern GHC Require extra-1.4.9 1.9.34, released 2016-06-01 #154, fix some incorrect line numbers in literate Haskell #161, fix literate Haskell and CPP 1.9.33, released 2016-05-30 #240, remove type-incorrect "on" hint #234, warn about join seq #232, suggest <|> instead of mplus in a few cases 1.9.32, released 2016-03-23 #53, require cpphs-1.20.1, has important fixes #224, treat select $ specially, as per esqueleto conventions #231, don't modify qualification on substitutions #229, add void/mapM_/forM_ hints 1.9.31, released 2016-03-01 #222, don't suggest removing ~ if the Strict extension is on 1.9.30, released 2016-02-26 #220, fix incorrect hints of foldr/foldl on a tuple accumulator 1.9.29, released 2016-02-25 #219, add warnings about foldable methods on tuple Put warnings before suggestions in the HTML report 1.9.28, released 2016-02-04 #215, spot newtype deriving inside classes 1.9.27, released 2016-02-01 #203, avoid recompiling everything twice #213, don't suggest removing bang patterns on let Rename HintStructure to HintPattern #208, add an hlint function to the HLint3 API #1, warn about unused DefaultSignatures extension #137, add -XHaskell2010 and fix -XHaskell98 Allow checking even if a function has different arities #193, don't warn on a -> (b -> c), it's sometimes sensible #182, make parse errors severity Error #181, warn on otherwise in a pattern variable #163, eta reduce fun x = f $ x #132, don't ever suggest liftM #99, downgrade built in hints, Error => Warning => Suggestion #99, add a Suggestion level severity #207, make sure you close file handles #205, add hint compare x y == EQ and /= #204, add hint concatMap id ==> concat #202, include refactorings is --json output 1.9.26, released 2016-01-02 #200, fix all lint warnings #143, expose argsSettings 1.9.25, released 2015-11-24 #192, fix stdin output and --refactor 1.9.24, released 2015-11-22 #188, improve spotting redundant brackets around patterns #138, reenable redundant where hint 1.9.23, released 2015-11-19 #184, require haskell-src-exts-1.17 #183, allow test_ as a prefix 1.9.22, released 2015-10-28 Don't suggest redundant lambda on view patterns Add --no-exit-code flag #174, don't suggest string literals #175, disable 'rec' stealing extensions by default #170, add hints for eta-reduced operators #149, integrate a --refactor flag #147, fix the -fglasgow-exts hint #140, better name for moving brackets to eliminate $ Extra hints for <$> Remove a redundant fmap hint #131, add =<< rules in addition to >>= 1.9.21, released 2015-05-26 #130, ignore a BOM if it exists #128, don't find files starting with . when searching directories Suggest concat even if the [] is written "" 1.9.20, released 2015-04-21 #122, fix the zipWith/repeat hint 1.9.19, released 2015-03-26 #119, don't remove RecursiveDo if they use the rec statement Add a suggestion concatMap/map ==> concatMap 1.9.18, released 2015-03-17 More GHC 7.10 warnings and build support 1.9.17, released 2015-02-25 #116, support hscolour-1.21 1.9.16, released 2015-01-09 #108, make "hlint ." work again 1.9.15, released 2015-01-03 #106, avoid warnings with GHC 7.10 #105, build with GHC 7.10 1.9.14, released 2014-12-24 #649, don't suggest const for values using RecordWildCards 1.9.13, released 2014-11-30 #97, remove the encoding bits of the API #98, add an HLint3 prototype API #93, make the --quickcheck tests work on GHC 7.8 Add --tempdir flag to the test mode 1.9.12, released 2014-11-09 #96, fix the --utf8 flag Make Encoding an alias for TextEncoding Default to UTF8 encoding 1.9.11, released 2014-11-07 #95, don't suggest camel case for names containing digits Add a dependency on the extra package #92, use a new way for determining the color default Add a dependency on ansi-terminal 1.9.10, released 2014-10-19 Spot unsafePerformIO without NOINLINE 1.9.9, released 2014-10-13 #89, fix compiling the executable with --flag=-gpl 1.9.8, released 2014-10-08 #82, don't crash on XmlHybrid modules #88, allow avoiding HsColour, as it is GPL licensed #87, don't push if down, since it can be type incorrect 1.9.7, released 2014-10-02 #86, don't use color unless $TERM claims to support it 1.9.6, released 2014-09-30 #85, fix the free variable matching check for lambda #84, suggest fmap for Either Make --json put each hint on a different line Support -X for extensions to the hse mode 1.9.5, released 2014-09-14 Remove support for GHC 7.2 Upgrade to haskell-src-exts-1.16 1.9.4, released 2014-08-27 #81, fixes for GHC 7.9 #78, add hints for list patterns #72, make --color the default on Linux 1.9.3, released 2014-07-28 #73, fix multithreading and exceptions 1.9.2, released 2014-07-23 #68, add --no-summary 1.9.1, released 2014-07-21 #65, add flip (>>=) ==> (=<<) and the reverse #61, add --json flag 1.9, released 2014-06-30 Remove not (isControl x) ==> isPrint (not true for '\173') #57, warn on invalid pragmas Make the API pass and require comments #59, make sure qualified operators match properly Rename notTypeSafe annotation to noTypeCheck Remove an invalid rule suggesting tanh #13, add a --quickcheck flag to test the hints Add --typecheck flag to test mode to type check the hints Remove incorrect for intercalate to unlines #37, remove incorrect hint for isAlphaNum #45, add mapMaybe id ==> catMaybes #42, add some repeat hints 1.8.61, released 2014-04-14 #40, allow haskell-src-exts-1.15 Don't detect redundant Generics extension 1.8.60, released 2014-04-02 #33, add --cpp-file to preinclude a file #34, add back --quiet flag #639, don't suggest evaluate, because not all Monad's are IO #31, delete the elem/notElem hints #30, remove weird "free module" matching #15, add prototype grep mode Change to make test a separate mode #12, more list based suggestions #637, turn off QuasiQuotes by default 1.8.59, released 2014-03-13 #27, fix up directory file searching 1.8.58, released 2014-03-11 Move the API to Language.Haskell.HLint2 #638, ensure $! doesn't break strictness with strict fields #24, don't remove DeriveFunctor even when only on a newtype #22, turn off UnboxedTuples by default #21, strip /* C style */ comments #635 and #18, require cpphs-1.18.1 Switch to CmdArgs for command line parsing Remove -x as a synonym for --cross 1.8.57, released 2014-02-04 #6, add a preview of an API #331, improve parse error locations for literate Haskell 1.8.56, released 2014-01-30 Remove support for GHC 6.12 and below #317, tone down the void hint #16, match not . not (and reverse . reverse etc) Suggest <$> instead of fmap f $ ... Tweak some priorities, make >=> a warn and void an error #3, make top of the file ANN pragmas work #10, add a suggestion to use unlines #11, add a few hints about characters #8, add CHANGES.txt to the Cabal package 1.8.55, released 2013-11-29 #627, fix the UnboxedTuples extension warning 1.8.54, released 2013-11-28 Fix a bug when suggesting const 1.8.53, released 2013-09-24 Fix some corner cases when suggesting foldr etc. #517, don't introduce new free variables in a replacement 1.8.52, released 2013-09-24 #2, Generic is not newtype derivable 1.8.51, released 2013-08-20 Upgrade to haskell-src-exts-1.14 1.8.50, released 2013-08-18 Eliminate upper bounds on all dependencies #617, fix up notIn to take account of Template Haskell variables #573, suggest removing various deriving language extensions 1.8.49, released 2013-07-23 Remove ^^ ==> ** hint Remove a duplicate sqrt hint Ensure that --test failures throws an error Fix up the copyright year in --help 1.8.48, released 2013-07-16 Brackets at the root of annotations are fine Reduce a few more lambda expressions 1.8.47, released 2013-06-28 #613, compatibility with base-4.7 1.8.46, released 2013-06-06 Remove incorrect isPrefixOf hints #586, add span/break/takeWhile/dropWhile hints #588, add sort/reverse hints #601, add replicate/map/repeat hints Add a hint about reverse/reverse Add side as an alias for _ Add hint as an alias for error 1.8.45, released 2013-05-12 #600, hints for unnecessary lazy annotations 1.8.44, released 2013-04-21 #598, warn on unnecessary bang patterns 1.8.43, released 2013-01-27 Change some hint error/warning levels 1.8.42, released 2013-01-23 Allow cpphs-1.16 1.8.41, released 2013-01-19 #586, add a rule for takeWhile/dropWhile ==> span #522, add hints for the state monad #499, fix up the test suite Fix the side conditions for the `isPrefixOf` hint Add hints about take/drop on non positive numbers Add isNat/isPos/isNeg/isNegPos as notes Make the notes a structured type Add --proof feature Retire the Prelude.catch hint Additional boolean equality hints 1.8.40, released 2013-01-06 #585, lots of additional list based hints 1.8.39, released 2012-12-06 #582, don't suggest renaming with trailingHashes# #578, treat _ bindings differently in lambdas 1.8.37, released 2012-12-01 #575, allow cpphs-1.15 1.8.36, released 2012-11-27 Make --with imply no default Hint files 1.8.35, released 2012-11-17 #567, avoid duplicate hints around (.) hints 1.8.34, released 2012-11-06 Switch license from GPL to BSD3 1.8.33, released 2012-10-23 Lots more hints on laziness, foldable and a few others Use mapM_ etc in more situations, when using explicit >>= 1.8.32, released 2012-10-23 Add notes about how to deal with imported fixites Add a --with flag for passing settings on the command line #563, make sure TypeSig hints get the right function name Update the copyright year to 2012 #564, allow brackets and type signatures on annotations Add a note that about using !! if the index is negative 1.8.31, released 2012-08-18 Avoid incomplete patterns when reading ANN pragmas #555, top-level expressions require TemplateHaskell 1.8.30, released 2012-07-11 Add elemIndex/elemIndices hints Allow cpphs-1.14 #551, allow case_ as a name with an underscore 1.8.29, released 2012-06-01 Allow hscolor-1.20.* #574, add a hint to for mapM/zip ==> zipWithM 1.8.28, released 2012-04-01 Fix a bug, >=> hint was missing check about removal of free var 1.8.27, released 2012-03-30 Allow haskell-src-exts-1.13.* 1.8.26, released 2012-03-27 Allow haskell-src-exts-1.12.* Don't suggest redundant brackets when turning ++ into : Add hints suggesting >=> and <=< 1.8.25, released 2012-03-25 Update the copyright year in the Cabal file Allow transformers-0.3.* 1.8.24, released 2012-02-20 #531, Make hlint.ghci well formed again 1.8.23, released 2012-02-05 Add hints for redundant seq/evaluate using isWHNF #526, don't hint for return $! (x :: Int) 1.8.22, released 2012-02-04 Add hint for $! where the RHS is not a variable 1.8.21, released 2012-01-26 #508, add lots of hints from the base library #317, add hints for a >> return () to void Add a fromMaybe/fmap ==> maybe hint #304, don't backet tuple sections Add foldl (++) [] ==> concat #512, detect unnecessary case construct When finding hints, don't abort on a parse error #507, add exitSuccess hint #505, suggest record patterns 1.8.20, released 2011-11-29 #500, make sure eta reduction has position information 1.8.19, released 2011-11-27 #498, eta reduce even if there is a where block #497, don't produce an incorrect lambda when suggesting flip 1.8.18, released 2011-11-05 #438, use Foo.Bar to mean Foo/Bar.hs Add a --path command line option to say where files live #441, avoid bad matches due to automatically eta reducing rules #489, import Foo as Foo is redundant #481, suggest liftM instead of fmap when using the Monad laws 1.8.17, released 2011-10-01 #479, allow - as the file to specify using stdin 1.8.16, released 2011-09-28 #478, allow cpphs-1.13.1 Never suggest view patterns (they aren't sufficiently better) Don't suggest use of Data.Ord.comparing, using `on` is better Only suggest elem/notElem on 3 or more items 1.8.15, released 2011-08-13 Add --cpp-ansi to turn on ANSI compat in cpphs 1.8.14, released 2011-08-12 #455, GHC 7.2 compatibility Add lots of hints from Lennart Augustsson 1.8.13, released 2011-07-05 #302, add a backup fixity analysis, if the HSE one fails Fix x /= y || x /= z ==> x `notElem` [y,z], should be && 1.8.12, released 2011-07-03 Allow cpphs-1.12 1.8.11, released 2011-06-18 #440, suggest removing redundant brackets under do #439, don't add redundant brackets under do 1.8.10, released 2011-06-12 Upgrade to hscolour-1.19 1.8.9, released 2011-05-26 #436, add a hint about mapMaybe/map Upgrade to haskell-src-exts-1.11.1 Add a --cross flag, to detect hints between multiple modules #428, don't suggest using String in an instance head 1.8.8, released 2011-04-03 #384, suggest collapsing multiple imports/exports #374, don't suggest the removal of necessary brackets #337, suggest Control.Exception.catch instead of Prelude.catch #412, add hints based on Control.Exception #378, suggest removing fromInteger/fromIntegral on literals #369, add notes to a few hints about possible pitfalls #409, fix a few cases where definitions suggested themselves #410, Support test* as ignored items in settings files #414, add isLit* pattern, and hint about ^^ ==> ** #420, make the suggestion to use let a warning #408, rework the when/unless hints, don't suggest on itself Add duplicate detector, for copy/pasted code #285, don't show duplicate filepath separators If the user enters directories containing no files then say Make suggesting curry/uncurry a warning instead of an error 1.8.7, released 2011-01-31 Relax the transformers dependency, works with 0.0.* and 0.1.* 1.8.6, released 2011-01-27 Export suggestionSeverity/Severity from the API Allow hint imports with "hlint", as well as the existing "hint" 1.8.5, released 2011-01-23 Update the copyright year to 2011 #400, support more encoding strings, give useful errors #401, rename the report template to report_template.html Replace filter f x /= [] with any f x, and 2 more similar 1.8.4, released 2011-01-12 #308, allow haskell-src-exts-1.10.1, which parses Unicode better import qualified Char ==> import qualified Data.Char as Char #393, fix suggestion for import IO, requires more than System.IO #376, note that RecordWildCards implies DisambiguateRecordFields 1.8.3, released 2010-11-10 Allow uniplate-1.6 Switch from mtl to transformers #373, require haskell-src-exts-1.9.6 Add a type signature for GHC 7 Suggest [x | x <- xs] ==> xs, if x is a variable 1.8.2, released 2010-10-23 #371, foo (\x -> y :: Int -> Int) is not a redundant bracket Add a hint to use just equality rather than isJust/fromJust 1.8.1, released 2010-10-15 Massive speed up for files with many naming hints #361, keep module names when suggesting infix Add support for wildcard matching on module names #357, don't camel case suggest on FOO_A #370, fix building with GHC 6.10.4 #313, upgrade to haskell-src-exts-1.9.4 Workaround for #358, disable empty where hints #355, make "--ignore=Parse error" work Add --cpp-simple to run a simple CPP to strip lines begining # Add bracketing information if the parent is a case Suggest intercalate 1.8, released 2010-09-11 Make --test --hint=file typecheck a file for valid hints #347, Suggest use of otherwise, instead of True, in pattern guards Add hints about redundant where statements Suggest removal of redundant guards Make hints about guards work on patterns/infix matches/case alts Make finding guards look a child functions Correctly collapse functions and lambdas using the same patterns Suggest promoting patterns bound to lambdas to functions Allow collapsing lambdas sharing pattern variables correctly #344, only give one warning for multiple collapsable lambdas #300, substantially improve module name resolution with imports BREAKING: imports in hint files require import "hint" HintFile #335, redundant id should only generate one warning Add a hint for using map (f &&& g) #328, for foo'bar suggest the naming fooBar #323, detect redundant brackets in field declarations #321, force the whole file before displaying a parse error Make --find more robust, fixes a potential parse error 1.7.3, released 2010-07-25 Upgrade to hscolour-1.17 1.7.2, released 2010-06-11 #318, match rules by expanding out (.) #319, don't remove lambdas on the right of infix operators 1.7.1, released 2010-06-07 Add a --quiet flag, to supress stdout (mainly for API users) 1.7, released 2010-06-06 Add support for HLint.Builtin.All Fix crash on (\x -> x) Make the library correctly honour the data directory Improve the manual, mainly language changes and hyperlinking Fix a bug in ListRec, could have _recursive_ in the result #315, spot list rec hints through $ and let Add hints based on (f $) ==> f, and change in ListRec hints Changes to the lambda suggestions, now gives a few more hints Don't suggest importing modules in old-locale/old-time Make the API return the suggestions, rather than just the count #278, add -XNoCpp to disable the C preprocessor #279, add -XExt/-XNoExt to choose extensions Remove some redundant brackets in type replacements #286, remove redundant brackets in match Additional bracket removal, application under sections #299, rework hints to use flip (suggest infix in some cases) Add some fromMaybe hints Fix bug where hints didn't always get names #306, make --find use the hints if there are files specified Upgrade to haskell-src-exts-1.9 #303, allow fixities to be specified in hint files 1.6.21, released 2010-04-07 #287, warn about Haskell 98 imports #297, add a hint to use mplus #288, detect redundant brackets under a lambda #302, remove error about ambiguous fixities #281, enhance the redundant monad return warnings #293, eliminate _noParen_ from the result #284, eliminate ViewPatterns from FindHints, hits compiler bug #283, don't suggest removal of RecordWildCards Add some hints about concat and (++) #273, require haskell-src-exts >= 1.8.2 1.6.20, released 2010-02-10 #275, add more acknowledgements (still very incomplete) #254, remove the foldr1/map hint Compress nested lambdas, \x -> \y -> ... ==> \x y -> ... Fix minor bug on \x -> \x -> foo x x #274, add redundant bracket inside record update/construct #272, don't mess up creating sections from qualified names Add some hints to suggest elem Add Paths_hlint to the .cabal file, or the library doesn't link #271, rewrite the match engine in terms of SYB 1.6.19, released 2010-02-06 #251, add automatic definition hunting with --find #268, rewrite the (.) expansion in hints to fix various bugs #269, replacing a case with an if should generate one hint Document the ANN pragmas Require haskell-src-exts-1.8.1 1.6.18, released 2010-02-02 Remove a hint replacing do x <- foo; bar x with foo >>= bar #263, support CPP files more fully Upgrade to hscolour-1.16 Upgrade to cpphs-1.11 1.6.17, released 2010-02-01 Force cpphs-1.10, since 1.11 breaks the interface More hints from the Data.Maybe module #262, add support for the TupleSections extension #264, upgrade to haskell-src-exts-1.8.*, fixes QuasiQuote pos Upgrade to cpphs 1.10 #266, don't match hints that appear to be the definitions #248, tone down the eta reduction hints Add support for WARNING pragma's to reclassify hints Support ignoring hints on types Give better error messages on incorrect settings files Add temporary haskell-src-exts 1.5/1.6 compatibility #327, add hints to use expressions infix #240, if a then True else False no longer suggests a || False Upgrade to haskell-src-exts-1.7.* #236, support changing the text encoding with --encoding/--utf8 #260, generate nicer lambdas for (($) . f) Add the hint (($) . f) ==> (f $) 1.6.16, released 2010-01-23 Further performance enhancements (for details see my blog) Update to uniplate 1.5.* (fixes performance bug) Improve speed based on profiling (roughly twice as fast) #245, add hints for excess brackets in types and patterns Make 100% redundant brackets an error Fix bug where qualified names did not match Remove dependency on SYB #234, allow TH top-level splices for ignore #110, add tests for ignoring commands 1.6.15, released 2010-01-12 Upgrade to uniplate 1.4.* (fixes performance bug) #192, make HLint into a fairly basic library Add --datadir to allow running with a different data directory #254, eliminate foldl/map fusion rules (which were untrue) Fix a few typos in the hint rules Upgrade to uniplate 1.3.* Upgrade to haskell-src-exts 1.6.* Add a .ghci file snippet #247, Fix bug matching expressions containing position info 1.6.14, released 2010-01-05 Upgrade to haskell-src-exts 1.5.* 1.6.13, released 2010-01-05 #246, redundant brackets in [(...)] Add fold/map fusion hints Don't suggest namings that are already used in the module #239, Add suggestions of and/or on foldl Add --extension flag, to find files not named .hs/.lhs Only activate the builtin hints when they are imported Fix matching bug, said "Use flip" on "\v -> f v . g" Suggest changing some pattern guards to view patterns 1.6.12, released 2009-11-06 Fix a bug with ignored hints being written to reports Upgrade to haskell-src-exts 1.3.* #228, suggest let instead of <- return in do statements #229, suggest comparing Qualify all non-Prelude function suggestions #225, Add redundant flip hint #226, Add ((+) x) ==> (x +) #223, TemplateHaskell may allow other extensions via code Fix incorrect suggestion on do x <- f ; g x x A few small additional hints (use flip, redundant id) 1.6.11, released 2009-09-13 Don't perform type eta reduction 1.6.10, released 2009-09-13 Fix bug, eta reduction on chained infix operators, i.e. x#y#z 1.6.9, released 2009-09-12 #217, don't suggest eta reduction on - or + Fix bug, PatternGuards under case alternatives were ignored 1.6.8, released 2009-09-07 #213, upgrade to cpphs 1.9 Add suggestion to replace lambda with operator sections Fix bug, ''Name decided TemplateHaskell was unnecessary HPC statistics, and increase in test coverage Fix bug, import A as Y; import A gave import A, missing the as Y Fix bug, type Foo a = Bar a a incorrectly suggested eta reduce 1.6.7, released 2009-08-31 NOTE: #213 has not been fixed, cpphs can cause hangs Add threaded flag to Cabal to disable -threaded mode #212, fix crash Fix bug, incorrectly decided TemplateHaskell was unnecessary 1.6.6, released 2009-08-29 Upgrade to hscolour 1.15 Add a hint for using unless #211, add hints for unused extensions #188, add pragma hints Add a few additional hints (Functor laws) #137, add cpphs support #189, give hints for redundant imports Upgrade to haskell-src-exts 1.1.* 1.6.5, released 2009-08-02 #206, better presentation of parse errors #208, give the correct precedence to ==> in source files 1.6.4, released 2009-07-12 Start of changelog hlint-3.1.6/src/0000755000000000000000000000000013674744765011643 5ustar0000000000000000hlint-3.1.6/src/Util.hs0000644000000000000000000000333613671470061013077 0ustar0000000000000000{-# LANGUAGE ExistentialQuantification, Rank2Types #-} module Util( forceList, gzip, universeParentBi, exitMessage, exitMessageImpure, getContentsUTF8 ) where import System.Exit import System.IO import System.IO.Unsafe import Unsafe.Coerce import Data.Data import Data.Generics.Uniplate.DataOnly --------------------------------------------------------------------- -- CONTROL.DEEPSEQ forceList :: [a] -> [a] forceList xs = length xs `seq` xs --------------------------------------------------------------------- -- SYSTEM.IO exitMessage :: String -> IO a exitMessage msg = do hPutStrLn stderr msg exitWith $ ExitFailure 1 exitMessageImpure :: String -> a exitMessageImpure = unsafePerformIO . exitMessage getContentsUTF8 :: IO String getContentsUTF8 = do hSetEncoding stdin utf8 getContents --------------------------------------------------------------------- -- DATA.GENERICS data Box = forall a . Data a => Box a gzip :: Data a => (forall b . Data b => b -> b -> c) -> a -> a -> Maybe [c] gzip f x y | toConstr x /= toConstr y = Nothing | otherwise = Just $ zipWith op (gmapQ Box x) (gmapQ Box y) -- unsafeCoerce is safe because gmapQ on the same constr gives the same fields -- in the same order where op (Box x) (Box y) = f x (unsafeCoerce y) --------------------------------------------------------------------- -- DATA.GENERICS.UNIPLATE.OPERATIONS universeParent :: Data a => a -> [(Maybe a, a)] universeParent x = (Nothing,x) : f x where f :: Data a => a -> [(Maybe a, a)] f x = concat [(Just x, y) : f y | y <- children x] universeParentBi :: (Data a, Data b) => a -> [(Maybe b, b)] universeParentBi = concatMap universeParent . childrenBi hlint-3.1.6/src/Timing.hs0000644000000000000000000000412313665031514013403 0ustar0000000000000000 module Timing( timed, timedIO, startTimings, printTimings ) where import qualified Data.HashMap.Strict as Map import Control.Exception import Data.IORef.Extra import Data.Tuple.Extra import Data.List.Extra import Control.Monad import System.Console.CmdArgs.Verbosity import System.Time.Extra import System.IO.Unsafe type Category = String type Item = String {-# NOINLINE useTimingsRef #-} useTimingsRef :: IORef Bool useTimingsRef = unsafePerformIO $ newIORef False {-# NOINLINE useTimings #-} useTimings :: Bool useTimings = unsafePerformIO $ readIORef useTimingsRef {-# NOINLINE timings #-} timings :: IORef (Map.HashMap (Category, Item) Seconds) timings = unsafePerformIO $ newIORef Map.empty {-# NOINLINE timed #-} timed :: Category -> Item -> a -> a timed c i x = if not useTimings then x else unsafePerformIO $ timedIO c i $ evaluate x timedIO :: Category -> Item -> IO a -> IO a timedIO c i x = if not useTimings then x else do let quiet = c == "Hint" unless quiet $ whenLoud $ putStr $ "Performing " ++ c ++ " of " ++ i ++ "... " (time, x) <- duration x atomicModifyIORef'_ timings $ Map.insertWith (+) (c, i) time unless quiet $ whenLoud $ putStrLn $ "took " ++ showDuration time pure x startTimings :: IO () startTimings = do writeIORef useTimingsRef True writeIORef timings Map.empty printTimings :: IO () printTimings = do mp <- readIORef timings let items = sortOn (sumSnd . snd) $ groupSort $ map (\((a,b),c) -> (a,(b,c))) $ Map.toList mp putStrLn $ unlines $ intercalate [""] $ map disp $ items ++ [("TOTAL", map (second sumSnd) items)] where sumSnd = sum . map snd disp (cat,xs) = ("Timing " ++ cat) : [" " ++ showDuration b ++ " " ++ a | (a,b) <- xs2] ++ [" " ++ showDuration (sumSnd xs2) ++ " TOTAL"] where xs2 = f $ splitAt 9 $ sortOn (negate . snd) xs f (xs,ys) | length ys <= 1 = xs ++ ys | otherwise = xs ++ [("Other items (" ++ show (length ys) ++ ")", sumSnd ys)] hlint-3.1.6/src/Report.hs0000644000000000000000000000550313656755416013450 0ustar0000000000000000{-# LANGUAGE RecordWildCards #-} module Report(writeReport) where import Idea import Data.Tuple.Extra import Data.List.Extra import qualified Data.List.NonEmpty as NE import Data.Maybe import Data.Version import Timing import Paths_hlint import HsColour import EmbedData import qualified GHC.Util as GHC writeTemplate :: FilePath -> [(String,[String])] -> FilePath -> IO () writeTemplate dataDir content to = writeFile to $ unlines $ concatMap f $ lines reportTemplate where f ('$':xs) = fromMaybe ['$':xs] $ lookup xs content f x = [x] writeReport :: FilePath -> FilePath -> [Idea] -> IO () writeReport dataDir file ideas = timedIO "Report" file $ writeTemplate dataDir inner file where generateIds :: [String] -> [(String,Int)] -- sorted by name generateIds = map (NE.head &&& length) . NE.group -- must be already sorted files = generateIds $ sort $ map (GHC.srcSpanFilename . ideaSpan) ideas hints = generateIds $ map hintName $ sortOn (negate . fromEnum . ideaSeverity &&& hintName) ideas hintName x = show (ideaSeverity x) ++ ": " ++ ideaHint x inner = if null ideas then emptyInner else nonEmptyInner emptyInner = [("VERSION",['v' : showVersion version]),("CONTENT", ["No hints"]), ("HINTS", ["
  • No hints
  • "]),("FILES", ["
  • No files
  • "])] nonEmptyInner = [("VERSION",['v' : showVersion version]),("CONTENT",content), ("HINTS",list "hint" hints),("FILES",list "file" files)] content = concatMap (\i -> writeIdea (getClass i) i) ideas getClass i = "hint" ++ f hints (hintName i) ++ " file" ++ f files (GHC.srcSpanFilename $ ideaSpan i) where f xs x = show $ fromJust $ findIndex ((==) x . fst) xs list mode = zipWithFrom f 0 where f i (name,n) = "
  • " ++ escapeHTML name ++ " (" ++ show n ++ ")
  • " where id = mode ++ show i writeIdea :: String -> Idea -> [String] writeIdea cls Idea{..} = ["
    " ,escapeHTML (GHC.showSrcSpan ideaSpan ++ ": " ++ show ideaSeverity ++ ": " ++ ideaHint) ++ "
    " ,"Found
    " ,hsColourHTML ideaFrom] ++ (case ideaTo of Nothing -> [] Just to -> ["Perhaps" ++ (if to == "" then " you should remove it." else "") ++ "
    " ,hsColourHTML to]) ++ [let n = showNotes ideaNote in if n /= "" then "Note: " ++ writeNote n ++ "" else "" ,"
    " ,""] -- Unescaped, but may have `backticks` for code writeNote :: String -> String writeNote = f . splitOn "`" where f (a:b:c) = escapeHTML a ++ "" ++ escapeHTML b ++ "" ++ f c f xs = concatMap escapeHTML xs hlint-3.1.6/src/Refact.hs0000644000000000000000000000474613671470061013374 0ustar0000000000000000{-# LANGUAGE LambdaCase #-} module Refact ( toRefactSrcSpan , toSS , checkRefactor, refactorPath, runRefactoring ) where import Control.Exception.Extra import Control.Monad import Data.Maybe import Data.Version.Extra import GHC.LanguageExtensions.Type import System.Directory.Extra import System.Exit import System.IO.Extra import System.Process.Extra import qualified Refact.Types as R import qualified SrcLoc as GHC toRefactSrcSpan :: GHC.SrcSpan -> R.SrcSpan toRefactSrcSpan = \case GHC.RealSrcSpan span -> R.SrcSpan (GHC.srcSpanStartLine span) (GHC.srcSpanStartCol span) (GHC.srcSpanEndLine span) (GHC.srcSpanEndCol span) GHC.UnhelpfulSpan _ -> R.SrcSpan (-1) (-1) (-1) (-1) -- | Don't crash in case ghc gives us a \"fake\" span, -- opting instead to show @-1 -1 -1 -1@ coordinates. toSS :: GHC.HasSrcSpan a => a -> R.SrcSpan toSS = toRefactSrcSpan . GHC.getLoc checkRefactor :: Maybe FilePath -> IO FilePath checkRefactor = refactorPath >=> either errorIO pure refactorPath :: Maybe FilePath -> IO (Either String FilePath) refactorPath rpath = do let excPath = fromMaybe "refactor" rpath mexc <- findExecutable excPath case mexc of Just exc -> do ver <- readVersion . tail <$> readProcess exc ["--version"] "" pure $ if versionBranch ver >= [0,7,0,0] then Right exc else Left "Your version of refactor is too old, please upgrade to the latest version" Nothing -> pure $ Left $ unlines [ "Could not find 'refactor' executable" , "Tried to find '" ++ excPath ++ "' on the PATH" , "'refactor' is provided by the 'apply-refact' package and has to be installed" , "" ] runRefactoring :: FilePath -> FilePath -> FilePath -> [Extension] -> [Extension] -> String -> IO ExitCode runRefactoring rpath fin hints enabled disabled opts = do let args = [fin, "-v0"] ++ words opts ++ ["--refact-file", hints] ++ [arg | e <- enabled, arg <- ["-X", show e]] ++ [arg | e <- disabled, arg <- ["-X", "No" ++ show e]] (_, _, _, phand) <- createProcess $ proc rpath args try $ hSetBuffering stdin LineBuffering :: IO (Either IOException ()) hSetBuffering stdout LineBuffering -- Propagate the exit code from the spawn process waitForProcess phand hlint-3.1.6/src/Parallel.hs0000644000000000000000000000230413630160617013706 0ustar0000000000000000{- The parallel function (specialised to lists) is equivalent to: import Control.Parallel.Strategies parallel :: [IO [a]] -> IO [[a]] parallel = pure . withStrategy (parList $ seqList r0) . map unsafePerformIO However, this version performs about 10% slower with 2 processors in GHC 6.12.1 -} module Parallel(parallel) where import System.IO.Unsafe import Control.Concurrent import Control.Exception import Control.Monad parallel :: Int -> [IO a] -> IO [a] parallel j = if j <= 1 then parallel1 else parallelN j parallel1 :: [IO a] -> IO [a] parallel1 [] = pure [] parallel1 (x:xs) = do x2 <- x xs2 <- unsafeInterleaveIO $ parallel1 xs pure $ x2:xs2 parallelN :: Int -> [IO a] -> IO [a] parallelN j xs = do ms <- mapM (const newEmptyMVar) xs chan <- newChan mapM_ (writeChan chan . Just) $ zip ms xs replicateM_ j (writeChan chan Nothing >> forkIO (f chan)) let throwE x = throw (x :: SomeException) parallel1 $ map (fmap (either throwE id) . takeMVar) ms where f chan = do v <- readChan chan case v of Nothing -> pure () Just (m,x) -> do putMVar m =<< try x f chan hlint-3.1.6/src/Main.hs0000644000000000000000000000037013637643241013045 0ustar0000000000000000 module Main(main) where import Language.Haskell.HLint import Control.Monad import System.Environment import System.Exit main :: IO () main = do args <- getArgs errs <- hlint args unless (null errs) $ exitWith $ ExitFailure 1 hlint-3.1.6/src/Idea.hs0000644000000000000000000001176213661522166013031 0ustar0000000000000000{-# LANGUAGE RecordWildCards, NoMonomorphismRestriction #-} module Idea( Idea(..), rawIdea, idea, suggest, suggestRemove, ideaRemove, warn, ignore, rawIdeaN, suggestN, ignoreNoSuggestion, showIdeasJson, showANSI, Note(..), showNotes, Severity(..), ) where import Data.Functor import Data.List.Extra import Config.Type import HsColour import Refact.Types hiding (SrcSpan) import qualified Refact.Types as R import Prelude import SrcLoc import Outputable import GHC.Util import Language.Haskell.GhclibParserEx.GHC.Utils.Outputable -- | An idea suggest by a 'Hint'. data Idea = Idea {ideaModule :: [String] -- ^ The modules the idea is for, usually a singleton. ,ideaDecl :: [String] -- ^ The declarations the idea is for, usually a singleton, typically the function name, but may be a type name. ,ideaSeverity :: Severity -- ^ The severity of the idea, e.g. 'Warning'. ,ideaHint :: String -- ^ The name of the hint that generated the idea, e.g. @\"Use reverse\"@. ,ideaSpan :: SrcSpan -- ^ The source code the idea relates to. ,ideaFrom :: String -- ^ The contents of the source code the idea relates to. ,ideaTo :: Maybe String -- ^ The suggested replacement, or 'Nothing' for no replacement (e.g. on parse errors). ,ideaNote :: [Note] -- ^ Notes about the effect of applying the replacement. ,ideaRefactoring :: [Refactoring R.SrcSpan] -- ^ How to perform this idea } deriving (Eq,Ord) -- I don't use aeson here for 2 reasons: -- 1) Aeson doesn't esape unicode characters, and I want to (allows me to ignore encoding) -- 2) I want to control the format so it's slightly human readable as well showIdeaJson :: Idea -> String showIdeaJson idea@Idea{ideaSpan=srcSpan@SrcSpan{..}, ..} = dict [("module", list $ map str ideaModule) ,("decl", list $ map str ideaDecl) ,("severity", str $ show ideaSeverity) ,("hint", str ideaHint) ,("file", str srcSpanFilename) ,("startLine", show srcSpanStartLine') ,("startColumn", show srcSpanStartColumn) ,("endLine", show srcSpanEndLine') ,("endColumn", show srcSpanEndColumn) ,("from", str ideaFrom) ,("to", maybe "null" str ideaTo) ,("note", list (map (str . show) ideaNote)) ,("refactorings", str $ show ideaRefactoring) ] where str x = "\"" ++ escapeJSON x ++ "\"" dict xs = "{" ++ intercalate "," [show k ++ ":" ++ v | (k,v) <- xs] ++ "}" list xs = "[" ++ intercalate "," xs ++ "]" showIdeasJson :: [Idea] -> String showIdeasJson ideas = "[" ++ intercalate "\n," (map showIdeaJson ideas) ++ "]" instance Show Idea where show = showEx id showANSI :: IO (Idea -> String) showANSI = showEx <$> hsColourConsole showEx :: (String -> String) -> Idea -> String showEx tt Idea{..} = unlines $ [showSrcSpan ideaSpan ++ ": " ++ (if ideaHint == "" then "" else show ideaSeverity ++ ": " ++ ideaHint)] ++ f "Found" (Just ideaFrom) ++ f "Perhaps" ideaTo ++ ["Note: " ++ n | let n = showNotes ideaNote, n /= ""] where f msg Nothing = [] f msg (Just x) | null xs = [msg ++ " you should remove it."] | otherwise = (msg ++ ":") : map (" "++) xs where xs = lines $ tt x rawIdea :: Severity -> String -> SrcSpan -> String -> Maybe String -> [Note]-> [Refactoring R.SrcSpan] -> Idea rawIdea = Idea [] [] rawIdeaN :: Severity -> String -> SrcSpan -> String -> Maybe String -> [Note] -> Idea rawIdeaN a b c d e f = Idea [] [] a b c d e f [] idea :: (HasSrcSpan a, Outputable.Outputable a, HasSrcSpan b, Outputable.Outputable b) => Severity -> String -> a -> b -> [Refactoring R.SrcSpan] -> Idea idea severity hint from to = rawIdea severity hint (getLoc from) (unsafePrettyPrint from) (Just $ unsafePrettyPrint to) [] -- Construct an Idea that suggests "Perhaps you should remove it." ideaRemove :: Severity -> String -> SrcSpan -> String -> [Refactoring R.SrcSpan] -> Idea ideaRemove severity hint span from = rawIdea severity hint span from (Just "") [] suggest :: (HasSrcSpan a, Outputable.Outputable a, HasSrcSpan b, Outputable.Outputable b) => String -> a -> b -> [Refactoring R.SrcSpan] -> Idea suggest = idea Suggestion suggestRemove :: String -> SrcSpan -> String -> [Refactoring R.SrcSpan] -> Idea suggestRemove = ideaRemove Suggestion warn :: (HasSrcSpan a, Outputable.Outputable a, HasSrcSpan b, Outputable.Outputable b) => String -> a -> b -> [Refactoring R.SrcSpan] -> Idea warn = idea Warning ignoreNoSuggestion :: (HasSrcSpan a, Outputable.Outputable a) => String -> a -> Idea ignoreNoSuggestion hint x = rawIdeaN Ignore hint (getLoc x) (unsafePrettyPrint x) Nothing [] ignore :: (HasSrcSpan a, Outputable.Outputable a) => String -> a -> a -> [Refactoring R.SrcSpan] -> Idea ignore = idea Ignore ideaN :: (HasSrcSpan a, Outputable.Outputable a) => Severity -> String -> a -> a -> Idea ideaN severity hint from to = idea severity hint from to [] suggestN :: (HasSrcSpan a, Outputable.Outputable a) => String -> a -> a -> Idea suggestN = ideaN Suggestion hlint-3.1.6/src/HsColour.hs0000644000000000000000000000106313627025257013717 0ustar0000000000000000{-# LANGUAGE CPP #-} module HsColour(hsColourHTML, hsColourConsole) where #ifdef GPL_SCARES_ME hsColourConsole :: IO (String -> String) hsColourConsole = pure id hsColourHTML :: String -> String hsColourHTML = id #else import Data.Functor import Prelude import Language.Haskell.HsColour.TTY as TTY import Language.Haskell.HsColour.Colourise import Language.Haskell.HsColour.CSS as CSS hsColourConsole :: IO (String -> String) hsColourConsole = TTY.hscolour <$> readColourPrefs hsColourHTML :: String -> String hsColourHTML = CSS.hscolour False 1 #endif hlint-3.1.6/src/HLint.hs0000644000000000000000000001766213671470061013207 0ustar0000000000000000{-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE RecordWildCards #-} {-# OPTIONS_GHC -Wno-incomplete-patterns #-} module HLint(hlint, readAllSettings) where import Control.Applicative import Control.Monad.Extra import Control.Exception.Extra import Control.Concurrent.Extra import System.Console.CmdArgs.Verbosity import GHC.Util.DynFlags import Data.List.Extra import GHC.Conc import System.Exit import System.IO.Extra import System.Time.Extra import Data.Tuple.Extra import Prelude import CmdLine import Config.Read import Config.Type import Config.Compute import Report import Idea import Apply import Test.All import Hint.All import Grep import Refact import Timing import Test.Proof import Parallel import GHC.All import CC import EmbedData -- | This function takes a list of command line arguments, and returns the given hints. -- To see a list of arguments type @hlint --help@ at the console. -- This function writes to the stdout/stderr streams, unless @--quiet@ is specified. -- -- As an example: -- -- > do hints <- hlint ["src", "--ignore=Use map","--quiet"] -- > when (length hints > 3) $ error "Too many hints!" -- -- /Warning:/ The flags provided by HLint are relatively stable, but do not have the same -- API stability guarantees as the rest of the strongly-typed API. Do not run this function -- on your server with untrusted input. hlint :: [String] -> IO [Idea] hlint args = do initGlobalDynFlags cmd <- getCmd args case cmd of CmdMain{} -> do startTimings (time, xs) <- duration $ hlintMain args cmd when (cmdTiming cmd) $ do printTimings putStrLn $ "Took " ++ showDuration time pure $ if cmdNoExitCode cmd then [] else xs CmdGrep{} -> hlintGrep cmd >> pure [] CmdTest{} -> hlintTest cmd >> pure [] hlintTest :: Cmd -> IO () hlintTest cmd@CmdTest{..} = if not $ null cmdProof then do files <- cmdHintFiles cmd s <- readFilesConfig files let reps = if cmdReports == ["report.html"] then ["report.txt"] else cmdReports mapM_ (proof reps s) cmdProof else do failed <- test cmd (\args -> do errs <- hlint args; unless (null errs) $ exitWith $ ExitFailure 1) cmdDataDir cmdGivenHints when (failed > 0) exitFailure cmdParseFlags :: Cmd -> ParseFlags cmdParseFlags cmd = parseFlagsSetLanguage (cmdExtensions cmd) $ defaultParseFlags{cppFlags=cmdCpp cmd} hlintGrep :: Cmd -> IO () hlintGrep cmd@CmdGrep{..} = if null cmdFiles then exitWithHelp else do files <- concatMapM (resolveFile cmd Nothing) cmdFiles if null files then errorIO "No files found" else runGrep cmdPattern (cmdParseFlags cmd) files withVerbosity :: Verbosity -> IO a -> IO a withVerbosity new act = do old <- getVerbosity (setVerbosity new >> act) `finally` setVerbosity old hlintMain :: [String] -> Cmd -> IO [Idea] hlintMain args cmd@CmdMain{..} | cmdDefault = do ideas <- if null cmdFiles then pure [] else withVerbosity Quiet $ runHlintMain args cmd{cmdJson=False,cmdSerialise=False,cmdRefactor=False} Nothing let bad = nubOrd $ map ideaHint ideas if null bad then putStr defaultYaml else do let group1:groups = splitOn ["",""] $ lines defaultYaml let group2 = "# Warnings currently triggered by your code" : ["- ignore: {name: " ++ show x ++ "}" | x <- bad] putStr $ unlines $ intercalate ["",""] $ group1:group2:groups pure [] | null cmdFiles && not (null cmdFindHints) = do hints <- concatMapM (resolveFile cmd Nothing) cmdFindHints mapM_ (putStrLn . fst <=< computeSettings (cmdParseFlags cmd)) hints >> pure [] | null cmdFiles = exitWithHelp | cmdRefactor = withTempFile $ runHlintMain args cmd . Just | otherwise = runHlintMain args cmd Nothing runHlintMain :: [String] -> Cmd -> Maybe FilePath -> IO [Idea] runHlintMain args cmd tmpFile = do (cmd, settings) <- readAllSettings args cmd runHints args settings =<< resolveFiles cmd tmpFile resolveFiles :: Cmd -> Maybe FilePath -> IO Cmd resolveFiles cmd@CmdMain{..} tmpFile = do files <- concatMapM (resolveFile cmd tmpFile) cmdFiles if null files then error "No files found" else pure cmd { cmdFiles = files } resolveFiles cmd _ = pure cmd readAllSettings :: [String] -> Cmd -> IO (Cmd, [Setting]) readAllSettings args1 cmd@CmdMain{..} = do files <- cmdHintFiles cmd settings1 <- readFilesConfig $ files ++ [("CommandLine.yaml",Just (enableGroup x)) | x <- cmdWithGroups] let args2 = [x | SettingArgument x <- settings1] cmd@CmdMain{..} <- if null args2 then pure cmd else getCmd $ args2 ++ args1 -- command line arguments are passed last settings2 <- concatMapM (fmap snd . computeSettings (cmdParseFlags cmd)) cmdFindHints let settings3 = [SettingClassify $ Classify Ignore x "" "" | x <- cmdIgnore] pure (cmd, settings1 ++ settings2 ++ settings3) where enableGroup groupName = unlines ["- group:" ," name: " ++ groupName ," enabled: true" ] runHints :: [String] -> [Setting] -> Cmd -> IO [Idea] runHints args settings cmd@CmdMain{..} = do j <- if cmdThreads == 0 then getNumProcessors else pure cmdThreads withNumCapabilities j $ do let outStrLn = whenNormal . putStrLn ideas <- getIdeas cmd settings ideas <- pure $ if cmdShowAll then ideas else filter (\i -> ideaSeverity i /= Ignore) ideas if cmdJson then putStrLn $ showIdeasJson ideas else if cmdCC then mapM_ (printIssue . fromIdea) ideas else if cmdSerialise then do hSetBuffering stdout NoBuffering print $ map (show &&& ideaRefactoring) ideas else if cmdRefactor then handleRefactoring ideas cmdFiles cmd else do usecolour <- cmdUseColour cmd showItem <- if usecolour then showANSI else pure show mapM_ (outStrLn . showItem) ideas handleReporting ideas cmd pure ideas getIdeas :: Cmd -> [Setting] -> IO [Idea] getIdeas cmd@CmdMain{..} settings = do settings <- pure $ settings ++ map (Builtin . fst) builtinHints let flags = cmdParseFlags cmd ideas <- if cmdCross then applyHintFiles flags settings cmdFiles else concat <$> parallel cmdThreads [evaluateList =<< applyHintFile flags settings x Nothing | x <- cmdFiles] pure $ if not (null cmdOnly) then [i | i <- ideas, ideaHint i `elem` cmdOnly] else ideas -- #746: run refactor even if no hint, which ensures consistent output -- whether there are hints or not. handleRefactoring :: [Idea] -> [String] -> Cmd -> IO () handleRefactoring ideas files cmd@CmdMain{..} = case cmdFiles of [file] -> do -- Ensure that we can find the executable path <- checkRefactor (if cmdWithRefactor == "" then Nothing else Just cmdWithRefactor) -- writeFile "hlint.refact" let hints = show $ map (show &&& ideaRefactoring) ideas withTempFile $ \f -> do writeFile f hints let ParseFlags{enabledExtensions, disabledExtensions} = cmdParseFlags cmd exitWith =<< runRefactoring path file f enabledExtensions disabledExtensions cmdRefactorOptions _ -> errorIO "Refactor flag can only be used with an individual file" handleReporting :: [Idea] -> Cmd -> IO () handleReporting showideas cmd@CmdMain{..} = do let outStrLn = whenNormal . putStrLn forM_ cmdReports $ \x -> do outStrLn $ "Writing report to " ++ x ++ " ..." writeReport cmdDataDir x showideas unless cmdNoSummary $ do let n = length showideas outStrLn $ if n == 0 then "No hints" else show n ++ " hint" ++ ['s' | n/=1] evaluateList :: [a] -> IO [a] evaluateList xs = do evaluate $ length xs pure xs hlint-3.1.6/src/Grep.hs0000644000000000000000000000271313671470061013055 0ustar0000000000000000 module Grep(runGrep) where import Hint.All import Apply import Config.Type import GHC.All import Control.Monad import Data.List import Util import Idea import qualified GHC.Hs as GHC import qualified BasicTypes as GHC import qualified Outputable import qualified ErrUtils import Lexer import Language.Haskell.GhclibParserEx.GHC.Hs.ExtendInstances import SrcLoc as GHC hiding (mkSrcSpan) import GHC.Util.DynFlags import Bag runGrep :: String -> ParseFlags -> [FilePath] -> IO () runGrep patt flags files = do exp <- case parseExpGhcWithMode flags patt of POk _ a -> pure a PFailed ps -> exitMessage $ let (_, errs) = getMessages ps baseDynFlags errMsg = head (bagToList errs) msg = Outputable.showSDoc baseDynFlags $ ErrUtils.pprLocErrMsg errMsg in "Failed to parse " ++ msg ++ ", when parsing:\n " ++ patt let ghcUnit = GHC.noLoc $ GHC.ExplicitTuple GHC.noExtField [] GHC.Boxed let rule = hintRules [HintRule Suggestion "grep" [] mempty (extendInstances exp) (extendInstances ghcUnit) Nothing] forM_ files $ \file -> do res <- parseModuleEx flags file Nothing case res of Left (ParseError sl msg ctxt) -> print $ rawIdeaN Error (if "Parse error" `isPrefixOf` msg then msg else "Parse error: " ++ msg) sl ctxt Nothing [] Right m -> forM_ (applyHints [] rule [m]) $ \i -> print i{ideaHint="", ideaTo=Nothing} hlint-3.1.6/src/Fixity.hs0000644000000000000000000000705613661521317013441 0ustar0000000000000000{-# LANGUAGE ViewPatterns #-} module Fixity( FixityInfo, Associativity(..), defaultFixities, fromFixitySig, toFixitySig, toFixity, ) where import GHC.Generics(Associativity(..)) import GHC.Hs.Binds import GHC.Hs.Extension import OccName import RdrName import SrcLoc import BasicTypes import Language.Haskell.GhclibParserEx.GHC.Types.Name.Reader import Language.Haskell.GhclibParserEx.Fixity -- Lots of things define a fixity. None define it quite right, so let's have our own type. -- | A Fixity definition, comprising the name the fixity applies to, -- the direction and the precedence. As an example, a source file containing: -- -- > infixr 3 `foo` -- -- would create @(\"foo\", RightAssociative, 3)@. type FixityInfo = (String, Associativity, Int) fromFixitySig :: FixitySig GhcPs -> [FixityInfo] fromFixitySig (FixitySig _ names (Fixity _ i dir)) = [(rdrNameStr name, f dir, i) | name <- names] where f InfixL = LeftAssociative f InfixR = RightAssociative f InfixN = NotAssociative fromFixitySig _ = [] toFixity :: FixityInfo -> (String, Fixity) toFixity (name, dir, i) = (name, Fixity NoSourceText i $ f dir) where f LeftAssociative = InfixL f RightAssociative = InfixR f NotAssociative = InfixN fromFixity :: (String, Fixity) -> FixityInfo fromFixity (name, Fixity _ i dir) = (name, assoc dir, i) where assoc dir = case dir of InfixL -> LeftAssociative InfixR -> RightAssociative InfixN -> NotAssociative toFixitySig :: FixityInfo -> FixitySig GhcPs toFixitySig (toFixity -> (name, x)) = FixitySig noExtField [noLoc $ mkRdrUnqual (mkVarOcc name)] x defaultFixities :: [FixityInfo] defaultFixities = map fromFixity $ customFixities ++ baseFixities ++ lensFixities ++ otherFixities -- List as provided at https://github.com/ndmitchell/hlint/issues/416. lensFixities :: [(String, Fixity)] lensFixities = concat [ infixr_ 4 ["%%@~","<%@~","%%~","<+~","<*~","<-~","","??"] , infixl_ 8 ["^.","^@."] , infixr_ 9 ["<.>","<.",".>"] , infixr_ 4 ["%@~",".~","+~","*~","-~","//~","^~","^^~","**~","&&~","<>~","||~","%~"] , infix_ 4 ["%@=",".=","+=","*=","-=","//=","^=","^^=","**=","&&=","<>=","||=","%="] , infixr_ 2 ["<~"] , infixr_ 2 ["`zoom`","`magnify`"] , infixl_ 8 ["^..","^?","^?!","^@..","^@?","^@?!"] , infixl_ 8 ["^#"] , infixr_ 4 ["<#~","#~","#%~","<#%~","#%%~"] , infix_ 4 ["<#=","#=","#%=","<#%=","#%%="] , infixl_ 9 [":>"] , infixr_ 4 ["~","<~","<.>~","<<.>~"] , infix_ 4 ["=","<=","<.>=","<<.>="] , infixr_ 4 [".|.~",".&.~","<.|.~","<.&.~"] , infix_ 4 [".|.=",".&.=","<.|.=","<.&.="] ] otherFixities :: [(String, Fixity)] otherFixities = concat -- hspec [ infix_ 1 ["shouldBe","shouldSatisfy","shouldStartWith","shouldEndWith","shouldContain","shouldMatchList" ,"shouldReturn","shouldNotBe","shouldNotSatisfy","shouldNotContain","shouldNotReturn","shouldThrow"] -- quickcheck , infixr_ 0 ["==>"] , infix_ 4 ["==="] -- esqueleto , infix_ 4 ["==."] -- lattices , infixr_ 5 ["\\/"] -- \/ , infixr_ 6 ["/\\"] -- /\ ] customFixities :: [(String, Fixity)] customFixities = infixl_ 1 ["`on`"] -- See https://github.com/ndmitchell/hlint/issues/425 -- otherwise GTK apps using `on` at a different fixity have -- spurious warnings. hlint-3.1.6/src/Extension.hs0000644000000000000000000000505413671470061014135 0ustar0000000000000000module Extension( defaultExtensions, configExtensions, extensionImpliedEnabledBy, extensionImplies ) where import Data.List.Extra import qualified Data.Map as Map import GHC.LanguageExtensions.Type import qualified Language.Haskell.GhclibParserEx.GHC.Driver.Session as GhclibParserEx badExtensions = reallyBadExtensions ++ [ Arrows -- steals proc , UnboxedTuples, UnboxedSums -- breaks (#) lens operator , QuasiQuotes -- breaks [x| ...], making whitespace free list comps break , {- DoRec , -} RecursiveDo -- breaks rec ] reallyBadExtensions = [ TransformListComp -- steals the group keyword , StaticPointers -- steals the static keyword {- , XmlSyntax , RegularPatterns -} -- steals a-b and < operators , AlternativeLayoutRule -- Does not play well with 'MultiWayIf' , NegativeLiterals -- Was not enabled by HSE and enabling breaks tests. , StarIsType -- conflicts with TypeOperators. StarIsType is currently enabled by default, -- so adding it here has no effect except avoiding passing it to apply-refact. -- See https://github.com/mpickering/apply-refact/issues/58 ] -- | Extensions we turn on by default when parsing. Aim to parse as -- many files as we can. defaultExtensions :: [Extension] defaultExtensions = enumerate \\ badExtensions -- | Extensions we turn on when reading config files, don't have to deal with the whole world -- of variations - in particular, we might require spaces in some places. configExtensions :: [Extension] configExtensions = enumerate \\ reallyBadExtensions -- | This extension implies the following extensions are -- enabled/disabled. extensionImplies :: Extension -> ([Extension], [Extension]) extensionImplies = \x ->Map.findWithDefault ([], []) x mp where mp = Map.fromList extensionImplications -- 'x' is implied enabled by the result extensions. extensionImpliedEnabledBy :: Extension -> [Extension] extensionImpliedEnabledBy = \x -> Map.findWithDefault [] x mp where mp = Map.fromListWith (++) [(b, [a]) | (a, (bs, _)) <- extensionImplications, b <- bs] -- 'x' is implied disabled by the result extensions. Not called at this time. _extensionImpliedDisabledBy :: Extension -> [Extension] _extensionImpliedDisabledBy = \x -> Map.findWithDefault [] x mp where mp = Map.fromListWith (++) [(b, [a]) | (a, (_, bs)) <- extensionImplications, b <- bs] -- | (a, bs) means extension a implies all of bs. Uses GHC source at -- DynFlags.impliedXFlags extensionImplications :: [(Extension, ([Extension], [Extension]))] extensionImplications = GhclibParserEx.extensionImplications hlint-3.1.6/src/EmbedData.hs0000644000000000000000000000070713657003027013765 0ustar0000000000000000{-# LANGUAGE TemplateHaskell #-} module EmbedData ( hlintYaml, defaultYaml, reportTemplate, ) where import Data.ByteString.UTF8 import Data.FileEmbed hlintYaml :: (FilePath, Maybe String) hlintYaml = ("data/hlint.yaml", Just $ toString $(embedFile "data/hlint.yaml")) defaultYaml :: String defaultYaml = toString $(embedFile "data/default.yaml") reportTemplate :: String reportTemplate = toString $(embedFile "data/report_template.html") hlint-3.1.6/src/CmdLine.hs0000644000000000000000000003653713674632146013515 0ustar0000000000000000{-# LANGUAGE PatternGuards, DeriveDataTypeable, TupleSections #-} {-# OPTIONS_GHC -Wno-missing-fields -fno-cse -O0 #-} module CmdLine( Cmd(..), getCmd, CppFlags(..), cmdCpp, cmdExtensions, cmdHintFiles, cmdUseColour, exitWithHelp, resolveFile ) where import Control.Monad.Extra import Control.Exception.Extra import qualified Data.ByteString as BS import Data.Char import Data.List.Extra import Data.Maybe import Data.Functor import GHC.All(CppFlags(..)) import GHC.LanguageExtensions.Type import Language.Haskell.GhclibParserEx.GHC.Driver.Session as GhclibParserEx import DynFlags hiding (verbosity) import Language.Preprocessor.Cpphs import System.Console.ANSI(hSupportsANSI) import System.Console.CmdArgs.Explicit(helpText, HelpFormat(..)) import System.Console.CmdArgs.Implicit import System.Directory.Extra import System.Environment import System.Exit import System.FilePath import System.IO import System.IO.Error import System.Info.Extra import System.Process import System.FilePattern import EmbedData import Util import Extension import Paths_hlint import Data.Version import Prelude getCmd :: [String] -> IO Cmd getCmd args = withArgs (map f args) $ automatic =<< cmdArgsRun mode where f x = if x == "-?" || x == "--help" then "--help=all" else x automatic :: Cmd -> IO Cmd automatic cmd = case cmd of CmdMain{} -> dataDir =<< path =<< git =<< extension cmd CmdGrep{} -> path =<< extension cmd CmdTest{} -> dataDir cmd where path cmd = pure $ if null $ cmdPath cmd then cmd{cmdPath=["."]} else cmd extension cmd = pure $ if null $ cmdExtension cmd then cmd{cmdExtension=["hs","lhs"]} else cmd dataDir cmd | cmdDataDir cmd /= "" = pure cmd | otherwise = do x <- getDataDir b <- doesDirectoryExist x if b then pure cmd{cmdDataDir=x} else do exe <- getExecutablePath pure cmd{cmdDataDir = takeDirectory exe "data"} git cmd | cmdGit cmd = do mgit <- findExecutable "git" case mgit of Nothing -> errorIO "Could not find git" Just git -> do let args = ["ls-files", "--cached", "--others", "--exclude-standard"] ++ map ("*." ++) (cmdExtension cmd) files <- readProcess git args "" pure cmd{cmdFiles = cmdFiles cmd ++ lines files} | otherwise = pure cmd exitWithHelp :: IO a exitWithHelp = do putStr $ show $ helpText [] HelpFormatAll mode exitSuccess -- | When to colour terminal output. data ColorMode = Never -- ^ Terminal output will never be coloured. | Always -- ^ Terminal output will always be coloured. | Auto -- ^ Terminal output will be coloured if $TERM and stdout appear to support it. deriving (Show, Typeable, Data) instance Default ColorMode where def = if isWindows then Never else Auto data Cmd = CmdMain {cmdFiles :: [FilePath] -- ^ which files to run it on, nothing = none given ,cmdReports :: [FilePath] -- ^ where to generate reports ,cmdGivenHints :: [FilePath] -- ^ which settignsfiles were explicitly given ,cmdWithGroups :: [String] -- ^ groups that are given on the command line ,cmdGit :: Bool -- ^ use git ls-files to find files ,cmdColor :: ColorMode -- ^ color the result ,cmdThreads :: Int -- ^ Numbmer of threads to use, 0 = whatever GHC has ,cmdIgnore :: [String] -- ^ the hints to ignore ,cmdShowAll :: Bool -- ^ display all skipped items ,cmdExtension :: [String] -- ^ extensions ,cmdLanguage :: [String] -- ^ the extensions (may be prefixed by "No") ,cmdCross :: Bool -- ^ work between source files, applies to hints such as duplicate code between modules ,cmdFindHints :: [FilePath] -- ^ source files to look for hints in ,cmdDataDir :: FilePath -- ^ the data directory ,cmdDefault :: Bool -- ^ Print a default file to stdout ,cmdPath :: [String] ,cmdCppDefine :: [String] ,cmdCppInclude :: [FilePath] ,cmdCppFile :: [FilePath] ,cmdCppSimple :: Bool ,cmdCppAnsi :: Bool ,cmdJson :: Bool -- ^ display hint data as JSON ,cmdCC :: Bool -- ^ display hint data as Code Climate Issues ,cmdNoSummary :: Bool -- ^ do not show the summary info ,cmdOnly :: [String] -- ^ specify which hints explicitly ,cmdNoExitCode :: Bool ,cmdTiming :: Bool ,cmdSerialise :: Bool -- ^ Display hints in serialisation format ,cmdRefactor :: Bool -- ^ Run the `refactor` executable to automatically perform hints ,cmdRefactorOptions :: String -- ^ Options to pass to the `refactor` executable. ,cmdWithRefactor :: FilePath -- ^ Path to refactor tool ,cmdIgnoreGlob :: [FilePattern] } | CmdGrep {cmdFiles :: [FilePath] -- ^ which files to run it on, nothing = none given ,cmdPattern :: String ,cmdExtension :: [String] -- ^ extensions ,cmdLanguage :: [String] -- ^ the extensions (may be prefixed by "No") ,cmdPath :: [String] ,cmdCppDefine :: [String] ,cmdCppInclude :: [FilePath] ,cmdCppFile :: [FilePath] ,cmdCppSimple :: Bool ,cmdCppAnsi :: Bool } | CmdTest {cmdProof :: [FilePath] -- ^ a proof script to check against ,cmdGivenHints :: [FilePath] -- ^ which settings files were explicitly given ,cmdDataDir :: FilePath -- ^ the data directory ,cmdReports :: [FilePath] -- ^ where to generate reports ,cmdTempDir :: FilePath -- ^ temporary directory to put the files in ,cmdQuickCheck :: Bool ,cmdTypeCheck :: Bool ,cmdWithRefactor :: FilePath ,cmdGenerateSummary :: Bool -- ^ Generate a summary of built-in hints } deriving (Data,Typeable,Show) mode = cmdArgsMode $ modes [CmdMain {cmdFiles = def &= args &= typ "FILE/DIR" ,cmdReports = nam "report" &= opt "report.html" &= typFile &= help "Generate a report in HTML" ,cmdGivenHints = nam "hint" &= typFile &= help "Hint/ignore file to use" ,cmdWithGroups = nam_ "with-group" &= typ "GROUP" &= help "Extra hint groups to use" ,cmdGit = nam "git" &= help "Run on files tracked by git" ,cmdColor = nam "colour" &= name "color" &= opt Always &= typ "always/never/auto" &= help "Color output (requires ANSI terminal; auto means on when $TERM is supported; by itself, selects always)" ,cmdThreads = 1 &= name "threads" &= name "j" &= opt (0 :: Int) &= help "Number of threads to use (-j for all)" ,cmdIgnore = nam "ignore" &= typ "HINT" &= help "Ignore a particular hint" ,cmdShowAll = nam "show" &= help "Show all ignored ideas" ,cmdExtension = nam "extension" &= typ "EXT" &= help "File extensions to search (default hs/lhs)" ,cmdLanguage = nam_ "language" &= name "X" &= typ "EXTENSION" &= help "Language extensions (Arrows, NoCPP)" ,cmdCross = nam_ "cross" &= help "Work between modules" ,cmdFindHints = nam "find" &= typFile &= help "Find hints in a Haskell file" ,cmdDataDir = nam_ "datadir" &= typDir &= help "Override the data directory" ,cmdDefault = nam "default" &= help "Print a default file to stdout" ,cmdPath = nam "path" &= help "Directory in which to search for files" ,cmdCppDefine = nam_ "cpp-define" &= typ "NAME[=VALUE]" &= help "CPP #define" ,cmdCppInclude = nam_ "cpp-include" &= typDir &= help "CPP include path" ,cmdCppFile = nam_ "cpp-file" &= typFile &= help "CPP pre-include file" ,cmdCppSimple = nam_ "cpp-simple" &= help "Use a simple CPP (strip # lines)" ,cmdCppAnsi = nam_ "cpp-ansi" &= help "Use CPP in ANSI compatibility mode" ,cmdJson = nam_ "json" &= help "Display hint data as JSON" ,cmdCC = nam_ "cc" &= help "Display hint data as Code Climate Issues" ,cmdNoSummary = nam_ "no-summary" &= help "Do not show summary information" ,cmdOnly = nam "only" &= typ "HINT" &= help "Specify which hints explicitly" ,cmdNoExitCode = nam_ "no-exit-code" &= help "Do not give a negative exit if hints" ,cmdTiming = nam_ "timing" &= help "Display timing information" ,cmdSerialise = nam_ "serialise" &= help "Serialise hint data for consumption by apply-refact" ,cmdRefactor = nam_ "refactor" &= help "Automatically invoke `refactor` to apply hints" ,cmdRefactorOptions = nam_ "refactor-options" &= typ "OPTIONS" &= help "Options to pass to the `refactor` executable" ,cmdWithRefactor = nam_ "with-refactor" &= help "Give the path to refactor" ,cmdIgnoreGlob = nam_ "ignore-glob" &= help "Ignore paths matching glob pattern" } &= auto &= explicit &= name "lint" ,CmdGrep {cmdFiles = def &= args &= typ "FILE/DIR" ,cmdPattern = def &= argPos 0 &= typ "PATTERN" } &= explicit &= name "grep" ,CmdTest {cmdProof = nam_ "proof" &= typFile &= help "Isabelle/HOLCF theory file" ,cmdTypeCheck = nam_ "typecheck" &= help "Use GHC to type check the hints" ,cmdQuickCheck = nam_ "quickcheck" &= help "Use QuickCheck to check the hints" ,cmdTempDir = nam_ "tempdir" &= help "Where to put temporary files (not cleaned up)" ,cmdGenerateSummary = nam_ "generate-summary" &= help "Generate a summary of built-in hints" } &= explicit &= name "test" &= details ["HLint gives hints on how to improve Haskell code." ,"" ,"To check all Haskell files in 'src' and generate a report type:" ," hlint src --report"] ] &= program "hlint" &= verbosity &= summary ("HLint v" ++ showVersion version ++ ", (C) Neil Mitchell 2006-2020") where nam xs = nam_ xs &= name [head xs] nam_ xs = def &= explicit &= name xs -- | Where should we find the configuration files? -- Either we use the implicit search, or we follow the cmdGivenHints -- We want more important hints to go last, since they override cmdHintFiles :: Cmd -> IO [(FilePath, Maybe String)] cmdHintFiles cmd = do let explicit = cmdGivenHints cmd bad <- filterM (notM . doesFileExist) explicit when (bad /= []) $ fail $ unlines $ "Failed to find requested hint files:" : map (" "++) bad -- if the user has given any explicit hints, ignore the local ones implicit <- if explicit /= [] then pure Nothing else do -- we follow the stylish-haskell config file search policy -- 1) current directory or its ancestors; 2) home directory curdir <- getCurrentDirectory -- Ignores home directory when it isn't present. home <- catchIOError ((:[]) <$> getHomeDirectory) (const $ pure []) findM doesFileExist $ map ( ".hlint.yaml") (ancestors curdir ++ home) -- to match Stylish Haskell pure $ hlintYaml : map (,Nothing) (maybeToList implicit ++ explicit) where ancestors = init . map joinPath . reverse . inits . splitPath cmdExtensions :: Cmd -> (Maybe Language, ([Extension], [Extension])) cmdExtensions = getExtensions . cmdLanguage cmdCpp :: Cmd -> CppFlags cmdCpp cmd | cmdCppSimple cmd = CppSimple | Cpp `elem` (fst . snd) (cmdExtensions cmd) = Cpphs defaultCpphsOptions {boolopts=defaultBoolOptions{hashline=False, stripC89=True, ansi=cmdCppAnsi cmd} ,includes = cmdCppInclude cmd ,preInclude = cmdCppFile cmd ,defines = ("__HLINT__","1") : [(a,drop1 b) | x <- cmdCppDefine cmd, let (a,b) = break (== '=') x] } | otherwise = NoCpp -- | Determines whether to use colour or not. cmdUseColour :: Cmd -> IO Bool cmdUseColour cmd = case cmdColor cmd of Always -> pure True Never -> pure False Auto -> hSupportsANSI stdout "." <\> x = x x <\> y = x y resolveFile :: Cmd -> Maybe FilePath -- ^ Temporary file -> FilePath -- ^ File to resolve, may be "-" for stdin -> IO [FilePath] resolveFile cmd = getFile (toPredicate $ cmdIgnoreGlob cmd) (cmdPath cmd) (cmdExtension cmd) where toPredicate :: [FilePattern] -> FilePath -> Bool toPredicate [] = const False toPredicate globs = \x -> not $ null $ m [((), cleanup x)] where m = matchMany (map ((),) globs) cleanup :: FilePath -> FilePath cleanup ('.':x:xs) | isPathSeparator x, not $ null xs = xs cleanup x = x getFile :: (FilePath -> Bool) -> [FilePath] -> [String] -> Maybe FilePath -> FilePath -> IO [FilePath] getFile _ path _ (Just tmpfile) "-" = -- make sure we don't reencode any Unicode BS.getContents >>= BS.writeFile tmpfile >> pure [tmpfile] getFile _ path _ Nothing "-" = pure ["-"] getFile _ [] exts _ file = exitMessage $ "Couldn't find file: " ++ file getFile ignore (p:ath) exts t file = do isDir <- doesDirectoryExist $ p <\> file if isDir then do let avoidDir x = let y = takeFileName x in "_" `isPrefixOf` y || ("." `isPrefixOf` y && not (all (== '.') y)) avoidFile x = let y = takeFileName x in "." `isPrefixOf` y || ignore x xs <- listFilesInside (pure . not . avoidDir) $ p <\> file pure [x | x <- xs, drop1 (takeExtension x) `elem` exts, not $ avoidFile x] else do isFil <- doesFileExist $ p <\> file if isFil then pure [p <\> file] else do res <- getModule p exts file case res of Just x -> pure [x] Nothing -> getFile ignore ath exts t file getModule :: FilePath -> [String] -> FilePath -> IO (Maybe FilePath) getModule path exts x | not (any isSpace x) && all isMod xs = f exts where xs = words $ map (\x -> if x == '.' then ' ' else x) x isMod (x:xs) = isUpper x && all (\x -> isAlphaNum x || x == '_') xs isMod _ = False pre = path <\> joinPath xs f [] = pure Nothing f (x:xs) = do let s = pre <.> x b <- doesFileExist s if b then pure $ Just s else f xs getModule _ _ _ = pure Nothing getExtensions :: [String] -> (Maybe Language, ([Extension], [Extension])) getExtensions args = (lang, foldl f (if null langs then (defaultExtensions, []) else ([], [])) exts) where lang = if null langs then Nothing else Just $ fromJust $ lookup (last langs) ls (langs, exts) = partition (isJust . flip lookup ls) args ls = [(show x, x) | x <- [Haskell98, Haskell2010]] f (a, e) "Haskell98" = ([], []) f (a, e) ('N':'o':x) | Just x <- GhclibParserEx.readExtension x, let xs = expandDisable x = (deletes xs a, xs ++ deletes xs e) f (a, e) x | Just x <- GhclibParserEx.readExtension x = (x : delete x a, delete x e) f (a, e) x = (a, e) -- Ignore unknown extension. deletes [] ys = ys deletes (x:xs) ys = deletes xs $ delete x ys -- if you disable a feature that implies another feature, sometimes we should disable both -- e.g. no one knows what TemplateHaskellQuotes is https://github.com/ndmitchell/hlint/issues/1038 expandDisable TemplateHaskell = [TemplateHaskell, TemplateHaskellQuotes] expandDisable x = [x] hlint-3.1.6/src/CC.hs0000644000000000000000000000752113627715314012453 0ustar0000000000000000{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} -- | -- -- Utility for formatting @'Idea'@ data in accordance with the Code Climate -- spec: -- module CC ( printIssue , fromIdea ) where import Data.Aeson (ToJSON(..), (.=), encode, object) import Data.Char (toUpper) import Data.Text (Text) import qualified Data.Text as T import qualified Data.ByteString.Lazy.Char8 as C8 import Idea (Idea(..), Severity(..)) import qualified SrcLoc as GHC import qualified GHC.Util as GHC data Issue = Issue { issueType :: Text , issueCheckName :: Text , issueDescription :: Text , issueContent :: Text , issueCategories :: [Text] , issueLocation :: Location , issueRemediationPoints :: Int } data Location = Location FilePath Position Position data Position = Position Int Int instance ToJSON Issue where toJSON Issue{..} = object [ "type" .= issueType , "check_name" .= issueCheckName , "description" .= issueDescription , "content" .= object [ "body" .= issueContent ] , "categories" .= issueCategories , "location" .= issueLocation , "remediation_points" .= issueRemediationPoints ] instance ToJSON Location where toJSON (Location path begin end) = object [ "path" .= path , "positions" .= object [ "begin" .= begin , "end" .= end ] ] instance ToJSON Position where toJSON (Position line column) = object [ "line" .= line , "column" .= column ] -- | Print an @'Issue'@ with trailing null-terminator and newline -- -- The trailing newline will be ignored, but makes the output more readable -- printIssue :: Issue -> IO () printIssue = C8.putStrLn . (<> "\0") . encode -- | Convert an hlint @'Idea'@ to a datatype more easily serialized for CC fromIdea :: Idea -> Issue fromIdea Idea{..} = Issue { issueType = "issue" , issueCheckName = "HLint/" <> T.pack (camelize ideaHint) , issueDescription = T.pack ideaHint , issueContent = content ideaFrom ideaTo <> listNotes ideaNote , issueCategories = categories ideaHint , issueLocation = fromSrcSpan ideaSpan , issueRemediationPoints = points ideaSeverity } where content from Nothing = T.unlines [ "Found" , "" , "```" , T.pack from , "```" , "" , "remove it." ] content from (Just to) = T.unlines [ "Found" , "" , "```" , T.pack from , "```" , "" , "Perhaps" , "" , "```" , T.pack to , "```" ] listNotes [] = "" listNotes notes = T.unlines $ [ "" , "Applying this change:" , "" ] ++ map (("* " <>) . T.pack . show) notes categories _ = ["Style"] points Ignore = 0 points Suggestion = basePoints points Warning = 5 * basePoints points Error = 10 * basePoints fromSrcSpan :: GHC.SrcSpan -> Location fromSrcSpan GHC.SrcSpan{..} = Location (locationFileName srcSpanFilename) (Position srcSpanStartLine' srcSpanStartColumn) (Position srcSpanEndLine' srcSpanEndColumn) where locationFileName ('.':'/':x) = x locationFileName x = x camelize :: String -> String camelize = concatMap capitalize . words capitalize :: String -> String capitalize [] = [] capitalize (c:rest) = toUpper c : rest -- "The baseline remediation points value is 50,000, which is the time it takes -- to fix a trivial code style issue like a missing semicolon on a single line, -- including the time for the developer to open the code, make the change, and -- confidently commit the fix. All other remediation points values are expressed -- in multiples of that Basic Remediation Point Value." basePoints :: Int basePoints = 50000 hlint-3.1.6/src/Apply.hs0000644000000000000000000001260213671470061013243 0ustar0000000000000000 module Apply(applyHints, applyHintFile, applyHintFiles) where import Control.Applicative import Data.Monoid import GHC.All import Hint.All import GHC.Util import Data.Generics.Uniplate.DataOnly import Idea import Data.Tuple.Extra import Data.Either import Data.List.Extra import Data.Maybe import Data.Ord import Config.Type import Config.Haskell import SrcLoc import GHC.Hs import Language.Haskell.GhclibParserEx.GHC.Hs import qualified Data.HashSet as Set import Prelude -- | Apply hints to a single file, you may have the contents of the file. applyHintFile :: ParseFlags -> [Setting] -> FilePath -> Maybe String -> IO [Idea] applyHintFile flags s file src = do res <- parseModuleApply flags s file src pure $ case res of Left err -> [err] Right m -> executeHints s [m] -- | Apply hints to multiple files, allowing cross-file hints to fire. applyHintFiles :: ParseFlags -> [Setting] -> [FilePath] -> IO [Idea] applyHintFiles flags s files = do (err, ms) <- partitionEithers <$> mapM (\file -> parseModuleApply flags s file Nothing) files pure $ err ++ executeHints s ms -- | Given a way of classifying results, and a 'Hint', apply to a set of modules generating a list of 'Idea's. -- The 'Idea' values will be ordered within a file. -- -- Given a set of modules, it may be faster to pass each to 'applyHints' in a singleton list. -- When given multiple modules at once this function attempts to find hints between modules, -- which is slower and often pointless (by default HLint passes modules singularly, using -- @--cross@ to pass all modules together). applyHints {- PUBLIC -} :: [Classify] -> Hint -> [ModuleEx] -> [Idea] applyHints cs = applyHintsReal $ map SettingClassify cs applyHintsReal :: [Setting] -> Hint -> [ModuleEx] -> [Idea] applyHintsReal settings hints_ ms = concat $ [ map (classify classifiers . removeRequiresExtensionNotes m) $ order [] (hintModule hints settings nm m) `merge` concat [order (maybeToList $ declName d) $ decHints d | d <- hsmodDecls $ unLoc $ ghcModule m] | (nm,m) <- mns , let classifiers = cls ++ mapMaybe readPragma (universeBi (ghcModule m)) ++ concatMap readComment (ghcComments m) , seq (length classifiers) True -- to force any errors from readPragma or readComment , let decHints = hintDecl hints settings nm m -- partially apply , let order n = map (\i -> i{ideaModule = f $ modName (ghcModule m) : ideaModule i, ideaDecl = f $ n ++ ideaDecl i}) . sortOn ideaSpan , let merge = mergeBy (comparing ideaSpan)] ++ [map (classify cls) (hintModules hints settings mns)] where f = nubOrd . filter (/= "") cls = [x | SettingClassify x <- settings] mns = map (\x -> (scopeCreate (unLoc $ ghcModule x), x)) ms hints = (if length ms <= 1 then noModules else id) hints_ noModules h = h{hintModules = \_ _ -> []} `mappend` mempty{hintModule = \s a b -> hintModules h s [(a,b)]} -- If the hint has said you RequiresExtension Foo, but Foo is enabled, drop the note removeRequiresExtensionNotes :: ModuleEx -> Idea -> Idea removeRequiresExtensionNotes m = \x -> x{ideaNote = filter keep $ ideaNote x} where exts = Set.fromList $ concatMap snd $ languagePragmas $ pragmas $ ghcAnnotations m keep (RequiresExtension x) = not $ x `Set.member` exts keep _ = True -- | Given a list of settings (a way to classify) and a list of hints, run them over a list of modules. executeHints :: [Setting] -> [ModuleEx] -> [Idea] executeHints s = applyHintsReal s (allHints s) -- | Return either an idea (a parse error) or the module. In IO because might call the C pre processor. parseModuleApply :: ParseFlags -> [Setting] -> FilePath -> Maybe String -> IO (Either Idea ModuleEx) parseModuleApply flags s file src = do res <- parseModuleEx (parseFlagsAddFixities [x | Infix x <- s] flags) file src case res of Right r -> pure $ Right r Left (ParseError sl msg ctxt) -> pure $ Left $ classify [x | SettingClassify x <- s] $ rawIdeaN Error (adjustMessage msg) sl ctxt Nothing [] where -- important the message has "Parse error:" as the prefix so "--ignore=Parse error" works -- try and tidy up things like "parse error (mismatched brackets)" to not look silly adjustMessage :: String -> String adjustMessage x = "Parse error: " ++ dropBrackets (dropPrefix "parse error " x) dropBrackets ('(':xs) | Just (xs,')') <- unsnoc xs = xs dropBrackets xs = xs -- | Find which hints a list of settings implies. allHints :: [Setting] -> Hint allHints xs = mconcat $ hintRules [x | SettingMatchExp x <- xs] : map f builtin where builtin = nubOrd $ concat [if x == "All" then map fst builtinHints else [x] | Builtin x <- xs] f x = fromMaybe (error $ "Unknown builtin hints: HLint.Builtin." ++ x) $ lookup x builtinHints -- | Given some settings, make sure the severity field of the Idea is correct. classify :: [Classify] -> Idea -> Idea classify xs i = let s = foldl' (f i) (ideaSeverity i) xs in s `seq` i{ideaSeverity=s} where -- figure out if we need to change the severity f :: Idea -> Severity -> Classify -> Severity f i r c | classifyHint c ~~= ideaHint i && classifyModule c ~= ideaModule i && classifyDecl c ~= ideaDecl i = classifySeverity c | otherwise = r x ~= y = x == "" || x `elem` y x ~~= y = x == "" || x == y || ((x ++ ":") `isPrefixOf` y) hlint-3.1.6/src/Test/0000755000000000000000000000000013674744765012562 5ustar0000000000000000hlint-3.1.6/src/Test/Util.hs0000644000000000000000000000510713674632146014023 0ustar0000000000000000{-# LANGUAGE RecordWildCards, GeneralizedNewtypeDeriving #-} module Test.Util( Test, withTests, passed, failed, progress, addIdeas, getIdeas, BuiltinSummary, BuiltinEx(..), addBuiltin, getBuiltins, ) where import Idea import Control.Monad import Control.Monad.Trans.Reader import Control.Monad.IO.Class import Data.IORef import Data.List.Extra import Data.Map (Map) import qualified Data.Map.Strict as Map -- | A map from (hint name, hint severity, does hint support refactoring) to an example. type BuiltinSummary = Map (String, Severity, Bool) BuiltinEx data BuiltinEx = BuiltinEx { builtinInp :: !String , builtinFrom :: !String , builtinTo :: !(Maybe String) } data S = S {failures :: !Int ,total :: !Int ,ideas :: [[Idea]] ,builtinHints :: BuiltinSummary -- ^ A summary of builtin hints } newtype Test a = Test (ReaderT (IORef S) IO a) deriving (Functor, Applicative, Monad, MonadIO) -- | Returns the number of failing tests. withTests :: Test a -> IO (Int, a) withTests (Test act) = do ref <- newIORef $ S 0 0 [] Map.empty res <- runReaderT act ref S{..} <- readIORef ref putStrLn "" putStrLn $ if failures == 0 then "Tests passed (" ++ show total ++ ")" else "Tests failed (" ++ show failures ++ " of " ++ show total ++ ")" pure (failures, res) addIdeas :: [Idea] -> Test () addIdeas xs = do ref <- Test ask liftIO $ modifyIORef' ref $ \s -> s{ideas = xs : ideas s} getIdeas :: Test [Idea] getIdeas = do ref <- Test ask liftIO $ concat . reverse . ideas <$> readIORef ref addBuiltin :: String -> Idea -> Test () addBuiltin inp idea@Idea{..} = unless ("Parse error" `isPrefixOf` ideaHint) $ do ref <- Test ask liftIO $ modifyIORef' ref $ \s -> let k = (ideaHint, ideaSeverity, notNull ideaRefactoring) v = BuiltinEx inp ideaFrom ideaTo -- Do not insert if the key already exists in the map. This has the effect -- of picking the first test case of a hint as the example in the summary. in s{builtinHints = Map.insertWith (curry snd) k v (builtinHints s)} getBuiltins :: Test BuiltinSummary getBuiltins = do ref <- Test ask liftIO $ builtinHints <$> readIORef ref progress :: Test () progress = liftIO $ putChar '.' passed :: Test () passed = do ref <- Test ask liftIO $ modifyIORef' ref $ \s -> s{total=total s+1} failed :: [String] -> Test () failed xs = do unless (null xs) $ liftIO $ putStrLn $ unlines $ "" : xs ref <- Test ask liftIO $ modifyIORef' ref $ \s -> s{total=total s+1, failures=failures s+1} hlint-3.1.6/src/Test/Translate.hs0000644000000000000000000001256113630153443015033 0ustar0000000000000000 -- | Translate the hints to Haskell and run with GHC. module Test.Translate(testTypeCheck, testQuickCheck) where import Config.Type import Control.Exception.Extra import Control.Monad.IO.Class import Test.Util testTypeCheck :: FilePath -> FilePath -> [[Setting]] -> Test () testTypeCheck _ _ _ = liftIO $ errorIO "Test.Translate is disabled." -- | Given a set of hints, do all the HintRule hints satisfy QuickCheck testQuickCheck :: FilePath -> FilePath -> [[Setting]] -> Test () testQuickCheck _ _ _ = liftIO $ errorIO "Test.Translate is disabled." {- import Control.Monad import Control.Monad.IO.Class import Data.List.Extra import System.IO.Extra import Data.Maybe import System.Process import System.Exit import System.FilePath import Language.Haskell.Exts.Util(FreeVars, freeVars) import qualified Data.Set as Set import Config.Type import HSE.All import Test.Util runMains :: FilePath -> FilePath -> [String] -> Test () runMains datadir tmpdir xs = do res <- liftIO $ (if tmpdir == "" then withTempDir else ($ tmpdir)) $ \dir -> do ms <- forM (zipFrom 1 xs) $ \(i,x) -> do let m = "I" ++ show i writeFile (dir m <.> "hs") $ replace "module Main" ("module " ++ m) x pure m writeFile (dir "Main.hs") $ unlines $ ["import qualified " ++ m | m <- ms] ++ ["main = do"] ++ [" " ++ m ++ ".main" | m <- ms] system $ "runhaskell -i" ++ dir ++ " -i" ++ datadir ++ " Main" replicateM_ (length xs) $ tested $ res == ExitSuccess -- | Given a set of hints, do all the HintRule hints type check testTypeCheck :: FilePath -> FilePath -> [[Setting]] -> Test () testTypeCheck = wrap toTypeCheck -- | Given a set of hints, do all the HintRule hints satisfy QuickCheck testQuickCheck :: FilePath -> FilePath -> [[Setting]] -> Test () testQuickCheck = wrap toQuickCheck wrap :: ([HintRule] -> [String]) -> FilePath -> FilePath -> [[Setting]] -> Test () wrap f datadir tmpdir hints = runMains datadir tmpdir [unlines $ body [x | SettingMatchExp x <- xs] | xs <- hints] where body xs = ["{-# LANGUAGE NoMonomorphismRestriction, ExtendedDefaultRules, ScopedTypeVariables, DeriveDataTypeable #-}" ,"{-# LANGUAGE FlexibleInstances, UndecidableInstances, OverlappingInstances #-}" ,"module Main(main) where"] ++ -- concat [map (prettyPrint . hackImport) $ scopeImports $ hintRuleScope x | x <- take 1 xs] ++ f xs {- -- Hack around haskell98 not being compatible with base anymore hackImport i@ImportDecl{importAs=Just a,importModule=b} | prettyPrint b `elem` words "Maybe List Monad IO Char" = i{importAs=Just b,importModule=a} hackImport i = i -} --------------------------------------------------------------------- -- TYPE CHECKING toTypeCheck :: [HintRule] -> [String] toTypeCheck hints = ["import HLint_TypeCheck hiding(main)" ,"main = pure ()"] ++ ["{-# LINE " ++ show (startLine $ ann rhs) ++ " " ++ show (fileName $ ann rhs) ++ " #-}\n" ++ prettyPrint (PatBind an (toNamed $ "test" ++ show i) bod Nothing) | (i, HintRule _ _ lhs rhs side _notes _ghcScope _ghcLhs _ghcRhs _ghcSide) <- zipFrom 1 hints, "noTypeCheck" `notElem` vars (maybeToList side) , let vs = map toNamed $ nubOrd $ filter isUnifyVar $ vars lhs ++ vars rhs , let inner = InfixApp an (Paren an lhs) (toNamed "==>") (Paren an rhs) , let bod = UnGuardedRhs an $ if null vs then inner else Lambda an vs inner] --------------------------------------------------------------------- -- QUICKCHECK toQuickCheck :: [HintRule] -> [String] toQuickCheck hints = ["import HLint_QuickCheck hiding(main)" ,"default(Maybe Bool,Int,Dbl)" ,prettyPrint $ PatBind an (toNamed "main") (UnGuardedRhs an $ toNamed "withMain" $$ Do an tests) Nothing] where str x = Lit an $ String an x (show x) int x = Lit an $ Int an (toInteger x) (show x) app = App an a $$ b = InfixApp an a (toNamed "$") b tests = [ Qualifier an $ Let an (BDecls an [PatBind an (toNamed "t") (UnGuardedRhs an bod) Nothing]) $ (toNamed "test" `app` str (fileName $ ann rhs) `app` int (startLine $ ann rhs) `app` str (prettyPrint lhs ++ " ==> " ++ prettyPrint rhs)) `app` toNamed "t" | (i, HintRule _ _ lhs rhs side note _ghcScope _ghcLhs _ghcRhs _ghcSide) <- zipFrom 1 hints, "noQuickCheck" `notElem` vars (maybeToList side) , let vs = map (restrict side) $ nubOrd $ filter isUnifyVar $ vars lhs ++ vars rhs , let op = if any isRemovesError note then "?==>" else "==>" , let inner = InfixApp an (Paren an lhs) (toNamed op) (Paren an rhs) , let bod = if null vs then Paren an inner else Lambda an vs inner] restrict (Just side) v | any (=~= App an (toNamed "isNegZero") (toNamed v)) (universe side) = PApp an (toNamed "NegZero") [toNamed v] | any (=~= App an (toNamed "isNat") (toNamed v)) (universe side) = PApp an (toNamed "Nat") [toNamed v] | any (=~= App an (toNamed "isCompare") (toNamed v)) (universe side) = PApp an (toNamed "Compare") [toNamed v] restrict _ v = toNamed v isRemovesError :: Note -> Bool isRemovesError RemovesError{} = True isRemovesError _ = False vars :: FreeVars a => a -> [String] vars = Set.toList . Set.map prettyPrint . freeVars -} hlint-3.1.6/src/Test/Summary.hs0000644000000000000000000000312213674632146014536 0ustar0000000000000000{-# LANGUAGE RecordWildCards #-} -- | Generate a markdown that summarizes the builtin hints. module Test.Summary (genBuiltinSummaryMd) where import qualified Data.Map as Map import Config.Type import Test.Util genBuiltinSummaryMd :: BuiltinSummary -> String genBuiltinSummaryMd builtins = unlines $ [ "# Built-in Hints" , "" , "This page is auto-generated from `cabal run hlint test -- --generate-summary`" , "or `stack run hlint test -- --generate-summary`." , "" ] ++ table builtins table :: BuiltinSummary -> [String] table builtins = [""] ++ row ["", "", ""] ++ Map.foldMapWithKey showHint builtins ++ ["
    HintSeveritySupport Refactoring?
    "] row :: [String] -> [String] row xs = [""] ++ xs ++ [""] -- | Render using if it is single-line, otherwise using
    .
    haskell :: String -> [String]
    haskell s
      | '\n' `elem` s = ["
    ", s, "
    "] | otherwise = ["", s, "", "
    "] showHint :: (String, Severity, Bool) -> BuiltinEx -> [String] showHint (hint, sev, refact) BuiltinEx{..} = row1 ++ row2 where row1 = row [ "" ++ hint ++ "" , "" ++ show sev ++ "" , "" ++ if refact then "Yes" else "No" ++ "" ] row2 = row example example = [ "" , "Example:" ] ++ haskell builtinInp ++ ["Found:"] ++ haskell builtinFrom ++ ["Suggestion:"] ++ haskell to ++ [""] to = case builtinTo of Nothing -> "" Just "" -> "Perhaps you should remove it." Just s -> s hlint-3.1.6/src/Test/Proof.hs0000644000000000000000000002204513630153376014166 0ustar0000000000000000 -- | Check the coverage of the hints given a list of Isabelle theorems module Test.Proof(proof) where import Config.Type import Control.Exception.Extra proof :: [FilePath] -> [Setting] -> FilePath -> IO () proof _ _ _ = errorIO "Test.Proof is disabled." {- import Data.Tuple.Extra import Control.Applicative import Control.Monad import Control.Monad.Trans.State import Language.Haskell.Exts.Util(paren, FreeVars, freeVars) import qualified Data.Set as Set import Data.Char import Data.List.Extra import Data.Maybe import Data.Function import System.FilePath import Config.Type import HSE.All import Prelude data Theorem = Theorem {original :: Maybe HintRule ,location :: String ,lemma :: String } instance Eq Theorem where t1 == t2 = lemma t1 == lemma t2 instance Ord Theorem where compare t1 t2 = compare (lemma t1) (lemma t2) instance Show Theorem where show Theorem{..} = location ++ ":\n" ++ maybe "" f original ++ lemma ++ "\n" where f HintRule{..} = "(* " ++ prettyPrint hintRuleLHS ++ " ==> " ++ prettyPrint hintRuleRHS ++ " *)\n" proof :: [FilePath] -> [Setting] -> FilePath -> IO () proof reports hints thy = do got <- isabelleTheorems (takeFileName thy) <$> readFile thy let want = nubOrd $ hintTheorems hints let unused = got \\ want let missing = want \\ got let reasons = map (\x -> (fst $ head x, map snd x)) $ groupBy ((==) `on` fst) $ sortBy (compare `on` fst) $ map (classifyMissing &&& id) missing let summary = table $ let (*) = (,) in ["HLint hints" * want ,"HOL proofs" * got ,"Useful proofs" * (got `intersect` want) ,"Unused proofs" * unused ,"Unproved hints" * missing] ++ [(" " ++ name) * ps | (name,ps) <- reasons] putStr $ unlines summary forM_ reports $ \report -> do let out = ("Unused proofs",unused) : map (first ("Unproved hints - " ++)) reasons writeFile report $ unlines $ summary ++ "" : concat [("== " ++ a ++ " ==") : "" : map show b | (a,b) <- out] putStrLn $ "Report written to " ++ report where table xs = [a ++ replicate (n + 6 - length a - length bb) ' ' ++ bb | (a,b) <- xs, let bb = show $ length b] where n = maximum $ map (length . fst) xs missingFuncs :: [(String, String)] missingFuncs = let a*b = [(b,a) | b <- words b] in concat ["IO" * "putChar putStr print putStrLn getLine getChar getContents hReady hPrint stdin" ,"Exit" * "exitSuccess" ,"Ord" * "(>) (<=) (>=) (<) compare minimum maximum sort sortBy" ,"Show" * "show shows showIntAtBase" ,"Read" * "reads read" ,"String" * "lines unlines words unwords" ,"Monad" * "mapM mapM_ sequence sequence_ msum mplus mzero liftM when unless return evaluate join void (>>=) (<=<) (>=>) forever ap" ,"Functor" * "fmap" ,"Numeric" * "(+) (*) fromInteger fromIntegral negate log (/) (-) (*) (^^) (^) subtract sqrt even odd" ,"Char" * "isControl isPrint isUpper isLower isAlpha isDigit" ,"Arrow" * "second first (***) (&&&)" ,"Applicative+" * "traverse for traverse_ for_ pure (<|>) (<**>)" ,"Exception" * "catch handle catchJust bracket error toException" ,"WeakPtr" * "mkWeak" ] -- | Guess why a theorem is missing classifyMissing :: Theorem -> String classifyMissing Theorem{original = Just HintRule{..}} | _:_ <- [v :: Exp_ | v@Case{} <- universeBi (hintRuleLHS,hintRuleRHS)] = "case" | _:_ <- [v :: Exp_ | v@ListComp{} <- universeBi (hintRuleLHS,hintRuleRHS)] = "list-comp" | v:_ <- mapMaybe (`lookup` missingFuncs) [prettyPrint (v :: Name S) | v <- universeBi (hintRuleLHS,hintRuleRHS)] = v classifyMissing _ = "?unknown" -- Extract theorems out of Isabelle code (HLint.thy) isabelleTheorems :: FilePath -> String -> [Theorem] isabelleTheorems file = find . lexer 1 where find ((i,"lemma"):(_,'\"':lemma):rest) = Theorem Nothing (file ++ ":" ++ show i) lemma : find rest find ((i,"lemma"):(_,name):(_,":"):(_,'\"':lemma):rest) = Theorem Nothing (file ++ ":" ++ show i) lemma : find rest find ((i,"lemma"):(_,"assumes"):(_,'\"':assumes):(_,"shows"):(_,'\"':lemma):rest) = Theorem Nothing (file ++ ":" ++ show i) (assumes ++ " \\ " ++ lemma) : find rest find ((i,"lemma"):rest) = Theorem Nothing (file ++ ":" ++ show i) "Unsupported lemma format" : find rest find (x:xs) = find xs find [] = [] lexer i x | i `seq` False = [] | Just x <- stripPrefix "(*" x, (a,b) <- breaks "*)" x = lexer (add a i) b | Just x <- stripPrefix "\"" x, (a,b) <- breaks "\"" x = (i,'\"':a) : lexer (add a i) b -- NOTE: drop the final " | x:xs <- x, isSpace x = lexer (add [x] i) xs | (a@(_:_),b) <- span (\y -> y == '_' || isAlpha y) x = (i,a) : lexer (add a i) b lexer i (x:xs) = (i,[x]) : lexer (add [x] i) xs lexer i [] = [] add s i = length (filter (== '\n') s) + i breaks s x | Just x <- stripPrefix s x = ("",x) breaks s (x:xs) = let (a,b) = breaks s xs in (x:a,b) breaks s [] = ([],[]) reparen :: Setting -> Setting reparen (SettingMatchExp m@HintRule{..}) = SettingMatchExp m{hintRuleLHS = f False hintRuleLHS, hintRuleRHS = f True hintRuleRHS} where f right x = if isLambda x || isIf x || badInfix x then Paren (ann x) x else x badInfix (InfixApp _ _ op _) = prettyPrint op `elem` words "|| && ." badInfix _ = False reparen x = x -- Extract theorems out of the hints hintTheorems :: [Setting] -> [Theorem] hintTheorems xs = [ Theorem (Just m) (loc $ ann hintRuleLHS) $ maybe "" assumes hintRuleSide ++ relationship hintRuleNotes a b | SettingMatchExp m@HintRule{..} <- map reparen xs, let a = exp1 $ typeclasses hintRuleNotes hintRuleLHS, let b = exp1 hintRuleRHS, a /= b] where loc (SrcSpanInfo (SrcSpan file ln _ _ _) _) = takeFileName file ++ ":" ++ show ln subs xs = flip lookup [(reverse b, reverse a) | x <- words xs, let (a,'=':b) = break (== '=') $ reverse x] funs = subs "id=ID not=neg or=the_or and=the_and (||)=tror (&&)=trand (++)=append (==)=eq (/=)=neq ($)=dollar" ops = subs "||=orelse &&=andalso .=oo ===eq /==neq ++=++ !!=!! $=dollar $!=dollarBang" pre = flip elem $ words "eq neq dollar dollarBang" cons = subs "True=TT False=FF" typeclasses hintRuleNotes x = foldr f x hintRuleNotes where f (ValidInstance cls var) x = evalState (transformM g x) True where g v@Var{} | v ~= var = do b <- get; put False pure $ if b then Paren an $ toNamed $ prettyPrint v ++ "::'a::" ++ cls ++ "_sym" else v g v = pure v :: State Bool Exp_ f _ x = x relationship hintRuleNotes a b | any lazier hintRuleNotes = a ++ " \\ " ++ b | DecreasesLaziness `elem` hintRuleNotes = b ++ " \\ " ++ a | otherwise = a ++ " = " ++ b where lazier IncreasesLaziness = True lazier RemovesError{} = True lazier _ = False assumes (App _ op var) | op ~= "isNat" = "le\\0\\" ++ prettyPrint var ++ " \\ FF \\ " | op ~= "isNegZero" = "gt\\0\\" ++ prettyPrint var ++ " \\ FF \\ " assumes (App _ op var) | op ~= "isWHNF" = prettyPrint var ++ " \\ \\ \\ " assumes _ = "" exp1 = exp . transformBi unqual -- Syntax translations exp (App _ a b) = exp a ++ "\\" ++ exp b exp (Paren _ x) = "(" ++ exp x ++ ")" exp (Var _ x) | Just x <- funs $ prettyPrint x = x exp (Con _ (Special _ (TupleCon _ _ i))) = "\\" ++ replicate (i-1) ',' ++ "\\" exp (Con _ x) | Just x <- cons $ prettyPrint x = x exp (Tuple _ _ xs) = "\\" ++ intercalate ", " (map exp xs) ++ "\\" exp (If _ a b c) = "If " ++ exp a ++ " then " ++ exp b ++ " else " ++ exp c exp (Lambda _ xs y) = "\\ " ++ unwords (map pat xs) ++ ". " ++ exp y exp (InfixApp _ x op y) | Just op <- ops $ prettyPrint op = if pre op then op ++ "\\" ++ exp (paren x) ++ "\\" ++ exp (paren y) else exp x ++ " " ++ op ++ " " ++ exp y -- Translations from the Haskell 2010 report exp (InfixApp l a (QVarOp _ b) c) = exp $ App l (App l (Var l b) a) c -- S3.4 exp x@(LeftSection l e op) = let v = fresh x in exp $ Paren l $ Lambda l [toNamed v] $ InfixApp l e op (toNamed v) -- S3.5 exp x@(RightSection l op e) = let v = fresh x in exp $ Paren l $ Lambda l [toNamed v] $ InfixApp l (toNamed v) op e -- S3.5 exp x = prettyPrint x pat (PTuple _ _ xs) = "\\" ++ intercalate ", " (map pat xs) ++ "\\" pat x = prettyPrint x fresh x = head $ ("z":["v" ++ show i | i <- [1..]]) \\ vars x vars :: FreeVars a => a -> [String] vars = Set.toList . Set.map prettyPrint . freeVars -} hlint-3.1.6/src/Test/InputOutput.hs0000644000000000000000000001042113633646646015426 0ustar0000000000000000{-# LANGUAGE PatternGuards, ScopedTypeVariables, RecordWildCards, ViewPatterns #-} -- | Check the input/output pairs in the tests/ directory module Test.InputOutput(testInputOutput) where import Control.Applicative import Data.Tuple.Extra import Control.Exception import Control.Monad import Control.Monad.IO.Class import Data.List.Extra import Data.IORef import System.Directory import System.FilePath import System.Console.CmdArgs.Explicit import System.Console.CmdArgs.Verbosity import System.Exit import System.IO.Extra import Prelude import Test.Util testInputOutput :: ([String] -> IO ()) -> Test () testInputOutput main = do xs <- liftIO $ getDirectoryContents "tests" xs <- pure $ filter ((==) ".test" . takeExtension) xs forM_ xs $ \file -> do ios <- liftIO $ parseInputOutputs <$> readFile ("tests" file) forM_ (zipFrom 1 ios) $ \(i,io@InputOutput{..}) -> do progress liftIO $ forM_ files $ \(name,contents) -> do createDirectoryIfMissing True $ takeDirectory name writeFile name contents checkInputOutput main io{name= "_" ++ takeBaseName file ++ "_" ++ show i} liftIO $ mapM_ (removeFile . fst) $ concatMap files ios data InputOutput = InputOutput {name :: String ,files :: [(FilePath, String)] ,run :: [String] ,output :: String ,exit :: Maybe ExitCode } deriving Eq parseInputOutputs :: String -> [InputOutput] parseInputOutputs = f z . lines where z = InputOutput "unknown" [] [] "" Nothing interest x = any (`isPrefixOf` x) ["----","FILE","RUN","OUTPUT","EXIT"] f io ((stripPrefix "RUN " -> Just flags):xs) = f io{run = splitArgs flags} xs f io ((stripPrefix "EXIT " -> Just code):xs) = f io{exit = Just $ let i = read code in if i == 0 then ExitSuccess else ExitFailure i} xs f io ((stripPrefix "FILE " -> Just file):xs) | (str,xs) <- g xs = f io{files = files io ++ [(file,unlines str)]} xs f io ("OUTPUT":xs) | (str,xs) <- g xs = f io{output = unlines str} xs f io ((isPrefixOf "----" -> True):xs) = [io | io /= z] ++ f z xs f io [] = [io | io /= z] f io (x:xs) = error $ "Unknown test item, " ++ x g = first (reverse . dropWhile null . reverse) . break interest --------------------------------------------------------------------- -- CHECK INPUT/OUTPUT PAIRS checkInputOutput :: ([String] -> IO ()) -> InputOutput -> Test () checkInputOutput main InputOutput{..} = do code <- liftIO $ newIORef ExitSuccess got <- liftIO $ fmap (reverse . dropWhile null . reverse . map trimEnd . lines . fst) $ captureOutput $ handle (\(e::SomeException) -> print e) $ handle (\(e::ExitCode) -> writeIORef code e) $ bracket getVerbosity setVerbosity $ const $ setVerbosity Normal >> main run code <- liftIO $ readIORef code (want,got) <- pure $ matchStarStar (lines output) got if maybe False (/= code) exit then failed ["TEST FAILURE IN tests/" ++ name ,"WRONG EXIT CODE" ,"GOT : " ++ show code ,"WANT: " ++ show exit ] else if length got == length want && and (zipWith matchStar want got) then passed else do let trail = replicate (max (length got) (length want)) "" let (i,g,w):_ = [(i,g,w) | (i,g,w) <- zip3 [1..] (got++trail) (want++trail), not $ matchStar w g] failed $ ["TEST FAILURE IN tests/" ++ name ,"DIFFER ON LINE: " ++ show i ,"GOT : " ++ g ,"WANT: " ++ w ,"FULL OUTPUT FOR GOT:"] ++ got -- | First string may have stars in it (the want) matchStar :: String -> String -> Bool matchStar ('*':xs) ys = any (matchStar xs) $ tails ys matchStar ('/':x:xs) ('\\':'\\':ys) | x /= '/' = matchStar (x:xs) ys -- JSON escaped newlines matchStar (x:xs) (y:ys) = eq x y && matchStar xs ys where -- allow path differences between Windows and Linux eq '/' y = isPathSeparator y eq x y = x == y matchStar [] [] = True matchStar _ _ = False matchStarStar :: [String] -> [String] -> ([String], [String]) matchStarStar want got = case break (== "**") want of (_, []) -> (want, got) (w1,_:w2) -> (w1++w2, g1 ++ takeEnd (length w2) g2) where (g1,g2) = splitAt (length w1) got hlint-3.1.6/src/Test/Annotations.hs0000644000000000000000000002070113674632146015400 0ustar0000000000000000{-# LANGUAGE CPP, PatternGuards, RecordWildCards, ViewPatterns #-} -- | Check the annotations within source and hint files. module Test.Annotations(testAnnotations) where import Control.Exception.Extra import Control.Monad import Control.Monad.IO.Class import Data.Char import Data.Either.Extra import Data.Function import Data.Functor import Data.List.Extra import Data.Maybe import Data.Tuple.Extra import System.Exit import System.FilePath import System.IO.Extra import GHC.All import qualified Data.ByteString.Char8 as BS import Config.Type import Idea import Apply import Extension import Refact import Test.Util import Prelude import Config.Yaml import FastString import GHC.Util import SrcLoc import Language.Haskell.GhclibParserEx.GHC.Utils.Outputable #ifdef HS_YAML import Data.YAML.Aeson (decode1Strict) import Data.YAML (Pos) import Data.ByteString (ByteString) decodeEither' :: ByteString -> Either (Pos, String) ConfigYaml decodeEither' = decode1Strict #else import Data.Yaml #endif -- Input, Output -- Output = Nothing, should not match -- Output = Just xs, should match xs data TestCase = TestCase SrcLoc Refactor String (Maybe String) [Setting] deriving (Show) data Refactor = TestRefactor | SkipRefactor deriving (Eq, Show) testAnnotations :: [Setting] -> FilePath -> Maybe FilePath -> Test () testAnnotations setting file rpath = do tests <- liftIO $ parseTestFile file mapM_ f tests where f (TestCase loc refact inp out additionalSettings) = do ideas <- liftIO $ try_ $ do res <- applyHintFile defaultParseFlags (setting ++ additionalSettings) file $ Just inp evaluate $ length $ show res pure res when ("src/Hint" `isPrefixOf` file) $ mapM_ (mapM_ (addBuiltin inp)) ideas -- the hints from data/Test.hs are really fake hints we don't actually deploy -- so don't record them when (takeFileName file /= "Test.hs") $ either (const $ pure ()) addIdeas ideas let good = case (out, ideas) of (Nothing, Right []) -> True (Just x, Right [idea]) | match x idea -> True _ -> False let bad = [failed $ ["TEST FAILURE (" ++ show (either (const 1) length ideas) ++ " hints generated)" ,"SRC: " ++ unsafePrettyPrint loc ,"INPUT: " ++ inp] ++ map ("OUTPUT: " ++) (either (pure . show) (map show) ideas) ++ ["WANTED: " ++ fromMaybe "" out] | not good] ++ [failed ["TEST FAILURE (BAD LOCATION)" ,"SRC: " ++ unsafePrettyPrint loc ,"INPUT: " ++ inp ,"OUTPUT: " ++ show i] | i@Idea{..} <- fromRight [] ideas, let SrcLoc{..} = srcSpanStart ideaSpan, srcFilename == "" || srcLine == 0 || srcColumn == 0] -- TODO: shouldn't these checks be == -1 instead? -- Skip refactoring test if the hlint test failed, or if the -- test is annotated with @NoRefactor. let skipRefactor = notNull bad || refact == SkipRefactor badRefactor <- if skipRefactor then pure [] else liftIO $ do refactorErr <- case ideas of Right [] -> testRefactor rpath Nothing inp Right [idea] -> testRefactor rpath (Just idea) inp -- Skip refactoring test if there are multiple hints _ -> pure [] pure $ [failed $ ["TEST FAILURE (BAD REFACTORING)" ,"SRC: " ++ unsafePrettyPrint loc ,"INPUT: " ++ inp] ++ refactorErr | notNull refactorErr] if null bad && null badRefactor then passed else sequence_ (bad ++ badRefactor) match "???" _ = True match (word1 -> ("@Message",msg)) i = ideaHint i == msg match (word1 -> ("@Note",note)) i = map show (ideaNote i) == [note] match "@NoNote" i = null (ideaNote i) match (word1 -> ('@':sev, msg)) i = sev == show (ideaSeverity i) && match msg i match msg i = on (==) norm (fromMaybe "" $ ideaTo i) msg -- FIXME: Should use a better check for expected results norm = filter $ \x -> not (isSpace x) && x /= ';' parseTestFile :: FilePath -> IO [TestCase] parseTestFile file = -- we remove all leading # symbols since Yaml only lets us do comments that way f Nothing TestRefactor . zipFrom 1 . map (dropPrefix "# ") . lines <$> readFile file where open :: String -> Maybe [Setting] open line | "" `isPrefixOf` line = let suffix = dropPrefix "" line config = decodeEither' $ BS.pack suffix in case config of Left err -> Just [] Right config -> Just $ settingsFromConfigYaml [config] | otherwise = Nothing shut :: String -> Bool shut = isPrefixOf "" f :: Maybe [Setting] -> Refactor -> [(Int, String)] -> [TestCase] f Nothing _ ((i,x):xs) = f (open x) TestRefactor xs f (Just s) refact ((i,x):xs) | shut x = f Nothing TestRefactor xs | Just (x',_) <- stripInfix "@NoRefactor" x = f (Just s) SkipRefactor ((i, trimEnd x' ++ ['\\' | "\\" `isSuffixOf` x]) : xs) | null x || "-- " `isPrefixOf` x = f (Just s) refact xs | Just x <- stripSuffix "\\" x, (_,y):ys <- xs = f (Just s) refact $ (i,x++"\n"++y):ys | otherwise = parseTest refact file i x s : f (Just s) TestRefactor xs f _ _ [] = [] parseTest :: Refactor -> String -> Int -> String -> [Setting] -> TestCase parseTest refact file i x = uncurry (TestCase (mkSrcLoc (mkFastString file) i 0) refact) $ f x where f x | Just x <- stripPrefix "" x = first ("--"++) $ f x f (' ':'-':'-':xs) | null xs || " " `isPrefixOf` xs = ("", Just $ trimStart xs) f (x:xs) = first (x:) $ f xs f [] = ([], Nothing) -- Returns an empty list if the refactoring test passes, otherwise -- returns error messages. testRefactor :: Maybe FilePath -> Maybe Idea -> String -> IO [String] -- Skip refactoring test if the refactor binary is not found. testRefactor Nothing _ _ = pure [] -- Skip refactoring test if the hint has no suggestion (i.e., a parse error). testRefactor _ (Just idea) _ | isNothing (ideaTo idea) = pure [] testRefactor (Just rpath) midea inp = withTempFile $ \tempInp -> withTempFile $ \tempHints -> do -- Note that we test the refactoring even if there are no suggestions, -- as an extra test of apply-refact, on which we rely. -- See https://github.com/ndmitchell/hlint/issues/958 for a discussion. let refacts = map (show &&& ideaRefactoring) (maybeToList midea) -- Ignores spaces and semicolons since apply-refact may change them. process = filter (\c -> not (isSpace c) && c /= ';') matched expected g actual = process expected `g` process actual x `isProperSubsequenceOf` y = x /= y && x `isSubsequenceOf` y writeFile tempInp inp writeFile tempHints (show refacts) exitCode <- runRefactoring rpath tempInp tempHints defaultExtensions [] "--inplace" refactored <- readFile tempInp pure $ case exitCode of ExitFailure ec -> ["Refactoring failed: exit code " ++ show ec] ExitSuccess -> case fmap ideaTo midea of -- No hints. Refactoring should be a no-op. Nothing | not (matched inp (==) refactored) -> ["Expected refactor output: " ++ inp, "Actual: " ++ refactored] -- The hint's suggested replacement is @Just ""@, which means the hint -- suggests removing something from the input. The refactoring output -- should be a proper subsequence of the input. Just (Just "") | not (matched refactored isProperSubsequenceOf inp) -> ["Refactor output is expected to be a proper subsequence of: " ++ inp, "Actual: " ++ refactored] -- The hint has a suggested replacement. The suggested replacement -- should be a substring of the refactoring output. Just (Just to) | not (matched to isInfixOf refactored) -> ["Refactor output is expected to contain: " ++ to, "Actual: " ++ refactored] _ -> [] hlint-3.1.6/src/Test/All.hs0000644000000000000000000000772213674632146013623 0ustar0000000000000000{-# LANGUAGE RecordWildCards #-} {-# OPTIONS_GHC -Wno-incomplete-patterns #-} module Test.All(test) where import Control.Exception import System.Console.CmdArgs import Control.Monad import Control.Monad.IO.Class import Data.Char import Data.Either.Extra import Data.Foldable import Data.List import Data.Maybe import System.Directory import System.FilePath import Data.Functor import Prelude import Config.Type import Config.Read import CmdLine import Refact import Hint.All import Test.Annotations import Test.InputOutput import Test.Summary import Test.Translate import Test.Util import System.IO.Extra import Language.Haskell.GhclibParserEx.GHC.Utils.Outputable test :: Cmd -> ([String] -> IO ()) -> FilePath -> [FilePath] -> IO Int test CmdTest{..} main dataDir files = do rpath <- refactorPath (if cmdWithRefactor == "" then Nothing else Just cmdWithRefactor) (failures, (ideas, builtins)) <- withBuffering stdout NoBuffering $ withTests $ do hasSrc <- liftIO $ doesFileExist "hlint.cabal" let useSrc = hasSrc && null files testFiles <- if files /= [] then pure files else do xs <- liftIO $ getDirectoryContents dataDir pure [dataDir x | x <- xs, takeExtension x `elem` [".yml",".yaml"]] testFiles <- liftIO $ forM testFiles $ \file -> do hints <- readFilesConfig [(file, Nothing),("CommandLine.yaml", Just "- group: {name: testing, enabled: true}")] pure (file, hints ++ (if takeBaseName file /= "Test" then [] else map (Builtin . fst) builtinHints)) let wrap msg act = do liftIO $ putStr (msg ++ " "); act; liftIO $ putStrLn "" liftIO $ putStrLn "Testing" liftIO $ checkCommentedYaml $ dataDir "default.yaml" when useSrc $ wrap "Source annotations" $ do config <- liftIO $ readFilesConfig [(".hlint.yaml",Nothing)] forM_ builtinHints $ \(name,_) -> do progress testAnnotations (Builtin name : if name == "Restrict" then config else []) ("src/Hint" name <.> "hs") (eitherToMaybe rpath) when useSrc $ wrap "Input/outputs" $ testInputOutput main wrap "Hint names" $ mapM_ (\x -> do progress; testNames $ snd x) testFiles wrap "Hint annotations" $ forM_ testFiles $ \(file,h) -> do progress; testAnnotations h file (eitherToMaybe rpath) let hs = [h | (file, h) <- testFiles, takeFileName file /= "Test.hs"] when cmdTypeCheck $ wrap "Hint typechecking" $ progress >> testTypeCheck cmdDataDir cmdTempDir hs when cmdQuickCheck $ wrap "Hint QuickChecking" $ progress >> testQuickCheck cmdDataDir cmdTempDir hs when (null files && not hasSrc) $ liftIO $ putStrLn "Warning, couldn't find source code, so non-hint tests skipped" (,) <$> getIdeas <*> getBuiltins whenLoud $ mapM_ print ideas when cmdGenerateSummary $ writeFile "builtin.md" (genBuiltinSummaryMd builtins) case rpath of Left refactorNotFound -> putStrLn $ unlines [refactorNotFound, "Refactoring tests skipped"] _ -> pure () pure failures --------------------------------------------------------------------- -- VARIOUS SMALL TESTS -- Check all hints in the standard config files get sensible names testNames :: [Setting] -> Test () testNames hints = sequence_ [ failed ["No name for the hint " ++ unsafePrettyPrint hintRuleLHS ++ " ==> " ++ unsafePrettyPrint hintRuleRHS] | SettingMatchExp x@HintRule{..} <- hints, hintRuleName == defaultHintName] -- Check that the default.yaml template I supply is valid when I strip off all the comments, since that's -- what a user gets with --default checkCommentedYaml :: FilePath -> IO () checkCommentedYaml file = do src <- lines <$> readFile' file let src2 = [x | x <- src, Just x <- [stripPrefix "# " x], not $ all (\x -> isAlpha x || x == '$') $ take 1 x] e <- readFilesConfig [(file, Just $ unlines src2)] void $ evaluate $ length e hlint-3.1.6/src/Language/0000755000000000000000000000000013674744764013365 5ustar0000000000000000hlint-3.1.6/src/Language/Haskell/0000755000000000000000000000000013674744764014750 5ustar0000000000000000hlint-3.1.6/src/Language/Haskell/HLint.hs0000644000000000000000000001423113671470061016302 0ustar0000000000000000{-# LANGUAGE PatternGuards, RecordWildCards #-} -- | This module provides a way to apply HLint hints. If you want to just run @hlint@ in-process -- and collect the results see 'hlint'. -- -- If you want to approximate the @hlint@ experience with -- a more structured API try: -- -- @ -- (flags, classify, hint) <- 'autoSettings' -- Right m <- 'parseModuleEx' flags \"MyFile.hs\" Nothing -- print $ 'applyHints' classify hint [m] -- @ module Language.Haskell.HLint( -- * Generate hints hlint, applyHints, -- * Idea data type Idea(..), Severity(..), Note(..), unpackSrcSpan, -- * Settings Classify(..), getHLintDataDir, autoSettings, argsSettings, findSettings, readSettingsFile, -- * Hints Hint, -- * Modules ModuleEx, parseModuleEx, createModuleEx, ParseError(..), -- * Parse flags defaultParseFlags, ParseFlags(..), CppFlags(..), FixityInfo, parseFlagsAddFixities, ) where import Config.Type import Config.Read import Control.Exception.Extra import Idea import qualified Apply as H import HLint import Fixity import FastString import GHC.All import Hint.All hiding (resolveHints) import qualified Hint.All as H import SrcLoc import CmdLine import Paths_hlint import Data.List.Extra import Data.Maybe import System.FilePath import Data.Functor import Prelude -- | Get the Cabal configured data directory of HLint. getHLintDataDir :: IO FilePath getHLintDataDir = getDataDir -- | The function produces a tuple containg 'ParseFlags' (for 'parseModuleEx'), -- and 'Classify' and 'Hint' for 'applyHints'. -- It approximates the normal HLint configuration steps, roughly: -- -- 1. Use 'findSettings' with 'readSettingsFile' to find and load the HLint settings files. -- -- 1. Use 'parseFlagsAddFixities' and 'resolveHints' to transform the outputs of 'findSettings'. -- -- If you want to do anything custom (e.g. using a different data directory, storing intermediate outputs, -- loading hints from a database) you are expected to copy and paste this function, then change it to your needs. autoSettings :: IO (ParseFlags, [Classify], Hint) autoSettings = do (fixities, classify, hints) <- findSettings (readSettingsFile Nothing) Nothing pure (parseFlagsAddFixities fixities defaultParseFlags, classify, hints) -- | A version of 'autoSettings' which respects some of the arguments supported by HLint. -- If arguments unrecognised by HLint are used it will result in an error. -- Arguments which have no representation in the return type are silently ignored. argsSettings :: [String] -> IO (ParseFlags, [Classify], Hint) argsSettings args = do cmd <- getCmd args case cmd of CmdMain{..} -> do -- FIXME: One thing that could be supported (but isn't) is 'cmdGivenHints' (_,settings) <- readAllSettings args cmd let (fixities, classify, hints) = splitSettings settings let flags = parseFlagsSetLanguage (cmdExtensions cmd) $ parseFlagsAddFixities fixities $ defaultParseFlags{cppFlags = cmdCpp cmd} let ignore = [Classify Ignore x "" "" | x <- cmdIgnore] pure (flags, classify ++ ignore, hints) _ -> errorIO "Can only invoke autoSettingsArgs with the root process" -- | Given a directory (or 'Nothing' to imply 'getHLintDataDir'), and a module name -- (e.g. @HLint.Default@), find the settings file associated with it, returning the -- name of the file, and (optionally) the contents. -- -- This function looks for all settings files starting with @HLint.@ in the directory -- argument, and all other files relative to the current directory. readSettingsFile :: Maybe FilePath -> String -> IO (FilePath, Maybe String) readSettingsFile dir x | takeExtension x `elem` [".yml",".yaml"] = do dir <- maybe getHLintDataDir pure dir pure (dir x, Nothing) | Just x <- "HLint." `stripPrefix` x = do dir <- maybe getHLintDataDir pure dir pure (dir x <.> "hs", Nothing) | otherwise = pure (x <.> "hs", Nothing) -- | Given a function to load a module (typically 'readSettingsFile'), and a module to start from -- (defaults to @hlint.yaml@) find the information from all settings files. findSettings :: (String -> IO (FilePath, Maybe String)) -> Maybe String -> IO ([FixityInfo], [Classify], Hint) findSettings load start = do (file,contents) <- load $ fromMaybe "hlint.yaml" start splitSettings <$> readFilesConfig [(file,contents)] -- | Split a list of 'Setting' for separate use in parsing and hint resolution splitSettings :: [Setting] -> ([FixityInfo], [Classify], Hint) splitSettings xs = ([x | Infix x <- xs] ,[x | SettingClassify x <- xs] ,H.resolveHints $ [Right x | SettingMatchExp x <- xs] ++ map Left enumerate) -- | Given a way of classifying results, and a 'Hint', apply to a set of modules generating a list of 'Idea's. -- The 'Idea' values will be ordered within a file. -- -- Given a set of modules, it may be faster to pass each to 'applyHints' in a singleton list. -- When given multiple modules at once this function attempts to find hints between modules, -- which is slower and often pointless (by default HLint passes modules singularly, using -- @--cross@ to pass all modules together). applyHints :: [Classify] -> Hint -> [ModuleEx] -> [Idea] applyHints = H.applyHints -- | Snippet from the documentation, if this changes, update the documentation _docs :: IO () _docs = do (flags, classify, hint) <- autoSettings Right m <- parseModuleEx flags "MyFile.hs" Nothing print $ applyHints classify hint [m] -- | Unpack a 'SrcSpan' value. Useful to allow using the 'Idea' information without -- adding a dependency on @ghc@ or @ghc-lib-parser@. Unpacking gives: -- -- > (filename, (startLine, startCol), (endLine, endCol)) -- -- Following the GHC API, he end column is the column /after/ the end of the error. -- Lines and columns are 1-based. Returns 'Nothing' if there is no helpful location information. unpackSrcSpan :: SrcSpan -> Maybe (FilePath, (Int, Int), (Int, Int)) unpackSrcSpan (RealSrcSpan x) = Just (unpackFS $ srcSpanFile x ,(srcSpanStartLine x, srcSpanStartCol x) ,(srcSpanEndLine x, srcSpanEndCol x)) unpackSrcSpan _ = Nothing hlint-3.1.6/src/Hint/0000755000000000000000000000000013674744765012545 5ustar0000000000000000hlint-3.1.6/src/Hint/Unsafe.hs0000644000000000000000000000664113671470061014307 0ustar0000000000000000 {- Find things that are unsafe {-# NOINLINE slaves #-}; slaves = unsafePerformIO newIO slaves = unsafePerformIO Multimap.newIO -- {-# NOINLINE slaves #-} ; slaves = unsafePerformIO Multimap.newIO slaves = unsafePerformIO $ f y where foo = 1 -- {-# NOINLINE slaves #-} ; slaves = unsafePerformIO $ f y where foo = 1 slaves v = unsafePerformIO $ Multimap.newIO where foo = 1 slaves v = x where x = unsafePerformIO $ Multimap.newIO slaves = x where x = unsafePerformIO $ Multimap.newIO -- {-# NOINLINE slaves #-} ; slaves = x where x = unsafePerformIO $ Multimap.newIO slaves = unsafePerformIO . bar slaves = unsafePerformIO . baz $ x -- {-# NOINLINE slaves #-} ; slaves = unsafePerformIO . baz $ x slaves = unsafePerformIO . baz $ x -- {-# NOINLINE slaves #-} ; slaves = unsafePerformIO . baz $ x -} module Hint.Unsafe(unsafeHint) where import Hint.Type(DeclHint,ModuleEx(..),Severity(..),rawIdea,toSS) import Data.List.Extra import Refact.Types hiding(Match) import Data.Generics.Uniplate.DataOnly import GHC.Hs import OccName import RdrName import FastString import BasicTypes import SrcLoc import Language.Haskell.GhclibParserEx.GHC.Hs.Expr import Language.Haskell.GhclibParserEx.GHC.Utils.Outputable -- The conditions on which to fire this hint are subtle. We are -- interested exclusively in application constants involving -- 'unsafePerformIO'. For example, -- @ -- f = \x -> unsafePerformIO x -- @ -- is not such a declaration (the right hand side is a lambda, not an -- application) whereas, -- @ -- f = g where g = unsafePerformIO Multimap.newIO -- @ -- is. We advise that such constants should have a @NOINLINE@ pragma. unsafeHint :: DeclHint unsafeHint _ (ModuleEx (L _ m) _) = \(L loc d) -> [rawIdea Hint.Type.Warning "Missing NOINLINE pragma" loc (unsafePrettyPrint d) (Just $ trimStart (unsafePrettyPrint $ gen x) ++ "\n" ++ unsafePrettyPrint d) [] [InsertComment (toSS (L loc d)) (unsafePrettyPrint $ gen x)] -- 'x' does not declare a new function. | d@(ValD _ FunBind {fun_id=L _ (Unqual x) , fun_matches=MG{mg_origin=FromSource,mg_alts=L _ [L _ Match {m_pats=[]}]}}) <- [d] -- 'x' is a synonym for an appliciation involing 'unsafePerformIO' , isUnsafeDecl d -- 'x' is not marked 'NOINLINE'. , x `notElem` noinline] where gen :: OccName -> LHsDecl GhcPs gen x = noLoc $ SigD noExtField (InlineSig noExtField (noLoc (mkRdrUnqual x)) (InlinePragma (SourceText "{-# NOINLINE") NoInline Nothing NeverActive FunLike)) noinline :: [OccName] noinline = [q | L _(SigD _ (InlineSig _ (L _ (Unqual q)) (InlinePragma _ NoInline Nothing NeverActive FunLike)) ) <- hsmodDecls m] isUnsafeDecl :: HsDecl GhcPs -> Bool isUnsafeDecl (ValD _ FunBind {fun_matches=MG {mg_origin=FromSource,mg_alts=L _ alts}}) = any isUnsafeApp (childrenBi alts) || any isUnsafeDecl (childrenBi alts) isUnsafeDecl _ = False -- Am I equivalent to @unsafePerformIO x@? isUnsafeApp :: HsExpr GhcPs -> Bool isUnsafeApp (OpApp _ (L _ l) op _ ) | isDol op = isUnsafeFun l isUnsafeApp (HsApp _ (L _ x) _) = isUnsafeFun x isUnsafeApp _ = False -- Am I equivalent to @unsafePerformIO . x@? isUnsafeFun :: HsExpr GhcPs -> Bool isUnsafeFun (HsVar _ (L _ x)) | x == mkVarUnqual (fsLit "unsafePerformIO") = True isUnsafeFun (OpApp _ (L _ l) op _) | isDot op = isUnsafeFun l isUnsafeFun _ = False hlint-3.1.6/src/Hint/Type.hs0000644000000000000000000000261113671470061014000 0ustar0000000000000000 module Hint.Type( DeclHint, ModuHint, CrossHint, Hint(..), module Export ) where import Data.Semigroup import Config.Type import GHC.All as Export import Idea as Export import Prelude import Refact as Export import GHC.Hs.Extension import GHC.Hs.Decls import GHC.Util.Scope type DeclHint = Scope -> ModuleEx -> LHsDecl GhcPs -> [Idea] type ModuHint = Scope -> ModuleEx -> [Idea] type CrossHint = [(Scope, ModuleEx)] -> [Idea] -- | Functions to generate hints, combined using the 'Monoid' instance. data Hint {- PUBLIC -} = Hint { hintModules :: [Setting] -> [(Scope, ModuleEx)] -> [Idea] -- ^ Given a list of modules (and their scope information) generate some 'Idea's. , hintModule :: [Setting] -> Scope -> ModuleEx -> [Idea] -- ^ Given a single module and its scope information generate some 'Idea's. , hintDecl :: [Setting] -> Scope -> ModuleEx -> LHsDecl GhcPs -> [Idea] -- ^ Given a declaration (with a module and scope) generate some 'Idea's. -- This function will be partially applied with one module/scope, then used on multiple 'Decl' values. } instance Semigroup Hint where Hint x1 x2 x3 <> Hint y1 y2 y3 = Hint (\a b -> x1 a b ++ y1 a b) (\a b c -> x2 a b c ++ y2 a b c) (\a b c d -> x3 a b c d ++ y3 a b c d) instance Monoid Hint where mempty = Hint (\_ _ -> []) (\_ _ _ -> []) (\_ _ _ _ -> []) mappend = (<>) hlint-3.1.6/src/Hint/Smell.hs0000644000000000000000000001177513671470061014146 0ustar0000000000000000 module Hint.Smell ( smellModuleHint, smellHint ) where {- [{smell: { type: many arg functions, limit: 2 }}] f :: Int -> Int \ f = undefined f :: Int -> Int -> Int \ f = undefined -- f :: Int -> Int \ f = undefined f :: Int -> Int -> Int \ f = undefined [{smell: { type: long functions, limit: 3}}] f = do \ x <- y \ return x -- f = do \ return z \ \ where \ z = do \ a \ b -- f = do \ return z \ \ where \ z = a f = Con \ { a = x \ , b = y \ , c = z \ } f = return x f = do \ x <- y \ return x f = return x [{smell: { type: long type lists, limit: 2}}] f :: Bool -> Int -> (Int -> Proxy '[a, b]) -- f :: Proxy '[a] f :: Proxy '[a, b] f :: Proxy '[a] [{smell: { type: many imports, limit: 2}}] import A; import B -- import A import A; import B import A -} import Hint.Type(ModuHint,ModuleEx(..),DeclHint,Idea(..),rawIdea,warn) import Config.Type import Data.Generics.Uniplate.DataOnly import Data.List.Extra import qualified Data.Map as Map import BasicTypes import GHC.Hs import RdrName import Outputable import Bag import SrcLoc import Language.Haskell.GhclibParserEx.GHC.Utils.Outputable smellModuleHint :: [Setting] -> ModuHint smellModuleHint settings scope m = let (L _ mod) = ghcModule m imports = hsmodImports mod in case Map.lookup SmellManyImports (smells settings) of Just n | length imports >= n -> let span = foldl1 combineSrcSpans $ getLoc <$> imports displayImports = unlines $ f <$> imports in [rawIdea Config.Type.Warning "Many imports" span displayImports Nothing [] [] ] where f :: LImportDecl GhcPs -> String f = trimStart . unsafePrettyPrint _ -> [] smellHint :: [Setting] -> DeclHint smellHint settings scope m d = sniff smellLongFunctions SmellLongFunctions ++ sniff smellLongTypeLists SmellLongTypeLists ++ sniff smellManyArgFunctions SmellManyArgFunctions where sniff f t = fmap (\i -> i {ideaTo = Nothing }) . take 1 $ maybe [] (f d) $ Map.lookup t (smells settings) smellLongFunctions :: LHsDecl GhcPs -> Int -> [Idea] smellLongFunctions d n = [ idea | (span, idea) <- declSpans d , spanLength span >= n ] -- I've tried to be faithful to the original here but I'm doubtful -- about it. I think I've replicated the behavior of the original but -- is the original correctly honoring the intent? -- A function with with one alternative, one rhs and its 'where' -- clause (perhaps we should be looping over alts and all guarded -- right hand sides?) declSpans :: LHsDecl GhcPs -> [(SrcSpan, Idea)] declSpans (L _ (ValD _ FunBind {fun_matches=MG { mg_origin=FromSource , mg_alts=(L _ [L _ Match { m_ctxt=ctx , m_grhss=GRHSs{grhssGRHSs=[locGrhs] , grhssLocalBinds=where_}}])}})) = -- The span of the right hand side and the spans of each binding in -- the where clause. rhsSpans ctx locGrhs ++ whereSpans where_ -- Any other kind of function. declSpans f@(L l (ValD _ FunBind {})) = [(l, warn "Long function" f f [])] declSpans _ = [] -- The span of a guarded right hand side. rhsSpans :: HsMatchContext RdrName -> LGRHS GhcPs (LHsExpr GhcPs) -> [(SrcSpan, Idea)] rhsSpans _ (L _ (GRHS _ _ (L _ RecordCon {}))) = [] -- record constructors get a pass rhsSpans ctx (L _ r@(GRHS _ _ (L l _))) = [(l, rawIdea Config.Type.Warning "Long function" l (showSDocUnsafe (pprGRHS ctx r)) Nothing [] [])] rhsSpans _ _ = [] -- The spans of a 'where' clause are the spans of its bindings. whereSpans :: LHsLocalBinds GhcPs -> [(SrcSpan, Idea)] whereSpans (L l (HsValBinds _ (ValBinds _ bs _))) = concatMap (declSpans . (\(L loc bind) -> L loc (ValD noExtField bind))) (bagToList bs) whereSpans _ = [] spanLength :: SrcSpan -> Int spanLength (RealSrcSpan span) = srcSpanEndLine span - srcSpanStartLine span + 1 spanLength (UnhelpfulSpan _) = -1 smellLongTypeLists :: LHsDecl GhcPs -> Int -> [Idea] smellLongTypeLists d@(L _ (SigD _ (TypeSig _ _ (HsWC _ (HsIB _ (L _ t)))))) n = warn "Long type list" d d [] <$ filter longTypeList (universe t) where longTypeList (HsExplicitListTy _ IsPromoted x) = length x >= n longTypeList _ = False smellLongTypeLists _ _ = [] smellManyArgFunctions :: LHsDecl GhcPs -> Int -> [Idea] smellManyArgFunctions d@(L _ (SigD _ (TypeSig _ _ (HsWC _ (HsIB _ (L _ t)))))) n = warn "Many arg function" d d [] <$ filter manyArgFunction (universe t) where manyArgFunction t = countFunctionArgs t >= n smellManyArgFunctions _ _ = [] countFunctionArgs :: HsType GhcPs -> Int countFunctionArgs (HsFunTy _ _ t) = 1 + countFunctionArgs (unLoc t) countFunctionArgs (HsParTy _ t) = countFunctionArgs (unLoc t) countFunctionArgs _ = 0 smells :: [Setting] -> Map.Map SmellType Int smells settings = Map.fromList [ (smellType, smellLimit) | SettingSmell smellType smellLimit <- settings] hlint-3.1.6/src/Hint/Restrict.hs0000644000000000000000000002046413671470061014664 0ustar0000000000000000{-# LANGUAGE LambdaCase #-} {-# LANGUAGE MultiWayIf #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE ViewPatterns #-} module Hint.Restrict(restrictHint) where {- -- These tests rely on the .hlint.yaml file in the root foo = unsafePerformIO -- foo = bar `unsafePerformIO` baz -- module Util where otherFunc = unsafePerformIO $ print 1 -- module Util where exitMessageImpure = System.IO.Unsafe.unsafePerformIO $ print 1 foo = unsafePerformOI import Data.List.NonEmpty as NE \ foo = NE.nub (NE.fromList [1, 2, 3]) -- import Hypothetical.Module \ foo = nub s -} import Hint.Type(ModuHint,ModuleEx(..),Idea(..),Severity(..),warn,rawIdea) import Config.Type import Data.Generics.Uniplate.DataOnly import qualified Data.List.NonEmpty as NonEmpty import qualified Data.Set as Set import qualified Data.Map as Map import Data.List.Extra import Data.Maybe import Data.Semigroup import Data.Tuple.Extra import Control.Applicative import Control.Monad import Prelude import GHC.Hs import RdrName import ApiAnnotation import Module import SrcLoc import OccName import Language.Haskell.GhclibParserEx.GHC.Hs import Language.Haskell.GhclibParserEx.GHC.Types.Name.Reader import GHC.Util -- FIXME: The settings should be partially applied, but that's hard to orchestrate right now restrictHint :: [Setting] -> ModuHint restrictHint settings scope m = let anns = ghcAnnotations m ps = pragmas anns opts = flags ps exts = languagePragmas ps in checkPragmas modu opts exts rOthers ++ maybe [] (checkImports modu $ hsmodImports (unLoc (ghcModule m))) (Map.lookup RestrictModule rOthers) ++ checkFunctions scope modu (hsmodDecls (unLoc (ghcModule m))) rFunction where modu = modName (ghcModule m) (rFunction, rOthers) = restrictions settings --------------------------------------------------------------------- -- UTILITIES data RestrictItem = RestrictItem {riAs :: [String] ,riWithin :: [(String, String)] ,riBadIdents :: [String] ,riMessage :: Maybe String } instance Semigroup RestrictItem where RestrictItem x1 x2 x3 x4 <> RestrictItem y1 y2 y3 y4 = RestrictItem (x1<>y1) (x2<>y2) (x3<>y3) (x4<>y4) -- Contains a map from module (Nothing if the rule is unqualified) to (within, message), so that we can -- distinguish functions with the same name. -- For example, this allows us to have separate rules for "Data.Map.fromList" and "Data.Set.fromList". -- Using newtype rather than type because we want to define (<>) as 'Map.unionWith (<>)'. newtype RestrictFunction = RestrictFun (Map.Map (Maybe String) ([(String, String)], Maybe String)) instance Semigroup RestrictFunction where RestrictFun m1 <> RestrictFun m2 = RestrictFun (Map.unionWith (<>) m1 m2) type RestrictFunctions = (Bool, Map.Map String RestrictFunction) type OtherRestrictItems = Map.Map RestrictType (Bool, Map.Map String RestrictItem) restrictions :: [Setting] -> (RestrictFunctions, OtherRestrictItems) restrictions settings = (rFunction, rOthers) where (map snd -> rfs, ros) = partition ((== RestrictFunction) . fst) [(restrictType x, x) | SettingRestrict x <- settings] rFunction = (all restrictDefault rfs, Map.fromListWith (<>) [mkRf s r | r <- rfs, s <- restrictName r]) mkRf s Restrict{..} = (name, RestrictFun $ Map.singleton modu (restrictWithin, restrictMessage)) where -- Parse module and name from s. module = Nothing if the rule is unqualified. (modu, name) = first (fmap NonEmpty.init . NonEmpty.nonEmpty) (breakEnd (== '.') s) rOthers = Map.map f $ Map.fromListWith (++) (map (second pure) ros) f rs = (all restrictDefault rs ,Map.fromListWith (<>) [(s, RestrictItem restrictAs restrictWithin restrictBadIdents restrictMessage) | Restrict{..} <- rs, s <- restrictName]) ideaMessage :: Maybe String -> Idea -> Idea ideaMessage (Just message) w = w{ideaNote=[Note message]} ideaMessage Nothing w = w{ideaNote=[noteMayBreak]} ideaNoTo :: Idea -> Idea ideaNoTo w = w{ideaTo=Nothing} noteMayBreak :: Note noteMayBreak = Note "may break the code" within :: String -> String -> [(String, String)] -> Bool within modu func = any (\(a,b) -> (a == modu || a == "") && (b == func || b == "")) --------------------------------------------------------------------- -- CHECKS checkPragmas :: String -> [(Located AnnotationComment, [String])] -> [(Located AnnotationComment, [String])] -> Map.Map RestrictType (Bool, Map.Map String RestrictItem) -> [Idea] checkPragmas modu flags exts mps = f RestrictFlag "flags" flags ++ f RestrictExtension "extensions" exts where f tag name xs = [(if null good then ideaNoTo else id) $ notes $ rawIdea Hint.Type.Warning ("Avoid restricted " ++ name) l c Nothing [] [] | Just (def, mp) <- [Map.lookup tag mps] , (L l (AnnBlockComment c), les) <- xs , let (good, bad) = partition (isGood def mp) les , let note = maybe noteMayBreak Note . (=<<) riMessage . flip Map.lookup mp , let notes w = w {ideaNote=note <$> bad} , not $ null bad] isGood def mp x = maybe def (within modu "" . riWithin) $ Map.lookup x mp checkImports :: String -> [LImportDecl GhcPs] -> (Bool, Map.Map String RestrictItem) -> [Idea] checkImports modu imp (def, mp) = [ ideaMessage riMessage $ if | not allowImport -> ideaNoTo $ warn "Avoid restricted module" i i [] | not allowIdent -> ideaNoTo $ warn "Avoid restricted identifiers" i i [] | not allowQual -> warn "Avoid restricted qualification" i (noLoc $ (unLoc i){ ideclAs=noLoc . mkModuleName <$> listToMaybe riAs} :: Located (ImportDecl GhcPs)) [] | otherwise -> error "checkImports: unexpected case" | i@(L _ ImportDecl {..}) <- imp , let RestrictItem{..} = Map.findWithDefault (RestrictItem [] [("","") | def] [] Nothing) (moduleNameString (unLoc ideclName)) mp , let allowImport = within modu "" riWithin , let allowIdent = Set.disjoint (Set.fromList riBadIdents) (Set.fromList (maybe [] (\(b, lxs) -> if b then [] else concatMap (importListToIdents . unLoc) (unLoc lxs)) ideclHiding)) , let allowQual = maybe True (\x -> null riAs || moduleNameString (unLoc x) `elem` riAs) ideclAs , not allowImport || not allowQual || not allowIdent ] importListToIdents :: IE GhcPs -> [String] importListToIdents = catMaybes . \case (IEVar _ n) -> [fromName n] (IEThingAbs _ n) -> [fromName n] (IEThingAll _ n) -> [fromName n] (IEThingWith _ n _ ns _) -> fromName n : map fromName ns _ -> [] where fromName :: LIEWrappedName (IdP GhcPs) -> Maybe String fromName wrapped = case unLoc wrapped of IEName n -> fromId (unLoc n) IEPattern n -> ("pattern " ++) <$> fromId (unLoc n) IEType n -> ("type " ++) <$> fromId (unLoc n) fromId :: IdP GhcPs -> Maybe String fromId (Unqual n) = Just $ occNameString n fromId (Qual _ n) = Just $ occNameString n fromId (Orig _ n) = Just $ occNameString n fromId (Exact _) = Nothing checkFunctions :: Scope -> String -> [LHsDecl GhcPs] -> RestrictFunctions -> [Idea] checkFunctions scope modu decls (def, mp) = [ (ideaMessage message $ ideaNoTo $ warn "Avoid restricted function" x x []){ideaDecl = [dname]} | d <- decls , let dname = fromMaybe "" (declName d) , x <- universeBi d :: [Located RdrName] , let xMods = possModules scope x , let (withins, message) = fromMaybe ([("","") | def], Nothing) (findFunction x xMods) , not $ within modu dname withins ] where -- Returns Just iff there are rules for x, which are either unqualified, or qualified with a module that is -- one of x's possible modules. -- If there are multiple matching rules (e.g., there's both an unqualified version and a qualified version), their -- withins and messages are concatenated with (<>). findFunction :: Located RdrName -> [ModuleName] -> Maybe ([(String, String)], Maybe String) findFunction (rdrNameStr -> x) (map moduleNameString -> possMods) | Just (RestrictFun mp) <- Map.lookup x mp = fmap sconcat . NonEmpty.nonEmpty . Map.elems $ Map.filterWithKey (const . maybe True (`elem` possMods)) mp | otherwise = Nothing hlint-3.1.6/src/Hint/Pragma.hs0000644000000000000000000001402213674632146014274 0ustar0000000000000000{-# LANGUAGE ViewPatterns #-} {-# LANGUAGE FlexibleContexts #-} {- Suggest better pragmas OPTIONS_GHC -cpp => LANGUAGE CPP OPTIONS_GHC -fglasgow-exts => LANGUAGE ... (in HSE) OPTIONS_GHC -XFoo => LANGUAGE Foo LANGUAGE A, A => LANGUAGE A -- do not do LANGUAGE A, LANGUAGE B to combine {-# OPTIONS_GHC -cpp #-} -- {-# LANGUAGE CPP #-} {-# OPTIONS -cpp #-} -- {-# LANGUAGE CPP #-} {-# OPTIONS_YHC -cpp #-} {-# OPTIONS_GHC -XFoo #-} -- {-# LANGUAGE Foo #-} {-# OPTIONS_GHC -fglasgow-exts #-} -- ??? @NoRefactor {-# LANGUAGE RebindableSyntax, EmptyCase, RebindableSyntax #-} -- {-# LANGUAGE RebindableSyntax, EmptyCase #-} {-# LANGUAGE RebindableSyntax, EmptyCase, DuplicateRecordFields, RebindableSyntax #-} -- {-# LANGUAGE RebindableSyntax, EmptyCase, DuplicateRecordFields #-} {-# LANGUAGE RebindableSyntax #-} {-# OPTIONS_GHC -cpp -foo #-} -- {-# LANGUAGE CPP #-} {-# OPTIONS_GHC -foo #-} @NoRefactor -foo is not a valid flag {-# OPTIONS_GHC -cpp -w #-} -- {-# LANGUAGE CPP #-} {-# OPTIONS_GHC -w #-} {-# OPTIONS_GHC -cpp #-} \ {-# LANGUAGE CPP, Text #-} -- {-# LANGUAGE RebindableSyntax #-} \ {-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE RebindableSyntax #-} \ {-# LANGUAGE EmptyCase, RebindableSyntax #-} -- {-# LANGUAGE RebindableSyntax, EmptyCase #-} -} module Hint.Pragma(pragmaHint) where import Hint.Type(ModuHint,ModuleEx(..),Idea(..),Severity(..),toSS,rawIdea) import Data.List.Extra import qualified Data.List.NonEmpty as NE import Data.Maybe import Refact.Types import qualified Refact.Types as R import ApiAnnotation import SrcLoc import GHC.Util import DynFlags pragmaHint :: ModuHint pragmaHint _ modu = let ps = pragmas (ghcAnnotations modu) opts = flags ps lang = languagePragmas ps in languageDupes lang ++ optToPragma opts lang optToPragma :: [(Located AnnotationComment, [String])] -> [(Located AnnotationComment, [String])] -> [Idea] optToPragma flags languagePragmas = [pragmaIdea (OptionsToComment (fst <$> old2) ys rs) | Just old2 <- [NE.nonEmpty old]] where (old, new, ns, rs) = unzip4 [(old, new, ns, r) | old <- flags, Just (new, ns) <- [optToLanguage old ls] , let r = mkRefact old new ns] ls = concatMap snd languagePragmas ns2 = nubOrd (concat ns) \\ ls ys = [mkLanguagePragmas noSrcSpan ns2 | ns2 /= []] ++ catMaybes new mkRefact :: (Located AnnotationComment, [String]) -> Maybe (Located AnnotationComment) -> [String] -> Refactoring R.SrcSpan mkRefact old (maybe "" comment -> new) ns = let ns' = map (\n -> comment (mkLanguagePragmas noSrcSpan [n])) ns in ModifyComment (toSS (fst old)) (intercalate "\n" (filter (not . null) (ns' `snoc` new))) data PragmaIdea = SingleComment (Located AnnotationComment) (Located AnnotationComment) | MultiComment (Located AnnotationComment) (Located AnnotationComment) (Located AnnotationComment) | OptionsToComment (NE.NonEmpty (Located AnnotationComment)) [Located AnnotationComment] [Refactoring R.SrcSpan] pragmaIdea :: PragmaIdea -> Idea pragmaIdea pidea = case pidea of SingleComment old new -> mkFewer (getLoc old) (comment old) (Just $ comment new) [] [ModifyComment (toSS old) (comment new)] MultiComment repl delete new -> mkFewer (getLoc repl) (f [repl, delete]) (Just $ comment new) [] [ ModifyComment (toSS repl) (comment new) , ModifyComment (toSS delete) ""] OptionsToComment old new r -> mkLanguage (getLoc . NE.head $ old) (f $ NE.toList old) (Just $ f new) [] r where f = unlines . map comment mkFewer = rawIdea Hint.Type.Warning "Use fewer LANGUAGE pragmas" mkLanguage = rawIdea Hint.Type.Warning "Use LANGUAGE pragmas" languageDupes :: [(Located AnnotationComment, [String])] -> [Idea] languageDupes ( (a@(L l _), les) : cs ) = (if nubOrd les /= les then [pragmaIdea (SingleComment a (mkLanguagePragmas l $ nubOrd les))] else [pragmaIdea (MultiComment a b (mkLanguagePragmas l (nubOrd $ les ++ les'))) | ( b@(L _ _), les' ) <- cs, not $ disjoint les les'] ) ++ languageDupes cs languageDupes _ = [] -- Given a pragma, can you extract some language features out? strToLanguage :: String -> Maybe [String] strToLanguage "-cpp" = Just ["CPP"] strToLanguage x | "-X" `isPrefixOf` x = Just [drop 2 x] strToLanguage "-fglasgow-exts" = Just $ map show glasgowExtsFlags strToLanguage _ = Nothing -- In 'optToLanguage p langexts', 'p' is an 'OPTIONS_GHC' pragma, -- 'langexts' a list of all language extensions in the module enabled -- by 'LANGUAGE' pragmas. -- -- If ALL of the flags in the pragma enable language extensions, -- 'return Nothing'. -- -- If some (or all) of the flags enable options that are not language -- extensions, compute a new options pragma with only non-language -- extension enabling flags. Return that together with a list of any -- language extensions enabled by this pragma that are not otherwise -- enabled by LANGUAGE pragmas in the module. optToLanguage :: (Located AnnotationComment, [String]) -> [String] -> Maybe (Maybe (Located AnnotationComment), [String]) optToLanguage (L loc _, flags) languagePragmas | any isJust vs = -- 'ls' is a list of language features enabled by this -- OPTIONS_GHC pragma that are not enabled by LANGUAGE pragmas -- in this module. let ls = filter (not . (`elem` languagePragmas)) (concat $ catMaybes vs) in Just (res, ls) where -- Try reinterpreting each flag as a list of language features -- (e.g. via '-X'..., '-fglasgow-exts'). vs = map strToLanguage flags -- e.g. '[Nothing, Just ["ScopedTypeVariables"], Nothing, ...]' -- Keep any flag that does not enable language extensions. keep = concat $ zipWith (\v f -> [f | isNothing v]) vs flags -- If there are flags to keep, 'res' is a new pragma setting just those flags. res = if null keep then Nothing else Just (mkFlags loc keep) optToLanguage _ _ = Nothing hlint-3.1.6/src/Hint/Pattern.hs0000644000000000000000000002554513671470061014507 0ustar0000000000000000{-# LANGUAGE ViewPatterns, PatternGuards, TypeFamilies #-} {- Improve the structure of code yes x y = if a then b else if c then d else e -- yes x y ; | a = b ; | c = d ; | otherwise = e x `yes` y = if a then b else if c then d else e -- x `yes` y ; | a = b ; | c = d ; | otherwise = e no x y = if a then b else c -- foo b | c <- f b = c -- foo (f -> c) = c -- foo x y b z | c:cs <- f g b = c -- foo x y (f g -> c:cs) z = c foo b | c <- f b = c + b foo b | c <- f b = c where f = here foo b | c <- f b = c where foo = b foo b | c <- f b = c \ | c <- f b = c foo x = yes x x where yes x y = if a then b else if c then d else e -- yes x y ; | a = b ; | c = d ; | otherwise = e foo x | otherwise = y -- foo x = y foo x = x + x where -- @NoRefactor: refactoring for "Redundant where" is not implemented foo x | a = b | True = d -- foo x | a = b ; | otherwise = d foo (Bar _ _ _ _) = x -- Bar{} foo (Bar _ x _ _) = x foo (Bar _ _) = x foo = case f v of _ -> x -- x foo = case v of v -> x -- x foo = case v of z -> z foo = case v of _ | False -> x foo x | x < -2 * 3 = 4 @NoRefactor: ghc-exactprint bug; -2 becomes 2. foo = case v of !True -> x -- True {-# LANGUAGE BangPatterns #-}; foo = case v of !True -> x -- True {-# LANGUAGE BangPatterns #-}; foo = case v of !(Just x) -> x -- (Just x) {-# LANGUAGE BangPatterns #-}; foo = case v of !(x : xs) -> x -- (x:xs) {-# LANGUAGE BangPatterns #-}; foo = case v of !1 -> x -- 1 {-# LANGUAGE BangPatterns #-}; foo = case v of !x -> x {-# LANGUAGE BangPatterns #-}; foo = case v of !(I# x) -> y -- (I# x) foo = let ~x = 1 in y -- x foo = let ~(x:xs) = y in z {-# LANGUAGE BangPatterns #-}; foo = let !x = undefined in y {-# LANGUAGE BangPatterns #-}; foo = let !(I# x) = 4 in x {-# LANGUAGE BangPatterns #-}; foo = let !(Just x) = Nothing in 3 {-# LANGUAGE BangPatterns #-}; foo = 1 where f !False = 2 -- False {-# LANGUAGE BangPatterns #-}; foo = 1 where !False = True {-# LANGUAGE BangPatterns #-}; foo = 1 where g (Just !True) = Nothing -- True {-# LANGUAGE BangPatterns #-}; foo = 1 where Just !True = Nothing foo otherwise = 1 -- _ @NoRefactor foo ~x = y -- x {-# LANGUAGE Strict #-} foo ~x = y {-# LANGUAGE BangPatterns #-}; foo !(x, y) = x -- (x, y) {-# LANGUAGE BangPatterns #-}; foo ![x] = x -- [x] foo !Bar { bar = x } = x -- Bar { bar = x } {-# LANGUAGE BangPatterns #-}; l !(() :: ()) = x -- (() :: ()) foo x@_ = x -- x foo x@Foo = x otherwise = True -} module Hint.Pattern(patternHint) where import Hint.Type(DeclHint,Idea,ghcAnnotations,ideaTo,toSS,toRefactSrcSpan,suggest,suggestRemove,warn) import Data.Generics.Uniplate.DataOnly import Data.Function import Data.List.Extra import Data.Tuple import Data.Maybe import Data.Either import Refact.Types hiding (RType(Pattern, Match), SrcSpan) import qualified Refact.Types as R (RType(Pattern, Match), SrcSpan) import GHC.Hs import SrcLoc import RdrName import OccName import Bag import BasicTypes import GHC.Util import Language.Haskell.GhclibParserEx.GHC.Hs.Pat import Language.Haskell.GhclibParserEx.GHC.Hs.Expr import Language.Haskell.GhclibParserEx.GHC.Utils.Outputable import Language.Haskell.GhclibParserEx.GHC.Types.Name.Reader patternHint :: DeclHint patternHint _scope modu x = concatMap (uncurry hints . swap) (asPattern x) ++ -- PatBind (used in 'let' and 'where') contains lazy-by-default -- patterns, everything else is strict. concatMap (patHint strict False) [p | PatBind _ p _ _ <- universeBi x :: [HsBind GhcPs]] ++ concatMap (patHint strict True) (universeBi $ transformBi noPatBind x) ++ concatMap expHint (universeBi x) where exts = nubOrd $ concatMap snd (languagePragmas (pragmas (ghcAnnotations modu))) -- language extensions enabled at source strict = "Strict" `elem` exts noPatBind :: LHsBind GhcPs -> LHsBind GhcPs noPatBind (L loc a@PatBind{}) = L loc a{pat_lhs=noLoc (WildPat noExtField)} noPatBind x = x {- -- Do not suggest view patterns, they aren't something everyone likes sufficiently hints gen (Pattern pats (GuardedRhss _ [GuardedRhs _ [Generator _ pat (App _ op (view -> Var_ p))] bod]) bind) | Just i <- findIndex (=~= (toNamed p :: Pat_)) pats , p `notElem` (vars bod ++ vars bind) , vars op `disjoint` decsBind, pvars pats `disjoint` vars op, pvars pat `disjoint` pvars pats = [gen "Use view patterns" $ Pattern (take i pats ++ [PParen an $ PViewPat an op pat] ++ drop (i+1) pats) (UnGuardedRhs an bod) bind] where decsBind = nub $ concatMap declBind $ childrenBi bind -} hints :: (String -> Pattern -> [Refactoring R.SrcSpan] -> Idea) -> Pattern -> [Idea] hints gen (Pattern l rtype pat (GRHSs _ [L _ (GRHS _ [] bod)] bind)) | length guards > 2 = [gen "Use guards" (Pattern l rtype pat (GRHSs noExtField guards bind)) [refactoring]] where rawGuards :: [(LHsExpr GhcPs, LHsExpr GhcPs)] rawGuards = asGuards bod mkGuard :: LHsExpr GhcPs -> (LHsExpr GhcPs -> GRHS GhcPs (LHsExpr GhcPs)) mkGuard a = GRHS noExtField [noLoc $ BodyStmt noExtField a noSyntaxExpr noSyntaxExpr] guards :: [LGRHS GhcPs (LHsExpr GhcPs)] guards = map (noLoc . uncurry mkGuard) rawGuards (lhs, rhs) = unzip rawGuards mkTemplate c ps = -- Check if the expression has been injected or is natural. zipWith checkLoc ps ['1' .. '9'] where checkLoc p@(L l _) v = if l == noSrcSpan then Left p else Right (c ++ [v], toSS p) patSubts = case pat of [p] -> [Left p] -- Substitution doesn't work properly for PatBinds. -- This will probably produce unexpected results if the pattern contains any template variables. ps -> mkTemplate "p100" ps guardSubts = mkTemplate "g100" lhs exprSubts = mkTemplate "e100" rhs templateGuards = map noLoc (zipWith (mkGuard `on` toString) guardSubts exprSubts) toString (Left e) = e toString (Right (v, _)) = strToVar v toString' (Left e) = e toString' (Right (v, _)) = strToPat v template = fromMaybe "" $ ideaTo (gen "" (Pattern l rtype (map toString' patSubts) (GRHSs noExtField templateGuards bind)) []) f :: [Either a (String, R.SrcSpan)] -> [(String, R.SrcSpan)] f = rights refactoring = Replace rtype (toRefactSrcSpan l) (f patSubts ++ f guardSubts ++ f exprSubts) template hints gen (Pattern l t pats o@(GRHSs _ [L _ (GRHS _ [test] bod)] bind)) | unsafePrettyPrint test `elem` ["otherwise", "True"] = [gen "Redundant guard" (Pattern l t pats o{grhssGRHSs=[noLoc (GRHS noExtField [] bod)]}) [Delete Stmt (toSS test)]] hints _ (Pattern l t pats bod@(GRHSs _ _ binds)) | f binds = [suggestRemove "Redundant where" whereSpan "where" [ {- TODO refactoring for redundant where -} ]] where f :: LHsLocalBinds GhcPs -> Bool f (L _ (HsValBinds _ (ValBinds _ bag _))) = isEmptyBag bag f (L _ (HsIPBinds _ (IPBinds _ l))) = null l f _ = False whereSpan = case l of UnhelpfulSpan s -> UnhelpfulSpan s RealSrcSpan s -> let end = realSrcSpanEnd s start = mkRealSrcLoc (srcSpanFile s) (srcLocLine end) (srcLocCol end - 5) in RealSrcSpan (mkRealSrcSpan start end) hints gen (Pattern l t pats o@(GRHSs _ (unsnoc -> Just (gs, L _ (GRHS _ [test] bod))) binds)) | unsafePrettyPrint test == "True" = let otherwise_ = noLoc $ BodyStmt noExtField (strToVar "otherwise") noSyntaxExpr noSyntaxExpr in [gen "Use otherwise" (Pattern l t pats o{grhssGRHSs = gs ++ [noLoc (GRHS noExtField [otherwise_] bod)]}) [Replace Expr (toSS test) [] "otherwise"]] hints _ _ = [] asGuards :: LHsExpr GhcPs -> [(LHsExpr GhcPs, LHsExpr GhcPs)] asGuards (L _ (HsPar _ x)) = asGuards x asGuards (L _ (HsIf _ _ a b c)) = (a, b) : asGuards c asGuards x = [(strToVar "otherwise", x)] data Pattern = Pattern SrcSpan R.RType [LPat GhcPs] (GRHSs GhcPs (LHsExpr GhcPs)) -- Invariant: Number of patterns may not change asPattern :: LHsDecl GhcPs -> [(Pattern, String -> Pattern -> [Refactoring R.SrcSpan] -> Idea)] asPattern (L loc x) = concatMap decl (universeBi x) where decl :: HsBind GhcPs -> [(Pattern, String -> Pattern -> [Refactoring R.SrcSpan] -> Idea)] decl o@(PatBind _ pat rhs _) = [(Pattern loc Bind [pat] rhs, \msg (Pattern _ _ [pat] rhs) rs -> suggest msg (L loc o :: LHsBind GhcPs) (noLoc (PatBind noExtField pat rhs ([], [])) :: LHsBind GhcPs) rs)] decl (FunBind _ _ (MG _ (L _ xs) _) _ _) = map match xs decl _ = [] match :: LMatch GhcPs (LHsExpr GhcPs) -> (Pattern, String -> Pattern -> [Refactoring R.SrcSpan] -> Idea) match o@(L loc (Match _ ctx pats grhss)) = (Pattern loc R.Match pats grhss, \msg (Pattern _ _ pats grhss) rs -> suggest msg o (noLoc (Match noExtField ctx pats grhss) :: LMatch GhcPs (LHsExpr GhcPs)) rs) match _ = undefined -- {-# COMPLETE L #-} -- First Bool is if 'Strict' is a language extension. Second Bool is -- if this pattern in this context is going to be evaluated strictly. patHint :: Bool -> Bool -> LPat GhcPs -> [Idea] patHint _ _ o@(L _ (ConPatIn name (PrefixCon args))) | length args >= 3 && all isPWildcard args = let rec_fields = HsRecFields [] Nothing :: HsRecFields GhcPs (LPat GhcPs) new = noLoc $ ConPatIn name (RecCon rec_fields) :: LPat GhcPs in [suggest "Use record patterns" o new [Replace R.Pattern (toSS o) [] (unsafePrettyPrint new)]] patHint _ _ o@(L _ (VarPat _ (L _ name))) | occNameString (rdrNameOcc name) == "otherwise" = [warn "Used otherwise as a pattern" o (noLoc (WildPat noExtField) :: LPat GhcPs) []] patHint lang strict o@(L _ (BangPat _ pat@(L _ x))) | strict, f x = [warn "Redundant bang pattern" o (noLoc x :: LPat GhcPs) [r]] where f :: Pat GhcPs -> Bool f (ParPat _ (L _ x)) = f x f (AsPat _ _ (L _ x)) = f x f LitPat {} = True f NPat {} = True f ConPatIn {} = True f TuplePat {} = True f ListPat {} = True f (SigPat _ (L _ p) _) = f p f _ = False r = Replace R.Pattern (toSS o) [("x", toSS pat)] "x" patHint False _ o@(L _ (LazyPat _ pat@(L _ x))) | f x = [warn "Redundant irrefutable pattern" o (noLoc x :: LPat GhcPs) [r]] where f :: Pat GhcPs -> Bool f (ParPat _ (L _ x)) = f x f (AsPat _ _ (L _ x)) = f x f WildPat{} = True f VarPat{} = True f _ = False r = Replace R.Pattern (toSS o) [("x", toSS pat)] "x" patHint _ _ o@(L _ (AsPat _ v (L _ (WildPat _)))) = [warn "Redundant as-pattern" o v []] patHint _ _ _ = [] expHint :: LHsExpr GhcPs -> [Idea] -- Note the 'FromSource' in these equations (don't warn on generated match groups). expHint o@(L _ (HsCase _ _ (MG _ (L _ [L _ (Match _ CaseAlt [L _ (WildPat _)] (GRHSs _ [L _ (GRHS _ [] e)] (L _ (EmptyLocalBinds _)))) ]) FromSource ))) = [suggest "Redundant case" o e [r]] where r = Replace Expr (toSS o) [("x", toSS e)] "x" expHint o@(L _ (HsCase _ (L _ (HsVar _ (L _ x))) (MG _ (L _ [L _ (Match _ CaseAlt [L _ (VarPat _ (L _ y))] (GRHSs _ [L _ (GRHS _ [] e)] (L _ (EmptyLocalBinds _)))) ]) FromSource ))) | occNameStr x == occNameStr y = [suggest "Redundant case" o e [r]] where r = Replace Expr (toSS o) [("x", toSS e)] "x" expHint _ = [] hlint-3.1.6/src/Hint/NewType.hs0000644000000000000000000001425013656755416014471 0ustar0000000000000000{-# LANGUAGE NamedFieldPuns #-} {- Suggest newtype instead of data for type declarations that have only one field. Don't suggest newtype for existentially quantified data types because it is not valid. data Foo = Foo Int -- newtype Foo = Foo Int @NoRefactor: refactoring for "Use newtype" is not implemented data Foo = Foo Int deriving (Show, Eq) -- newtype Foo = Foo Int deriving (Show, Eq) @NoRefactor data Foo = Foo { field :: Int } deriving Show -- newtype Foo = Foo { field :: Int } deriving Show @NoRefactor data Foo a b = Foo a -- newtype Foo a b = Foo a @NoRefactor data Foo = Foo { field1, field2 :: Int} data S a = forall b . Show b => S b @NoRefactor: apply-refact 0.6 requires RankNTypes pragma {-# LANGUAGE RankNTypes #-}; data S a = forall b . Show b => S b {-# LANGUAGE RankNTypes #-}; data Foo = Foo (forall a. a) -- newtype Foo = Foo (forall a. a) @NoRefactor data Color a = Red a | Green a | Blue a data Pair a b = Pair a b data Foo = Bar data Foo a = Eq a => MkFoo a data Foo a = () => Foo a -- newtype Foo a = Foo a @NoRefactor data X = Y {-# UNPACK #-} !Int -- newtype X = Y Int @NoRefactor data A = A {b :: !C} -- newtype A = A {b :: C} @NoRefactor data A = A Int# @NoRefactor {-# LANGUAGE UnboxedTuples #-}; data WithAnn x = WithAnn (# Ann, x #) {-# LANGUAGE UnboxedTuples #-}; data WithAnn x = WithAnn {getWithAnn :: (# Ann, x #)} data A = A () -- newtype A = A () @NoRefactor newtype Foo = Foo Int deriving (Show, Eq) -- newtype Foo = Foo { getFoo :: Int } deriving (Show, Eq) -- newtype Foo = Foo Int deriving stock Show -} module Hint.NewType (newtypeHint) where import Hint.Type (Idea, DeclHint, Note(DecreasesLaziness), ideaNote, ignoreNoSuggestion, suggestN) import Data.List (isSuffixOf) import GHC.Hs.Decls import GHC.Hs import Outputable import SrcLoc newtypeHint :: DeclHint newtypeHint _ _ x = newtypeHintDecl x ++ newTypeDerivingStrategiesHintDecl x newtypeHintDecl :: LHsDecl GhcPs -> [Idea] newtypeHintDecl old | Just WarnNewtype{newDecl, insideType} <- singleSimpleField old = [(suggestN "Use newtype instead of data" old newDecl) {ideaNote = [DecreasesLaziness | warnBang insideType]}] newtypeHintDecl _ = [] newTypeDerivingStrategiesHintDecl :: LHsDecl GhcPs -> [Idea] newTypeDerivingStrategiesHintDecl decl@(L _ (TyClD _ (DataDecl _ _ _ _ dataDef))) = [ignoreNoSuggestion "Use DerivingStrategies" decl | not $ isData dataDef, not $ hasAllStrategies dataDef] newTypeDerivingStrategiesHintDecl _ = [] hasAllStrategies :: HsDataDefn GhcPs -> Bool hasAllStrategies (HsDataDefn _ NewType _ _ _ _ (L _ xs)) = all hasStrategyClause xs hasAllStrategies _ = False isData :: HsDataDefn GhcPs -> Bool isData (HsDataDefn _ NewType _ _ _ _ _) = False isData (HsDataDefn _ DataType _ _ _ _ _) = True isData _ = False hasStrategyClause :: LHsDerivingClause GhcPs -> Bool hasStrategyClause (L _ (HsDerivingClause _ (Just _) _)) = True hasStrategyClause _ = False data WarnNewtype = WarnNewtype { newDecl :: LHsDecl GhcPs , insideType :: HsType GhcPs } -- | Given a declaration, returns the suggested \"newtype\"ized declaration following these guidelines: -- * Types ending in a \"#\" are __ignored__, because they are usually unboxed primitives - @data X = X Int#@ -- * @ExistentialQuantification@ stuff is __ignored__ - @data X = forall t. X t@ -- * Constructors with (nonempty) constraints are __ignored__ - @data X a = (Eq a) => X a@ -- * Single field constructors get newtyped - @data X = X Int@ -> @newtype X = X Int@ -- * Single record field constructors get newtyped - @data X = X {getX :: Int}@ -> @newtype X = X {getX :: Int}@ -- * All other declarations are ignored. singleSimpleField :: LHsDecl GhcPs -> Maybe WarnNewtype singleSimpleField (L loc (TyClD ext decl@(DataDecl _ _ _ _ dataDef@(HsDataDefn _ DataType _ _ _ [L _ constructor] _)))) | Just inType <- simpleCons constructor = Just WarnNewtype { newDecl = L loc $ TyClD ext decl {tcdDataDefn = dataDef { dd_ND = NewType , dd_cons = map (\(L consloc x) -> L consloc $ dropConsBang x) $ dd_cons dataDef }} , insideType = inType } singleSimpleField _ = Nothing -- | Checks whether its argument is a \"simple constructor\" (see criteria in 'singleSimpleFieldNew') -- returning the type inside the constructor if it is. This is needed for strictness analysis. simpleCons :: ConDecl GhcPs -> Maybe (HsType GhcPs) simpleCons (ConDeclH98 _ _ _ [] context (PrefixCon [L _ inType]) _) | emptyOrNoContext context , not $ isUnboxedTuple inType , not $ isHashy inType = Just inType simpleCons (ConDeclH98 _ _ _ [] context (RecCon (L _ [L _ (ConDeclField _ [_] (L _ inType) _)])) _) | emptyOrNoContext context , not $ isUnboxedTuple inType , not $ isHashy inType = Just inType simpleCons _ = Nothing isHashy :: HsType GhcPs -> Bool isHashy (HsTyVar _ _ identifier) = "#" `isSuffixOf` showSDocUnsafe (ppr identifier) isHashy _ = False warnBang :: HsType GhcPs -> Bool warnBang (HsBangTy _ (HsSrcBang _ _ SrcStrict) _) = False warnBang _ = True emptyOrNoContext :: Maybe (LHsContext GhcPs) -> Bool emptyOrNoContext Nothing = True emptyOrNoContext (Just (L _ [])) = True emptyOrNoContext _ = False -- | The \"Bang\" here refers to 'HsSrcBang', which notably also includes @UNPACK@ pragmas! dropConsBang :: ConDecl GhcPs -> ConDecl GhcPs dropConsBang decl@(ConDeclH98 _ _ _ _ _ (PrefixCon fields) _) = decl {con_args = PrefixCon $ map getBangType fields} dropConsBang decl@(ConDeclH98 _ _ _ _ _ (RecCon (L recloc conDeclFields)) _) = decl {con_args = RecCon $ cL recloc $ removeUnpacksRecords conDeclFields} where removeUnpacksRecords :: [LConDeclField GhcPs] -> [LConDeclField GhcPs] removeUnpacksRecords = map (\(L conDeclFieldLoc x) -> L conDeclFieldLoc $ removeConDeclFieldUnpacks x) removeConDeclFieldUnpacks :: ConDeclField GhcPs -> ConDeclField GhcPs removeConDeclFieldUnpacks conDeclField@(ConDeclField _ _ fieldType _) = conDeclField {cd_fld_type = getBangType fieldType} removeConDeclFieldUnpacks x = x dropConsBang x = x isUnboxedTuple :: HsType GhcPs -> Bool isUnboxedTuple (HsTupleTy _ HsUnboxedTuple _) = True isUnboxedTuple _ = False hlint-3.1.6/src/Hint/Naming.hs0000644000000000000000000001146613671470061014300 0ustar0000000000000000{-# LANGUAGE ViewPatterns #-} {-# LANGUAGE FlexibleContexts #-} {- Suggest the use of camelCase Only permit: _*[A-Za-z]*_*#*'* Apply this to things that would get exported by default only Also allow prop_ as it's a standard QuickCheck idiom Also allow case_ as it's a standard test-framework-th idiom Also allow test_ as it's a standard tasty-th idiom Also allow numbers separated by _ Also don't suggest anything mentioned elsewhere in the module Don't suggest for FFI, since they match their C names data Yes = Foo | Bar'Test data Yes = Bar | Test_Bar -- data Yes = Bar | TestBar @NoRefactor data No = a :::: b data Yes = Foo {bar_cap :: Int} data No = FOO | BarBAR | BarBBar yes_foo = yes_foo + yes_foo -- yesFoo = ... @NoRefactor yes_fooPattern Nothing = 0 -- yesFooPattern Nothing = ... @NoRefactor no = 1 where yes_foo = 2 a -== b = 1 myTest = 1; my_test = 1 semiring'laws = 1 data Yes = FOO_A | Foo_B -- data Yes = FOO_A | FooB @NoRefactor case_foo = 1 test_foo = 1 cast_foo = 1 -- castFoo = ... @NoRefactor replicateM_ = 1 _foo__ = 1 section_1_1 = 1 runMutator# = 1 @NoRefactor foreign import ccall hexml_node_child :: IO () -} module Hint.Naming(namingHint) where import Hint.Type (Idea,DeclHint,suggest,ghcModule) import Data.Generics.Uniplate.DataOnly import Data.List.Extra (nubOrd, isPrefixOf) import Data.Data import Data.Char import Data.Maybe import qualified Data.Set as Set import BasicTypes import FastString import GHC.Hs.Decls import GHC.Hs.Extension import GHC.Hs import OccName import SrcLoc import Language.Haskell.GhclibParserEx.GHC.Hs.Decls import Language.Haskell.GhclibParserEx.GHC.Utils.Outputable import GHC.Util namingHint :: DeclHint namingHint _ modu = naming $ Set.fromList $ concatMap getNames $ hsmodDecls $ unLoc (ghcModule modu) naming :: Set.Set String -> LHsDecl GhcPs -> [Idea] naming seen originalDecl = [ suggest "Use camelCase" (shorten originalDecl) (shorten replacedDecl) [ -- https://github.com/mpickering/apply-refact/issues/39 ] | not $ null suggestedNames ] where suggestedNames = [ (originalName, suggestedName) | not $ isForD originalDecl , originalName <- nubOrd $ getNames originalDecl , Just suggestedName <- [suggestName originalName] , not $ suggestedName `Set.member` seen ] replacedDecl = replaceNames suggestedNames originalDecl shorten :: LHsDecl GhcPs -> LHsDecl GhcPs shorten (L locDecl (ValD ttg0 bind@(FunBind _ _ matchGroup@(MG _ (L locMatches matches) FromSource) _ _))) = L locDecl (ValD ttg0 bind {fun_matches = matchGroup {mg_alts = L locMatches $ map shortenMatch matches}}) shorten (L locDecl (ValD ttg0 bind@(PatBind _ _ grhss@(GRHSs _ rhss _) _))) = L locDecl (ValD ttg0 bind {pat_rhs = grhss {grhssGRHSs = map shortenLGRHS rhss}}) shorten x = x shortenMatch :: LMatch GhcPs (LHsExpr GhcPs) -> LMatch GhcPs (LHsExpr GhcPs) shortenMatch (L locMatch match@(Match _ _ _ grhss@(GRHSs _ rhss _))) = L locMatch match {m_grhss = grhss {grhssGRHSs = map shortenLGRHS rhss}} shortenMatch x = x shortenLGRHS :: LGRHS GhcPs (LHsExpr GhcPs) -> LGRHS GhcPs (LHsExpr GhcPs) shortenLGRHS (L locGRHS (GRHS ttg0 guards (L locExpr _))) = L locGRHS (GRHS ttg0 guards (cL locExpr dots)) where dots :: HsExpr GhcPs dots = HsLit noExtField (HsString (SourceText "...") (mkFastString "...")) shortenLGRHS x = x getNames :: LHsDecl GhcPs -> [String] getNames decl = maybeToList (declName decl) ++ getConstructorNames (unLoc decl) getConstructorNames :: HsDecl GhcPs -> [String] getConstructorNames (TyClD _ (DataDecl _ _ _ _ (HsDataDefn _ _ _ _ _ cons _))) = concatMap (map unsafePrettyPrint . getConNames . unLoc) cons getConstructorNames _ = [] isSym :: String -> Bool isSym (x:_) = not $ isAlpha x || x `elem` "_'" isSym _ = False suggestName :: String -> Maybe String suggestName original | isSym original || good || not (any isLower original) || any isDigit original || any (`isPrefixOf` original) ["prop_","case_","unit_","test_","spec_","scprop_","hprop_"] = Nothing | otherwise = Just $ f original where good = all isAlphaNum $ drp '_' $ drp '#' $ filter (/= '\'') $ reverse $ drp '_' original drp x = dropWhile (== x) f xs = us ++ g ys where (us,ys) = span (== '_') xs g x | x `elem` ["_","'","_'"] = x g (a:x:xs) | a `elem` "_'" && isAlphaNum x = toUpper x : g xs g (x:xs) | isAlphaNum x = x : g xs | otherwise = g xs g [] = [] replaceNames :: Data a => [(String, String)] -> a -> a replaceNames rep = transformBi replace where replace :: OccName -> OccName replace (unsafePrettyPrint -> name) = mkOccName srcDataName $ fromMaybe name $ lookup name rep hlint-3.1.6/src/Hint/Monad.hs0000644000000000000000000003040713671470061014121 0ustar0000000000000000{-# LANGUAGE LambdaCase, ViewPatterns, PatternGuards, FlexibleContexts #-} {- Find and match: mapM, foldM, forM, replicateM, sequence, zipWithM not at the last line of a do statement, or to the left of >> Use let x = y instead of x <- return y, unless x is contained within y, or bound more than once in that do block. yes = do mapM print a; return b -- mapM_ print a yes = do _ <- mapM print a; return b -- mapM_ print a no = mapM print a no = do foo ; mapM print a yes = do (bar+foo) -- no = do bar ; foo yes = do bar; a <- foo; return a -- do bar; foo no = do bar; a <- foo; return b yes = do x <- bar; x -- do join bar no = do x <- bar; x; x yes = do x <- bar; return (f x) -- do f <$> bar yes = do x <- bar; return $ f x -- do f <$> bar yes = do x <- bar; pure $ f x -- do f <$> bar yes = do x <- bar; return $ f (g x) -- do f . g <$> bar yes = do x <- bar; return (f $ g x) -- do f . g <$> bar yes = do x <- bar $ baz; return (f $ g x) no = do x <- bar; return (f x x) {-# LANGUAGE RecursiveDo #-}; no = mdo hook <- mkTrigger pat (act >> rmHook hook) ; return hook yes = do x <- return y; foo x -- @Suggestion let x = y yes = do x <- return $ y + z; foo x -- let x = y + z no = do x <- return x; foo x no = do x <- return y; x <- return y; foo x yes = do forM files $ \x -> return (); return () -- forM_ files $ \x -> return () yes = do if a then forM x y else return (); return 12 -- forM_ x y yes = do case a of {_ -> forM x y; x:xs -> foo xs}; return () -- forM_ x y foldM_ f a xs = foldM f a xs >> return () folder f a xs = foldM f a xs >> return () -- foldM_ f a xs folder f a xs = foldM f a xs >>= \_ -> return () -- foldM_ f a xs yes = mapM async ds >>= mapM wait >> return () -- mapM async ds >>= mapM_ wait main = "wait" ~> do f a $ sleep 10 {-# LANGUAGE BlockArguments #-}; main = print do 17 + 25 {-# LANGUAGE BlockArguments #-}; main = print do 17 -- main = f $ do g a $ sleep 10 -- main = do f a $ sleep 10 -- @Ignore main = do foo x; return 3; bar z -- do foo x; bar z main = void $ forM_ f xs -- forM_ f xs main = void $ forM f xs -- void $ forM_ f xs main = do _ <- forM_ f xs; bar -- forM_ f xs main = do bar; forM_ f xs; return () -- do bar; forM_ f xs main = do a; when b c; return () -- do a; when b c bar = 1 * do {\x -> x+x} + y issue978 = do \ print "x" \ if False then main else do \ return () -} module Hint.Monad(monadHint) where import Hint.Type(DeclHint,Idea(..),Severity(..),ideaNote,warn,ideaRemove,toSS,suggest,Note(Note)) import GHC.Hs import SrcLoc import BasicTypes import TcEvidence import RdrName import OccName import Bag import Language.Haskell.GhclibParserEx.GHC.Hs.Pat import Language.Haskell.GhclibParserEx.GHC.Hs.Expr import Language.Haskell.GhclibParserEx.GHC.Utils.Outputable import Language.Haskell.GhclibParserEx.GHC.Types.Name.Reader import GHC.Util import Data.Generics.Uniplate.DataOnly import Data.Tuple.Extra import Data.Maybe import Data.List.Extra import Refact.Types hiding (Match) import qualified Refact.Types as R badFuncs :: [String] badFuncs = ["mapM","foldM","forM","replicateM","sequence","zipWithM","traverse","for","sequenceA"] unitFuncs :: [String] unitFuncs = ["when","unless","void"] monadHint :: DeclHint monadHint _ _ d = concatMap (f Nothing Nothing) $ childrenBi d where decl = declName d f parentDo parentExpr x = monadExp decl parentDo parentExpr x ++ concat [f (if isHsDo x then Just x else parentDo) (Just (i, x)) c | (i, c) <- zipFrom 0 $ children x] isHsDo (L _ HsDo{}) = True isHsDo _ = False -- | Call with the name of the declaration, -- the nearest enclosing `do` expression -- the nearest enclosing expression -- the expression of interest monadExp :: Maybe String -> Maybe (LHsExpr GhcPs) -> Maybe (Int, LHsExpr GhcPs) -> LHsExpr GhcPs -> [Idea] monadExp decl parentDo parentExpr x = case x of (view -> App2 op x1 x2) | isTag ">>" op -> f x1 (view -> App2 op x1 (view -> LamConst1 _)) | isTag ">>=" op -> f x1 (L l (HsApp _ op x)) | isTag "void" op -> seenVoid (cL l . HsApp noExtField op) x (L l (OpApp _ op dol x)) | isTag "void" op, isDol dol -> seenVoid (cL l . OpApp noExtField op dol) x (L loc (HsDo _ ctx (L loc2 [L loc3 (BodyStmt _ y _ _ )]))) -> let doOrMDo = case ctx of MDoExpr -> "mdo"; _ -> "do" in [ ideaRemove Ignore ("Redundant " ++ doOrMDo) (doSpan doOrMDo loc) doOrMDo [Replace Expr (toSS x) [("y", toSS y)] "y"] | not $ doAsBrackets parentExpr y , not $ doAsAvoidingIndentation parentDo x ] (L loc (HsDo _ DoExpr (L _ xs))) -> monadSteps (cL loc . HsDo noExtField DoExpr . noLoc) xs ++ [suggest "Use let" from to [r] | (from, to, r) <- monadLet xs] ++ concat [f x | (L _ (BodyStmt _ x _ _)) <- dropEnd1 xs] ++ concat [f x | (L _ (BindStmt _ (LL _ WildPat{}) x _ _)) <- dropEnd1 xs] _ -> [] where f = monadNoResult (fromMaybe "" decl) id seenVoid wrap x = monadNoResult (fromMaybe "" decl) wrap x ++ [warn "Redundant void" (wrap x) x [] | returnsUnit x] doSpan doOrMDo = \case UnhelpfulSpan s -> UnhelpfulSpan s RealSrcSpan s -> let start = realSrcSpanStart s end = mkRealSrcLoc (srcSpanFile s) (srcLocLine start) (srcLocCol start + length doOrMDo) in RealSrcSpan (mkRealSrcSpan start end) -- Sometimes people write 'a * do a + b', to avoid brackets, -- or using BlockArguments they can write 'a do a b', -- or using indentation a * do {\b -> c} * d -- Return True if they are using do as brackets doAsBrackets :: Maybe (Int, LHsExpr GhcPs) -> LHsExpr GhcPs -> Bool doAsBrackets (Just (2, L _ (OpApp _ _ op _ ))) _ | isDol op = False -- not quite atomic, but close enough doAsBrackets (Just (i, o)) x = needBracket i o x doAsBrackets Nothing x = False -- Sometimes people write do, to avoid identation, see -- https://github.com/ndmitchell/hlint/issues/978 -- Return True if they are using do as avoiding identation doAsAvoidingIndentation :: Maybe (LHsExpr GhcPs) -> LHsExpr GhcPs -> Bool doAsAvoidingIndentation (Just (L _ (HsDo _ _ (L (RealSrcSpan a) _)))) (L _ (HsDo _ _ (L (RealSrcSpan b) _))) = srcSpanStartCol a == srcSpanStartCol b doAsAvoidingIndentation parent self = False returnsUnit :: LHsExpr GhcPs -> Bool returnsUnit (L _ (HsPar _ x)) = returnsUnit x returnsUnit (L _ (HsApp _ x _)) = returnsUnit x returnsUnit (L _ (OpApp _ x op _)) | isDol op = returnsUnit x returnsUnit (L _ (HsVar _ (L _ x))) = occNameStr x `elem` map (++ "_") badFuncs ++ unitFuncs returnsUnit _ = False -- See through HsPar, and down HsIf/HsCase, return the name to use in -- the hint, and the revised expression. monadNoResult :: String -> (LHsExpr GhcPs -> LHsExpr GhcPs) -> LHsExpr GhcPs -> [Idea] monadNoResult inside wrap (L l (HsPar _ x)) = monadNoResult inside (wrap . cL l . HsPar noExtField) x monadNoResult inside wrap (L l (HsApp _ x y)) = monadNoResult inside (\x -> wrap $ cL l (HsApp noExtField x y)) x monadNoResult inside wrap (L l (OpApp _ x tag@(L _ (HsVar _ (L _ op))) y)) | isDol tag = monadNoResult inside (\x -> wrap $ cL l (OpApp noExtField x tag y)) x | occNameStr op == ">>=" = monadNoResult inside (wrap . cL l . OpApp noExtField x tag) y monadNoResult inside wrap x | x2 : _ <- filter (`isTag` x) badFuncs , let x3 = x2 ++ "_" = [warn ("Use " ++ x3) (wrap x) (wrap $ strToVar x3) [Replace Expr (toSS x) [] x3] | inside /= x3] monadNoResult inside wrap (replaceBranches -> (bs, rewrap)) = map (\x -> x{ideaNote=nubOrd $ Note "May require adding void to other branches" : ideaNote x}) $ concat [monadNoResult inside id b | b <- bs] monadStep :: ([ExprLStmt GhcPs] -> LHsExpr GhcPs) -> [ExprLStmt GhcPs] -> [Idea] -- Rewrite 'do return x; $2' as 'do $2'. monadStep wrap os@(o@(L _ (BodyStmt _ (fromRet -> Just (ret, _)) _ _ )) : xs@(_:_)) = [warn ("Redundant " ++ ret) (wrap os) (wrap xs) [Delete Stmt (toSS o)]] -- Rewrite 'do a <- $1; return a' as 'do $1'. monadStep wrap o@[ g@(L _ (BindStmt _ (LL _ (VarPat _ (L _ p))) x _ _ )) , q@(L _ (BodyStmt _ (fromRet -> Just (ret, L _ (HsVar _ (L _ v)))) _ _))] | occNameStr p == occNameStr v = [warn ("Redundant " ++ ret) (wrap o) (wrap [noLoc $ BodyStmt noExtField x noSyntaxExpr noSyntaxExpr]) [Replace Stmt (toSS g) [("x", toSS x)] "x", Delete Stmt (toSS q)]] -- Suggest to use join. Rewrite 'do x <- $1; x; $2' as 'do join $1; $2'. monadStep wrap o@(g@(L _ (BindStmt _ (view -> PVar_ p) x _ _)):q@(L _ (BodyStmt _ (view -> Var_ v) _ _)):xs) | p == v && v `notElem` varss xs = let app = noLoc $ HsApp noExtField (strToVar "join") x body = noLoc $ BodyStmt noExtField (rebracket1 app) noSyntaxExpr noSyntaxExpr stmts = body : xs in [warn "Use join" (wrap o) (wrap stmts) r] where r = [Replace Stmt (toSS g) [("x", toSS x)] "join x", Delete Stmt (toSS q)] -- Redundant variable capture. Rewrite 'do _ <- ; $1' as -- 'do ; $1'. monadStep wrap (o@(L loc (BindStmt _ p x _ _)) : rest) | isPWildcard p, returnsUnit x = let body = cL loc $ BodyStmt noExtField x noSyntaxExpr noSyntaxExpr :: ExprLStmt GhcPs in [warn "Redundant variable capture" o body []] -- Redundant unit return : 'do ; return ()'. monadStep wrap o@[ L _ (BodyStmt _ x _ _) , L _ (BodyStmt _ (fromRet -> Just (ret, L _ (HsVar _ (L _ unit)))) _ _)] | returnsUnit x, occNameStr unit == "()" = [warn ("Redundant " ++ ret) (wrap o) (wrap $ take 1 o) []] -- Rewrite 'do x <- $1; return $ f $ g x' as 'f . g <$> x' monadStep wrap o@[g@(L _ (BindStmt _ (view -> PVar_ u) x _ _)) , q@(L _ (BodyStmt _ (fromApplies -> (ret:f:fs, view -> Var_ v)) _ _))] | isReturn ret, notDol x, u == v, length fs < 3, all isSimple (f : fs), v `notElem` vars (f : fs) = [warn "Use <$>" (wrap o) (wrap [noLoc $ BodyStmt noExtField (noLoc $ OpApp noExtField (foldl' (\acc e -> noLoc $ OpApp noExtField acc (strToVar ".") e) f fs) (strToVar "<$>") x) noSyntaxExpr noSyntaxExpr]) [Replace Stmt (toSS g) (("x", toSS x):zip vs (toSS <$> f:fs)) (intercalate " . " (take (length fs + 1) vs) ++ " <$> x"), Delete Stmt (toSS q)]] where isSimple (fromApps -> xs) = all isAtom (x : xs) vs = ('f':) . show <$> [0..] notDol :: LHsExpr GhcPs -> Bool notDol (L _ (OpApp _ _ op _)) = not $ isDol op notDol _ = True monadStep _ _ = [] -- Suggest removing a return monadSteps :: ([ExprLStmt GhcPs] -> LHsExpr GhcPs) -> [ExprLStmt GhcPs] -> [Idea] monadSteps wrap (x : xs) = monadStep wrap (x : xs) ++ monadSteps (wrap . (x :)) xs monadSteps _ _ = [] -- | Rewrite 'do ...; x <- return y; ...' as 'do ...; let x = y; ...'. monadLet :: [ExprLStmt GhcPs] -> [(ExprLStmt GhcPs, ExprLStmt GhcPs, Refactoring R.SrcSpan)] monadLet xs = mapMaybe mkLet xs where vs = concatMap pvars [p | (L _ (BindStmt _ p _ _ _)) <- xs] mkLet :: ExprLStmt GhcPs -> Maybe (ExprLStmt GhcPs, ExprLStmt GhcPs, Refactoring R.SrcSpan) mkLet x@(L _ (BindStmt _ v@(view -> PVar_ p) (fromRet -> Just (_, y)) _ _ )) | p `notElem` vars y, p `notElem` delete p vs = Just (x, template p y, refact) where refact = Replace Stmt (toSS x) [("lhs", toSS v), ("rhs", toSS y)] (unsafePrettyPrint $ template "lhs" (strToVar "rhs")) mkLet _ = Nothing template :: String -> LHsExpr GhcPs -> ExprLStmt GhcPs template lhs rhs = let p = noLoc $ mkRdrUnqual (mkVarOcc lhs) grhs = noLoc (GRHS noExtField [] rhs) grhss = GRHSs noExtField [grhs] (noLoc (EmptyLocalBinds noExtField)) match = noLoc $ Match noExtField (FunRhs p Prefix NoSrcStrict) [] grhss fb = noLoc $ FunBind noExtField p (MG noExtField (noLoc [match]) Generated) WpHole [] binds = unitBag fb valBinds = ValBinds noExtField binds [] localBinds = noLoc $ HsValBinds noExtField valBinds in noLoc $ LetStmt noExtField localBinds fromApplies :: LHsExpr GhcPs -> ([LHsExpr GhcPs], LHsExpr GhcPs) fromApplies (L _ (HsApp _ f x)) = first (f:) $ fromApplies (fromParen x) fromApplies (L _ (OpApp _ f (isDol -> True) x)) = first (f:) $ fromApplies x fromApplies x = ([], x) fromRet :: LHsExpr GhcPs -> Maybe (String, LHsExpr GhcPs) fromRet (L _ (HsPar _ x)) = fromRet x fromRet (L _ (OpApp _ x (L _ (HsVar _ (L _ y))) z)) | occNameStr y == "$" = fromRet $ noLoc (HsApp noExtField x z) fromRet (L _ (HsApp _ x y)) | isReturn x = Just (unsafePrettyPrint x, y) fromRet _ = Nothing hlint-3.1.6/src/Hint/Match.hs0000644000000000000000000002676213674736420014137 0ustar0000000000000000{-# LANGUAGE RecordWildCards, NamedFieldPuns, TupleSections #-} {-# LANGUAGE PatternGuards, ViewPatterns, FlexibleContexts #-} {- The matching does a fairly simple unification between the two terms, treating any single letter variable on the left as a free variable. After the matching we substitute, transform and check the side conditions. We also "see through" both ($) and (.) functions on the right. TRANSFORM PATTERNS _noParen_ - don't bracket this particular item SIDE CONDITIONS (&&), (||), not - boolean connectives isAtom x - does x never need brackets isFoo x - is the root constructor of x a "Foo" notEq x y - are x and y not equal notIn xs ys - are all x variables not in ys expressions noTypeCheck, noQuickCheck - no semantics, a hint for testing only ($) AND (.) We see through ($)/(.) by expanding it if nothing else matches. We also see through (.) by translating rules that have (.) equivalents to separate rules. For example: concat (map f x) ==> concatMap f x -- we spot both these rules can eta reduce with respect to x concat . map f ==> concatMap f -- we use the associativity of (.) to add concat . map f . x ==> concatMap f . x -- currently 36 of 169 rules have (.) equivalents We see through (.) if the RHS is dull using id, e.g. not (not x) ==> x not . not ==> id not . not . x ==> x -} module Hint.Match(readMatch) where import Hint.Type (ModuleEx,Idea,idea,ideaNote,toSS) import Util import Timing import qualified Data.Set as Set import qualified Refact.Types as R import Control.Monad import Data.Tuple.Extra import Data.Maybe import Config.Type import Data.Generics.Uniplate.DataOnly import Bag import GHC.Hs import SrcLoc import BasicTypes import RdrName import OccName import Data.Data import GHC.Util import Language.Haskell.GhclibParserEx.GHC.Hs.Expr import Language.Haskell.GhclibParserEx.GHC.Hs.ExtendInstances import Language.Haskell.GhclibParserEx.GHC.Utils.Outputable import Language.Haskell.GhclibParserEx.GHC.Types.Name.Reader readMatch :: [HintRule] -> Scope -> ModuleEx -> LHsDecl GhcPs -> [Idea] readMatch settings = findIdeas (concatMap readRule settings) readRule :: HintRule -> [HintRule] readRule m@HintRule{ hintRuleLHS=(stripLocs . unextendInstances -> hintRuleLHS) , hintRuleRHS=(stripLocs . unextendInstances -> hintRuleRHS) , hintRuleSide=((stripLocs . unextendInstances <$>) -> hintRuleSide) } = (:) m{ hintRuleLHS=extendInstances hintRuleLHS , hintRuleRHS=extendInstances hintRuleRHS , hintRuleSide=extendInstances <$> hintRuleSide } $ do (l, v1) <- dotVersion hintRuleLHS (r, v2) <- dotVersion hintRuleRHS guard $ v1 == v2 && not (null l) && (length l > 1 || length r > 1) && Set.notMember v1 (Set.map occNameString (freeVars $ maybeToList hintRuleSide ++ l ++ r)) if not (null r) then [ m{ hintRuleLHS=extendInstances (dotApps l), hintRuleRHS=extendInstances (dotApps r), hintRuleSide=extendInstances <$> hintRuleSide } , m{ hintRuleLHS=extendInstances (dotApps (l ++ [strToVar v1])), hintRuleRHS=extendInstances (dotApps (r ++ [strToVar v1])), hintRuleSide=extendInstances <$> hintRuleSide } ] else if length l > 1 then [ m{ hintRuleLHS=extendInstances (dotApps l), hintRuleRHS=extendInstances (strToVar "id"), hintRuleSide=extendInstances <$> hintRuleSide } , m{ hintRuleLHS=extendInstances (dotApps (l++[strToVar v1])), hintRuleRHS=extendInstances (strToVar v1), hintRuleSide=extendInstances <$> hintRuleSide}] else [] -- Find a dot version of this rule, return the sequence of app -- prefixes, and the var. dotVersion :: LHsExpr GhcPs -> [([LHsExpr GhcPs], String)] dotVersion (view -> Var_ v) | isUnifyVar v = [([], v)] dotVersion (L _ (HsApp _ ls rs)) = first (ls :) <$> dotVersion (fromParen rs) dotVersion (L l (OpApp _ x op y)) = -- In a GHC parse tree, raw sections aren't valid application terms. -- To be suitable as application terms, they must be enclosed in -- parentheses. -- If a == b then -- x is 'a', op is '==' and y is 'b' and, let lSec = addParen (cL l (SectionL noExtField x op)) -- (a == ) rSec = addParen (cL l (SectionR noExtField op y)) -- ( == b) in (first (lSec :) <$> dotVersion y) ++ (first (rSec :) <$> dotVersion x) -- [([(a ==)], b), ([(b == )], a])]. dotVersion _ = [] --------------------------------------------------------------------- -- PERFORM THE MATCHING findIdeas :: [HintRule] -> Scope -> ModuleEx -> LHsDecl GhcPs -> [Idea] findIdeas matches s _ decl = timed "Hint" "Match apply" $ forceList [ (idea (hintRuleSeverity m) (hintRuleName m) x y [r]){ideaNote=notes} | (name, expr) <- findDecls decl , (parent,x) <- universeParentExp expr , m <- matches, Just (y, tpl, notes, subst) <- [matchIdea s name m parent x] , let r = R.Replace R.Expr (toSS x) subst (unsafePrettyPrint tpl) ] -- | A list of root expressions, with their associated names findDecls :: LHsDecl GhcPs -> [(String, LHsExpr GhcPs)] findDecls x@(L _ (InstD _ (ClsInstD _ ClsInstDecl{cid_binds}))) = [(fromMaybe "" $ bindName xs, x) | xs <- bagToList cid_binds, x <- childrenBi xs] findDecls (L _ RuleD{}) = [] -- Often rules contain things that HLint would rewrite. findDecls x = map (fromMaybe "" $ declName x,) $ childrenBi x matchIdea :: Scope -> String -> HintRule -> Maybe (Int, LHsExpr GhcPs) -> LHsExpr GhcPs -> Maybe (LHsExpr GhcPs, LHsExpr GhcPs, [Note], [(String, R.SrcSpan)]) matchIdea sb declName HintRule{..} parent x = do let lhs = unextendInstances hintRuleLHS rhs = unextendInstances hintRuleRHS sa = hintRuleScope nm a b = scopeMatch (sa, a) (sb, b) (u, extra) <- unifyExp nm True lhs x u <- validSubst astEq u -- Need to check free vars before unqualification, but after subst -- (with 'e') need to unqualify before substitution (with 'res'). let rhs' | Just fun <- extra = rebracket1 $ noLoc (HsApp noExtField fun rhs) | otherwise = rhs (e, tpl) = substitute u rhs' noParens = [varToStr $ fromParen x | L _ (HsApp _ (varToStr -> "_noParen_") x) <- universe tpl] u <- pure (removeParens noParens u) let res = addBracketTy (addBracket parent $ performSpecial $ fst $ substitute u $ unqualify sa sb rhs') guard $ (freeVars e Set.\\ Set.filter (not . isUnifyVar . occNameString) (freeVars rhs')) `Set.isSubsetOf` freeVars x -- Check no unexpected new free variables. -- Check it isn't going to get broken by QuasiQuotes as per #483. If -- we have lambdas we might be moving, and QuasiQuotes, we might -- inadvertantly break free vars because quasi quotes don't show -- what free vars they make use of. guard $ not (any isLambda $ universe lhs) || not (any isQuasiQuote $ universe x) guard $ checkSide (unextendInstances <$> hintRuleSide) $ ("original", x) : ("result", res) : fromSubst u guard $ checkDefine declName parent rhs (u, tpl) <- pure $ if any ((== noSrcSpan) . getLoc . snd) (fromSubst u) then (mempty, res) else (u, tpl) tpl <- pure $ unqualify sa sb (performSpecial tpl) pure (res, tpl, hintRuleNotes, [(s, toSS pos) | (s, pos) <- fromSubst u, getLoc pos /= noSrcSpan]) --------------------------------------------------------------------- -- SIDE CONDITIONS checkSide :: Maybe (LHsExpr GhcPs) -> [(String, LHsExpr GhcPs)] -> Bool checkSide x bind = maybe True bool x where bool :: LHsExpr GhcPs -> Bool bool (L _ (OpApp _ x op y)) | varToStr op == "&&" = bool x && bool y | varToStr op == "||" = bool x || bool y | varToStr op == "==" = expr (fromParen1 x) `astEq` expr (fromParen1 y) bool (L _ (HsApp _ x y)) | varToStr x == "not" = not $ bool y bool (L _ (HsPar _ x)) = bool x bool (L _ (HsApp _ cond (sub -> y))) | 'i' : 's' : typ <- varToStr cond = isType typ y bool (L _ (HsApp _ (L _ (HsApp _ cond (sub -> x))) (sub -> y))) | varToStr cond == "notIn" = and [extendInstances (stripLocs x) `notElem` map (extendInstances . stripLocs) (universe y) | x <- list x, y <- list y] | varToStr cond == "notEq" = not (x `astEq` y) bool x | varToStr x == "noTypeCheck" = True bool x | varToStr x == "noQuickCheck" = True bool x = error $ "Hint.Match.checkSide, unknown side condition: " ++ unsafePrettyPrint x expr :: LHsExpr GhcPs -> LHsExpr GhcPs expr (L _ (HsApp _ (varToStr -> "subst") x)) = sub $ fromParen1 x expr x = x isType "Compare" x = True -- Just a hint for proof stuff isType "Atom" x = isAtom x isType "WHNF" x = isWHNF x isType "Wildcard" x = any isFieldPun (universeBi x) || any hasFieldsDotDot (universeBi x) isType "Nat" (asInt -> Just x) | x >= 0 = True isType "Pos" (asInt -> Just x) | x > 0 = True isType "Neg" (asInt -> Just x) | x < 0 = True isType "NegZero" (asInt -> Just x) | x <= 0 = True isType "LitInt" (L _ (HsLit _ HsInt{})) = True isType "LitInt" (L _ (HsOverLit _ (OverLit _ HsIntegral{} _))) = True isType "LitString" (L _ (HsLit _ HsString{})) = True isType "Var" (L _ HsVar{}) = True isType "App" (L _ HsApp{}) = True isType "InfixApp" (L _ x@OpApp{}) = True isType "Paren" (L _ x@HsPar{}) = True isType "Tuple" (L _ ExplicitTuple{}) = True isType typ (L _ x) = let top = showConstr (toConstr x) in typ == top asInt :: LHsExpr GhcPs -> Maybe Integer asInt (L _ (HsPar _ x)) = asInt x asInt (L _ (NegApp _ x _)) = negate <$> asInt x asInt (L _ (HsLit _ (HsInt _ (IL _ neg x)) )) = Just $ if neg then -x else x asInt (L _ (HsOverLit _ (OverLit _ (HsIntegral (IL _ neg x)) _))) = Just $ if neg then -x else x asInt _ = Nothing list :: LHsExpr GhcPs -> [LHsExpr GhcPs] list (L _ (ExplicitList _ _ xs)) = xs list x = [x] sub :: LHsExpr GhcPs -> LHsExpr GhcPs sub = transform f where f (view -> Var_ x) | Just y <- lookup x bind = y f x = x -- Does the result look very much like the declaration? checkDefine :: String -> Maybe (Int, LHsExpr GhcPs) -> LHsExpr GhcPs -> Bool checkDefine declName Nothing y = let funOrOp expr = case expr of L _ (HsApp _ fun _) -> funOrOp fun L _ (OpApp _ _ op _) -> funOrOp op other -> other in declName /= varToStr (transformBi unqual $ funOrOp y) checkDefine _ _ _ = True --------------------------------------------------------------------- -- TRANSFORMATION -- If it has '_noParen_', remove the brackets (if exist). performSpecial :: LHsExpr GhcPs -> LHsExpr GhcPs performSpecial = transform fNoParen where fNoParen :: LHsExpr GhcPs -> LHsExpr GhcPs fNoParen (L _ (HsApp _ e x)) | varToStr e == "_noParen_" = fromParen x fNoParen x = x -- Contract : 'Data.List.foo' => 'foo' if 'Data.List' is loaded. unqualify :: Scope -> Scope -> LHsExpr GhcPs -> LHsExpr GhcPs unqualify from to = transformBi f where f :: Located RdrName -> Located RdrName f x@(L _ (Unqual s)) | isUnifyVar (occNameString s) = x f x = scopeMove (from, x) to addBracket :: Maybe (Int, LHsExpr GhcPs) -> LHsExpr GhcPs -> LHsExpr GhcPs addBracket (Just (i, p)) c | needBracketOld i p c = noLoc $ HsPar noExtField c addBracket _ x = x -- Type substitution e.g. 'Foo Int' for 'a' in 'Proxy a' can lead to a -- need to bracket type applications in This doesn't come up in HSE -- because the pretty printer inserts them. addBracketTy :: LHsExpr GhcPs -> LHsExpr GhcPs addBracketTy= transformBi f where f :: LHsType GhcPs -> LHsType GhcPs f (L _ (HsAppTy _ t x@(L _ HsAppTy{}))) = noLoc (HsAppTy noExtField t (noLoc (HsParTy noExtField x))) f x = x hlint-3.1.6/src/Hint/ListRec.hs0000644000000000000000000002105013671470061014422 0ustar0000000000000000{-# LANGUAGE PatternGuards, ViewPatterns #-} {-# LANGUAGE RecordWildCards #-} {- map f [] = [] map f (x:xs) = f x : map f xs foldr f z [] = z foldr f z (x:xs) = f x (foldr f z xs) foldl f z [] = z foldl f z (x:xs) = foldl f (f z x) xs -} {- f (x:xs) = negate x + f xs ; f [] = 0 -- f xs = foldr ((+) . negate) 0 xs f (x:xs) = x + 1 : f xs ; f [] = [] -- f xs = map (+ 1) xs f z (x:xs) = f (z*x) xs ; f z [] = z -- f z xs = foldl (*) z xs f a (x:xs) b = x + a + b : f a xs b ; f a [] b = [] -- f a xs b = map (\ x -> x + a + b) xs f [] a = return a ; f (x:xs) a = a + x >>= \fax -> f xs fax -- f xs a = foldM (+) a xs f (x:xs) a = a + x >>= \fax -> f xs fax ; f [] a = pure a -- f xs a = foldM (+) a xs foos [] x = x; foos (y:ys) x = foo y $ foos ys x -- foos ys x = foldr foo x ys f [] y = y; f (x:xs) y = f xs $ g x y -- f xs y = foldl (flip g) y xs f [] y = y; f (x : xs) y = let z = g x y in f xs z -- f xs y = foldl (flip g) y xs f [] y = y; f (x:xs) y = f xs (f xs z) fun [] = []; fun (x:xs) = f x xs ++ fun xs -} module Hint.ListRec(listRecHint) where import Hint.Type (DeclHint, Severity(Suggestion, Warning), idea, toSS) import Data.Generics.Uniplate.DataOnly import Data.List.Extra import Data.Maybe import Data.Either.Extra import Control.Monad import Refact.Types hiding (RType(Match)) import SrcLoc import GHC.Hs.Extension import GHC.Hs.Pat import GHC.Hs.Types import TysWiredIn import RdrName import GHC.Hs.Binds import GHC.Hs.Expr import GHC.Hs.Decls import BasicTypes import GHC.Util import Language.Haskell.GhclibParserEx.GHC.Hs.Pat import Language.Haskell.GhclibParserEx.GHC.Hs.Expr import Language.Haskell.GhclibParserEx.GHC.Hs.ExtendInstances import Language.Haskell.GhclibParserEx.GHC.Utils.Outputable import Language.Haskell.GhclibParserEx.GHC.Types.Name.Reader listRecHint :: DeclHint listRecHint _ _ = concatMap f . universe where f o = maybeToList $ do let x = o (x, addCase) <- findCase x (use,severity,x) <- matchListRec x let y = addCase x guard $ recursiveStr `notElem` varss y -- Maybe we can do better here maintaining source -- formatting? pure $ idea severity ("Use " ++ use) o y [Replace Decl (toSS o) [] (unsafePrettyPrint y)] recursiveStr :: String recursiveStr = "_recursive_" recursive = strToVar recursiveStr data ListCase = ListCase [String] -- recursion parameters (LHsExpr GhcPs) -- nil case (String, String, LHsExpr GhcPs) -- cons case -- For cons-case delete any recursive calls with 'xs' in them. Any -- recursive calls are marked "_recursive_". data BList = BNil | BCons String String deriving (Eq, Ord, Show) data Branch = Branch String -- function name [String] -- parameters Int -- list position BList (LHsExpr GhcPs) -- list type/body --------------------------------------------------------------------- -- MATCH THE RECURSION matchListRec :: ListCase -> Maybe (String, Severity, LHsExpr GhcPs) matchListRec o@(ListCase vs nil (x, xs, cons)) -- Suggest 'map'? | [] <- vs, varToStr nil == "[]", (L _ (OpApp _ lhs c rhs)) <- cons, varToStr c == ":" , astEq (fromParen rhs) recursive, xs `notElem` vars lhs = Just $ (,,) "map" Hint.Type.Warning $ appsBracket [ strToVar "map", niceLambda [x] lhs, strToVar xs] -- Suggest 'foldr'? | [] <- vs, App2 op lhs rhs <- view cons , xs `notElem` (vars op ++ vars lhs) -- the meaning of xs changes, see #793 , astEq (fromParen rhs) recursive = Just $ (,,) "foldr" Suggestion $ appsBracket [ strToVar "foldr", niceLambda [x] $ appsBracket [op,lhs], nil, strToVar xs] -- Suggest 'foldl'? | [v] <- vs, view nil == Var_ v, (L _ (HsApp _ r lhs)) <- cons , astEq (fromParen r) recursive , xs `notElem` vars lhs = Just $ (,,) "foldl" Suggestion $ appsBracket [ strToVar "foldl", niceLambda [v,x] lhs, strToVar v, strToVar xs] -- Suggest 'foldM'? | [v] <- vs, (L _ (HsApp _ ret res)) <- nil, isReturn ret, varToStr res == "()" || view res == Var_ v , [L _ (BindStmt _ (view -> PVar_ b1) e _ _), L _ (BodyStmt _ (fromParen -> (L _ (HsApp _ r (view -> Var_ b2)))) _ _)] <- asDo cons , b1 == b2, astEq r recursive, xs `notElem` vars e , name <- "foldM" ++ ['_' | varToStr res == "()"] = Just $ (,,) name Suggestion $ appsBracket [strToVar name, niceLambda [v,x] e, strToVar v, strToVar xs] -- Nope, I got nothing ¯\_(ツ)_/¯. | otherwise = Nothing -- Very limited attempt to convert >>= to do, only useful for -- 'foldM' / 'foldM_'. asDo :: LHsExpr GhcPs -> [LStmt GhcPs (LHsExpr GhcPs)] asDo (view -> App2 bind lhs (L _ (HsLam _ MG { mg_origin=FromSource , mg_alts=L _ [ L _ Match { m_ctxt=LambdaExpr , m_pats=[v@(L _ VarPat{})] , m_grhss=GRHSs _ [L _ (GRHS _ [] rhs)] (L _ (EmptyLocalBinds _))}]})) ) = [ noLoc $ BindStmt noExtField v lhs noSyntaxExpr noSyntaxExpr , noLoc $ BodyStmt noExtField rhs noSyntaxExpr noSyntaxExpr ] asDo (L _ (HsDo _ DoExpr (L _ stmts))) = stmts asDo x = [noLoc $ BodyStmt noExtField x noSyntaxExpr noSyntaxExpr] --------------------------------------------------------------------- -- FIND THE CASE ANALYSIS findCase :: LHsDecl GhcPs -> Maybe (ListCase, LHsExpr GhcPs -> LHsDecl GhcPs) findCase x = do -- Match a function binding with two alternatives. (L _ (ValD _ FunBind {fun_matches= MG{mg_origin=FromSource, mg_alts= (L _ [ x1@(L _ Match{..}) -- Match fields. , x2]), ..} -- Match group fields. , ..} -- Fun. bind fields. )) <- pure x Branch name1 ps1 p1 c1 b1 <- findBranch x1 Branch name2 ps2 p2 c2 b2 <- findBranch x2 guard (name1 == name2 && ps1 == ps2 && p1 == p2) [(BNil, b1), (BCons x xs, b2)] <- pure $ sortOn fst [(c1, b1), (c2, b2)] b2 <- transformAppsM (delCons name1 p1 xs) b2 (ps, b2) <- pure $ eliminateArgs ps1 b2 let ps12 = let (a, b) = splitAt p1 ps1 in map strToPat (a ++ xs : b) -- Function arguments. emptyLocalBinds = noLoc $ EmptyLocalBinds noExtField -- Empty where clause. gRHS e = noLoc $ GRHS noExtField [] e :: LGRHS GhcPs (LHsExpr GhcPs) -- Guarded rhs. gRHSSs e = GRHSs noExtField [gRHS e] emptyLocalBinds -- Guarded rhs set. match e = Match{m_ext=noExtField,m_pats=ps12, m_grhss=gRHSSs e, ..} -- Match. matchGroup e = MG{mg_alts=noLoc [noLoc $ match e], mg_origin=Generated, ..} -- Match group. funBind e = FunBind {fun_matches=matchGroup e, ..} :: HsBindLR GhcPs GhcPs -- Fun bind. pure (ListCase ps b1 (x, xs, b2), noLoc . ValD noExtField . funBind) delCons :: String -> Int -> String -> LHsExpr GhcPs -> Maybe (LHsExpr GhcPs) delCons func pos var (fromApps -> (view -> Var_ x) : xs) | func == x = do (pre, (view -> Var_ v) : post) <- pure $ splitAt pos xs guard $ v == var pure $ apps $ recursive : pre ++ post delCons _ _ _ x = pure x eliminateArgs :: [String] -> LHsExpr GhcPs -> ([String], LHsExpr GhcPs) eliminateArgs ps cons = (remove ps, transform f cons) where args = [zs | z : zs <- map fromApps $ universeApps cons, astEq z recursive] elim = [all (\xs -> length xs > i && view (xs !! i) == Var_ p) args | (i, p) <- zipFrom 0 ps] ++ repeat False remove = concat . zipWith (\b x -> [x | not b]) elim f (fromApps -> x : xs) | astEq x recursive = apps $ x : remove xs f x = x --------------------------------------------------------------------- -- FIND A BRANCH findBranch :: LMatch GhcPs (LHsExpr GhcPs) -> Maybe Branch findBranch (L _ x) = do Match { m_ctxt = FunRhs {mc_fun=(L _ name)} , m_pats = ps , m_grhss = GRHSs {grhssGRHSs=[L l (GRHS _ [] body)] , grhssLocalBinds=L _ (EmptyLocalBinds _) } } <- pure x (a, b, c) <- findPat ps pure $ Branch (occNameStr name) a b c $ simplifyExp body findPat :: [LPat GhcPs] -> Maybe ([String], Int, BList) findPat ps = do ps <- mapM readPat ps [i] <- pure $ findIndices isRight ps let (left, [right]) = partitionEithers ps pure (left, i, right) readPat :: LPat GhcPs -> Maybe (Either String BList) readPat (view -> PVar_ x) = Just $ Left x readPat (L _ (ParPat _ (L _ (ConPatIn (L _ n) (InfixCon (view -> PVar_ x) (view -> PVar_ xs)))))) | n == consDataCon_RDR = Just $ Right $ BCons x xs readPat (L _ (ConPatIn (L _ n) (PrefixCon []))) | n == nameRdrName nilDataConName = Just $ Right BNil readPat _ = Nothing hlint-3.1.6/src/Hint/List.hs0000644000000000000000000002534413671470061014002 0ustar0000000000000000{-# LANGUAGE ViewPatterns, PatternGuards, FlexibleContexts #-} {- Find and match: yes = 1:2:[] -- [1,2] yes = ['h','e','l','l','o'] yes (1:2:[]) = 1 -- [1,2] yes ['h','e'] = 1 -- [a]++b -> a : b, but only if not in a chain of ++'s yes = [x] ++ xs -- x : xs no = "x" ++ xs no = [x] ++ xs ++ ys no = xs ++ [x] ++ ys yes = [if a then b else c] ++ xs -- (if a then b else c) : xs yes = [1] : [2] : [3] : [4] : [5] : [] -- [[1], [2], [3], [4], [5]] yes = if x == e then l2 ++ xs else [x] ++ check_elem xs -- x : check_elem xs data Yes = Yes (Maybe [Char]) -- Maybe String yes = y :: [Char] -> a -- String -> a instance C [Char] foo = [a b] ++ xs -- a b : xs foo = [myexpr | True, a] -- [myexpr | a] foo = [myexpr | False] -- [] foo = map f [x + 1 | x <- [1..10]] -- [f (x + 1) | x <- [1..10]] foo = [x + 1 | x <- [1..10], feature] -- [x + 1 | feature, x <- [1..10]] foo = [x + 1 | x <- [1..10], even x] foo = [x + 1 | x <- [1..10], even x, dont_reoder_guards] foo = [x + 1 | x <- [1..10], let y = even x, y] foo = [x + 1 | x <- [1..10], let q = even 1, q] -- [x + 1 | let q = even 1, q, x <- [1..10]] foo = [fooValue | Foo{..} <- y, fooField] issue619 = [pkgJobs | Pkg{pkgGpd, pkgJobs} <- pkgs, not $ null $ C.condTestSuites pkgGpd] {-# LANGUAGE MonadComprehensions #-}\ foo = [x | False, x <- [1 .. 10]] -- [] foo = [_ | x <- _, let _ = A{x}] issue1039 = foo (map f [1 | _ <- []]) -- [f 1 | _ <- []] -} module Hint.List(listHint) where import Control.Applicative import Data.Generics.Uniplate.DataOnly import Data.List.Extra import Data.Maybe import Prelude import Hint.Type(DeclHint,Idea,suggest,ignore,toRefactSrcSpan,toSS) import Refact.Types hiding (SrcSpan) import qualified Refact.Types as R import GHC.Hs import SrcLoc import BasicTypes import RdrName import Name import FastString import TysWiredIn import GHC.Util import Language.Haskell.GhclibParserEx.GHC.Hs.Pat import Language.Haskell.GhclibParserEx.GHC.Hs.Expr import Language.Haskell.GhclibParserEx.GHC.Hs.Types import Language.Haskell.GhclibParserEx.GHC.Hs.ExtendInstances import Language.Haskell.GhclibParserEx.GHC.Utils.Outputable import Language.Haskell.GhclibParserEx.GHC.Types.Name.Reader listHint :: DeclHint listHint _ _ = listDecl listDecl :: LHsDecl GhcPs -> [Idea] listDecl x = concatMap (listExp False) (childrenBi x) ++ stringType x ++ concatMap listPat (childrenBi x) ++ concatMap listComp (universeBi x) -- Refer to https://github.com/ndmitchell/hlint/issues/775 for the -- structure of 'listComp'. listComp :: LHsExpr GhcPs -> [Idea] listComp o@(L _ (HsDo _ ListComp (L _ stmts))) = listCompCheckGuards o ListComp stmts listComp o@(L _ (HsDo _ MonadComp (L _ stmts))) = listCompCheckGuards o MonadComp stmts listComp (L _ HsPar{}) = [] -- App2 "sees through" paren, which causes duplicate hints with universeBi listComp o@(view -> App2 mp f (L _ (HsDo _ ListComp (L _ stmts)))) = listCompCheckMap o mp f ListComp stmts listComp o@(view -> App2 mp f (L _ (HsDo _ MonadComp (L _ stmts)))) = listCompCheckMap o mp f MonadComp stmts listComp _ = [] listCompCheckGuards :: LHsExpr GhcPs -> HsStmtContext Name -> [ExprLStmt GhcPs] -> [Idea] listCompCheckGuards o ctx stmts = let revs = reverse stmts e@(L _ LastStmt{}) = head revs -- In a ListComp, this is always last. xs = reverse (tail revs) in list_comp_aux e xs where list_comp_aux e xs | "False" `elem` cons = [suggest "Short-circuited list comprehension" o o' (suggestExpr o o')] | "True" `elem` cons = [suggest "Redundant True guards" o o2 (suggestExpr o o2)] | not (astListEq xs ys) = [suggest "Move guards forward" o o3 (suggestExpr o o3)] | otherwise = [] where ys = moveGuardsForward xs o' = noLoc $ ExplicitList noExtField Nothing [] o2 = noLoc $ HsDo noExtField ctx (noLoc (filter ((/= Just "True") . qualCon) xs ++ [e])) o3 = noLoc $ HsDo noExtField ctx (noLoc $ ys ++ [e]) cons = mapMaybe qualCon xs qualCon :: ExprLStmt GhcPs -> Maybe String qualCon (L _ (BodyStmt _ (L _ (HsVar _ (L _ x))) _ _)) = Just (occNameStr x) qualCon _ = Nothing listCompCheckMap :: LHsExpr GhcPs -> LHsExpr GhcPs -> LHsExpr GhcPs -> HsStmtContext Name -> [ExprLStmt GhcPs] -> [Idea] listCompCheckMap o mp f ctx stmts | varToStr mp == "map" = [suggest "Move map inside list comprehension" o o2 (suggestExpr o o2)] where revs = reverse stmts L _ (LastStmt _ body b s) = head revs -- In a ListComp, this is always last. last = noLoc $ LastStmt noExtField (noLoc $ HsApp noExtField (paren f) (paren body)) b s o2 =noLoc $ HsDo noExtField ctx (noLoc $ reverse (tail revs) ++ [last]) listCompCheckMap _ _ _ _ _ = [] suggestExpr :: LHsExpr GhcPs -> LHsExpr GhcPs -> [Refactoring R.SrcSpan] suggestExpr o o2 = [Replace Expr (toSS o) [] (unsafePrettyPrint o2)] moveGuardsForward :: [ExprLStmt GhcPs] -> [ExprLStmt GhcPs] moveGuardsForward = reverse . f [] . reverse where f guards (x@(L _ (BindStmt _ p _ _ _)) : xs) = reverse stop ++ x : f move xs where (move, stop) = span (if any hasPFieldsDotDot (universeBi x) || any isPFieldWildcard (universeBi x) then const False else \x -> let pvs = pvars p in -- See this code from 'RdrHsSyn.hs' (8.10.1): -- plus_RDR, pun_RDR :: RdrName -- plus_RDR = mkUnqual varName (fsLit "+") -- Hack -- pun_RDR = mkUnqual varName (fsLit "pun-right-hand-side") -- Todo (SF, 2020-03-28): Try to make this better somehow. pvs `disjoint` varss x && "pun-right-hand-side" `notElem` pvs ) guards f guards (x@(L _ BodyStmt{}):xs) = f (x:guards) xs f guards (x@(L _ LetStmt{}):xs) = f (x:guards) xs f guards xs = reverse guards ++ xs listExp :: Bool -> LHsExpr GhcPs -> [Idea] listExp b (fromParen -> x) = if null res then concatMap (listExp $ isAppend x) $ children x else [head res] where res = [suggest name x x2 [r] | (name, f) <- checks , Just (x2, subts, temp) <- [f b x] , let r = Replace Expr (toSS x) subts temp ] listPat :: LPat GhcPs -> [Idea] listPat x = if null res then concatMap listPat $ children x else [head res] where res = [suggest name x x2 [r] | (name, f) <- pchecks , Just (x2, subts, temp) <- [f x] , let r = Replace Pattern (toSS x) subts temp ] isAppend :: View a App2 => a -> Bool isAppend (view -> App2 op _ _) = varToStr op == "++" isAppend _ = False checks ::[(String, Bool -> LHsExpr GhcPs -> Maybe (LHsExpr GhcPs, [(String, R.SrcSpan)], String))] checks = let (*) = (,) in drop1 -- see #174 [ "Use string literal" * useString , "Use list literal" * useList , "Use :" * useCons ] pchecks :: [(String, LPat GhcPs -> Maybe (LPat GhcPs, [(String, R.SrcSpan)], String))] pchecks = let (*) = (,) in drop1 -- see #174 [ "Use string literal pattern" * usePString , "Use list literal pattern" * usePList ] usePString :: LPat GhcPs -> Maybe (LPat GhcPs, [a], String) usePString (L _ (ListPat _ xs)) | not $ null xs, Just s <- mapM fromPChar xs = let literal = noLoc $ LitPat noExtField (HsString NoSourceText (fsLit (show s))) in Just (literal, [], unsafePrettyPrint literal) usePString _ = Nothing usePList :: LPat GhcPs -> Maybe (LPat GhcPs, [(String, R.SrcSpan)], String) usePList = fmap ( (\(e, s) -> (noLoc (ListPat noExtField e) , map (fmap toRefactSrcSpan . fst) s , unsafePrettyPrint (noLoc $ ListPat noExtField (map snd s) :: LPat GhcPs)) ) . unzip ) . f True ['a'..'z'] where f first _ x | patToStr x == "[]" = if first then Nothing else Just [] f first (ident:cs) (view -> PApp_ ":" [a, b]) = ((a, g ident a) :) <$> f False cs b f first _ _ = Nothing g :: Char -> LPat GhcPs -> ((String, SrcSpan), LPat GhcPs) g c (getLoc -> loc) = (([c], loc), noLoc $ VarPat noExtField (noLoc $ mkVarUnqual (fsLit [c]))) useString :: p -> LHsExpr GhcPs -> Maybe (LHsExpr GhcPs, [a], String) useString b (L _ (ExplicitList _ _ xs)) | not $ null xs, Just s <- mapM fromChar xs = let literal = noLoc (HsLit noExtField (HsString NoSourceText (fsLit (show s)))) in Just (literal, [], unsafePrettyPrint literal) useString _ _ = Nothing useList :: p -> LHsExpr GhcPs -> Maybe (LHsExpr GhcPs, [(String, R.SrcSpan)], String) useList b = fmap ( (\(e, s) -> (noLoc (ExplicitList noExtField Nothing e) , map (fmap toSS) s , unsafePrettyPrint (noLoc $ ExplicitList noExtField Nothing (map snd s) :: LHsExpr GhcPs)) ) . unzip ) . f True ['a'..'z'] where f first _ x | varToStr x == "[]" = if first then Nothing else Just [] f first (ident:cs) (view -> App2 c a b) | varToStr c == ":" = ((a, g ident a) :) <$> f False cs b f first _ _ = Nothing g :: Char -> LHsExpr GhcPs -> (String, LHsExpr GhcPs) g c p = ([c], L (getLoc p) (unLoc $ strToVar [c])) useCons :: View a App2 => Bool -> a -> Maybe (LHsExpr GhcPs, [(String, R.SrcSpan)], String) useCons False (view -> App2 op x y) | varToStr op == "++" , Just (newX, tplX, spanX) <- f x , not $ isAppend y = Just (gen newX y , [("x", spanX), ("xs", toSS y)] , unsafePrettyPrint $ gen tplX (strToVar "xs") ) where f :: LHsExpr GhcPs -> Maybe (LHsExpr GhcPs, LHsExpr GhcPs, R.SrcSpan) f (L _ (ExplicitList _ _ [x])) | isAtom x || isApp x = Just (x, strToVar "x", toSS x) | otherwise = Just (addParen x, addParen (strToVar "x"), toSS x) f _ = Nothing gen :: LHsExpr GhcPs -> LHsExpr GhcPs -> LHsExpr GhcPs gen x = noLoc . OpApp noExtField x (noLoc (HsVar noExtField (noLoc consDataCon_RDR))) useCons _ _ = Nothing typeListChar :: LHsType GhcPs typeListChar = noLoc $ HsListTy noExtField (noLoc (HsTyVar noExtField NotPromoted (noLoc (mkVarUnqual (fsLit "Char"))))) typeString :: LHsType GhcPs typeString = noLoc $ HsTyVar noExtField NotPromoted (noLoc (mkVarUnqual (fsLit "String"))) stringType :: LHsDecl GhcPs -> [Idea] stringType (L _ x) = case x of InstD _ ClsInstD{ cid_inst= ClsInstDecl{cid_binds=x, cid_tyfam_insts=y, cid_datafam_insts=z}} -> f x ++ f y ++ f z -- Pretty much everthing but the instance type. _ -> f x where f x = concatMap g $ childrenBi x g :: LHsType GhcPs -> [Idea] g e@(fromTyParen -> x) = [ignore "Use String" x (transform f x) rs | not . null $ rs] where f x = if astEq x typeListChar then typeString else x rs = [Replace Type (toSS t) [] (unsafePrettyPrint typeString) | t <- universe x, astEq t typeListChar] hlint-3.1.6/src/Hint/Lambda.hs0000644000000000000000000003404413671470061014244 0ustar0000000000000000{-# LANGUAGE LambdaCase, PatternGuards, ViewPatterns #-} {- Concept: Remove all the lambdas you can be inserting only sections Never create a right section with +-# as the operator (they are misparsed) Rules: fun a = \x -> y -- promote lambdas, provided no where's outside the lambda fun x = y x -- eta reduce, x /= mr and foo /= symbol \x -> y x ==> y -- eta reduce ((#) x) ==> (x #) -- rotate operators (flip op x) ==> (`op` x) -- rotate operators \x y -> x + y ==> (+) -- insert operator \x y -> op y x ==> flip op \x -> x + y ==> (+ y) -- insert section, \x -> op x y ==> (`op` y) -- insert section \x -> y + x ==> (y +) -- insert section \x -> \y -> ... ==> \x y -- lambda compression \x -> (x +) ==> (+) -- operator reduction f a = \x -> x + x -- f a x = x + x f a = \a -> a + a -- f _ a = a + a a = \x -> x + x -- a x = x + x f (Just a) = \a -> a + a -- f (Just _) a = a + a f (Foo a b c) = \c -> c + c -- f (Foo a b _) c = c + c f a = \x -> x + x where _ = test f (test -> a) = \x -> x + x f = \x -> x + x -- f x = x + x fun x y z = f x y z -- fun = f @NoRefactor: refactoring for eta reduce is not implemented fun x y z = f x x y z -- fun x = f x x @NoRefactor fun x y z = f g z -- fun x y = f g @NoRefactor fun x = f . g $ x -- fun = f . g @NoRefactor f = foo (\y -> g x . h $ y) -- g x . h f = foo (\y -> g x . h $ y) -- @Message Avoid lambda f = foo ((*) x) -- (x *) @NoRefactor f = (*) x f = foo (flip op x) -- (`op` x) @NoRefactor f = foo (flip op x) -- @Message Use section @NoRefactor foo x = bar (\ d -> search d table) -- (`search` table) foo x = bar (\ d -> search d table) -- @Message Avoid lambda using `infix` f = flip op x f = foo (flip (*) x) -- (* x) @NoRefactor f = foo (flip (-) x) f = foo (\x y -> fun x y) -- @Warning fun f = foo (\x y z -> fun x y z) -- @Warning fun f = foo (\z -> f x $ z) -- f x f = foo (\x y -> x + y) -- (+) f = foo (\x -> x * y) -- @Suggestion (* y) f = foo (\x -> x # y) f = foo (\x -> \y -> x x y y) -- \x y -> x x y y f = foo (\x -> \x -> foo x x) -- \_ x -> foo x x f = foo (\(foo -> x) -> \y -> x x y y) f = foo (\(x:xs) -> \x -> foo x x) -- \(_:xs) x -> foo x x @NoRefactor f = foo (\x -> \y -> \z -> x x y y z z) -- \x y z -> x x y y z z x ! y = fromJust $ lookup x y f = foo (\i -> writeIdea (getClass i) i) f = bar (flip Foo.bar x) -- (`Foo.bar` x) @NoRefactor f = a b (\x -> c x d) -- (`c` d) yes = \x -> a x where -- a yes = \x y -> op y x where -- flip op f = \y -> nub $ reverse y where -- nub . reverse f = \z -> foo $ bar $ baz z where -- foo . bar . baz f = \z -> foo $ bar x $ baz z where -- foo . bar x . baz f = \z -> foo $ z $ baz z where f = \x -> bar map (filter x) where -- bar map . filter f = bar &+& \x -> f (g x) foo = [\column -> set column [treeViewColumnTitle := printf "%s (match %d)" name (length candidnates)]] foo = [\x -> x] foo = [\m x -> insert x x m] foo a b c = bar (flux ++ quux) c where flux = a -- foo a b = bar (flux ++ quux) @NoRefactor foo a b c = bar (flux ++ quux) c where flux = c yes = foo (\x -> Just x) -- @Warning Just foo = bar (\x -> (x `f`)) -- f foo = bar (\x -> shakeRoot "src" x) baz = bar (\x -> (x +)) -- (+) @NoRefactor xs `withArgsFrom` args = f args foo = bar (\x -> case x of Y z -> z) -- \(Y z) -> z @NoRefactor yes = blah (\ x -> case x of A -> a; B -> b) -- \ case A -> a; B -> b @NoRefactor yes = blah (\ x -> case x of A -> a; B -> b) -- @Note may require `{-# LANGUAGE LambdaCase #-}` adding to the top of the file @NoRefactor no = blah (\ x -> case x of A -> a x; B -> b x) yes = blah (\ x -> (y, x)) -- (y,) @NoRefactor yes = blah (\ x -> (y, x, z+q)) -- (y, , z+q) @NoRefactor yes = blah (\ x -> (y, x, y, u, v)) -- (y, , y, u, v) @NoRefactor yes = blah (\ x -> (y, x, z+q)) -- @Note may require `{-# LANGUAGE TupleSections #-}` adding to the top of the file @NoRefactor yes = blah (\ x -> (y, x, z+x)) tmp = map (\ x -> runST $ action x) yes = map (\f -> dataDir f) dataFiles -- (dataDir ) {-# LANGUAGE TypeApplications #-}; noBug545 = coerce ((<>) @[a]) {-# LANGUAGE QuasiQuotes #-}; authOAuth2 name = authOAuth2Widget [whamlet|Login via #{name}|] name {-# LANGUAGE QuasiQuotes #-}; authOAuth2 = foo (\name -> authOAuth2Widget [whamlet|Login via #{name}|] name) f = {- generates a hint using hlint.yaml only -} map (flip (,) "a") "123" f = {- generates a hint using hlint.yaml only -} map ((,) "a") "123" f = map (\s -> MkFoo s 0 s) ["a","b","c"] -} module Hint.Lambda(lambdaHint) where import Hint.Type (DeclHint, Idea, Note(RequiresExtension), suggest, warn, toSS, suggestN, ideaNote) import Util import Data.List.Extra import Data.Set (Set) import qualified Data.Set as Set import Refact.Types hiding (RType(Match)) import Data.Generics.Uniplate.DataOnly (universe, universeBi, transformBi) import BasicTypes import GHC.Hs import OccName import RdrName import SrcLoc import Language.Haskell.GhclibParserEx.GHC.Hs.Expr (isTypeApp, isOpApp, isLambda, isQuasiQuote, isVar, isDol, strToVar) import Language.Haskell.GhclibParserEx.GHC.Utils.Outputable import Language.Haskell.GhclibParserEx.GHC.Types.Name.Reader import GHC.Util.Brackets (isAtom) import GHC.Util.FreeVars (free, allVars, freeVars, pvars, vars, varss) import GHC.Util.HsExpr (allowLeftSection, allowRightSection, niceLambdaR, lambda) import GHC.Util.View lambdaHint :: DeclHint lambdaHint _ _ x = concatMap (uncurry lambdaExp) (universeParentBi x) ++ concatMap lambdaDecl (universe x) lambdaDecl :: LHsDecl GhcPs -> [Idea] lambdaDecl o@(L _ (ValD _ origBind@FunBind {fun_id = funName@(L loc1 _), fun_matches = MG {mg_alts = L _ [L _ (Match _ ctxt@(FunRhs _ Prefix _) pats (GRHSs _ [L _ (GRHS _ [] origBody@(L loc2 _))] bind))]}})) | L _ (EmptyLocalBinds noExtField) <- bind , isLambda $ fromParen origBody , null (universeBi pats :: [HsExpr GhcPs]) = [warn "Redundant lambda" o (gen pats origBody) [Replace Decl (toSS o) subts template]] | length pats2 < length pats, pvars (drop (length pats2) pats) `disjoint` varss bind = [warn "Eta reduce" (reform pats origBody) (reform pats2 bod2) [ -- Disabled, see apply-refact #3 ] ] where reform :: [LPat GhcPs] -> LHsExpr GhcPs -> LHsDecl GhcPs reform ps b = L loc $ ValD noExtField $ origBind {fun_matches = MG noExtField (noLoc [noLoc $ Match noExtField ctxt ps $ GRHSs noExtField [noLoc $ GRHS noExtField [] b] $ noLoc $ EmptyLocalBinds noExtField]) Generated} loc = combineSrcSpans loc1 loc2 gen :: [LPat GhcPs] -> LHsExpr GhcPs -> LHsDecl GhcPs gen ps = uncurry reform . fromLambda . lambda ps (finalpats, body) = fromLambda . lambda pats $ origBody (pats2, bod2) = etaReduce pats origBody (origPats, subtsVars) = mkOrigPats (Just (rdrNameStr funName)) finalpats subts = ("body", toSS body) : zipWith (\x y -> ([x],y)) subtsVars (map toSS finalpats) template = unsafePrettyPrint (reform origPats varBody) lambdaDecl _ = [] etaReduce :: [LPat GhcPs] -> LHsExpr GhcPs -> ([LPat GhcPs], LHsExpr GhcPs) etaReduce (unsnoc -> Just (ps, view -> PVar_ p)) (L _ (HsApp _ x (view -> Var_ y))) | p == y , y `notElem` vars x , not $ any isQuasiQuote $ universe x = etaReduce ps x etaReduce ps (L loc (OpApp _ x (isDol -> True) y)) = etaReduce ps (L loc (HsApp noExtField x y)) etaReduce ps x = (ps, x) --Section refactoring is not currently implemented. lambdaExp :: Maybe (LHsExpr GhcPs) -> LHsExpr GhcPs -> [Idea] lambdaExp _ o@(L _ (HsPar _ (L _ (HsApp _ oper@(L _ (HsVar _ (L _ (rdrNameOcc -> f)))) y)))) | isSymOcc f -- is this an operator? , isAtom y , allowLeftSection $ occNameString f , not $ isTypeApp y = [suggestN "Use section" o $ noLoc $ HsPar noExtField $ noLoc $ SectionL noExtField y oper] lambdaExp _ o@(L _ (HsPar _ (view -> App2 (view -> Var_ "flip") origf@(view -> Var_ f) y))) | allowRightSection f, not $ "(" `isPrefixOf` f = [suggestN "Use section" o $ noLoc $ HsPar noExtField $ noLoc $ SectionR noExtField origf y] lambdaExp p o@(L _ HsLam{}) | not $ any isOpApp p , (res, refact) <- niceLambdaR [] o , not $ isLambda res , not $ any isQuasiQuote $ universe res , not $ "runST" `Set.member` Set.map occNameString (freeVars o) , let name = "Avoid lambda" ++ (if countRightSections res > countRightSections o then " using `infix`" else "") -- If the lambda's parent is an HsPar, and the result is also an HsPar, the span should include the parentheses. , let from = case (p, res) of (Just p@(L _ (HsPar _ (L _ HsLam{}))), L _ HsPar{}) -> p _ -> o = [(if isVar res then warn else suggest) name from res (refact $ toSS from)] where countRightSections :: LHsExpr GhcPs -> Int countRightSections x = length [() | L _ (SectionR _ (view -> Var_ _) _) <- universe x] lambdaExp p o@(SimpleLambda origPats origBody) | isLambda (fromParen origBody) , null (universeBi origPats :: [HsExpr GhcPs]) -- TODO: I think this checks for view patterns only, so maybe be more explicit about that? , maybe True (not . isLambda) p = [suggest "Collapse lambdas" o (lambda pats body) [Replace Expr (toSS o) subts template]] where (pats, body) = fromLambda o (oPats, subtsVars) = mkOrigPats Nothing pats subts = ("body", toSS body) : zipWith (\x y -> ([x],y)) subtsVars (map toSS pats) template = unsafePrettyPrint (lambda oPats varBody) -- match a lambda with a variable pattern, with no guards and no where clauses lambdaExp _ o@(SimpleLambda [view -> PVar_ x] (L _ expr)) = case expr of -- suggest TupleSections instead of lambdas ExplicitTuple _ args boxity -- is there exactly one argument that is exactly x? | ([_x], ys) <- partition ((==Just x) . tupArgVar) args -- the other arguments must not have a nested x somewhere in them , Set.notMember x $ Set.map occNameString $ freeVars ys -> [(suggestN "Use tuple-section" o $ noLoc $ ExplicitTuple noExtField (map removeX args) boxity) {ideaNote = [RequiresExtension "TupleSections"]}] -- suggest @LambdaCase@/directly matching in a lambda instead of doing @\x -> case x of ...@ HsCase _ (view -> Var_ x') matchGroup -- is the case being done on the variable from our original lambda? | x == x' -- x must not be used in some other way inside the matches , Set.notMember x $ Set.map occNameString $ free $ allVars matchGroup -> case matchGroup of -- is there a single match? - suggest match inside the lambda -- -- we need to -- * add brackets to the match, because matches in lambdas require them -- * mark match as being in a lambda context so that it's printed properly oldMG@(MG _ (L _ [L _ oldmatch]) _) -> [suggestN "Use lambda" o $ noLoc $ HsLam noExtField oldMG { mg_alts = noLoc [noLoc oldmatch { m_pats = map mkParPat $ m_pats oldmatch , m_ctxt = LambdaExpr } ] } ] -- otherwise we should use @LambdaCase@ MG _ (L _ xs) _ -> [(suggestN "Use lambda-case" o $ noLoc $ HsLamCase noExtField matchGroup) {ideaNote=[RequiresExtension "LambdaCase"]}] _ -> [] _ -> [] where -- | Filter out tuple arguments, converting the @x@ (matched in the lambda) variable argument -- to a missing argument, so that we get the proper section. removeX :: LHsTupArg GhcPs -> LHsTupArg GhcPs removeX arg@(L _ (Present _ (view -> Var_ x'))) | x == x' = noLoc $ Missing noExtField removeX y = y -- | Extract the name of an argument of a tuple if it's present and a variable. tupArgVar :: LHsTupArg GhcPs -> Maybe String tupArgVar (L _ (Present _ (view -> Var_ x))) = Just x tupArgVar _ = Nothing lambdaExp _ _ = [] varBody :: LHsExpr GhcPs varBody = strToVar "body" -- | Squash lambdas and replace any repeated pattern variable with @_@ fromLambda :: LHsExpr GhcPs -> ([LPat GhcPs], LHsExpr GhcPs) fromLambda (SimpleLambda ps1 (fromLambda . fromParen -> (ps2,x))) = (transformBi (f $ pvars ps2) ps1 ++ ps2, x) where f :: [String] -> Pat GhcPs -> Pat GhcPs f bad (VarPat _ (rdrNameStr -> x)) | x `elem` bad = WildPat noExtField f bad x = x fromLambda x = ([], x) -- | For each pattern, if it does not contain wildcards, replace it with a variable pattern. -- -- The second component of the result is a list of substitution variables, which is ['a'..'z'], -- excluding variables that occur in the function name or patterns with wildcards. For example, given -- 'f (Foo a b _) = ...', 'f', 'a' and 'b' are removed. mkOrigPats :: Maybe String -> [LPat GhcPs] -> ([LPat GhcPs], [Char]) mkOrigPats funName pats = (zipWith munge subtsVars pats', subtsVars) where (Set.unions -> used, pats') = unzip (map f pats) -- Remove variables that occur in the function name or patterns with wildcards subtsVars = filter (\c -> c `Set.notMember` used && Just [c] /= funName) ['a'..'z'] -- Returns (chars in the pattern if the pattern contains wildcards, (whether the pattern contains wildcards, the pattern)) f :: LPat GhcPs -> (Set Char, (Bool, LPat GhcPs)) f p | any isWildPat (universe p) = let used = Set.fromList [c | (L _ (VarPat _ (rdrNameStr -> [c]))) <- universe p] in (used, (True, p)) | otherwise = (mempty, (False, p)) isWildPat :: LPat GhcPs -> Bool isWildPat = \case (L _ (WildPat _)) -> True; _ -> False -- Replace the pattern with a variable pattern if the pattern doesn't contain wildcards. munge :: Char -> (Bool, LPat GhcPs) -> LPat GhcPs munge _ (True, p) = p munge ident (False, L ploc _) = L ploc (VarPat noExtField (L ploc $ mkRdrUnqual $ mkVarOcc [ident])) hlint-3.1.6/src/Hint/Import.hs0000644000000000000000000001216213671470061014333 0ustar0000000000000000{-# LANGUAGE LambdaCase, PatternGuards, RecordWildCards #-} {- Reduce the number of import declarations. Two import declarations can be combined if: (note, A[] is A with whatever import list, or none) import A[]; import A[] = import A[] import A(B); import A(C) = import A(B,C) import A; import A(C) = import A import A; import A hiding (C) = import A import A[]; import A[] as Y = import A[] as Y import A; import A -- import A import A; import A; import A -- import A import A(Foo) ; import A -- import A import A ;import A(Foo) -- import A import A(Bar(..)); import {-# SOURCE #-} A import A; import B import A(B) ; import A(C) -- import A(B,C) import A; import A hiding (C) -- import A import A; import A as Y -- import A as Y import A; import qualified A as Y import A as B; import A as C import A as A -- import A import qualified A as A -- import qualified A import A; import B; import A -- import A import qualified A; import A import B; import A; import A -- import A import A hiding(Foo); import A hiding(Bar) import A (foo) \ import A (bar) \ import A (baz) -- import A ( foo, bar, baz ) -} module Hint.Import(importHint) where import Hint.Type(ModuHint,ModuleEx(..),Idea(..),Severity(..),suggest,toSS,rawIdea) import Refact.Types hiding (ModuleName) import qualified Refact.Types as R import Data.Tuple.Extra import Data.List.Extra import Data.Generics.Uniplate.DataOnly import Data.Maybe import Control.Applicative import Prelude import FastString import BasicTypes import GHC.Hs import SrcLoc import Language.Haskell.GhclibParserEx.GHC.Utils.Outputable importHint :: ModuHint importHint _ ModuleEx {ghcModule=L _ HsModule{hsmodImports=ms}} = -- Ideas for combining multiple imports. concatMap (reduceImports . snd) ( groupSort [((n, pkg), i) | i <- ms , not $ ideclSource (unLoc i) , let i' = unLoc i , let n = unLoc $ ideclName i' , let pkg = unpackFS . sl_fs <$> ideclPkgQual i']) ++ -- Ideas for removing redundant 'as' clauses. concatMap stripRedundantAlias ms reduceImports :: [LImportDecl GhcPs] -> [Idea] reduceImports [] = [] reduceImports ms@(m:_) = [rawIdea Hint.Type.Warning "Use fewer imports" (getLoc m) (f ms) (Just $ f x) [] rs | Just (x, rs) <- [simplify ms]] where f = unlines . map unsafePrettyPrint simplify :: [LImportDecl GhcPs] -> Maybe ([LImportDecl GhcPs], [Refactoring R.SrcSpan]) simplify [] = Nothing simplify (x : xs) = case simplifyHead x xs of Nothing -> first (x:) <$> simplify xs Just (xs, rs) -> let deletions = filter (\case Delete{} -> True; _ -> False) rs in Just $ maybe (xs, rs) (second (++ deletions)) $ simplify xs simplifyHead :: LImportDecl GhcPs -> [LImportDecl GhcPs] -> Maybe ([LImportDecl GhcPs], [Refactoring R.SrcSpan]) simplifyHead x (y : ys) = case combine x y of Nothing -> first (y:) <$> simplifyHead x ys Just (xy, rs) -> Just (xy : ys, rs) simplifyHead x [] = Nothing combine :: LImportDecl GhcPs -> LImportDecl GhcPs -> Maybe (LImportDecl GhcPs, [Refactoring R.SrcSpan]) combine x@(L loc x') y@(L _ y') -- Both (un/)qualified, common 'as', same names : Delete the second. | qual, as, specs = Just (x, [Delete Import (toSS y)]) -- Both (un/)qualified, common 'as', different names : Merge the -- second into the first and delete it. | qual, as , Just (False, xs) <- ideclHiding x' , Just (False, ys) <- ideclHiding y' = let newImp = L loc x'{ideclHiding = Just (False, noLoc (unLoc xs ++ unLoc ys))} in Just (newImp, [Replace Import (toSS x) [] (unsafePrettyPrint (unLoc newImp)) , Delete Import (toSS y)]) -- Both (un/qualified), common 'as', one has names the other doesn't -- : Delete the one with names. | qual, as, isNothing (ideclHiding x') || isNothing (ideclHiding y') = let (newImp, toDelete) = if isNothing (ideclHiding x') then (x, y) else (y, x) in Just (newImp, [Delete Import (toSS toDelete)]) -- Both unqualified, same names, one (and only one) has an 'as' -- clause : Delete the one without an 'as'. | ideclQualified x' == NotQualified, qual, specs, length ass == 1 = let (newImp, toDelete) = if isJust (ideclAs x') then (x, y) else (y, x) in Just (newImp, [Delete Import (toSS toDelete)]) -- No hints. | otherwise = Nothing where eqMaybe:: Eq a => Maybe (Located a) -> Maybe (Located a) -> Bool eqMaybe (Just x) (Just y) = x `eqLocated` y eqMaybe Nothing Nothing = True eqMaybe _ _ = False qual = ideclQualified x' == ideclQualified y' as = ideclAs x' `eqMaybe` ideclAs y' ass = mapMaybe ideclAs [x', y'] specs = transformBi (const noSrcSpan) (ideclHiding x') == transformBi (const noSrcSpan) (ideclHiding y') stripRedundantAlias :: LImportDecl GhcPs -> [Idea] stripRedundantAlias x@(L loc i@ImportDecl {..}) -- Suggest 'import M as M' be just 'import M'. | Just (unLoc ideclName) == fmap unLoc ideclAs = [suggest "Redundant as" x (cL loc i{ideclAs=Nothing} :: LImportDecl GhcPs) [RemoveAsKeyword (toSS x)]] stripRedundantAlias _ = [] hlint-3.1.6/src/Hint/Extensions.hs0000644000000000000000000004501013671470061015216 0ustar0000000000000000{-# LANGUAGE LambdaCase, NamedFieldPuns #-} {- Suggest removal of unnecessary extensions i.e. They have {-# LANGUAGE RecursiveDo #-} but no mdo keywords {-# LANGUAGE Arrows #-} \ f = id -- {-# LANGUAGE RebindableSyntax #-} \ f = id {-# LANGUAGE RebindableSyntax, ParallelListComp, ImplicitParams #-} \ f = [(a,c) | a <- b | c <- d] -- {-# LANGUAGE RebindableSyntax, ParallelListComp #-} {-# LANGUAGE EmptyDataDecls #-} \ data Foo {-# LANGUAGE TemplateHaskell #-} \ $(deriveNewtypes typeInfo) {-# LANGUAGE TemplateHaskell #-} \ main = foo ''Bar {-# LANGUAGE PatternGuards #-} \ test = case x of _ | y <- z -> w {-# LANGUAGE TemplateHaskell,EmptyDataDecls #-} \ $(fmap return $ dataD (return []) (mkName "Void") [] [] []) {-# LANGUAGE RecursiveDo #-} \ main = mdo x <- y; return y {-# LANGUAGE RecursiveDo #-} \ main = do {rec {x <- return 1}; print x} {-# LANGUAGE ImplicitParams, BangPatterns #-} \ sort :: (?cmp :: a -> a -> Bool) => [a] -> [a] \ sort !f = undefined {-# LANGUAGE KindSignatures #-} \ data Set (cxt :: * -> *) a = Set [a] {-# LANGUAGE BangPatterns #-} \ foo x = let !y = x in y {-# LANGUAGE BangPatterns #-} \ data Foo = Foo !Int -- {-# LANGUAGE TypeOperators #-} \ data (<+>) a b = Foo a b {-# LANGUAGE TypeOperators #-} \ data Foo a b = a :+ b -- {-# LANGUAGE TypeOperators #-} \ type (<+>) a b = Foo a b {-# LANGUAGE TypeOperators #-} \ type Foo a b = a :+ b {-# LANGUAGE TypeOperators, TypeFamilies #-} \ type family Foo a b :: Type where Foo a b = a :+ b {-# LANGUAGE TypeOperators, TypeFamilies #-} \ type family Foo a b :: Type where Foo a b = (<+>) a b -- {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeOperators, TypeFamilies #-} \ class Foo a where data (<+>) a {-# LANGUAGE TypeOperators, TypeFamilies #-} \ class Foo a where foo :: a -> Int <+> Bool {-# LANGUAGE TypeOperators #-} \ class (<+>) a where {-# LANGUAGE TypeOperators #-} \ foo :: Int -> Double <+> Bool \ foo x = y {-# LANGUAGE TypeOperators #-} \ foo :: Int -> (<+>) Double Bool \ foo x = y -- {-# LANGUAGE TypeOperators #-} \ (<+>) :: Int -> Int -> Int \ x <+> y = x + y -- {-# LANGUAGE RecordWildCards #-} \ record field = Record{..} {-# LANGUAGE RecordWildCards #-} \ record = 1 -- @Note may require `{-# LANGUAGE DisambiguateRecordFields #-}` adding to the top of the file {-# LANGUAGE RecordWildCards #-} \ {-# LANGUAGE DisambiguateRecordFields #-} \ record = 1 -- @NoNote {-# LANGUAGE UnboxedTuples #-} \ record = 1 -- {-# LANGUAGE TemplateHaskell #-} \ foo {-# LANGUAGE GeneralizedNewtypeDeriving, DeriveDataTypeable #-} \ record = 1 -- {-# LANGUAGE GeneralizedNewtypeDeriving, DeriveDataTypeable #-} \ newtype Foo = Foo Int deriving Data -- {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE GeneralizedNewtypeDeriving, DeriveDataTypeable #-} \ data Foo = Foo Int deriving Data -- {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE GeneralizedNewtypeDeriving, DeriveDataTypeable #-} \ newtype Foo = Foo Int deriving Class -- {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE GeneralizedNewtypeDeriving, DeriveDataTypeable #-} \ data Foo = Foo Int deriving Class -- {-# LANGUAGE DeriveFunctor #-} \ data Foo = Foo Int deriving Functor {-# LANGUAGE DeriveFunctor #-} \ newtype Foo = Foo Int deriving Functor {-# LANGUAGE GeneralizedNewtypeDeriving #-} \ newtype Foo = Foo Int deriving Functor {-# LANGUAGE GeneralizedNewtypeDeriving #-} \ newtype Foo = Foo Int deriving Data -- {-# LANGUAGE DeriveFunctor, GeneralizedNewtypeDeriving, StandaloneDeriving #-} \ deriving instance Functor Bar {-# LANGUAGE DeriveFunctor, GeneralizedNewtypeDeriving, StandaloneDeriving #-} \ deriving instance Show Bar -- {-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE DeriveGeneric, GeneralizedNewtypeDeriving #-} \ newtype Micro = Micro Int deriving Generic -- {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} \ instance Class Int where {newtype MyIO a = MyIO a deriving NewClass} {-# LANGUAGE UnboxedTuples #-} \ f :: Int -> (# Int, Int #) {-# LANGUAGE UnboxedTuples #-} \ f :: x -> (x, x); f x = (x, x) -- {-# LANGUAGE UnboxedTuples #-} \ f x = case x of (# a, b #) -> a {-# LANGUAGE GeneralizedNewtypeDeriving,UnboxedTuples #-} \ newtype T m a = T (m a) deriving (PrimMonad) {-# LANGUAGE InstanceSigs #-} \ instance Eq a => Eq (T a) where \ (==) :: T a -> T a -> Bool \ (==) (T x) (T y) = x==y {-# LANGUAGE InstanceSigs #-} \ instance Eq a => Eq (T a) where \ (==) (T x) (T y) = x==y -- {-# LANGUAGE DefaultSignatures #-} \ class Val a where; val :: a -- {-# LANGUAGE DefaultSignatures #-} \ class Val a where; val :: a; default val :: Int {-# LANGUAGE TypeApplications #-} \ foo = id -- {-# LANGUAGE TypeApplications #-} \ foo = id @Int {-# LANGUAGE LambdaCase #-} \ foo = id -- {-# LANGUAGE LambdaCase #-} \ foo = \case () -> () {-# LANGUAGE NumDecimals #-} \ foo = 12.3e2 {-# LANGUAGE NumDecimals #-} \ foo = id -- {-# LANGUAGE NumDecimals #-} \ foo = 12.345e2 -- {-# LANGUAGE TupleSections #-} \ main = map (,1,2) xs {-# LANGUAGE TupleSections #-} \ main = id -- {-# LANGUAGE OverloadedStrings #-} \ main = "test" {-# LANGUAGE OverloadedStrings #-} \ main = id -- {-# LANGUAGE OverloadedLists #-} \ main = [1] {-# LANGUAGE OverloadedLists #-} \ main [1] = True {-# LANGUAGE OverloadedLists #-} \ main = id -- {-# LANGUAGE OverloadedLabels #-} \ main = #foo {-# LANGUAGE OverloadedLabels #-} \ main = id -- {-# LANGUAGE DeriveAnyClass #-} \ main = id -- {-# LANGUAGE DeriveAnyClass #-} \ data Foo = Foo deriving Bob {-# LANGUAGE DeriveAnyClass #-} \ data Foo a = Foo a deriving (Eq,Data,Functor) -- {-# LANGUAGE MagicHash #-} \ foo# = id {-# LANGUAGE MagicHash #-} \ main = "foo"# {-# LANGUAGE MagicHash #-} \ main = 5# {-# LANGUAGE MagicHash #-} \ main = 'a'# {-# LANGUAGE MagicHash #-} \ main = 5.6# {-# LANGUAGE MagicHash #-} \ foo = id -- {-# LANGUAGE GeneralizedNewtypeDeriving #-} \ newtype X = X Int deriving newtype Show {-# LANGUAGE EmptyCase #-} \ main = case () of {} {-# LANGUAGE EmptyCase #-} \ main = case () of x -> x -- {-# LANGUAGE EmptyCase #-} \ main = case () of x -> x -- {-# LANGUAGE PolyKinds, KindSignatures #-} -- {-# LANGUAGE PolyKinds #-} {-# LANGUAGE PolyKinds, KindSignatures #-} \ data Set (cxt :: * -> *) a = Set [a] -- @Note Extension KindSignatures is implied by PolyKinds {-# LANGUAGE QuasiQuotes, OverloadedStrings #-} \ main = putStrLn [f|{T.intercalate "blah" []}|] {-# LANGUAGE NamedFieldPuns #-} \ foo = x{bar} {-# LANGUAGE PatternSynonyms #-} \ module Foo (pattern Bar) where x = 42 {-# LANGUAGE PatternSynonyms #-} \ import Foo (pattern Bar); x = 42 {-# LANGUAGE PatternSynonyms #-} \ pattern Foo s <- Bar s _ where Foo s = Bar s s {-# LANGUAGE PatternSynonyms #-} \ x = 42 -- {-# LANGUAGE MultiWayIf #-} \ x = if | b1 -> v1 | b2 -> v2 | otherwise -> v3 {-# LANGUAGE MultiWayIf #-} \ x = if b1 then v1 else if b2 then v2 else v3 -- static = 42 {-# LANGUAGE NamedFieldPuns #-} \ foo Foo{x} = x {-# LANGUAGE NamedFieldPuns #-} \ foo = Foo{x} {-# LANGUAGE NamedFieldPuns #-} \ foo = bar{x} {-# LANGUAGE NamedFieldPuns #-} -- {-# LANGUAGE NumericUnderscores #-} \ lessThanPi = (< 3.141_592_653_589_793) {-# LANGUAGE NumericUnderscores #-} \ oneMillion = 0xf4__240 {-# LANGUAGE NumericUnderscores #-} \ avogadro = 6.022140857e+23 -- {-# LANGUAGE StaticPointers #-} \ static = 42 -- {-# LANGUAGE Trustworthy #-} {-# LANGUAGE Trustworthy, NamedFieldPuns #-} -- {-# LANGUAGE Trustworthy #-} {-# LANGUAGE Haskell2010 #-} {-# LANGUAGE NoStarIsType, ExplicitNamespaces #-} \ import GHC.TypeLits(KnownNat, type (+), type (*)) {-# LANGUAGE LambdaCase, MultiWayIf, NoRebindableSyntax #-} \ foo = \case True -> 3 -- {-# LANGUAGE LambdaCase, NoRebindableSyntax #-} -} module Hint.Extensions(extensionsHint) where import Hint.Type(ModuHint, rawIdea,Severity(Warning),Note(..),toSS,ghcAnnotations,ghcModule) import Extension import Data.Generics.Uniplate.DataOnly import Control.Monad.Extra import Data.Maybe import Data.List.Extra import Data.Data import Refact.Types import qualified Data.Set as Set import qualified Data.Map as Map import SrcLoc import GHC.Hs import BasicTypes import Class import RdrName import ForeignCall import GHC.Util import GHC.LanguageExtensions.Type import Language.Haskell.GhclibParserEx.GHC.Hs.Pat import Language.Haskell.GhclibParserEx.GHC.Hs.Expr import Language.Haskell.GhclibParserEx.GHC.Hs.Types import Language.Haskell.GhclibParserEx.GHC.Hs.Decls import Language.Haskell.GhclibParserEx.GHC.Hs.Binds import Language.Haskell.GhclibParserEx.GHC.Hs.ImpExp import Language.Haskell.GhclibParserEx.GHC.Driver.Session import Language.Haskell.GhclibParserEx.GHC.Utils.Outputable import Language.Haskell.GhclibParserEx.GHC.Types.Name.Reader extensionsHint :: ModuHint extensionsHint _ x = [ rawIdea Hint.Type.Warning "Unused LANGUAGE pragma" sl (comment (mkLanguagePragmas sl exts)) (Just newPragma) ( [RequiresExtension (show gone) | (_, Just x) <- before \\ after, gone <- Map.findWithDefault [] x disappear] ++ [ Note $ "Extension " ++ show x ++ " is " ++ reason x | (_, Just x) <- explainedRemovals]) [ModifyComment (toSS (mkLanguagePragmas sl exts)) newPragma] | (L sl _, exts) <- languagePragmas $ pragmas (ghcAnnotations x) , let before = [(x, readExtension x) | x <- exts] , let after = filter (maybe True (`Set.member` keep) . snd) before , before /= after , let explainedRemovals | null after && not (any (`Map.member` implied) $ mapMaybe snd before) = [] | otherwise = before \\ after , let newPragma = if null after then "" else comment (mkLanguagePragmas sl $ map fst after) ] where usedTH :: Bool usedTH = used TemplateHaskell (ghcModule x) || used QuasiQuotes (ghcModule x) -- If TH or QuasiQuotes is on, can use all other extensions -- programmatically. -- All the extensions defined to be used. extensions :: Set.Set Extension extensions = Set.fromList $ mapMaybe readExtension $ concatMap snd $ languagePragmas (pragmas (ghcAnnotations x)) -- Those extensions we detect to be useful. useful :: Set.Set Extension useful = if usedTH then extensions else Set.filter (`usedExt` ghcModule x) extensions -- Those extensions which are useful, but implied by other useful -- extensions. implied :: Map.Map Extension Extension implied = Map.fromList [ (e, a) | e <- Set.toList useful , a:_ <- [filter (`Set.member` useful) $ extensionImpliedEnabledBy e] ] -- Those we should keep. keep :: Set.Set Extension keep = useful `Set.difference` Map.keysSet implied -- The meaning of (a,b) is a used to imply b, but has gone, so -- suggest enabling b. disappear :: Map.Map Extension [Extension] disappear = Map.fromListWith (++) $ nubOrdOn snd -- Only keep one instance for each of a. [ (e, [a]) | e <- Set.toList $ extensions `Set.difference` keep , a <- fst $ extensionImplies e , a `Set.notMember` useful , usedTH || usedExt a (ghcModule x) ] reason :: Extension -> String reason x = case Map.lookup x implied of Just a -> "implied by " ++ show a Nothing -> "not used" deriveHaskell = ["Eq","Ord","Enum","Ix","Bounded","Read","Show"] deriveGenerics = ["Data","Typeable","Generic","Generic1","Lift"] deriveCategory = ["Functor","Foldable","Traversable"] -- | Classes that can't require newtype deriving noDeriveNewtype = delete "Enum" deriveHaskell ++ -- Enum can't always be derived on a newtype deriveGenerics -- Generics stuff can't newtype derive since it has the ctor in it -- | Classes that can appear as stock, and can't appear as anyclass deriveStock :: [String] deriveStock = deriveHaskell ++ deriveGenerics ++ deriveCategory usedExt :: Extension -> Located (HsModule GhcPs) -> Bool usedExt NumDecimals = hasS isWholeFrac -- Only whole number fractions are permitted by NumDecimals -- extension. Anything not-whole raises an error. usedExt DeriveLift = hasDerive ["Lift"] usedExt DeriveAnyClass = not . null . derivesAnyclass . derives usedExt x = used x used :: Extension -> Located (HsModule GhcPs) -> Bool used RecursiveDo = hasS isMDo ||^ hasS isRecStmt used ParallelListComp = hasS isParComp used FunctionalDependencies = hasT (un :: FunDep (Located RdrName)) used ImplicitParams = hasT (un :: HsIPName) used TypeApplications = hasS isTypeApp used EmptyDataDecls = hasS f where f :: HsDataDefn GhcPs -> Bool f (HsDataDefn _ _ _ _ _ [] _) = True f _ = False used EmptyCase = hasS f where f :: HsExpr GhcPs -> Bool f (HsCase _ _ (MG _ (L _ []) _)) = True f (HsLamCase _ (MG _ (L _ []) _)) = True f _ = False used KindSignatures = hasT (un :: HsKind GhcPs) used BangPatterns = hasS isPBangPat ||^ hasS isStrictMatch used TemplateHaskell = hasT2' (un :: (HsBracket GhcPs, HsSplice GhcPs)) ||^ hasS f ||^ hasS isSpliceDecl where f :: HsBracket GhcPs -> Bool f VarBr{} = True f TypBr{} = True f _ = False used ForeignFunctionInterface = hasT (un :: CCallConv) used PatternGuards = hasS f where f :: GRHS GhcPs (LHsExpr GhcPs) -> Bool f (GRHS _ xs _) = g xs f _ = False -- Extension constructor g :: [GuardLStmt GhcPs] -> Bool g [] = False g [L _ BodyStmt{}] = False g _ = True used StandaloneDeriving = hasS isDerivD used TypeOperators = hasS tyOpInSig ||^ hasS tyOpInDecl where tyOpInSig :: HsType GhcPs -> Bool tyOpInSig = \case HsOpTy{} -> True; _ -> False tyOpInDecl :: HsDecl GhcPs -> Bool tyOpInDecl = \case (TyClD _ (FamDecl _ FamilyDecl{fdLName})) -> isOp fdLName (TyClD _ SynDecl{tcdLName}) -> isOp tcdLName (TyClD _ DataDecl{tcdLName}) -> isOp tcdLName (TyClD _ ClassDecl{tcdLName, tcdATs}) -> any isOp (tcdLName : [fdLName famDecl | L _ famDecl <- tcdATs]) _ -> False isOp (L _ name) = isSymbolRdrName name used RecordWildCards = hasS hasFieldsDotDot ||^ hasS hasPFieldsDotDot used RecordPuns = hasS isPFieldPun ||^ hasS isFieldPun ||^ hasS isFieldPunUpdate used UnboxedTuples = hasS isUnboxedTuple ||^ hasS (== Unboxed) ||^ hasS isDeriving where -- detect if there are deriving declarations or data ... deriving stuff -- by looking for the deriving strategy both contain (even if its Nothing) -- see https://github.com/ndmitchell/hlint/issues/833 for why we care isDeriving :: Maybe (LDerivStrategy GhcPs) -> Bool isDeriving _ = True used PackageImports = hasS f where f :: ImportDecl GhcPs -> Bool f ImportDecl{ideclPkgQual=Just _} = True f _ = False used QuasiQuotes = hasS isQuasiQuote ||^ hasS isTyQuasiQuote used ViewPatterns = hasS isPViewPat used InstanceSigs = hasS f where f :: HsDecl GhcPs -> Bool f (InstD _ decl) = hasT (un :: Sig GhcPs) decl f _ = False used DefaultSignatures = hasS isClsDefSig used DeriveDataTypeable = hasDerive ["Data","Typeable"] used DeriveFunctor = hasDerive ["Functor"] used DeriveFoldable = hasDerive ["Foldable"] used DeriveTraversable = hasDerive ["Traversable","Foldable","Functor"] used DeriveGeneric = hasDerive ["Generic","Generic1"] used GeneralizedNewtypeDeriving = not . null . derivesNewtype' . derives used MultiWayIf = hasS isMultiIf used NumericUnderscores = hasS f where f :: OverLitVal -> Bool f (HsIntegral (IL (SourceText t) _ _)) = '_' `elem` t f (HsFractional (FL (SourceText t) _ _)) = '_' `elem` t f _ = False used LambdaCase = hasS isLCase used TupleSections = hasS isTupleSection used OverloadedStrings = hasS isString used OverloadedLists = hasS isListExpr ||^ hasS isListPat where isListExpr :: HsExpr GhcPs -> Bool isListExpr ExplicitList{} = True isListExpr ArithSeq{} = True isListExpr _ = False isListPat :: Pat GhcPs -> Bool isListPat ListPat{} = True isListPat _ = False used OverloadedLabels = hasS isLabel where isLabel :: HsExpr GhcPs -> Bool isLabel HsOverLabel{} = True isLabel _ = False used Arrows = hasS isProc used TransformListComp = hasS isTransStmt used MagicHash = hasS f ||^ hasS isPrimLiteral where f :: RdrName -> Bool f s = "#" `isSuffixOf` occNameStr s used PatternSynonyms = hasS isPatSynBind ||^ hasS isPatSynIE used _= const True hasDerive :: [String] -> Located (HsModule GhcPs) -> Bool hasDerive want = any (`elem` want) . derivesStock' . derives -- Derivations can be implemented using any one of 3 strategies, so for each derivation -- add it to all the strategies that might plausibly implement it data Derives = Derives {derivesStock' :: [String] ,derivesAnyclass :: [String] ,derivesNewtype' :: [String] } instance Semigroup Derives where Derives x1 x2 x3 <> Derives y1 y2 y3 = Derives (x1 ++ y1) (x2 ++ y2) (x3 ++ y3) instance Monoid Derives where mempty = Derives [] [] [] mappend = (<>) addDerives :: Maybe NewOrData -> Maybe (DerivStrategy GhcPs) -> [String] -> Derives addDerives _ (Just s) xs = case s of StockStrategy -> mempty{derivesStock' = xs} AnyclassStrategy -> mempty{derivesAnyclass = xs} NewtypeStrategy -> mempty{derivesNewtype' = xs} ViaStrategy{} -> mempty addDerives nt _ xs = mempty {derivesStock' = stock ,derivesAnyclass = other ,derivesNewtype' = if maybe True isNewType nt then filter (`notElem` noDeriveNewtype) xs else []} where (stock, other) = partition (`elem` deriveStock) xs derives :: Located (HsModule GhcPs) -> Derives derives (L _ m) = mconcat $ map decl (childrenBi m) ++ map idecl (childrenBi m) where idecl :: Located (DataFamInstDecl GhcPs) -> Derives idecl (L _ (DataFamInstDecl (HsIB _ FamEqn {feqn_rhs=HsDataDefn {dd_ND=dn, dd_derivs=(L _ ds)}}))) = g dn ds idecl _ = mempty decl :: LHsDecl GhcPs -> Derives decl (L _ (TyClD _ (DataDecl _ _ _ _ HsDataDefn {dd_ND=dn, dd_derivs=(L _ ds)}))) = g dn ds -- Data declaration. decl (L _ (DerivD _ (DerivDecl _ (HsWC _ sig) strategy _))) = addDerives Nothing (fmap unLoc strategy) [derivedToStr sig] -- A deriving declaration. decl _ = mempty g :: NewOrData -> [LHsDerivingClause GhcPs] -> Derives g dn ds = mconcat [addDerives (Just dn) (fmap unLoc strategy) $ map derivedToStr tys | L _ (HsDerivingClause _ strategy (L _ tys)) <- ds] derivedToStr :: LHsSigType GhcPs -> String derivedToStr (HsIB _ t) = ih t where ih :: LHsType GhcPs -> String ih (L _ (HsQualTy _ _ a)) = ih a ih (L _ (HsParTy _ a)) = ih a ih (L _ (HsAppTy _ a _)) = ih a ih (L _ (HsTyVar _ _ a)) = unsafePrettyPrint $ unqual a ih (L _ a) = unsafePrettyPrint a -- I don't anticipate this case is called. derivedToStr _ = "" -- new ctor un = undefined hasT t x = not $ null (universeBi x `asTypeOf` [t]) hasT2' ~(t1,t2) = hasT t1 ||^ hasT t2 hasS :: (Data x, Data a) => (a -> Bool) -> x -> Bool hasS test = any test . universeBi hlint-3.1.6/src/Hint/Export.hs0000644000000000000000000000335213656755416014360 0ustar0000000000000000{- Suggest using better export declarations main = 1 module Foo where foo = 1 -- module Foo(module Foo) where @NoRefactor module Foo(foo) where foo = 1 module Foo(module Foo) where foo = 1 -- @Ignore module Foo(...) where @NoRefactor module Foo(module Foo, foo) where foo = 1 -- module Foo(..., foo) where @NoRefactor -} {-# LANGUAGE TypeFamilies #-} module Hint.Export(exportHint) where import Hint.Type(ModuHint, ModuleEx(..),ideaNote,ignore,Note(..)) import GHC.Hs import Module import SrcLoc import OccName import RdrName exportHint :: ModuHint exportHint _ (ModuleEx (L s m@HsModule {hsmodName = Just name, hsmodExports = exports}) _) | Nothing <- exports = let r = o{ hsmodExports = Just (noLoc [noLoc (IEModuleContents noExtField name)] )} in [(ignore "Use module export list" (L s o) (noLoc r) []){ideaNote = [Note "an explicit list is usually better"]}] | Just (L _ xs) <- exports , mods <- [x | x <- xs, isMod x] , modName <- moduleNameString (unLoc name) , names <- [ moduleNameString (unLoc n) | (L _ (IEModuleContents _ n)) <- mods] , exports' <- [x | x <- xs, not (matchesModName modName x)] , modName `elem` names = let dots = mkRdrUnqual (mkVarOcc " ... ") r = o{ hsmodExports = Just (noLoc (noLoc (IEVar noExtField (noLoc (IEName (noLoc dots)))) : exports') )} in [ignore "Use explicit module export list" (L s o) (noLoc r) []] where o = m{hsmodImports=[], hsmodDecls=[], hsmodDeprecMessage=Nothing, hsmodHaddockModHeader=Nothing } isMod (L _ (IEModuleContents _ _)) = True isMod _ = False matchesModName m (L _ (IEModuleContents _ (L _ n))) = moduleNameString n == m matchesModName _ _ = False exportHint _ _ = [] hlint-3.1.6/src/Hint/Duplicate.hs0000644000000000000000000001070313674632146015001 0ustar0000000000000000{-# LANGUAGE PatternGuards, ScopedTypeVariables #-} {-# LANGUAGE FlexibleContexts #-} {- Find bindings within a let, and lists of statements If you have n the same, error out foo = a where {a = 1; b = 2; c = 3} \ bar = a where {a = 1; b = 2; c = 3} -- ??? @NoRefactor main = do a; a; a; a main = do a; a; a; a; a; a -- ??? @NoRefactor: refactoring not supported for duplication hints. main = do a; a; a; a; a; a; a -- ??? @NoRefactor main = do (do b; a; a; a); do (do c; a; a; a) -- ??? @NoRefactor main = do a; a; a; b; a; a; a -- ??? @NoRefactor main = do a; a; a; b; a; a {-# ANN main "HLint: ignore Reduce duplication" #-}; main = do a; a; a; a; a; a -- @Ignore ??? @NoRefactor {-# HLINT ignore main "Reduce duplication" #-}; main = do a; a; a; a; a; a -- @Ignore ??? @NoRefactor {- HLINT ignore main "Reduce duplication" -}; main = do a; a; a; a; a; a -- @Ignore ??? @NoRefactor -} module Hint.Duplicate(duplicateHint) where import Hint.Type (CrossHint, ModuleEx(..), Idea(..),rawIdeaN,Severity(Suggestion,Warning)) import Data.Data import Data.Generics.Uniplate.DataOnly import Data.Default import Data.Maybe import Data.Tuple.Extra import Data.List hiding (find) import qualified Data.List.NonEmpty as NE import qualified Data.Map as Map import SrcLoc import GHC.Hs import Outputable import Bag import GHC.Util import Language.Haskell.GhclibParserEx.GHC.Hs import Language.Haskell.GhclibParserEx.GHC.Hs.ExtendInstances import Language.Haskell.GhclibParserEx.GHC.Utils.Outputable duplicateHint :: CrossHint duplicateHint ms = -- Do expressions. dupes [ (m, d, y) | (m, d, x) <- ds , HsDo _ _ (L _ y) :: HsExpr GhcPs <- universeBi x ] ++ -- Bindings in a 'let' expression or a 'where' clause. dupes [ (m, d, y) | (m, d, x) <- ds , HsValBinds _ (ValBinds _ b _ ) :: HsLocalBinds GhcPs <- universeBi x , let y = bagToList b ] where ds = [(modName m, fromMaybe "" (declName d), unLoc d) | ModuleEx m _ <- map snd ms , d <- hsmodDecls (unLoc m)] dupes :: (Outputable e, Data e) => [(String, String, [Located e])] -> [Idea] dupes ys = [(rawIdeaN (if length xs >= 5 then Hint.Type.Warning else Suggestion) "Reduce duplication" p1 (unlines $ map unsafePrettyPrint xs) (Just $ "Combine with " ++ showSrcSpan p2) [] ){ideaModule = [m1, m2], ideaDecl = [d1, d2]} | ((m1, d1, SrcSpanD p1), (m2, d2, SrcSpanD p2), xs) <- duplicateOrdered 3 $ map f ys] where f (m, d, xs) = [((m, d, SrcSpanD (getLoc x)), extendInstances (stripLocs x)) | x <- xs] --------------------------------------------------------------------- -- DUPLICATE FINDING -- | The position to return if we match at this point, and the map of where to go next -- If two runs have the same vals, always use the first pos you find data Dupe pos val = Dupe pos (Map.Map val (Dupe pos val)) find :: Ord val => [val] -> Dupe pos val -> (pos, Int) find (v:vs) (Dupe p mp) | Just d <- Map.lookup v mp = second (+1) $ find vs d find _ (Dupe p mp) = (p, 0) add :: Ord val => pos -> [val] -> Dupe pos val -> Dupe pos val add pos [] d = d add pos (v:vs) (Dupe p mp) = Dupe p $ Map.insertWith f v (add pos vs $ Dupe pos Map.empty) mp where f new = add pos vs duplicateOrdered :: forall pos val. (Ord pos, Default pos, Ord val) => Int -> [[(pos,val)]] -> [(pos,pos,[val])] duplicateOrdered threshold xs = concat $ concat $ snd $ mapAccumL f (Dupe def Map.empty) xs where f :: Dupe pos val -> [(pos, val)] -> (Dupe pos val, [[(pos, pos, [val])]]) f d xs = second overlaps $ mapAccumL (g pos) d $ onlyAtLeast threshold $ tails xs where pos = Map.fromList $ zip (map fst xs) [0..] g :: Map.Map pos Int -> Dupe pos val -> NE.NonEmpty (pos, val) -> (Dupe pos val, [(pos, pos, [val])]) g pos d xs = (d2, res) where res = [(p,pme,take mx vs) | i >= threshold ,let mx = maybe i (\x -> min i $ (pos Map.! pme) - x) $ Map.lookup p pos ,mx >= threshold] vs = NE.toList $ snd <$> xs (p,i) = find vs d pme = fst $ NE.head xs d2 = add pme vs d onlyAtLeast n = mapMaybe $ \l -> case l of x:xs | length l >= n -> Just (x NE.:| xs) _ -> Nothing overlaps (x@((_,_,n):_):xs) = x : overlaps (drop (length n - 1) xs) overlaps (x:xs) = x : overlaps xs overlaps [] = [] hlint-3.1.6/src/Hint/Comment.hs0000644000000000000000000000307413656755416014502 0ustar0000000000000000 {- {- MISSING HASH #-} -- {-# MISSING HASH #-} {- INLINE X -} {- INLINE Y -} -- {-# INLINE Y #-} {- INLINE[~k] f -} -- {-# INLINE[~k] f #-} {- NOINLINE Y -} -- {-# NOINLINE Y #-} {- UNKNOWN Y -} INLINE X -} module Hint.Comment(commentHint) where import Hint.Type import Data.Char import Data.List.Extra import Refact.Types(Refactoring(ModifyComment)) import SrcLoc import ApiAnnotation import GHC.Util directives :: [String] directives = words $ "LANGUAGE OPTIONS_GHC INCLUDE WARNING DEPRECATED MINIMAL INLINE NOINLINE INLINABLE " ++ "CONLIKE LINE SPECIALIZE SPECIALISE UNPACK NOUNPACK SOURCE" commentHint :: ModuHint commentHint _ m = concatMap chk (ghcComments m) where chk :: Located AnnotationComment -> [Idea] chk comm | isMultiline, "#" `isSuffixOf` s && not ("#" `isPrefixOf` s) = [grab "Fix pragma markup" comm $ '#':s] | isMultiline, name `elem` directives = [grab "Use pragma syntax" comm $ "# " ++ trim s ++ " #"] where isMultiline = isCommentMultiline comm s = commentText comm name = takeWhile (\x -> isAlphaNum x || x == '_') $ trimStart s chk _ = [] grab :: String -> Located AnnotationComment -> String -> Idea grab msg o@(L pos _) s2 = let s1 = commentText o in rawIdea Suggestion msg pos (f s1) (Just $ f s2) [] refact where f s = if isCommentMultiline o then "{-" ++ s ++ "-}" else "--" ++ s refact = [ModifyComment (toRefactSrcSpan pos) (f s2)] hlint-3.1.6/src/Hint/Bracket.hs0000644000000000000000000002510313671470061014433 0ustar0000000000000000{-# LANGUAGE ViewPatterns, ScopedTypeVariables #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE TypeFamilies #-} {- Raise an error if you are bracketing an atom, or are enclosed by a list bracket. -- expression bracket reduction yes = (f x) x -- @Suggestion f x x no = f (x x) yes = (foo) -- foo yes = (foo bar) -- @Suggestion foo bar yes = foo (bar) -- @Warning bar yes = foo ((x x)) -- @Suggestion (x x) yes = (f x) ||| y -- @Suggestion f x ||| y yes = if (f x) then y else z -- @Suggestion if f x then y else z yes = if x then (f y) else z -- @Suggestion if x then f y else z yes = (a foo) :: Int -- @Suggestion a foo :: Int yes = [(foo bar)] -- @Suggestion [foo bar] yes = foo ((x y), z) -- @Suggestion (x y, z) yes = C { f = (e h) } -- @Suggestion C {f = e h} yes = \ x -> (x && x) -- @Suggestion \x -> x && x no = \(x -> y) -> z yes = (`foo` (bar baz)) -- @Suggestion (`foo` bar baz) yes = f ((x)) -- @Warning x main = do f; (print x) -- @Suggestion do f print x yes = f (x) y -- @Warning x no = f (+x) y no = f ($x) y no = ($x) yes = (($x)) no = ($1) yes = (($1)) -- @Warning ($1) no = (+5) yes = ((+5)) -- @Warning (+5) issue909 = case 0 of { _ | n <- (0 :: Int) -> n } issue909 = foo (\((x :: z) -> y) -> 9 + x * 7) issue909 = foo (\((x : z) -> y) -> 9 + x * 7) -- \(x : z -> y) -> 9 + x * 7 issue909 = let ((x:: y) -> z) = q in q issue909 = do {((x :: y) -> z) <- e; return 1} issue970 = (f x +) (g x) -- f x + (g x) @NoRefactor issue969 = (Just \x -> x || x) *> Just True -- type bracket reduction foo :: (Int -> Int) -> Int foo :: (Maybe Int) -> a -- @Suggestion Maybe Int -> a instance Named (DeclHead S) data Foo = Foo {foo :: (Maybe Foo)} -- @Suggestion foo :: Maybe Foo -- pattern bracket reduction foo (x:xs) = 1 foo (True) = 1 -- @Warning True foo ((True)) = 1 -- @Warning True foo (A{}) = True -- A{} f x = case x of (Nothing) -> 1; _ -> 2 -- Nothing -- dollar reduction tests no = groupFsts . sortFst $ mr yes = split "to" $ names -- split "to" names yes = white $ keysymbol -- white keysymbol yes = operator foo $ operator -- operator foo operator no = operator foo $ operator bar yes = return $ Record{a=b} no = f $ [1,2..5] -- f [1,2..5] @NoRefactor: apply-refact bug; see apply-refact #51 -- $/bracket rotation tests yes = (b $ c d) ++ e -- b (c d) ++ e yes = (a b $ c d) ++ e -- a b (c d) ++ e no = (f . g $ a) ++ e no = quickCheck ((\h -> cySucc h == succ h) :: Hygiene -> Bool) foo = (case x of y -> z; q -> w) :: Int -- backup fixity resolution main = do a += b . c; return $ a . b -- <$> bracket tests yes = (foo . bar x) <$> baz q -- foo . bar x <$> baz q no = foo . bar x <$> baz q -- annotations main = 1; {-# ANN module ("HLint: ignore Use camelCase" :: String) #-} main = 1; {-# ANN module (1 + (2)) #-} -- 2 -- special case from esqueleto, see #224 main = operate <$> (select $ from $ \user -> return $ user ^. UserEmail) -- unknown fixity, see #426 bad x = x . (x +? x . x) -- special case people don't like to warn on special = foo $ f{x=1} special = foo $ Rec{x=1} special = foo (f{x=1}) loadCradleOnlyonce = skipManyTill anyMessage (message @PublishDiagnosticsNotification) -} module Hint.Bracket(bracketHint) where import Hint.Type(DeclHint,Idea(..),rawIdea,warn,suggest,Severity(..),toRefactSrcSpan,toSS) import Data.Data import Data.List.Extra import Data.Generics.Uniplate.DataOnly import Refact.Types import GHC.Hs import Outputable import SrcLoc import GHC.Util import Language.Haskell.GhclibParserEx.GHC.Hs.Expr import Language.Haskell.GhclibParserEx.GHC.Utils.Outputable bracketHint :: DeclHint bracketHint _ _ x = concatMap (\x -> bracket prettyExpr isPartialAtom True x ++ dollar x) (childrenBi (descendBi annotations x) :: [LHsExpr GhcPs]) ++ concatMap (bracket unsafePrettyPrint (const False) False) (childrenBi x :: [LHsType GhcPs]) ++ concatMap (bracket unsafePrettyPrint (const False) False) (childrenBi x :: [LPat GhcPs]) ++ concatMap fieldDecl (childrenBi x) where -- Brackets the roots of annotations are fine, so we strip them. annotations :: AnnDecl GhcPs -> AnnDecl GhcPs annotations= descendBi $ \x -> case (x :: LHsExpr GhcPs) of L l (HsPar _ x) -> x x -> x -- If we find ourselves in the context of a section and we want to -- issue a warning that a child therein has unneccessary brackets, -- we'd rather report 'Found : (`Foo` (Bar Baz))' rather than 'Found : -- `Foo` (Bar Baz)'. If left to 'unsafePrettyPrint' we'd get the -- latter (in contrast to the HSE pretty printer). This patches things -- up. prettyExpr :: LHsExpr GhcPs -> String prettyExpr s@(L _ SectionL{}) = unsafePrettyPrint (noLoc (HsPar noExtField s) :: LHsExpr GhcPs) prettyExpr s@(L _ SectionR{}) = unsafePrettyPrint (noLoc (HsPar noExtField s) :: LHsExpr GhcPs) prettyExpr x = unsafePrettyPrint x -- Dirty, should add to Brackets type class I think tyConToRtype :: String -> RType tyConToRtype "Exp" = Expr tyConToRtype "Type" = Type tyConToRtype "HsType" = Type tyConToRtype "Pat" = Pattern tyConToRtype _ = Expr findType :: (Data a) => a -> RType findType = tyConToRtype . dataTypeName . dataTypeOf -- 'Just _' if at least one set of parens were removed. 'Nothing' if -- zero parens were removed. remParens' :: Brackets a => a -> Maybe a remParens' = fmap go . remParen where go e = maybe e go (remParen e) isPartialAtom :: LHsExpr GhcPs -> Bool -- Might be '$x', which was really '$ x', but TH enabled misparsed it. isPartialAtom (L _ (HsSpliceE _ (HsTypedSplice _ HasDollar _ _) )) = True isPartialAtom (L _ (HsSpliceE _ (HsUntypedSplice _ HasDollar _ _) )) = True isPartialAtom x = isRecConstr x || isRecUpdate x bracket :: forall a . (Data a, Data (SrcSpanLess a), HasSrcSpan a, Outputable a, Brackets a) => (a -> String) -> (a -> Bool) -> Bool -> a -> [Idea] bracket pretty isPartialAtom root = f Nothing where msg = "Redundant bracket" -- 'f' is a (generic) function over types in 'Brackets -- (expressions, patterns and types). Arguments are, 'f (Maybe -- (index, parent, gen)) child'. f :: (HasSrcSpan a, Data a, Outputable a, Brackets a) => Maybe (Int, a , a -> a) -> a -> [Idea] -- No context. Removing parentheses from 'x' succeeds? f Nothing o@(remParens' -> Just x) -- If at the root, or 'x' is an atom, 'x' parens are redundant. | root || isAtom x , not $ isPartialAtom x = (if isAtom x then bracketError else bracketWarning) msg o x : g x -- In some context, removing parentheses from 'x' succeeds and 'x' -- is atomic? f Just{} o@(remParens' -> Just x) | isAtom x , not $ isPartialAtom x = bracketError msg o x : g x -- In some context, removing parentheses from 'x' succeeds. Does -- 'x' actually need bracketing in this context? f (Just (i, o, gen)) v@(remParens' -> Just x) | not $ needBracket i o x, not $ isPartialAtom x = rawIdea Suggestion msg (getLoc v) (pretty o) (Just (pretty (gen x))) [] [r] : g x where typ = findType (unLoc v) r = Replace typ (toSS v) [("x", toSS x)] "x" -- Regardless of the context, there are no parentheses to remove -- from 'x'. f _ x = g x g :: (HasSrcSpan a, Data a, Outputable a, Brackets a) => a -> [Idea] -- Enumerate over all the immediate children of 'o' looking for -- redundant parentheses in each. g o = concat [f (Just (i, o, gen)) x | (i, (x, gen)) <- zipFrom 0 $ holes o] bracketWarning :: (HasSrcSpan a, HasSrcSpan b, Data (SrcSpanLess b), Outputable a, Outputable b) => String -> a -> b -> Idea bracketWarning msg o x = suggest msg o x [Replace (findType (unLoc x)) (toSS o) [("x", toSS x)] "x"] bracketError :: (HasSrcSpan a, HasSrcSpan b, Data (SrcSpanLess b), Outputable a, Outputable b ) => String -> a -> b -> Idea bracketError msg o x = warn msg o x [Replace (findType (unLoc x)) (toSS o) [("x", toSS x)] "x"] fieldDecl :: LConDeclField GhcPs -> [Idea] fieldDecl o@(L loc f@ConDeclField{cd_fld_type=v@(L l (HsParTy _ c))}) = let r = L loc (f{cd_fld_type=c}) :: LConDeclField GhcPs in [rawIdea Suggestion "Redundant bracket" l (showSDocUnsafe $ ppr_fld o) -- Note this custom printer! (Just (showSDocUnsafe $ ppr_fld r)) [] [Replace Type (toSS v) [("x", toSS c)] "x"]] where -- If we call 'unsafePrettyPrint' on a field decl, we won't like -- the output (e.g. "[foo, bar] :: T"). Here we use a custom -- printer to work around (snarfed from -- https://hackage.haskell.org/package/ghc-lib-parser-8.8.1/docs/src/HsTypes.html#pprConDeclFields). ppr_fld (L _ ConDeclField { cd_fld_names = ns, cd_fld_type = ty, cd_fld_doc = doc }) = ppr_names ns <+> dcolon <+> ppr ty <+> ppr_mbDoc doc ppr_fld (L _ (XConDeclField x)) = ppr x ppr_names [n] = ppr n ppr_names ns = sep (punctuate comma (map ppr ns)) fieldDecl _ = [] -- This function relies heavily on fixities having been applied to the -- raw parse tree. dollar :: LHsExpr GhcPs -> [Idea] dollar = concatMap f . universe where f x = [ (suggest "Redundant $" x y [r]){ideaSpan = getLoc d} | o@(L _ (OpApp _ a d b)) <- [x], isDol d , let y = noLoc (HsApp noExtField a b) :: LHsExpr GhcPs , not $ needBracket 0 y a , not $ needBracket 1 y b , not $ isPartialAtom b , let r = Replace Expr (toSS x) [("a", toSS a), ("b", toSS b)] "a b"] ++ [ suggest "Move brackets to avoid $" x (t y) [r] |(t, e@(L _ (HsPar _ (L _ (OpApp _ a1 op1 a2))))) <- splitInfix x , isDol op1 , isVar a1 || isApp a1 || isPar a1, not $ isAtom a2 , varToStr a1 /= "select" -- special case for esqueleto, see #224 , let y = noLoc $ HsApp noExtField a1 (noLoc (HsPar noExtField a2)) , let r = Replace Expr (toSS e) [("a", toSS a1), ("b", toSS a2)] "a (b)" ] ++ -- Special case of (v1 . v2) <$> v3 [ (suggest "Redundant bracket" x y [r]){ideaSpan = locPar} | L _ (OpApp _ (L locPar (HsPar _ o1@(L locNoPar (OpApp _ v1 (isDot -> True) v2)))) o2 v3) <- [x], varToStr o2 == "<$>" , let y = noLoc (OpApp noExtField o1 o2 v3) :: LHsExpr GhcPs , let r = Replace Expr (toRefactSrcSpan locPar) [("a", toRefactSrcSpan locNoPar)] "a"] ++ [ suggest "Redundant section" x y [] | L _ (HsApp _ (L _ (HsPar _ (L _ (SectionL _ a b)))) c) <- [x] -- , error $ show (unsafePrettyPrint a, gshow b, unsafePrettyPrint c) , let y = noLoc $ OpApp noExtField a b c :: LHsExpr GhcPs] splitInfix :: LHsExpr GhcPs -> [(LHsExpr GhcPs -> LHsExpr GhcPs, LHsExpr GhcPs)] splitInfix (L l (OpApp _ lhs op rhs)) = [(L l . OpApp noExtField lhs op, rhs), (\lhs -> L l (OpApp noExtField lhs op rhs), lhs)] splitInfix _ = [] hlint-3.1.6/src/Hint/All.hs0000644000000000000000000000506013661521317013570 0ustar0000000000000000 module Hint.All( Hint(..), ModuHint, resolveHints, hintRules, builtinHints ) where import Data.Monoid import Config.Type import Data.Either import Data.List.Extra import Hint.Type import Timing import Util import Prelude import Hint.Match import Hint.List import Hint.ListRec import Hint.Monad import Hint.Lambda import Hint.Bracket import Hint.Naming import Hint.Pattern import Hint.Import import Hint.Export import Hint.Pragma import Hint.Restrict import Hint.Extensions import Hint.Duplicate import Hint.Comment import Hint.Unsafe import Hint.NewType import Hint.Smell -- | A list of the builtin hints wired into HLint. -- This list is likely to grow over time. data HintBuiltin = HintList | HintListRec | HintMonad | HintLambda | HintBracket | HintNaming | HintPattern | HintImport | HintExport | HintPragma | HintExtensions | HintUnsafe | HintDuplicate | HintRestrict | HintComment | HintNewType | HintSmell deriving (Show,Eq,Ord,Bounded,Enum) builtin :: HintBuiltin -> Hint builtin x = case x of -- Ghc. HintLambda -> decl lambdaHint HintImport -> modu importHint HintExport -> modu exportHint HintComment -> modu commentHint HintPragma -> modu pragmaHint HintDuplicate -> mods duplicateHint HintRestrict -> mempty{hintModule=restrictHint} HintList -> decl listHint HintNewType -> decl newtypeHint HintUnsafe -> decl unsafeHint HintListRec -> decl listRecHint HintNaming -> decl namingHint HintBracket -> decl bracketHint HintSmell -> mempty{hintDecl=smellHint,hintModule=smellModuleHint} HintPattern -> decl patternHint HintMonad -> decl monadHint HintExtensions -> modu extensionsHint where wrap = timed "Hint" (drop 4 $ show x) . forceList decl f = mempty{hintDecl=const $ \a b c -> wrap $ f a b c} modu f = mempty{hintModule=const $ \a b -> wrap $ f a b} mods f = mempty{hintModules=const $ \a -> wrap $ f a} -- | A list of builtin hints, currently including entries such as @\"List\"@ and @\"Bracket\"@. builtinHints :: [(String, Hint)] builtinHints = [(drop 4 $ show h, builtin h) | h <- enumerate] -- | Transform a list of 'HintBuiltin' or 'HintRule' into a 'Hint'. resolveHints :: [Either HintBuiltin HintRule] -> Hint resolveHints xs = mconcat $ mempty{hintDecl=const $ readMatch rights} : map builtin (nubOrd lefts) where (lefts,rights) = partitionEithers xs -- | Transform a list of 'HintRule' into a 'Hint'. hintRules :: [HintRule] -> Hint hintRules = resolveHints . map Right hlint-3.1.6/src/GHC/0000755000000000000000000000000013674744765012244 5ustar0000000000000000hlint-3.1.6/src/GHC/Util.hs0000644000000000000000000000531213671470061013474 0ustar0000000000000000{-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE ViewPatterns #-} module GHC.Util ( module GHC.Util.View , module GHC.Util.FreeVars , module GHC.Util.ApiAnnotation , module GHC.Util.HsDecl , module GHC.Util.HsExpr , module GHC.Util.SrcLoc , module GHC.Util.DynFlags , module GHC.Util.Scope , module GHC.Util.Unify , parsePragmasIntoDynFlags , fileToModule , pattern SrcSpan, srcSpanFilename, srcSpanStartLine', srcSpanStartColumn, srcSpanEndLine', srcSpanEndColumn , pattern SrcLoc, srcFilename, srcLine, srcColumn , showSrcSpan, ) where import GHC.Util.View import GHC.Util.FreeVars import GHC.Util.ApiAnnotation import GHC.Util.HsExpr import GHC.Util.HsDecl import GHC.Util.SrcLoc import GHC.Util.DynFlags import GHC.Util.Scope import GHC.Util.Unify import Language.Haskell.GhclibParserEx.GHC.Parser (parseFile) import Language.Haskell.GhclibParserEx.GHC.Driver.Session (parsePragmasIntoDynFlags) import Language.Haskell.GhclibParserEx.GHC.Utils.Outputable import GHC.Hs import Lexer import SrcLoc import DynFlags import FastString import System.FilePath import Language.Preprocessor.Unlit fileToModule :: FilePath -> String -> DynFlags -> ParseResult (Located (HsModule GhcPs)) fileToModule filename str flags = parseFile filename flags (if takeExtension filename /= ".lhs" then str else unlit filename str) {-# COMPLETE SrcSpan #-} -- | The \"Line'\" thing is because there is already e.g. 'SrcLoc.srcSpanStartLine' pattern SrcSpan :: String -> Int -> Int -> Int -> Int -> SrcSpan pattern SrcSpan { srcSpanFilename , srcSpanStartLine' , srcSpanStartColumn , srcSpanEndLine' , srcSpanEndColumn } <- (toOldeSpan -> ( srcSpanFilename , srcSpanStartLine' , srcSpanStartColumn , srcSpanEndLine' , srcSpanEndColumn )) toOldeSpan :: SrcSpan -> (String, Int, Int, Int, Int) toOldeSpan (RealSrcSpan span) = ( unpackFS $ srcSpanFile span , srcSpanStartLine span , srcSpanStartCol span , srcSpanEndLine span , srcSpanEndCol span ) -- TODO: the bad locations are all (-1) right now -- is this fine? it should be, since noLoc from HSE previously also used (-1) as an invalid location toOldeSpan (UnhelpfulSpan str) = ( unpackFS str , -1 , -1 , -1 , -1 ) {-# COMPLETE SrcLoc #-} pattern SrcLoc :: String -> Int -> Int -> SrcLoc pattern SrcLoc { srcFilename , srcLine , srcColumn } <- (toOldeLoc -> ( srcFilename , srcLine , srcColumn )) toOldeLoc :: SrcLoc -> (String, Int, Int) toOldeLoc (RealSrcLoc loc) = ( unpackFS $ srcLocFile loc , srcLocLine loc , srcLocCol loc ) toOldeLoc (UnhelpfulLoc str) = ( unpackFS str , -1 , -1 ) showSrcSpan :: SrcSpan -> String showSrcSpan = unsafePrettyPrint hlint-3.1.6/src/GHC/All.hs0000644000000000000000000002136613671470061013276 0ustar0000000000000000{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE ViewPatterns #-} module GHC.All( CppFlags(..), ParseFlags(..), defaultParseFlags, parseFlagsAddFixities, parseFlagsSetLanguage, ParseError(..), ModuleEx(..), parseModuleEx, createModuleEx, ghcComments, parseExpGhcWithMode, parseImportDeclGhcWithMode, parseDeclGhcWithMode, ) where import Util import Data.Char import Data.List.Extra import Timing import Language.Preprocessor.Cpphs import qualified Data.Map as Map import System.IO.Extra import Fixity import Extension import FastString import GHC.Hs import SrcLoc import ErrUtils import Outputable import Lexer hiding (context) import GHC.LanguageExtensions.Type import ApiAnnotation import DynFlags hiding (extensions) import Bag import Language.Haskell.GhclibParserEx.GHC.Parser import Language.Haskell.GhclibParserEx.Fixity import GHC.Util -- | What C pre processor should be used. data CppFlags = NoCpp -- ^ No pre processing is done. | CppSimple -- ^ Lines prefixed with @#@ are stripped. | Cpphs CpphsOptions -- ^ The @cpphs@ library is used. -- | Created with 'defaultParseFlags', used by 'parseModuleEx'. data ParseFlags = ParseFlags {cppFlags :: CppFlags -- ^ How the file is preprocessed (defaults to 'NoCpp'). ,baseLanguage :: Maybe Language -- ^ Base language (e.g. Haskell98, Haskell2010), defaults to 'Nothing'. ,enabledExtensions :: [Extension] -- ^ List of extensions enabled for parsing, defaults to many non-conflicting extensions. ,disabledExtensions :: [Extension] -- ^ List of extensions disabled for parsing, usually empty. ,fixities :: [FixityInfo] -- ^ List of fixities to be aware of, defaults to those defined in @base@. } -- | Default value for 'ParseFlags'. defaultParseFlags :: ParseFlags defaultParseFlags = ParseFlags NoCpp Nothing defaultExtensions [] defaultFixities -- | Given some fixities, add them to the existing fixities in 'ParseFlags'. parseFlagsAddFixities :: [FixityInfo] -> ParseFlags -> ParseFlags parseFlagsAddFixities fx x = x{fixities = fx ++ fixities x} parseFlagsSetLanguage :: (Maybe Language, ([Extension], [Extension])) -> ParseFlags -> ParseFlags parseFlagsSetLanguage (l, (es, ds)) x = x{baseLanguage = l, enabledExtensions = es, disabledExtensions = ds} runCpp :: CppFlags -> FilePath -> String -> IO String runCpp NoCpp _ x = pure x runCpp CppSimple _ x = pure $ unlines [if "#" `isPrefixOf` trimStart x then "" else x | x <- lines x] runCpp (Cpphs o) file x = dropLine <$> runCpphs o file x where -- LINE pragmas always inserted when locations=True dropLine (line1 -> (a,b)) | "{-# LINE " `isPrefixOf` a = b dropLine x = x --------------------------------------------------------------------- -- PARSING -- | A parse error. data ParseError = ParseError { parseErrorLocation :: SrcSpan -- ^ Location of the error. , parseErrorMessage :: String -- ^ Message about the cause of the error. , parseErrorContents :: String -- ^ Snippet of several lines (typically 5) including a @>@ character pointing at the faulty line. } -- | Result of 'parseModuleEx', representing a parsed module. data ModuleEx = ModuleEx { ghcModule :: Located (HsModule GhcPs) , ghcAnnotations :: ApiAnns } -- | Extract a list of all of a parsed module's comments. ghcComments :: ModuleEx -> [Located AnnotationComment] ghcComments m = concat (Map.elems $ snd (ghcAnnotations m)) -- | The error handler invoked when GHC parsing has failed. ghcFailOpParseModuleEx :: String -> FilePath -> String -> (SrcSpan, ErrUtils.MsgDoc) -> IO (Either ParseError ModuleEx) ghcFailOpParseModuleEx ppstr file str (loc, err) = do let pe = case loc of RealSrcSpan r -> context (srcSpanStartLine r) ppstr _ -> "" msg = Outputable.showSDoc baseDynFlags err pure $ Left $ ParseError loc msg pe -- GHC extensions to enable/disable given HSE parse flags. ghcExtensionsFromParseFlags :: ParseFlags -> ([Extension], [Extension]) ghcExtensionsFromParseFlags ParseFlags{enabledExtensions=es, disabledExtensions=ds}= (es, ds) -- GHC fixities given HSE parse flags. ghcFixitiesFromParseFlags :: ParseFlags -> [(String, Fixity)] ghcFixitiesFromParseFlags = map toFixity . fixities -- These next two functions get called frorm 'Config/Yaml.hs' for user -- defined hint rules. parseModeToFlags :: ParseFlags -> DynFlags parseModeToFlags parseMode = flip lang_set (baseLanguage parseMode) $ foldl' xopt_unset (foldl' xopt_set baseDynFlags enable) disable where (enable, disable) = ghcExtensionsFromParseFlags parseMode parseExpGhcWithMode :: ParseFlags -> String -> ParseResult (LHsExpr GhcPs) parseExpGhcWithMode parseMode s = let fixities = ghcFixitiesFromParseFlags parseMode in case parseExpression s $ parseModeToFlags parseMode of POk pst a -> POk pst $ applyFixities fixities a f@PFailed{} -> f parseImportDeclGhcWithMode :: ParseFlags -> String -> ParseResult (LImportDecl GhcPs) parseImportDeclGhcWithMode parseMode s = parseImport s $ parseModeToFlags parseMode parseDeclGhcWithMode :: ParseFlags -> String -> ParseResult (LHsDecl GhcPs) parseDeclGhcWithMode parseMode s = let fixities = ghcFixitiesFromParseFlags parseMode in case parseDeclaration s $ parseModeToFlags parseMode of POk pst a -> POk pst $ applyFixities fixities a f@PFailed{} -> f -- | Create a 'ModuleEx' from GHC annotations and module tree. It -- is assumed the incoming parse module has not been adjusted to -- account for operator fixities (it uses the HLint default fixities). createModuleEx :: ApiAnns -> Located (HsModule GhcPs) -> ModuleEx createModuleEx anns ast = ModuleEx (applyFixities (fixitiesFromModule ast ++ map toFixity defaultFixities) ast) anns -- | Parse a Haskell module. Applies the C pre processor, and uses -- best-guess fixity resolution if there are ambiguities. The -- filename @-@ is treated as @stdin@. Requires some flags (often -- 'defaultParseFlags'), the filename, and optionally the contents of -- that file. -- -- Note that certain programs, e.g. @main = do@ successfully parse with GHC, but then -- fail with an error in the renamer. These programs will return a successful parse. parseModuleEx :: ParseFlags -> FilePath -> Maybe String -> IO (Either ParseError ModuleEx) parseModuleEx flags file str = timedIO "Parse" file $ do str <- case str of Just x -> pure x Nothing | file == "-" -> getContentsUTF8 | otherwise -> readFileUTF8' file str <- pure $ dropPrefix "\65279" str -- remove the BOM if it exists, see #130 ppstr <- runCpp (cppFlags flags) file str let enableDisableExts = ghcExtensionsFromParseFlags flags dynFlags <- parsePragmasIntoDynFlags baseDynFlags enableDisableExts file ppstr case dynFlags of Right ghcFlags -> do ghcFlags <- pure $ lang_set ghcFlags $ baseLanguage flags case fileToModule file ppstr ghcFlags of POk s a -> do let errs = bagToList . snd $ getMessages s ghcFlags if not $ null errs then handleParseFailure ghcFlags ppstr file str errs else do let anns = ( Map.fromListWith (++) $ annotations s , Map.fromList ((noSrcSpan, comment_q s) : annotations_comments s) ) let fixes = fixitiesFromModule a ++ ghcFixitiesFromParseFlags flags pure $ Right (ModuleEx (applyFixities fixes a) anns) PFailed s -> handleParseFailure ghcFlags ppstr file str $ bagToList . snd $ getMessages s ghcFlags Left msg -> do -- Parsing GHC flags from dynamic pragmas in the source -- has failed. When this happens, it's reported by -- exception. It's impossible or at least fiddly getting a -- location so we skip that for now. Synthesize a parse -- error. let loc = mkSrcLoc (mkFastString file) (1 :: Int) (1 :: Int) pure $ Left (ParseError (mkSrcSpan loc loc) msg ppstr) where handleParseFailure ghcFlags ppstr file str errs = let errMsg = head errs loc = errMsgSpan errMsg doc = formatErrDoc ghcFlags (errMsgDoc errMsg) in ghcFailOpParseModuleEx ppstr file str (loc, doc) -- | Given a line number, and some source code, put bird ticks around the appropriate bit. context :: Int -> String -> String context lineNo src = unlines $ dropWhileEnd (all isSpace) $ dropWhile (all isSpace) $ zipWith (++) ticks $ take 5 $ drop (lineNo - 3) $ lines src ++ ["","","","",""] where ticks = drop (3 - lineNo) [" "," ","> "," "," "] hlint-3.1.6/src/GHC/Util/0000755000000000000000000000000013674744765013161 5ustar0000000000000000hlint-3.1.6/src/GHC/Util/View.hs0000644000000000000000000000407013661521317014406 0ustar0000000000000000{-# LANGUAGE ViewPatterns, MultiParamTypeClasses, FlexibleInstances, PatternSynonyms #-} module GHC.Util.View ( fromParen , View(..) , Var_(Var_), PVar_(PVar_), PApp_(PApp_), App2(App2),LamConst1(LamConst1) , pattern SimpleLambda ) where import GHC.Hs import SrcLoc import BasicTypes import Language.Haskell.GhclibParserEx.GHC.Types.Name.Reader fromParen :: LHsExpr GhcPs -> LHsExpr GhcPs fromParen (L _ (HsPar _ x)) = fromParen x fromParen x = x fromPParen :: LPat GhcPs -> LPat GhcPs fromPParen (L _ (ParPat _ x)) = fromPParen x fromPParen x = x class View a b where view :: a -> b data Var_ = NoVar_ | Var_ String deriving Eq data PVar_ = NoPVar_ | PVar_ String data PApp_ = NoPApp_ | PApp_ String [LPat GhcPs] data App2 = NoApp2 | App2 (LHsExpr GhcPs) (LHsExpr GhcPs) (LHsExpr GhcPs) data LamConst1 = NoLamConst1 | LamConst1 (LHsExpr GhcPs) instance View (LHsExpr GhcPs) LamConst1 where view (fromParen -> (L _ (HsLam _ (MG _ (L _ [L _ (Match _ LambdaExpr [L _ WildPat {}] (GRHSs _ [L _ (GRHS _ [] x)] (L _ (EmptyLocalBinds _))))]) FromSource)))) = LamConst1 x view _ = NoLamConst1 instance View (LHsExpr GhcPs) Var_ where view (fromParen -> (L _ (HsVar _ (rdrNameStr -> x)))) = Var_ x view _ = NoVar_ instance View (LHsExpr GhcPs) App2 where view (fromParen -> L _ (OpApp _ lhs op rhs)) = App2 op lhs rhs view (fromParen -> L _ (HsApp _ (L _ (HsApp _ f x)) y)) = App2 f x y view _ = NoApp2 instance View (Located (Pat GhcPs)) PVar_ where view (fromPParen -> L _ (VarPat _ (L _ x))) = PVar_ $ occNameStr x view _ = NoPVar_ instance View (Located (Pat GhcPs)) PApp_ where view (fromPParen -> L _ (ConPatIn (L _ x) (PrefixCon args))) = PApp_ (occNameStr x) args view (fromPParen -> L _ (ConPatIn (L _ x) (InfixCon lhs rhs))) = PApp_ (occNameStr x) [lhs, rhs] view _ = NoPApp_ -- A lambda with no guards and no where clauses pattern SimpleLambda :: [LPat GhcPs] -> LHsExpr GhcPs -> LHsExpr GhcPs pattern SimpleLambda vs body <- L _ (HsLam _ (MG _ (L _ [L _ (Match _ _ vs (GRHSs _ [L _ (GRHS _ [] body)] (L _ (EmptyLocalBinds _))))]) _)) hlint-3.1.6/src/GHC/Util/Unify.hs0000644000000000000000000002567513674736166014622 0ustar0000000000000000{-# LANGUAGE PatternGuards, ViewPatterns, FlexibleContexts, ScopedTypeVariables, TupleSections #-} {-# LANGUAGE GeneralizedNewtypeDeriving, DeriveFunctor #-} module GHC.Util.Unify( Subst, fromSubst, validSubst, removeParens, substitute, unifyExp ) where import Control.Applicative import Control.Monad import Data.Generics.Uniplate.DataOnly import Data.Char import Data.Data import Data.List.Extra import Util import GHC.Hs import SrcLoc import Outputable hiding ((<>)) import RdrName import Language.Haskell.GhclibParserEx.GHC.Hs.Pat import Language.Haskell.GhclibParserEx.GHC.Hs.Expr import Language.Haskell.GhclibParserEx.GHC.Utils.Outputable import Language.Haskell.GhclibParserEx.GHC.Types.Name.Reader import GHC.Util.HsExpr import GHC.Util.View import FastString isUnifyVar :: String -> Bool isUnifyVar [x] = x == '?' || isAlpha x isUnifyVar [] = False isUnifyVar xs = all (== '?') xs --------------------------------------------------------------------- -- SUBSTITUTION DATA TYPE -- A list of substitutions. A key may be duplicated, you need to call -- 'check' to ensure the substitution is valid. newtype Subst a = Subst [(String, a)] deriving (Semigroup, Monoid, Functor) -- Unpack the substitution. fromSubst :: Subst a -> [(String, a)] fromSubst (Subst xs) = xs instance Outputable a => Show (Subst a) where show (Subst xs) = unlines [a ++ " = " ++ unsafePrettyPrint b | (a,b) <- xs] -- Check the unification is valid and simplify it. validSubst :: (a -> a -> Bool) -> Subst a -> Maybe (Subst a) validSubst eq = fmap Subst . mapM f . groupSort . fromSubst where f (x, y : ys) | all (eq y) ys = Just (x, y) f _ = Nothing -- Remove unnecessary brackets from a Subst. The first argument is a list of unification variables -- for which brackets should be removed from their substitutions. removeParens :: [String] -> Subst (LHsExpr GhcPs) -> Subst (LHsExpr GhcPs) removeParens noParens (Subst xs) = Subst $ map (\(x, y) -> if x `elem` noParens then (x, fromParen y) else (x, y)) xs -- Peform a substition. -- Returns (suggested replacement, refactor template), both with brackets added -- as needed. -- Example: (traverse foo (bar baz), traverse f (x)) substitute :: Subst (LHsExpr GhcPs) -> LHsExpr GhcPs -> (LHsExpr GhcPs, LHsExpr GhcPs) substitute (Subst bind) = transformBracketOld exp . transformBi pat . transformBi typ where exp :: LHsExpr GhcPs -> Maybe (LHsExpr GhcPs) -- Variables. exp (L _ (HsVar _ x)) = lookup (rdrNameStr x) bind -- Operator applications. exp (L loc (OpApp _ lhs (L _ (HsVar _ x)) rhs)) | Just y <- lookup (rdrNameStr x) bind = Just (cL loc (OpApp noExtField lhs y rhs)) -- Left sections. exp (L loc (SectionL _ exp (L _ (HsVar _ x)))) | Just y <- lookup (rdrNameStr x) bind = Just (cL loc (SectionL noExtField exp y)) -- Right sections. exp (L loc (SectionR _ (L _ (HsVar _ x)) exp)) | Just y <- lookup (rdrNameStr x) bind = Just (cL loc (SectionR noExtField y exp)) exp _ = Nothing pat :: LPat GhcPs -> LPat GhcPs -- Pattern variables. pat (L _ (VarPat _ x)) | Just y@(L _ HsVar{}) <- lookup (rdrNameStr x) bind = strToPat $ varToStr y pat x = x :: LPat GhcPs typ :: LHsType GhcPs -> LHsType GhcPs -- Type variables. typ (L _ (HsTyVar _ _ x)) | Just (L _ (HsAppType _ _ (HsWC _ y))) <- lookup (rdrNameStr x) bind = y typ x = x :: LHsType GhcPs --------------------------------------------------------------------- -- UNIFICATION type NameMatch = Located RdrName -> Located RdrName -> Bool -- | Unification, obeys the property that if @unify a b = s@, then -- @substitute s a = b@. unify' :: Data a => NameMatch -> Bool -> a -> a -> Maybe (Subst (LHsExpr GhcPs)) unify' nm root x y | Just (x, y) <- cast (x, y) = unifyExp' nm root x y | Just (x, y) <- cast (x, y) = unifyPat' nm x y | Just (x, y) <- cast (x, y) = unifyType' nm x y | Just (x, y) <- cast (x, y) = if (x :: FastString) == y then Just mempty else Nothing | Just (x :: SrcSpan) <- cast x = Just mempty | otherwise = unifyDef' nm x y unifyDef' :: Data a => NameMatch -> a -> a -> Maybe (Subst (LHsExpr GhcPs)) unifyDef' nm x y = fmap mconcat . sequence =<< gzip (unify' nm False) x y unifyComposed' :: NameMatch -> LHsExpr GhcPs -> LHsExpr GhcPs -> LHsExpr GhcPs -> LHsExpr GhcPs -> Maybe (Subst (LHsExpr GhcPs), Maybe (LHsExpr GhcPs)) unifyComposed' nm x1 y11 dot y12 = ((, Just y11) <$> unifyExp' nm False x1 y12) <|> case y12 of (L _ (OpApp _ y121 dot' y122)) | isDot dot' -> unifyComposed' nm x1 (noLoc (OpApp noExtField y11 dot y121)) dot' y122 _ -> Nothing -- unifyExp handles the cases where both x and y are HsApp, or y is OpApp. Otherwise, -- delegate to unifyExp'. These are the cases where we potentially need to call -- unifyComposed' to handle left composition. -- -- y is allowed to partially match x (the lhs of the hint), if y is a function application where -- the function is a composition of functions. In this case the second component of the result is -- the unmatched part of y, which will be attached to the rhs of the hint after substitution. -- -- Example: -- x = head (drop n x) -- y = foo . bar . baz . head $ drop 2 xs -- result = (Subst [(n, 2), (x, xs)], Just (foo . bar . baz)) unifyExp :: NameMatch -> Bool -> LHsExpr GhcPs -> LHsExpr GhcPs -> Maybe (Subst (LHsExpr GhcPs), Maybe (LHsExpr GhcPs)) -- Match wildcard operators. unifyExp nm root (L _ (OpApp _ lhs1 (L _ (HsVar _ (rdrNameStr -> v))) rhs1)) (L _ (OpApp _ lhs2 (L _ (HsVar _ (rdrNameStr -> op2))) rhs2)) | isUnifyVar v = (, Nothing) . (Subst [(v, strToVar op2)] <>) <$> liftA2 (<>) (unifyExp' nm False lhs1 lhs2) (unifyExp' nm False rhs1 rhs2) -- Options: match directly, and expand through '.' unifyExp nm root x@(L _ (HsApp _ x1 x2)) (L _ (HsApp _ y1 y2)) = ((, Nothing) <$> liftA2 (<>) (unifyExp' nm False x1 y1) (unifyExp' nm False x2 y2)) <|> unifyComposed where -- Unify a function application where the function is a composition of functions. unifyComposed | (L _ (OpApp _ y11 dot y12)) <- fromParen y1, isDot dot = if not root then -- Attempt #1: rewrite '(fun1 . fun2) arg' as 'fun1 (fun2 arg)', and unify it with 'x'. -- The guard ensures that you don't get duplicate matches because the matching engine -- auto-generates hints in dot-form. (, Nothing) <$> unifyExp' nm root x (noLoc (HsApp noExtField y11 (noLoc (HsApp noExtField y12 y2)))) else do -- Attempt #2: rewrite '(fun1 . fun2 ... funn) arg' as 'fun1 $ (fun2 ... funn) arg', -- 'fun1 . fun2 $ (fun3 ... funn) arg', 'fun1 . fun2 . fun3 $ (fun4 ... funn) arg', -- and so on, unify the rhs of '$' with 'x', and store the lhs of '$' into 'extra'. -- You can only add to extra if you are at the root (otherwise 'extra' has nowhere to go). rhs <- unifyExp' nm False x2 y2 (lhs, extra) <- unifyComposed' nm x1 y11 dot y12 pure (lhs <> rhs, extra) | otherwise = Nothing -- Options: match directly, then expand through '$', then desugar infix. unifyExp nm root x (L _ (OpApp _ lhs2 op2@(L _ (HsVar _ op2')) rhs2)) | (L _ (OpApp _ lhs1 op1@(L _ (HsVar _ op1')) rhs1)) <- x = guard (nm op1' op2') >> (, Nothing) <$> liftA2 (<>) (unifyExp' nm False lhs1 lhs2) (unifyExp' nm False rhs1 rhs2) | isDol op2 = unifyExp nm root x $ noLoc (HsApp noExtField lhs2 rhs2) | otherwise = unifyExp nm root x $ noLoc (HsApp noExtField (noLoc (HsApp noExtField op2 (addPar lhs2))) (addPar rhs2)) where -- add parens around when desugaring the expression, if necessary addPar :: LHsExpr GhcPs -> LHsExpr GhcPs addPar x = if isAtom x then x else addParen x unifyExp nm root x y = (, Nothing) <$> unifyExp' nm root x y -- | If we "throw away" the extra than we have no where to put it, and the substitution is wrong noExtra :: Maybe (Subst (LHsExpr GhcPs), Maybe (LHsExpr GhcPs)) -> Maybe (Subst (LHsExpr GhcPs)) noExtra (Just (x, Nothing)) = Just x noExtra _ = Nothing -- App/InfixApp are analysed specially for performance reasons. If -- 'root = True', this is the outside of the expr. Do not expand out a -- dot at the root, since otherwise you get two matches because of -- 'readRule' (Bug #570). unifyExp' :: NameMatch -> Bool -> LHsExpr GhcPs -> LHsExpr GhcPs -> Maybe (Subst (LHsExpr GhcPs)) -- Brackets are not added when expanding '$' in user code, so tolerate -- them in the match even if they aren't in the user code. unifyExp' nm root x y | not root, isPar x, not $ isPar y = unifyExp' nm root (fromParen x) y -- Don't subsitute for type apps, since no one writes rules imagining -- they exist. unifyExp' nm root (L _ (HsVar _ (rdrNameStr -> v))) y | isUnifyVar v, not $ isTypeApp y = Just $ Subst [(v, y)] unifyExp' nm root (L _ (HsVar _ x)) (L _ (HsVar _ y)) | nm x y = Just mempty unifyExp' nm root x@(L _ (OpApp _ lhs1 (L _ (HsVar _ (rdrNameStr -> v))) rhs1)) y@(L _ (OpApp _ lhs2 (L _ (HsVar _ op2)) rhs2)) = noExtra $ unifyExp nm root x y unifyExp' nm root (L _ (SectionL _ exp1 (L _ (HsVar _ (rdrNameStr -> v))))) (L _ (SectionL _ exp2 (L _ (HsVar _ (rdrNameStr -> op2))))) | isUnifyVar v = (Subst [(v, strToVar op2)] <>) <$> unifyExp' nm False exp1 exp2 unifyExp' nm root (L _ (SectionR _ (L _ (HsVar _ (rdrNameStr -> v))) exp1)) (L _ (SectionR _ (L _ (HsVar _ (rdrNameStr -> op2))) exp2)) | isUnifyVar v = (Subst [(v, strToVar op2)] <>) <$> unifyExp' nm False exp1 exp2 unifyExp' nm root x@(L _ (HsApp _ x1 x2)) y@(L _ (HsApp _ y1 y2)) = noExtra $ unifyExp nm root x y unifyExp' nm root x y@(L _ (OpApp _ lhs2 op2@(L _ (HsVar _ op2')) rhs2)) = noExtra $ unifyExp nm root x y unifyExp' nm root x y | isOther x, isOther y = unifyDef' nm x y where -- Types that are not already handled in unify. {-# INLINE isOther #-} isOther :: LHsExpr GhcPs -> Bool isOther (L _ HsVar{}) = False isOther (L _ HsApp{}) = False isOther (L _ OpApp{}) = False isOther _ = True unifyExp' _ _ _ _ = Nothing unifyPat' :: NameMatch -> LPat GhcPs -> LPat GhcPs -> Maybe (Subst (LHsExpr GhcPs)) unifyPat' nm (L _ (VarPat _ x)) (L _ (VarPat _ y)) = Just $ Subst [(rdrNameStr x, strToVar(rdrNameStr y))] unifyPat' nm (L _ (VarPat _ x)) (L _ (WildPat _)) = let s = rdrNameStr x in Just $ Subst [(s, strToVar("_" ++ s))] unifyPat' nm (L _ (ConPatIn x _)) (L _ (ConPatIn y _)) | rdrNameStr x /= rdrNameStr y = Nothing unifyPat' nm x y = unifyDef' nm x y unifyType' :: NameMatch -> LHsType GhcPs -> LHsType GhcPs -> Maybe (Subst (LHsExpr GhcPs)) unifyType' nm (L loc (HsTyVar _ _ x)) y = let wc = HsWC noExtField y :: LHsWcType (NoGhcTc GhcPs) unused = strToVar "__unused__" :: LHsExpr GhcPs appType = cL loc (HsAppType noExtField unused wc) :: LHsExpr GhcPs in Just $ Subst [(rdrNameStr x, appType)] unifyType' nm x y = unifyDef' nm x y hlint-3.1.6/src/GHC/Util/SrcLoc.hs0000644000000000000000000000111313671470061014654 0ustar0000000000000000{-# LANGUAGE GeneralizedNewtypeDeriving #-} module GHC.Util.SrcLoc ( stripLocs , SrcSpanD(..) ) where import SrcLoc import Outputable import Data.Default import Data.Data import Data.Generics.Uniplate.DataOnly -- 'stripLocs x' is 'x' with all contained source locs replaced by -- 'noSrcSpan'. stripLocs :: (Data from, HasSrcSpan from) => from -> from stripLocs = transformBi (const noSrcSpan) -- 'Duplicates.hs' requires 'SrcSpan' be in 'Default'. newtype SrcSpanD = SrcSpanD SrcSpan deriving (Outputable, Eq, Ord) instance Default SrcSpanD where def = SrcSpanD noSrcSpan hlint-3.1.6/src/GHC/Util/Scope.hs0000644000000000000000000001073613671470061014553 0ustar0000000000000000 {-# LANGUAGE ViewPatterns #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} module GHC.Util.Scope ( Scope ,scopeCreate,scopeMatch,scopeMove,possModules ) where import GHC.Hs import SrcLoc import BasicTypes import Module import FastString import RdrName import OccName import Language.Haskell.GhclibParserEx.GHC.Types.Name.Reader import Language.Haskell.GhclibParserEx.GHC.Utils.Outputable import Data.List.Extra import Data.Maybe -- A scope is a list of import declarations. newtype Scope = Scope [LImportDecl GhcPs] deriving (Monoid, Semigroup) instance Show Scope where show (Scope x) = unsafePrettyPrint x -- Create a 'Scope from a module's import declarations. scopeCreate :: HsModule GhcPs -> Scope scopeCreate xs = Scope $ [prelude | not $ any isPrelude res] ++ res where -- Package qualifier of an import declaration. pkg :: LImportDecl GhcPs -> Maybe StringLiteral pkg (L _ x) = ideclPkgQual x -- The import declaraions contained by the module 'xs'. res :: [LImportDecl GhcPs] res = [x | x <- hsmodImports xs , pkg x /= Just (StringLiteral NoSourceText (fsLit "hint"))] -- Mock up an import declaraion corresponding to 'import Prelude'. prelude :: LImportDecl GhcPs prelude = noLoc $ simpleImportDecl (mkModuleName "Prelude") -- Predicate to test for a 'Prelude' import declaration. isPrelude :: LImportDecl GhcPs -> Bool isPrelude (L _ x) = moduleNameString (unLoc (ideclName x)) == "Prelude" -- Test if two names in two scopes may be referring to the same -- thing. This is the case if the names are equal and (1) denote a -- builtin type or data constructor or (2) the intersection of the -- candidate modules where the two names arise is non-empty. scopeMatch :: (Scope, Located RdrName) -> (Scope, Located RdrName) -> Bool scopeMatch (a, x) (b, y) | isSpecial x && isSpecial y = rdrNameStr x == rdrNameStr y | isSpecial x || isSpecial y = False | otherwise = rdrNameStr (unqual x) == rdrNameStr (unqual y) && not (possModules a x `disjointOrd` possModules b y) -- Given a name in a scope, and a new scope, create a name for the new -- scope that will refer to the same thing. If the resulting name is -- ambiguous, pick a plausible candidate. scopeMove :: (Scope, Located RdrName) -> Scope -> Located RdrName scopeMove (a, x@(fromQual -> Just name)) (Scope b) = case imps of [] -> headDef x real imp:_ | all (\x -> ideclQualified x /= NotQualified) imps -> noLoc $ mkRdrQual (unLoc . fromMaybe (ideclName imp) $ firstJust ideclAs imps) name | otherwise -> unqual x where real :: [Located RdrName] real = [noLoc $ mkRdrQual m name | m <- possModules a x] imps :: [ImportDecl GhcPs] imps = [unLoc i | r <- real, i <- b, possImport i r] scopeMove (_, x) _ = x -- Calculate which modules a name could possibly lie in. If 'x' is -- qualified but no imported element matches it, assume the user just -- lacks an import. possModules :: Scope -> Located RdrName -> [ModuleName] possModules (Scope is) x = f x where res :: [ModuleName] res = [unLoc $ ideclName $ unLoc i | i <- is, possImport i x] f :: Located RdrName -> [ModuleName] f n | isSpecial n = [mkModuleName ""] f (L _ (Qual mod _)) = [mod | null res] ++ res f _ = res -- Determine if 'x' could possibly lie in the module named by the -- import declaration 'i'. possImport :: LImportDecl GhcPs -> Located RdrName -> Bool possImport i n | isSpecial n = False possImport (L _ i) (L _ (Qual mod x)) = mod `elem` ms && possImport (noLoc i{ideclQualified=NotQualified}) (noLoc $ mkRdrUnqual x) where ms = map unLoc $ ideclName i : maybeToList (ideclAs i) possImport (L _ i) (L _ (Unqual x)) = ideclQualified i == NotQualified && maybe True f (ideclHiding i) where f :: (Bool, Located [LIE GhcPs]) -> Bool f (hide, L _ xs) = if hide then Just True `notElem` ms else Nothing `elem` ms || Just True `elem` ms where ms = map g xs tag :: String tag = occNameString x g :: LIE GhcPs -> Maybe Bool -- Does this import cover the name 'x'? g (L _ (IEVar _ y)) = Just $ tag == unwrapName y g (L _ (IEThingAbs _ y)) = Just $ tag == unwrapName y g (L _ (IEThingAll _ y)) = if tag == unwrapName y then Just True else Nothing g (L _ (IEThingWith _ y _wildcard ys _fields)) = Just $ tag `elem` unwrapName y : map unwrapName ys g _ = Just False unwrapName :: LIEWrappedName RdrName -> String unwrapName x = occNameString (rdrNameOcc $ ieWrappedName (unLoc x)) possImport _ _ = False hlint-3.1.6/src/GHC/Util/HsExpr.hs0000644000000000000000000003062313671470061014710 0ustar0000000000000000{-# LANGUAGE TypeFamilies #-} {-# LANGUAGE ViewPatterns, MultiParamTypeClasses, FlexibleInstances, FlexibleContexts #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE TupleSections #-} module GHC.Util.HsExpr ( dotApps, lambda , simplifyExp, niceLambda, niceLambdaR , Brackets(..) , rebracket1, appsBracket, transformAppsM, fromApps, apps, universeApps, universeParentExp , paren , replaceBranches , needBracketOld, transformBracketOld, fromParen1 , allowLeftSection, allowRightSection ) where import GHC.Hs import BasicTypes import SrcLoc import FastString import RdrName import OccName import Bag(bagToList) import GHC.Util.Brackets import GHC.Util.FreeVars import GHC.Util.View import Control.Applicative import Control.Monad.Trans.State import Data.Data import Data.Generics.Uniplate.DataOnly import Data.List.Extra import Data.Tuple.Extra import Refact (toSS) import Refact.Types hiding (SrcSpan, Match) import qualified Refact.Types as R (SrcSpan) import Language.Haskell.GhclibParserEx.GHC.Hs.Pat import Language.Haskell.GhclibParserEx.GHC.Hs.Expr import Language.Haskell.GhclibParserEx.GHC.Hs.ExtendInstances import Language.Haskell.GhclibParserEx.GHC.Utils.Outputable import Language.Haskell.GhclibParserEx.GHC.Types.Name.Reader -- | 'dotApp a b' makes 'a . b'. dotApp :: LHsExpr GhcPs -> LHsExpr GhcPs -> LHsExpr GhcPs dotApp x y = noLoc $ OpApp noExtField x (noLoc $ HsVar noExtField (noLoc $ mkVarUnqual (fsLit "."))) y dotApps :: [LHsExpr GhcPs] -> LHsExpr GhcPs dotApps [] = error "GHC.Util.HsExpr.dotApps', does not work on an empty list" dotApps [x] = x dotApps (x : xs) = dotApp x (dotApps xs) -- | @lambda [p0, p1..pn] body@ makes @\p1 p1 .. pn -> body@ lambda :: [LPat GhcPs] -> LHsExpr GhcPs -> LHsExpr GhcPs lambda vs body = noLoc $ HsLam noExtField (MG noExtField (noLoc [noLoc $ Match noExtField LambdaExpr vs (GRHSs noExtField [noLoc $ GRHS noExtField [] body] (noLoc $ EmptyLocalBinds noExtField))]) Generated) -- | 'paren e' wraps 'e' in parens if 'e' is non-atomic. paren :: LHsExpr GhcPs -> LHsExpr GhcPs paren x | isAtom x = x | otherwise = addParen x universeParentExp :: Data a => a -> [(Maybe (Int, LHsExpr GhcPs), LHsExpr GhcPs)] universeParentExp xs = concat [(Nothing, x) : f x | x <- childrenBi xs] where f p = concat [(Just (i,p), c) : f c | (i,c) <- zipFrom 0 $ children p] apps :: [LHsExpr GhcPs] -> LHsExpr GhcPs apps = foldl1' mkApp where mkApp x y = noLoc (HsApp noExtField x y) fromApps :: LHsExpr GhcPs -> [LHsExpr GhcPs] fromApps (L _ (HsApp _ x y)) = fromApps x ++ [y] fromApps x = [x] childrenApps :: LHsExpr GhcPs -> [LHsExpr GhcPs] childrenApps (L _ (HsApp _ x y)) = childrenApps x ++ [y] childrenApps x = children x universeApps :: LHsExpr GhcPs -> [LHsExpr GhcPs] universeApps x = x : concatMap universeApps (childrenApps x) descendAppsM :: Monad m => (LHsExpr GhcPs -> m (LHsExpr GhcPs)) -> LHsExpr GhcPs -> m (LHsExpr GhcPs) descendAppsM f (L l (HsApp _ x y)) = liftA2 (\x y -> L l $ HsApp noExtField x y) (descendAppsM f x) (f y) descendAppsM f x = descendM f x transformAppsM :: Monad m => (LHsExpr GhcPs -> m (LHsExpr GhcPs)) -> LHsExpr GhcPs -> m (LHsExpr GhcPs) transformAppsM f x = f =<< descendAppsM (transformAppsM f) x descendIndex :: Data a => (Int -> a -> a) -> a -> a descendIndex f x = flip evalState 0 $ flip descendM x $ \y -> do i <- get modify (+1) pure $ f i y -- There are differences in pretty-printing between GHC and HSE. This -- version never removes brackets. descendBracket :: (LHsExpr GhcPs -> (Bool, LHsExpr GhcPs)) -> LHsExpr GhcPs -> LHsExpr GhcPs descendBracket op x = descendIndex g x where g i y = if a then f i b else b where (a, b) = op y f i y@(L _ e) | needBracket i x y = addParen y f _ y = y -- Add brackets as suggested 'needBracket at 1-level of depth. rebracket1 :: LHsExpr GhcPs -> LHsExpr GhcPs rebracket1 = descendBracket (True, ) -- A list of application, with any necessary brackets. appsBracket :: [LHsExpr GhcPs] -> LHsExpr GhcPs appsBracket = foldl1 mkApp where mkApp x y = rebracket1 (noLoc $ HsApp noExtField x y) simplifyExp :: LHsExpr GhcPs -> LHsExpr GhcPs -- Replace appliciations 'f $ x' with 'f (x)'. simplifyExp (L l (OpApp _ x op y)) | isDol op = L l (HsApp noExtField x (noLoc (HsPar noExtField y))) simplifyExp e@(L _ (HsLet _ (L _ (HsValBinds _ (ValBinds _ binds []))) z)) = -- An expression of the form, 'let x = y in z'. case bagToList binds of [L _ (FunBind _ _(MG _ (L _ [L _ (Match _(FunRhs (L _ x) _ _) [] (GRHSs _[L _ (GRHS _ [] y)] (L _ (EmptyLocalBinds _))))]) _) _ _)] -- If 'x' is not in the free variables of 'y', beta-reduce to -- 'z[(y)/x]'. | occNameStr x `notElem` vars y && length [() | Unqual a <- universeBi z, a == rdrNameOcc x] <= 1 -> transform f z where f (view -> Var_ x') | occNameStr x == x' = paren y f x = x _ -> e simplifyExp e = e -- Rewrite '($) . b' as 'b'. niceDotApp :: LHsExpr GhcPs -> LHsExpr GhcPs -> LHsExpr GhcPs niceDotApp (L _ (HsVar _ (L _ r))) b | occNameStr r == "$" = b niceDotApp a b = dotApp a b -- Generate a lambda expression but prettier if possible. niceLambda :: [String] -> LHsExpr GhcPs -> LHsExpr GhcPs niceLambda ss e = fst (niceLambdaR ss e)-- We don't support refactorings yet. allowRightSection :: String -> Bool allowRightSection x = x `notElem` ["-","#"] allowLeftSection :: String -> Bool allowLeftSection x = x /= "#" -- Implementation. Try to produce special forms (e.g. sections, -- compositions) where we can. niceLambdaR :: [String] -> LHsExpr GhcPs -> (LHsExpr GhcPs, R.SrcSpan -> [Refactoring R.SrcSpan]) -- Rewrite @\ -> e@ as @e@ -- These are encountered as recursive calls. niceLambdaR xs (SimpleLambda [] x) = niceLambdaR xs x -- Rewrite @\xs -> (e)@ as @\xs -> e@. niceLambdaR xs (L _ (HsPar _ x)) = niceLambdaR xs x -- @\vs v -> ($) e v@ ==> @\vs -> e@ -- @\vs v -> e $ v@ ==> @\vs -> e@ niceLambdaR (unsnoc -> Just (vs, v)) (view -> App2 f e (view -> Var_ v')) | isDol f , v == v' , vars e `disjoint` [v] = niceLambdaR vs e -- @\v -> thing + v@ ==> @\v -> (thing +)@ (heuristic: @v@ must be a single -- lexeme, or it all gets too complex) niceLambdaR [v] (L _ (OpApp _ e f (view -> Var_ v'))) | isLexeme e , v == v' , vars e `disjoint` [v] , L _ (HsVar _ (L _ fname)) <- f , isSymOcc $ rdrNameOcc fname = let res = noLoc $ HsPar noExtField $ noLoc $ SectionL noExtField e f in (res, \s -> [Replace Expr s [] (unsafePrettyPrint res)]) -- @\vs v -> f x v@ ==> @\vs -> f x@ niceLambdaR (unsnoc -> Just (vs, v)) (L _ (HsApp _ f (view -> Var_ v'))) | v == v' , vars f `disjoint` [v] = niceLambdaR vs f -- @\vs v -> (v `f`)@ ==> @\vs -> f@ niceLambdaR (unsnoc -> Just (vs, v)) (L _ (SectionL _ (view -> Var_ v') f)) | v == v' = niceLambdaR vs f -- Strip one variable pattern from the end of a lambdas match, and place it in our list of factoring variables. niceLambdaR xs (SimpleLambda ((view -> PVar_ v):vs) x) | v `notElem` xs = niceLambdaR (xs++[v]) $ lambda vs x -- Rewrite @\x -> x + a@ as @(+ a)@ (heuristic: @a@ must be a single -- lexeme, or it all gets too complex). niceLambdaR [x] (view -> App2 op@(L _ (HsVar _ (L _ tag))) l r) | isLexeme r, view l == Var_ x, x `notElem` vars r, allowRightSection (occNameStr tag) = let e = rebracket1 $ addParen (noLoc $ SectionR noExtField op r) in (e, \s -> [Replace Expr s [] (unsafePrettyPrint e)]) -- Rewrite (1) @\x -> f (b x)@ as @f . b@, (2) @\x -> f $ b x@ as @f . b@. niceLambdaR [x] y | Just (z, subts) <- factor y, x `notElem` vars z = (z, \s -> [mkRefact subts s]) where -- Factor the expression with respect to x. factor :: LHsExpr GhcPs -> Maybe (LHsExpr GhcPs, [LHsExpr GhcPs]) factor y@(L _ (HsApp _ ini lst)) | view lst == Var_ x = Just (ini, [ini]) factor y@(L _ (HsApp _ ini lst)) | Just (z, ss) <- factor lst = let r = niceDotApp ini z in if astEq r z then Just (r, ss) else Just (r, ini : ss) factor (L _ (OpApp _ y op (factor -> Just (z, ss))))| isDol op = let r = niceDotApp y z in if astEq r z then Just (r, ss) else Just (r, y : ss) factor (L _ (HsPar _ y@(L _ HsApp{}))) = factor y factor _ = Nothing mkRefact :: [LHsExpr GhcPs] -> R.SrcSpan -> Refactoring R.SrcSpan mkRefact subts s = let tempSubts = zipWith (\a b -> ([a], toSS b)) ['a' .. 'z'] subts template = dotApps (map (strToVar . fst) tempSubts) in Replace Expr s tempSubts (unsafePrettyPrint template) -- Rewrite @\x y -> x + y@ as @(+)@. niceLambdaR [x,y] (L _ (OpApp _ (view -> Var_ x1) op@(L _ HsVar {}) (view -> Var_ y1))) | x == x1, y == y1, vars op `disjoint` [x, y] = (op, \s -> [Replace Expr s [] (unsafePrettyPrint op)]) -- Rewrite @\x y -> f y x@ as @flip f@. niceLambdaR [x, y] (view -> App2 op (view -> Var_ y1) (view -> Var_ x1)) | x == x1, y == y1, vars op `disjoint` [x, y] = ( gen op , \s -> [Replace Expr s [("x", toSS op)] (unsafePrettyPrint $ gen (strToVar "x"))] ) where gen = noLoc . HsApp noExtField (strToVar "flip") -- We're done factoring, but have no variables left, so we shouldn't make a lambda. -- @\ -> e@ ==> @e@ niceLambdaR [] e = (e, const []) -- Base case. Just a good old fashioned lambda. niceLambdaR ss e = let grhs = noLoc $ GRHS noExtField [] e :: LGRHS GhcPs (LHsExpr GhcPs) grhss = GRHSs {grhssExt = noExtField, grhssGRHSs=[grhs], grhssLocalBinds=noLoc $ EmptyLocalBinds noExtField} match = noLoc $ Match {m_ext=noExtField, m_ctxt=LambdaExpr, m_pats=map strToPat ss, m_grhss=grhss} :: LMatch GhcPs (LHsExpr GhcPs) matchGroup = MG {mg_ext=noExtField, mg_origin=Generated, mg_alts=noLoc [match]} in (noLoc $ HsLam noExtField matchGroup, const []) -- 'case' and 'if' expressions have branches, nothing else does (this -- doesn't consider 'HsMultiIf' perhaps it should?). replaceBranches :: LHsExpr GhcPs -> ([LHsExpr GhcPs], [LHsExpr GhcPs] -> LHsExpr GhcPs) replaceBranches (L l (HsIf _ _ a b c)) = ([b, c], \[b, c] -> cL l (HsIf noExtField Nothing a b c)) replaceBranches (L s (HsCase _ a (MG _ (L l bs) FromSource))) = (concatMap f bs, \xs -> cL s (HsCase noExtField a (MG noExtField (cL l (g bs xs)) Generated))) where f :: LMatch GhcPs (LHsExpr GhcPs) -> [LHsExpr GhcPs] f (L _ (Match _ CaseAlt _ (GRHSs _ xs _))) = [x | (L _ (GRHS _ _ x)) <- xs] f _ = error "GHC.Util.HsExpr.replaceBranches: unexpected XMatch" g :: [LMatch GhcPs (LHsExpr GhcPs)] -> [LHsExpr GhcPs] -> [LMatch GhcPs (LHsExpr GhcPs)] g (L s1 (Match _ CaseAlt a (GRHSs _ ns b)) : rest) xs = cL s1 (Match noExtField CaseAlt a (GRHSs noExtField [cL a (GRHS noExtField gs x) | (L a (GRHS _ gs _), x) <- zip ns as] b)) : g rest bs where (as, bs) = splitAt (length ns) xs g [] [] = [] g _ _ = error "GHC.Util.HsExpr.replaceBranches': internal invariant failed, lists are of differing lengths" replaceBranches x = ([], \[] -> x) -- Like needBracket, but with a special case for 'a . b . b', which was -- removed from haskell-src-exts-util-0.2.2. needBracketOld :: Int -> LHsExpr GhcPs -> LHsExpr GhcPs -> Bool needBracketOld i parent child | isDotApp parent, isDotApp child, i == 2 = False | otherwise = needBracket i parent child transformBracketOld :: (LHsExpr GhcPs -> Maybe (LHsExpr GhcPs)) -> LHsExpr GhcPs -> (LHsExpr GhcPs, LHsExpr GhcPs) transformBracketOld op = first snd . g where g = first f . descendBracketOld g f x = maybe (False, x) (True, ) (op x) -- Descend, and if something changes then add/remove brackets -- appropriately. Returns (suggested replacement, refactor template). -- Whenever a bracket is added to the suggested replacement, a -- corresponding bracket is added to the refactor template. descendBracketOld :: (LHsExpr GhcPs -> ((Bool, LHsExpr GhcPs), LHsExpr GhcPs)) -> LHsExpr GhcPs -> (LHsExpr GhcPs, LHsExpr GhcPs) descendBracketOld op x = (descendIndex g1 x, descendIndex g2 x) where g i y = if a then (f1 i b z, f2 i b z) else (b, z) where ((a, b), z) = op y g1 = (fst .) . g g2 = (snd .) . g f i (L _ (HsPar _ y)) z | not $ needBracketOld i x y = (y, z) f i y z | needBracketOld i x y = (addParen y, addParen z) -- https://github.com/mpickering/apply-refact/issues/7 | isOp y = (y, addParen z) f _ y z = (y, z) f1 = ((fst .) .) . f f2 = ((snd .) .) . f isOp = \case L _ (HsVar _ (L _ name)) -> isSymbolRdrName name _ -> False fromParen1 :: LHsExpr GhcPs -> LHsExpr GhcPs fromParen1 (L _ (HsPar _ x)) = x fromParen1 x = x hlint-3.1.6/src/GHC/Util/HsDecl.hs0000644000000000000000000000274213661521317014642 0ustar0000000000000000{-# LANGUAGE NamedFieldPuns #-} module GHC.Util.HsDecl (declName,bindName) where import GHC.Hs import SrcLoc import Language.Haskell.GhclibParserEx.GHC.Types.Name.Reader -- | @declName x@ returns the \"new name\" that is created (for -- example a function declaration) by @x@. If @x@ isn't a declaration -- that creates a new name (for example an instance declaration), -- 'Nothing' is returned instead. This is useful because we don't -- want to tell users to rename binders that they aren't creating -- right now and therefore usually cannot change. declName :: LHsDecl GhcPs -> Maybe String declName (L _ x) = occNameStr <$> case x of TyClD _ FamDecl{tcdFam=FamilyDecl{fdLName}} -> Just $ unLoc fdLName TyClD _ SynDecl{tcdLName} -> Just $ unLoc tcdLName TyClD _ DataDecl{tcdLName} -> Just $ unLoc tcdLName TyClD _ ClassDecl{tcdLName} -> Just $ unLoc tcdLName ValD _ FunBind{fun_id} -> Just $ unLoc fun_id ValD _ VarBind{var_id} -> Just var_id ValD _ (PatSynBind _ PSB{psb_id}) -> Just $ unLoc psb_id SigD _ (TypeSig _ (x:_) _) -> Just $ unLoc x SigD _ (PatSynSig _ (x:_) _) -> Just $ unLoc x SigD _ (ClassOpSig _ _ (x:_) _) -> Just $ unLoc x ForD _ ForeignImport{fd_name} -> Just $ unLoc fd_name ForD _ ForeignExport{fd_name} -> Just $ unLoc fd_name _ -> Nothing bindName :: LHsBind GhcPs -> Maybe String bindName (L _ FunBind{fun_id}) = Just $ rdrNameStr fun_id bindName (L _ VarBind{var_id}) = Just $ occNameStr var_id bindName _ = Nothing hlint-3.1.6/src/GHC/Util/FreeVars.hs0000644000000000000000000003164413671470061015220 0ustar0000000000000000{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE TypeFamilies #-} module GHC.Util.FreeVars ( vars, varss, pvars, Vars (..), FreeVars(..) , AllVars (..) ) where import RdrName import GHC.Hs.Types import OccName import Name import GHC.Hs import SrcLoc import Bag (bagToList) import Data.Generics.Uniplate.DataOnly import Data.Monoid import Data.Semigroup import Data.List.Extra import Data.Set (Set) import qualified Data.Set as Set import Prelude ( ^+ ) :: Set OccName -> Set OccName -> Set OccName ( ^+ ) = Set.union ( ^- ) :: Set OccName -> Set OccName -> Set OccName ( ^- ) = Set.difference -- See [Note : Space leaks lurking here?] below. data Vars = Vars{bound :: Set OccName, free :: Set OccName} -- Useful for debugging. instance Show Vars where show (Vars bs fs) = "bound : " ++ show (map occNameString (Set.toList bs)) ++ ", free : " ++ show (map occNameString (Set.toList fs)) instance Semigroup Vars where Vars x1 x2 <> Vars y1 y2 = Vars (x1 ^+ y1) (x2 ^+ y2) instance Monoid Vars where mempty = Vars Set.empty Set.empty mconcat vs = Vars (Set.unions $ map bound vs) (Set.unions $ map free vs) -- A type `a` is a model of `AllVars a` if exists a function -- `allVars` for producing a pair of the bound and free varaiable -- sets in a value of `a`. class AllVars a where -- | Return the variables, erring on the side of more free -- variables. allVars :: a -> Vars -- A type `a` is a model of `FreeVars a` if exists a function -- `freeVars` for producing a set of free varaiable of a value of -- `a`. class FreeVars a where -- | Return the variables, erring on the side of more free -- variables. freeVars :: a -> Set OccName -- Trivial instances. instance AllVars Vars where allVars = id instance FreeVars (Set OccName) where freeVars = id -- [Note : Space leaks lurking here?] -- ================================== -- We make use of `foldr`. @cocreature suggests we want bangs on `data -- Vars` and replace usages of `mconcat` with `foldl`. instance (AllVars a) => AllVars [a] where allVars = mconcatMap allVars instance (FreeVars a) => FreeVars [a] where freeVars = Set.unions . map freeVars -- Construct a `Vars` value with no bound vars. freeVars_ :: (FreeVars a) => a -> Vars freeVars_ = Vars Set.empty . freeVars -- `inFree a b` is the set of free variables in a together with the -- free variables in b not bound in a. inFree :: (AllVars a, FreeVars b) => a -> b -> Set OccName inFree a b = free aa ^+ (freeVars b ^- bound aa) where aa = allVars a -- `inVars a b` is a value of `Vars_` with bound variables the union -- of the bound variables of a and b and free variables the union -- of the free variables of a and the free variables of b not -- bound by a. inVars :: (AllVars a, AllVars b) => a -> b -> Vars inVars a b = Vars (bound aa ^+ bound bb) (free aa ^+ (free bb ^- bound aa)) where aa = allVars a bb = allVars b -- Get an `OccName` out of a reader name. unqualNames :: Located RdrName -> [OccName] unqualNames (L _ (Unqual x)) = [x] unqualNames (L _ (Exact x)) = [nameOccName x] unqualNames _ = [] instance FreeVars (LHsExpr GhcPs) where freeVars (L _ (HsVar _ x)) = Set.fromList $ unqualNames x -- Variable. freeVars (L _ (HsUnboundVar _ x)) = Set.fromList [unboundVarOcc x] -- Unbound variable; also used for "holes". freeVars (L _ (HsLam _ mg)) = free (allVars mg) -- Lambda abstraction. Currently always a single match. freeVars (L _ (HsLamCase _ MG{mg_alts=(L _ ms)})) = free (allVars ms) -- Lambda case freeVars (L _ (HsCase _ of_ MG{mg_alts=(L _ ms)})) = freeVars of_ ^+ free (allVars ms) -- Case expr. freeVars (L _ (HsLet _ binds e)) = inFree binds e -- Let (rec). freeVars (L _ (HsDo _ ctxt (L _ stmts))) = free (allVars stmts) -- Do block. freeVars (L _ (RecordCon _ _ (HsRecFields flds _))) = Set.unions $ map freeVars flds -- Record construction. freeVars (L _ (RecordUpd _ e flds)) = Set.unions $ freeVars e : map freeVars flds -- Record update. freeVars (L _ (HsMultiIf _ grhss)) = free (allVars grhss) -- Multi-way if. freeVars (L _ HsConLikeOut{}) = mempty -- After typechecker. freeVars (L _ HsRecFld{}) = mempty -- Variable pointing to a record selector. freeVars (L _ HsOverLabel{}) = mempty -- Overloaded label. The id of the in-scope fromLabel. freeVars (L _ HsIPVar{}) = mempty -- Implicit parameter. freeVars (L _ HsOverLit{}) = mempty -- Overloaded literal. freeVars (L _ HsLit{}) = mempty -- Simple literal. freeVars (L _ HsRnBracketOut{}) = mempty -- Renamer produces these. freeVars (L _ HsTcBracketOut{}) = mempty -- Typechecker produces these. freeVars (L _ HsWrap{}) = mempty -- Typechecker output. -- freeVars (e@(L _ HsAppType{})) = freeVars $ children e -- Visible type application e.g. f @ Int x y. -- freeVars (e@(L _ HsApp{})) = freeVars $ children e -- Application. -- freeVars (e@(L _ OpApp{})) = freeVars $ children e -- Operator application. -- freeVars (e@(L _ NegApp{})) = freeVars $ children e -- Negation operator. -- freeVars (e@(L _ HsPar{})) = freeVars $ children e -- Parenthesized expr. -- freeVars (e@(L _ SectionL{})) = freeVars $ children e -- Left section. -- freeVars (e@(L _ SectionR{})) = freeVars $ children e -- Right section. -- freeVars (e@(L _ ExplicitTuple{})) = freeVars $ children e -- Explicit tuple and sections thereof. -- freeVars (e@(L _ ExplicitSum{})) = freeVars $ children e -- Used for unboxed sum types. -- freeVars (e@(L _ HsIf{})) = freeVars $ children e -- If. -- freeVars (e@(L _ ExplicitList{})) = freeVars $ children e -- Syntactic list e.g. [a, b, c]. -- freeVars (e@(L _ ExprWithTySig{})) = freeVars $ children e -- Expr with type signature. -- freeVars (e@(L _ ArithSeq {})) = freeVars $ children e -- Arithmetic sequence. -- freeVars (e@(L _ HsSCC{})) = freeVars $ children e -- Set cost center pragma (expr whose const is to be measured). -- freeVars (e@(L _ HsCoreAnn{})) = freeVars $ children e -- Pragma. -- freeVars (e@(L _ HsBracket{})) = freeVars $ children e -- Haskell bracket. -- freeVars (e@(L _ HsSpliceE{})) = freeVars $ children e -- Template haskell splice expr. -- freeVars (e@(L _ HsProc{})) = freeVars $ children e -- Proc notation for arrows. -- freeVars (e@(L _ HsStatic{})) = freeVars $ children e -- Static pointers extension. -- freeVars (e@(L _ HsArrApp{})) = freeVars $ children e -- Arrow tail or arrow application. -- freeVars (e@(L _ HsArrForm{})) = freeVars $ children e -- Come back to it. Arrow tail or arrow application. -- freeVars (e@(L _ HsTick{})) = freeVars $ children e -- Haskell program coverage (Hpc) support. -- freeVars (e@(L _ HsBinTick{})) = freeVars $ children e -- Haskell program coverage (Hpc) support. -- freeVars (e@(L _ HsTickPragma{})) = freeVars $ children e -- Haskell program coverage (Hpc) support. -- freeVars (e@(L _ EAsPat{})) = freeVars $ children e -- Expr as pat. -- freeVars (e@(L _ EViewPat{})) = freeVars $ children e -- View pattern. -- freeVars (e@(L _ ELazyPat{})) = freeVars $ children e -- Lazy pattern. freeVars e = freeVars $ children e instance FreeVars (LHsTupArg GhcPs) where freeVars (L _ (Present _ args)) = freeVars args freeVars _ = mempty instance FreeVars (LHsRecField GhcPs (LHsExpr GhcPs)) where freeVars o@(L _ (HsRecField x _ True)) = Set.singleton $ occName $ unLoc $ rdrNameFieldOcc $ unLoc x -- a pun freeVars o@(L _ (HsRecField _ x _)) = freeVars x instance FreeVars (LHsRecUpdField GhcPs) where freeVars (L _ (HsRecField _ x _)) = freeVars x instance AllVars (Located (Pat GhcPs)) where allVars (L _ (VarPat _ (L _ x))) = Vars (Set.singleton $ rdrNameOcc x) Set.empty -- Variable pattern. allVars (L _ (AsPat _ n x)) = allVars (noLoc $ VarPat noExtField n :: LPat GhcPs) <> allVars x -- As pattern. allVars (L _ (ConPatIn _ (RecCon (HsRecFields flds _)))) = allVars flds allVars (L _ (NPlusKPat _ n _ _ _ _)) = allVars (noLoc $ VarPat noExtField n :: LPat GhcPs) -- n+k pattern. allVars (L _ (ViewPat _ e p)) = freeVars_ e <> allVars p -- View pattern. allVars (L _ WildPat{}) = mempty -- Wildcard pattern. allVars (L _ ConPatOut{}) = mempty -- Renamer/typechecker. allVars (L _ LitPat{}) = mempty -- Literal pattern. allVars (L _ NPat{}) = mempty -- Natural pattern. -- allVars p@SplicePat{} = allVars $ children p -- Splice pattern (includes quasi-quotes). -- allVars p@SigPat{} = allVars $ children p -- Pattern with a type signature. -- allVars p@CoPat{} = allVars $ children p -- Coercion pattern. -- allVars p@LazyPat{} = allVars $ children p -- Lazy pattern. -- allVars p@ParPat{} = allVars $ children p -- Parenthesized pattern. -- allVars p@BangPat{} = allVars $ children p -- Bang pattern. -- allVars p@ListPat{} = allVars $ children p -- Syntactic list. -- allVars p@TuplePat{} = allVars $ children p -- Tuple sub patterns. -- allVars p@SumPat{} = allVars $ children p -- Anonymous sum pattern. allVars p = allVars $ children p instance AllVars (LHsRecField GhcPs (Located (Pat GhcPs))) where allVars (L _ (HsRecField _ x _)) = allVars x instance AllVars (LStmt GhcPs (LHsExpr GhcPs)) where allVars (L _ (LastStmt _ expr _ _)) = freeVars_ expr -- The last stmt of a ListComp, MonadComp, DoExpr,MDoExpr. allVars (L _ (BindStmt _ pat expr _ _)) = allVars pat <> freeVars_ expr -- A generator e.g. x <- [1, 2, 3]. allVars (L _ (BodyStmt _ expr _ _)) = freeVars_ expr -- A boolean guard e.g. even x. allVars (L _ (LetStmt _ binds)) = allVars binds -- A local declaration e.g. let y = x + 1 allVars (L _ (TransStmt _ _ stmts _ using by _ _ fmap_)) = allVars stmts <> freeVars_ using <> maybe mempty freeVars_ by <> freeVars_ (noLoc fmap_ :: Located (HsExpr GhcPs)) -- Apply a function to a list of statements in order. allVars (L _ (RecStmt _ stmts _ _ _ _ _)) = allVars stmts -- A recursive binding for a group of arrows. allVars (L _ ApplicativeStmt{}) = mempty -- Generated by the renamer. allVars (L _ ParStmt{}) = mempty -- Parallel list thing. Come back to it. allVars _ = mempty -- New ctor. instance AllVars (LHsLocalBinds GhcPs) where allVars (L _ (HsValBinds _ (ValBinds _ binds _))) = allVars (bagToList binds) -- Value bindings. allVars (L _ (HsIPBinds _ (IPBinds _ binds))) = allVars binds -- Implicit parameter bindings. allVars (L _ EmptyLocalBinds{}) = mempty -- The case of no local bindings (signals the empty `let` or `where` clause). allVars _ = mempty -- New ctor. instance AllVars (LIPBind GhcPs) where allVars (L _ (IPBind _ _ e)) = freeVars_ e allVars _ = mempty -- New ctor. instance AllVars (LHsBind GhcPs) where allVars (L _ FunBind{fun_id=n, fun_matches=MG{mg_alts=(L _ ms)}}) = allVars (noLoc $ VarPat noExtField n :: LPat GhcPs) <> allVars ms -- Function bindings and simple variable bindings e.g. f x = e, f !x = 3, f = e, !x = e, x `f` y = e allVars (L _ PatBind{pat_lhs=n, pat_rhs=grhss}) = allVars n <> allVars grhss -- Ctor patterns and some other interesting cases e.g. Just x = e, (x) = e, x :: Ty = e. allVars (L _ (PatSynBind _ PSB{})) = mempty -- Come back to it. allVars (L _ VarBind{}) = mempty -- Typechecker. allVars (L _ AbsBinds{}) = mempty -- Not sure but I think renamer. allVars _ = mempty -- New ctor. instance AllVars (MatchGroup GhcPs (LHsExpr GhcPs)) where allVars (MG _ _alts@(L _ alts) _) = inVars (foldMap (allVars . m_pats) ms) (allVars (map m_grhss ms)) where ms = map unLoc alts allVars _ = mempty -- New ctor. instance AllVars (LMatch GhcPs (LHsExpr GhcPs)) where allVars (L _ (Match _ FunRhs {mc_fun=name} pats grhss)) = allVars (noLoc $ VarPat noExtField name :: LPat GhcPs) <> allVars pats <> allVars grhss -- A pattern matching on an argument of a function binding. allVars (L _ (Match _ (StmtCtxt ctxt) pats grhss)) = allVars ctxt <> allVars pats <> allVars grhss -- Pattern of a do-stmt, list comprehension, pattern guard etc. allVars (L _ (Match _ _ pats grhss)) = inVars (allVars pats) (allVars grhss) -- Everything else. allVars _ = mempty -- New ctor. instance AllVars (HsStmtContext RdrName) where allVars (PatGuard FunRhs{mc_fun=n}) = allVars (noLoc $ VarPat noExtField n :: LPat GhcPs) allVars ParStmtCtxt{} = mempty -- Come back to it. allVars TransStmtCtxt{} = mempty -- Come back to it. allVars _ = mempty -- Everything else (correct). instance AllVars (GRHSs GhcPs (LHsExpr GhcPs)) where allVars (GRHSs _ grhss binds) = inVars binds (mconcatMap allVars grhss) allVars _ = mempty -- New ctor. instance AllVars (LGRHS GhcPs (LHsExpr GhcPs)) where allVars (L _ (GRHS _ guards expr)) = Vars (bound gs) (free gs ^+ (freeVars expr ^- bound gs)) where gs = allVars guards allVars _ = mempty -- New ctor. instance AllVars (LHsDecl GhcPs) where allVars (L l (ValD _ bind)) = allVars (L l bind :: LHsBind GhcPs) allVars _ = mempty -- We only consider value bindings. vars :: FreeVars a => a -> [String] vars = Set.toList . Set.map occNameString . freeVars varss :: AllVars a => a -> [String] varss = Set.toList . Set.map occNameString . free . allVars pvars :: AllVars a => a -> [String] pvars = Set.toList . Set.map occNameString . bound . allVars hlint-3.1.6/src/GHC/Util/DynFlags.hs0000644000000000000000000000156213671470061015206 0ustar0000000000000000module GHC.Util.DynFlags (initGlobalDynFlags, baseDynFlags) where import DynFlags import GHC.LanguageExtensions.Type import Data.List.Extra import Language.Haskell.GhclibParserEx.GHC.Settings.Config baseDynFlags :: DynFlags baseDynFlags = -- The list of default enabled extensions is empty except for -- 'TemplateHaskellQuotes'. This is because: -- * The extensions to enable/disable are set exclusively in -- 'parsePragmasIntoDynFlags' based solely on HSE parse flags -- (and source level annotations); -- * 'TemplateHaskellQuotes' is not a known HSE extension but IS -- needed if the GHC parse is to succeed for the unit-test at -- hlint.yaml:860 let enable = [TemplateHaskellQuotes] in foldl' xopt_set (defaultDynFlags fakeSettings fakeLlvmConfig) enable initGlobalDynFlags :: IO () initGlobalDynFlags = setUnsafeGlobalDynFlags baseDynFlags hlint-3.1.6/src/GHC/Util/Brackets.hs0000644000000000000000000001357413656755416015260 0ustar0000000000000000{-# LANGUAGE MultiParamTypeClasses , FlexibleInstances, FlexibleContexts #-} {-# OPTIONS_GHC -Wno-incomplete-patterns -Wno-overlapping-patterns #-} module GHC.Util.Brackets (Brackets(..), isApp,isOpApp,isAnyApp) where import GHC.Hs import SrcLoc import BasicTypes import Language.Haskell.GhclibParserEx.GHC.Hs.Expr class Brackets a where remParen :: a -> Maybe a -- Remove one paren or nothing if there is no paren. addParen :: a -> a -- Write out a paren. -- | Is this item lexically requiring no bracketing ever i.e. is -- totally atomic. isAtom :: a -> Bool -- | Is the child safe free from brackets in the parent -- position. Err on the side of caution, True = don't know. needBracket :: Int -> a -> a -> Bool instance Brackets (LHsExpr GhcPs) where -- When GHC parses a section in concrete syntax, it will produce an -- 'HsPar (Section[L|R])'. There is no concrete syntax that will -- result in a "naked" section. Consequently, given an expression, -- when stripping brackets (c.f. 'Hint.Brackets), don't remove the -- paren's surrounding a section - they are required. remParen (L _ (HsPar _ (L _ SectionL{}))) = Nothing remParen (L _ (HsPar _ (L _ SectionR{}))) = Nothing remParen (L _ (HsPar _ x)) = Just x remParen _ = Nothing addParen e = noLoc $ HsPar noExtField e isAtom (L _ x) = case x of HsVar{} -> True HsUnboundVar{} -> True HsRecFld{} -> True HsOverLabel{} -> True HsIPVar{} -> True -- Note that sections aren't atoms (but parenthesized sections are). HsPar{} -> True ExplicitTuple{} -> True ExplicitSum{} -> True ExplicitList{} -> True RecordCon{} -> True RecordUpd{} -> True ArithSeq{}-> True HsBracket{} -> True HsSpliceE {} -> True HsOverLit _ x | not $ isNegativeOverLit x -> True HsLit _ x | not $ isNegativeLit x -> True _ -> False where isNegativeLit (HsInt _ i) = il_neg i isNegativeLit (HsRat _ f _) = fl_neg f isNegativeLit (HsFloatPrim _ f) = fl_neg f isNegativeLit (HsDoublePrim _ f) = fl_neg f isNegativeLit (HsIntPrim _ x) = x < 0 isNegativeLit (HsInt64Prim _ x) = x < 0 isNegativeLit (HsInteger _ x _) = x < 0 isNegativeLit _ = False isNegativeOverLit OverLit {ol_val=HsIntegral i} = il_neg i isNegativeOverLit OverLit {ol_val=HsFractional f} = fl_neg f isNegativeOverLit _ = False isAtom _ = False -- '{-# COMPLETE L #-}' needBracket i parent child -- Note: i is the index in children, not in the AST. | isAtom child = False | isSection parent, L _ HsApp{} <- child = False | L _ OpApp{} <- parent, L _ HsApp{} <- child, i /= 0 || isAtomOrApp child = False | L _ ExplicitList{} <- parent = False | L _ ExplicitTuple{} <- parent = False | L _ HsIf{} <- parent, isAnyApp child = False | L _ HsApp{} <- parent, i == 0, L _ HsApp{} <- child = False | L _ ExprWithTySig{} <- parent, i == 0, isApp child = False | L _ RecordCon{} <- parent = False | L _ RecordUpd{} <- parent, i /= 0 = False -- These all have view patterns embedded within them, or are naturally followed by ->, so we have to watch out for -- @(x::y) -> z@ which is valid, as either a type annotation, or a view pattern. | L _ HsLet{} <- parent, isApp child = False | L _ HsDo{} <- parent, isAnyApp child = False | L _ HsLam{} <- parent, isAnyApp child = False | L _ HsCase{} <- parent, isAnyApp child = False | L _ HsPar{} <- parent = False | otherwise = True -- | Am I an HsApp such that having me in an infix doesn't require brackets. -- Before BlockArguments that was _all_ HsApps. Now, imagine: -- -- (f \x -> x) *> ... -- (f do x) *> ... isAtomOrApp :: LHsExpr GhcPs -> Bool isAtomOrApp x | isAtom x = True isAtomOrApp (L _ (HsApp _ _ x)) = isAtomOrApp x isAtomOrApp _ = False instance Brackets (Located (Pat GhcPs)) where remParen (L _ (ParPat _ x)) = Just x remParen _ = Nothing addParen e = noLoc $ ParPat noExtField e isAtom (L _ x) = case x of ParPat{} -> True TuplePat{} -> True ListPat{} -> True ConPatIn _ RecCon{} -> True ConPatIn _ (PrefixCon []) -> True VarPat{} -> True WildPat{} -> True SumPat{} -> True AsPat{} -> True SplicePat{} -> True LitPat _ x | not $ isSignedLit x -> True _ -> False where isSignedLit HsInt{} = True isSignedLit HsIntPrim{} = True isSignedLit HsInt64Prim{} = True isSignedLit HsInteger{} = True isSignedLit HsRat{} = True isSignedLit HsFloatPrim{} = True isSignedLit HsDoublePrim{} = True isSignedLit _ = False isAtom _ = False -- '{-# COMPLETE L #-}' needBracket _ parent child | isAtom child = False | L _ TuplePat{} <- parent = False | L _ ListPat{} <- parent = False | otherwise = True instance Brackets (LHsType GhcPs) where remParen (L _ (HsParTy _ x)) = Just x remParen _ = Nothing addParen e = noLoc $ HsParTy noExtField e isAtom (L _ x) = case x of HsParTy{} -> True HsTupleTy{} -> True HsListTy{} -> True HsExplicitTupleTy{} -> True HsExplicitListTy{} -> True HsTyVar{} -> True HsSumTy{} -> True HsSpliceTy{} -> True HsWildCardTy{} -> True _ -> False isAtom _ = False -- '{-# COMPLETE L #-}' needBracket _ parent child | isAtom child = False -- a -> (b -> c) is not a required bracket, but useful for documentation about arity etc. -- | TyFun{} <- parent, i == 1, TyFun{} <- child = False | L _ HsFunTy{} <- parent, L _ HsAppTy{} <- child = False | L _ HsTupleTy{} <- parent = False | L _ HsListTy{} <- parent = False | L _ HsExplicitTupleTy{} <- parent = False | L _ HsListTy{} <- parent = False | L _ HsExplicitListTy{} <- parent = False | L _ HsOpTy{} <- parent, L _ HsAppTy{} <- child = False | L _ HsParTy{} <- parent = False | otherwise = True hlint-3.1.6/src/GHC/Util/ApiAnnotation.hs0000644000000000000000000000706513653537201016247 0ustar0000000000000000 module GHC.Util.ApiAnnotation ( comment, commentText, isCommentMultiline , pragmas, flags, languagePragmas , mkFlags, mkLanguagePragmas ) where import ApiAnnotation import SrcLoc import Control.Applicative import qualified Data.Map.Strict as Map import Data.Maybe import Data.List.Extra trimCommentStart :: String -> String trimCommentStart s | Just s <- stripPrefix "{-" s = s | Just s <- stripPrefix "--" s = s | otherwise = s trimCommentEnd :: String -> String trimCommentEnd s | Just s <- stripSuffix "-}" s = s | otherwise = s trimCommentDelims :: String -> String trimCommentDelims = trimCommentEnd . trimCommentStart -- | A comment as a string. comment :: Located AnnotationComment -> String comment (L _ (AnnBlockComment s)) = s comment (L _ (AnnLineComment s)) = s comment (L _ (AnnDocOptions s)) = s comment (L _ (AnnDocCommentNamed s)) = s comment (L _ (AnnDocCommentPrev s)) = s comment (L _ (AnnDocCommentNext s)) = s comment (L _ (AnnDocSection _ s)) = s -- | The comment string with delimiters removed. commentText :: Located AnnotationComment -> String commentText = trimCommentDelims . comment isCommentMultiline :: Located AnnotationComment -> Bool isCommentMultiline (L _ (AnnBlockComment _)) = True isCommentMultiline _ = False -- GHC parse trees don't contain pragmas. We work around this with -- (nasty) parsing of comments. -- Pragmas. Comments not associated with a span in the annotations -- that have the form @{-# ...#-}@. pragmas :: ApiAnns -> [(Located AnnotationComment, String)] pragmas anns = -- 'ApiAnns' stores pragmas in reverse order to how they were -- encountered in the source file with the last at the head of the -- list (makes sense when you think about it). reverse [ (c, s) | c@(L _ (AnnBlockComment comm)) <- fromMaybe [] $ Map.lookup noSrcSpan (snd anns) , let body = trimCommentDelims comm , Just rest <- [stripSuffix "#" =<< stripPrefix "#" body] , let s = trim rest ] -- Utility for a case insensitive prefix strip. stripPrefixCI :: String -> String -> Maybe String stripPrefixCI pref str = let pref' = lower pref (str_pref, rest) = splitAt (length pref') str in if lower str_pref == pref' then Just rest else Nothing -- Flags. The first element of the pair is the (located) annotation -- comment that sets the flags enumerated in the second element of the -- pair. flags :: [(Located AnnotationComment, String)] -> [(Located AnnotationComment, [String])] flags ps = -- Old versions of GHC accepted 'OPTIONS' rather than 'OPTIONS_GHC' (but -- this is deprecated). [(c, opts) | (c, s) <- ps , Just rest <- [stripPrefixCI "OPTIONS_GHC " s <|> stripPrefixCI "OPTIONS " s] , let opts = words rest] -- Language pragmas. The first element of the -- pair is the (located) annotation comment that enables the -- pragmas enumerated by he second element of the pair. languagePragmas :: [(Located AnnotationComment, String)] -> [(Located AnnotationComment, [String])] languagePragmas ps = [(c, exts) | (c, s) <- ps , Just rest <- [stripPrefixCI "LANGUAGE " s] , let exts = map trim (splitOn "," rest)] -- Given a list of flags, make a GHC options pragma. mkFlags :: SrcSpan -> [String] -> Located AnnotationComment mkFlags loc flags = L loc $ AnnBlockComment ("{-# " ++ "OPTIONS_GHC " ++ unwords flags ++ " #-}") mkLanguagePragmas :: SrcSpan -> [String] -> Located AnnotationComment mkLanguagePragmas loc exts = L loc $ AnnBlockComment ("{-# " ++ "LANGUAGE " ++ intercalate ", " exts ++ " #-}") hlint-3.1.6/src/Config/0000755000000000000000000000000013674744765013050 5ustar0000000000000000hlint-3.1.6/src/Config/Yaml.hs0000644000000000000000000003325013671470061014267 0ustar0000000000000000{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE OverloadedStrings, ViewPatterns, RecordWildCards, GeneralizedNewtypeDeriving, TupleSections #-} {-# LANGUAGE CPP #-} module Config.Yaml( ConfigYaml, readFileConfigYaml, settingsFromConfigYaml ) where import Config.Type import Data.Either import Data.Maybe import Data.List.Extra import Data.Tuple.Extra import Control.Monad.Extra import qualified Data.Text as T import qualified Data.Vector as V import qualified Data.ByteString.Char8 as BS import qualified Data.HashMap.Strict as Map import Data.Generics.Uniplate.DataOnly import GHC.All import Fixity import Extension import Module import Data.Functor import Data.Semigroup import Timing import Prelude import Bag import Lexer import ErrUtils hiding (Severity) import Outputable import GHC.Hs import SrcLoc import RdrName import OccName import GHC.Util (baseDynFlags, Scope, scopeCreate) import Language.Haskell.GhclibParserEx.GHC.Hs.ExtendInstances import Language.Haskell.GhclibParserEx.GHC.Types.Name.Reader import Data.Char #ifdef HS_YAML import Data.YAML (Pos) import Data.YAML.Aeson (encode1Strict, decode1Strict) import Data.Aeson hiding (encode) import Data.Aeson.Types (Parser) import qualified Data.ByteString as BSS decodeFileEither :: FilePath -> IO (Either (Pos, String) ConfigYaml) decodeFileEither path = decode1Strict <$> BSS.readFile path decodeEither' :: BSS.ByteString -> Either (Pos, String) ConfigYaml decodeEither' = decode1Strict displayException :: (Pos, String) -> String displayException = show encode :: Value -> BSS.ByteString encode = encode1Strict #else import Data.Yaml import Control.Exception.Extra #endif -- | Read a config file in YAML format. Takes a filename, and optionally the contents. -- Fails if the YAML doesn't parse or isn't valid HLint YAML readFileConfigYaml :: FilePath -> Maybe String -> IO ConfigYaml readFileConfigYaml file contents = timedIO "Config" file $ do val <- case contents of Nothing -> decodeFileEither file Just src -> pure $ decodeEither' $ BS.pack src case val of Left e -> fail $ "Failed to read YAML configuration file " ++ file ++ "\n " ++ displayException e Right v -> pure v --------------------------------------------------------------------- -- YAML DATA TYPE newtype ConfigYaml = ConfigYaml [ConfigItem] deriving (Semigroup,Monoid,Show) data ConfigItem = ConfigPackage Package | ConfigGroup Group | ConfigSetting [Setting] deriving Show data Package = Package {packageName :: String ,packageModules :: [HsExtendInstances (LImportDecl GhcPs)] } deriving Show data Group = Group {groupName :: String ,groupEnabled :: Bool ,groupImports :: [Either String (HsExtendInstances (LImportDecl GhcPs))] ,groupRules :: [Either HintRule Classify] -- HintRule has scope set to mempty } deriving Show --------------------------------------------------------------------- -- YAML PARSING LIBRARY data Val = Val Value -- the actual value I'm focused on [(String, Value)] -- the path of values I followed (for error messages) newVal :: Value -> Val newVal x = Val x [("root", x)] getVal :: Val -> Value getVal (Val x _) = x addVal :: String -> Value -> Val -> Val addVal key v (Val focus path) = Val v $ (key,v) : path -- | Failed when parsing some value, give an informative error message. parseFail :: Val -> String -> Parser a parseFail (Val focus path) msg = fail $ "Error when decoding YAML, " ++ msg ++ "\n" ++ "Along path: " ++ unwords steps ++ "\n" ++ "When at: " ++ fst (word1 $ show focus) ++ "\n" ++ -- aim to show a smallish but relevant context dotDot (fromMaybe (encode focus) $ listToMaybe $ dropWhile (\x -> BS.length x > 250) $ map encode contexts) where (steps, contexts) = unzip $ reverse path dotDot x = let (a,b) = BS.splitAt 250 x in BS.unpack a ++ (if BS.null b then "" else "...") parseArray :: Val -> Parser [Val] parseArray v@(getVal -> Array xs) = concatMapM parseArray $ zipWithFrom (\i x -> addVal (show i) x v) 0 $ V.toList xs parseArray v = pure [v] parseObject :: Val -> Parser (Map.HashMap T.Text Value) parseObject (getVal -> Object x) = pure x parseObject v = parseFail v "Expected an Object" parseObject1 :: Val -> Parser (String, Val) parseObject1 v = do mp <- parseObject v case Map.keys mp of [T.unpack -> s] -> (s,) <$> parseField s v _ -> parseFail v $ "Expected exactly one key but got " ++ show (Map.size mp) parseString :: Val -> Parser String parseString (getVal -> String x) = pure $ T.unpack x parseString v = parseFail v "Expected a String" parseInt :: Val -> Parser Int parseInt (getVal -> s@Number{}) = parseJSON s parseInt v = parseFail v "Expected an Int" parseArrayString :: Val -> Parser [String] parseArrayString = parseArray >=> mapM parseString maybeParse :: (Val -> Parser a) -> Maybe Val -> Parser (Maybe a) maybeParse parseValue Nothing = pure Nothing maybeParse parseValue (Just value) = Just <$> parseValue value parseBool :: Val -> Parser Bool parseBool (getVal -> Bool b) = pure b parseBool v = parseFail v "Expected a Bool" parseField :: String -> Val -> Parser Val parseField s v = do x <- parseFieldOpt s v case x of Nothing -> parseFail v $ "Expected a field named " ++ s Just v -> pure v parseFieldOpt :: String -> Val -> Parser (Maybe Val) parseFieldOpt s v = do mp <- parseObject v case Map.lookup (T.pack s) mp of Nothing -> pure Nothing Just x -> pure $ Just $ addVal s x v allowFields :: Val -> [String] -> Parser () allowFields v allow = do mp <- parseObject v let bad = map T.unpack (Map.keys mp) \\ allow when (bad /= []) $ parseFail v $ "Not allowed keys: " ++ unwords bad parseGHC :: (ParseFlags -> String -> ParseResult v) -> Val -> Parser v parseGHC parser v = do x <- parseString v case parser defaultParseFlags{enabledExtensions=configExtensions, disabledExtensions=[]} x of POk _ x -> pure x PFailed ps -> let (_, errs) = getMessages ps baseDynFlags errMsg = head (bagToList errs) msg = Outputable.showSDoc baseDynFlags $ ErrUtils.pprLocErrMsg errMsg in parseFail v $ "Failed to parse " ++ msg ++ ", when parsing:\n " ++ x --------------------------------------------------------------------- -- YAML TO DATA TYPE instance FromJSON ConfigYaml where parseJSON Null = pure mempty parseJSON x = parseConfigYaml $ newVal x parseConfigYaml :: Val -> Parser ConfigYaml parseConfigYaml v = do vs <- parseArray v fmap ConfigYaml $ forM vs $ \o -> do (s, v) <- parseObject1 o case s of "package" -> ConfigPackage <$> parsePackage v "group" -> ConfigGroup <$> parseGroup v "arguments" -> ConfigSetting . map SettingArgument <$> parseArrayString v "fixity" -> ConfigSetting <$> parseFixity v "smell" -> ConfigSetting <$> parseSmell v _ | isJust $ getSeverity s -> ConfigGroup . ruleToGroup <$> parseRule o _ | Just r <- getRestrictType s -> ConfigSetting . map SettingRestrict <$> (parseArray v >>= mapM (parseRestrict r)) _ -> parseFail v "Expecting an object with a 'package' or 'group' key, a hint or a restriction" parsePackage :: Val -> Parser Package parsePackage v = do packageName <- parseField "name" v >>= parseString packageModules <- parseField "modules" v >>= parseArray >>= mapM (fmap extendInstances <$> parseGHC parseImportDeclGhcWithMode) allowFields v ["name","modules"] pure Package{..} parseFixity :: Val -> Parser [Setting] parseFixity v = parseArray v >>= concatMapM (parseGHC parseDeclGhcWithMode >=> f) where f (L _ (SigD _ (FixSig _ x))) = pure $ map Infix $ fromFixitySig x f _ = parseFail v "Expected fixity declaration" parseSmell :: Val -> Parser [Setting] parseSmell v = do smellName <- parseField "type" v >>= parseString smellType <- require v "Expected SmellType" $ getSmellType smellName smellLimit <- parseField "limit" v >>= parseInt pure [SettingSmell smellType smellLimit] where require :: Val -> String -> Maybe a -> Parser a require _ _ (Just a) = pure a require val err Nothing = parseFail val err parseGroup :: Val -> Parser Group parseGroup v = do groupName <- parseField "name" v >>= parseString groupEnabled <- parseFieldOpt "enabled" v >>= maybe (pure True) parseBool groupImports <- parseFieldOpt "imports" v >>= maybe (pure []) (parseArray >=> mapM parseImport) groupRules <- parseFieldOpt "rules" v >>= maybe (pure []) parseArray >>= concatMapM parseRule allowFields v ["name","enabled","imports","rules"] pure Group{..} where parseImport v = do x <- parseString v case word1 x of ("package", x) -> pure $ Left x _ -> Right . extendInstances <$> parseGHC parseImportDeclGhcWithMode v ruleToGroup :: [Either HintRule Classify] -> Group ruleToGroup = Group "" True [] parseRule :: Val -> Parser [Either HintRule Classify] parseRule v = do (severity, v) <- parseSeverityKey v isRule <- isJust <$> parseFieldOpt "lhs" v if isRule then do hintRuleNotes <- parseFieldOpt "note" v >>= maybe (pure []) (fmap (map asNote) . parseArrayString) lhs <- parseField "lhs" v >>= parseGHC parseExpGhcWithMode rhs <- parseField "rhs" v >>= parseGHC parseExpGhcWithMode hintRuleSide <- parseFieldOpt "side" v >>= maybe (pure Nothing) (fmap (Just . extendInstances) . parseGHC parseExpGhcWithMode) hintRuleName <- parseFieldOpt "name" v >>= maybe (pure $ guessName lhs rhs) parseString allowFields v ["lhs","rhs","note","name","side"] let hintRuleScope = mempty pure [Left HintRule{hintRuleSeverity=severity,hintRuleLHS=extendInstances lhs,hintRuleRHS=extendInstances rhs, ..}] else do names <- parseFieldOpt "name" v >>= maybe (pure []) parseArrayString within <- parseFieldOpt "within" v >>= maybe (pure [("","")]) (parseArray >=> concatMapM parseWithin) pure [Right $ Classify severity n a b | (a,b) <- within, n <- ["" | null names] ++ names] parseRestrict :: RestrictType -> Val -> Parser Restrict parseRestrict restrictType v = do def <- parseFieldOpt "default" v case def of Just def -> do b <- parseBool def allowFields v ["default"] pure $ Restrict restrictType b [] [] [] [] Nothing Nothing -> do restrictName <- parseFieldOpt "name" v >>= maybe (pure []) parseArrayString restrictWithin <- parseFieldOpt "within" v >>= maybe (pure [("","")]) (parseArray >=> concatMapM parseWithin) restrictAs <- parseFieldOpt "as" v >>= maybe (pure []) parseArrayString restrictBadIdents <- parseFieldOpt "badidents" v >>= maybe (pure []) parseArrayString restrictMessage <- parseFieldOpt "message" v >>= maybeParse parseString allowFields v $ ["as" | restrictType == RestrictModule] ++ ["badidents", "name", "within", "message"] pure Restrict{restrictDefault=True,..} parseWithin :: Val -> Parser [(String, String)] -- (module, decl) parseWithin v = do x <- parseGHC parseExpGhcWithMode v case x of L _ (HsVar _ (L _ (Unqual x))) -> pure $ f "" (occNameString x) L _ (HsVar _ (L _ (Qual mod x))) -> pure $ f (moduleNameString mod) (occNameString x) _ -> parseFail v "Bad classification rule" where f mod name@(c:_) | isUpper c = [(mod,name),(mod ++ ['.' | mod /= ""] ++ name, "")] f mod name = [(mod, name)] parseSeverityKey :: Val -> Parser (Severity, Val) parseSeverityKey v = do (s, v) <- parseObject1 v case getSeverity s of Just sev -> pure (sev, v) _ -> parseFail v $ "Key should be a severity (e.g. warn/error/suggest) but got " ++ s guessName :: LHsExpr GhcPs -> LHsExpr GhcPs -> String guessName lhs rhs | n:_ <- rs \\ ls = "Use " ++ n | n:_ <- ls \\ rs = "Redundant " ++ n | otherwise = defaultHintName where (ls, rs) = both f (lhs, rhs) f :: LHsExpr GhcPs -> [String] f x = [y | L _ (HsVar _ (L _ x)) <- universe x, let y = occNameStr x, not $ isUnifyVar y, y /= "."] asNote :: String -> Note asNote "IncreasesLaziness" = IncreasesLaziness asNote "DecreasesLaziness" = DecreasesLaziness asNote (word1 -> ("RemovesError",x)) = RemovesError x asNote (word1 -> ("ValidInstance",x)) = uncurry ValidInstance $ word1 x asNote (word1 -> ("RequiresExtension",x)) = RequiresExtension x asNote x = Note x --------------------------------------------------------------------- -- SETTINGS settingsFromConfigYaml :: [ConfigYaml] -> [Setting] settingsFromConfigYaml (mconcat -> ConfigYaml configs) = settings ++ concatMap f groups where packages = [x | ConfigPackage x <- configs] groups = [x | ConfigGroup x <- configs] settings = concat [x | ConfigSetting x <- configs] packageMap' = Map.fromListWith (++) [(packageName, fmap unextendInstances packageModules) | Package{..} <- packages] groupMap = Map.fromListWith (\new old -> new) [(groupName, groupEnabled) | Group{..} <- groups] f Group{..} | Map.lookup groupName groupMap == Just False = [] | otherwise = map (either (\r -> SettingMatchExp r{hintRuleScope=scope'}) SettingClassify) groupRules where scope'= asScope' packageMap' (map (fmap unextendInstances) groupImports) asScope' :: Map.HashMap String [LImportDecl GhcPs] -> [Either String (LImportDecl GhcPs)] -> Scope asScope' packages xs = scopeCreate (HsModule Nothing Nothing (concatMap f xs) [] Nothing Nothing) where f (Right x) = [x] f (Left x) | Just pkg <- Map.lookup x packages = pkg | otherwise = error $ "asScope' failed to do lookup, " ++ x hlint-3.1.6/src/Config/Type.hs0000644000000000000000000001321113637734575014320 0ustar0000000000000000 module Config.Type( Severity(..), Classify(..), HintRule(..), Note(..), Setting(..), Restrict(..), RestrictType(..), SmellType(..), defaultHintName, isUnifyVar, showNotes, getSeverity, getRestrictType, getSmellType ) where import Data.Char import Data.List.Extra import Prelude import qualified GHC.Hs import Fixity import GHC.Util import Language.Haskell.GhclibParserEx.GHC.Hs.ExtendInstances getSeverity :: String -> Maybe Severity getSeverity "ignore" = Just Ignore getSeverity "warn" = Just Warning getSeverity "warning" = Just Warning getSeverity "suggest" = Just Suggestion getSeverity "suggestion" = Just Suggestion getSeverity "error" = Just Error getSeverity "hint" = Just Suggestion getSeverity _ = Nothing getRestrictType :: String -> Maybe RestrictType getRestrictType "modules" = Just RestrictModule getRestrictType "extensions" = Just RestrictExtension getRestrictType "flags" = Just RestrictFlag getRestrictType "functions" = Just RestrictFunction getRestrictType _ = Nothing defaultHintName :: String defaultHintName = "Use alternative" -- | How severe an issue is. data Severity = Ignore -- ^ The issue has been explicitly ignored and will usually be hidden (pass @--show@ on the command line to see ignored ideas). | Suggestion -- ^ Suggestions are things that some people may consider improvements, but some may not. | Warning -- ^ Warnings are suggestions that are nearly always a good idea to apply. | Error -- ^ Available as a setting for the user. Only parse errors have this setting by default. deriving (Eq,Ord,Show,Read,Bounded,Enum) -- Any 1-letter variable names are assumed to be unification variables isUnifyVar :: String -> Bool isUnifyVar [x] = x == '?' || isAlpha x isUnifyVar [] = False isUnifyVar xs = all (== '?') xs --------------------------------------------------------------------- -- TYPE -- | A note describing the impact of the replacement. data Note = IncreasesLaziness -- ^ The replacement is increases laziness, for example replacing @reverse (reverse x)@ with @x@ makes the code lazier. | DecreasesLaziness -- ^ The replacement is decreases laziness, for example replacing @(fst x, snd x)@ with @x@ makes the code stricter. | RemovesError String -- ^ The replacement removes errors, for example replacing @foldr1 (+)@ with @sum@ removes an error on @[]@, and might contain the text @\"on []\"@. | ValidInstance String String -- ^ The replacement assumes standard type class lemmas, a hint with the note @ValidInstance \"Eq\" \"x\"@ might only be valid if -- the @x@ variable has a reflexive @Eq@ instance. | RequiresExtension String -- ^ The replacement requires this extension to be available. | Note String -- ^ An arbitrary note. deriving (Eq,Ord) instance Show Note where show IncreasesLaziness = "increases laziness" show DecreasesLaziness = "decreases laziness" show (RemovesError x) = "removes error " ++ x show (ValidInstance x y) = "requires a valid `" ++ x ++ "` instance for `" ++ y ++ "`" show (RequiresExtension x) = "may require `{-# LANGUAGE " ++ x ++ " #-}` adding to the top of the file" show (Note x) = x showNotes :: [Note] -> String showNotes = intercalate ", " . map show . filter use where use ValidInstance{} = False -- Not important enough to tell an end user use _ = True -- | How to classify an 'Idea'. If any matching field is @\"\"@ then it matches everything. data Classify = Classify {classifySeverity :: Severity -- ^ Severity to set the 'Idea' to. ,classifyHint :: String -- ^ Match on 'Idea' field 'ideaHint'. ,classifyModule :: String -- ^ Match on 'Idea' field 'ideaModule'. ,classifyDecl :: String -- ^ Match on 'Idea' field 'ideaDecl'. } deriving Show -- | A @LHS ==> RHS@ style hint rule. data HintRule = HintRule {hintRuleSeverity :: Severity -- ^ Default severity for the hint. ,hintRuleName :: String -- ^ Name for the hint. ,hintRuleNotes :: [Note] -- ^ Notes about application of the hint. ,hintRuleScope :: Scope -- ^ Module scope in which the hint operates (GHC parse tree). -- We wrap these GHC elements in 'HsExtendInstances' in order that we may derive 'Show'. ,hintRuleLHS :: HsExtendInstances (GHC.Hs.LHsExpr GHC.Hs.GhcPs) -- ^ LHS (GHC parse tree). ,hintRuleRHS :: HsExtendInstances (GHC.Hs.LHsExpr GHC.Hs.GhcPs) -- ^ RHS (GHC parse tree). ,hintRuleSide :: Maybe (HsExtendInstances (GHC.Hs.LHsExpr GHC.Hs.GhcPs)) -- ^ Side condition (GHC parse tree). } deriving Show data RestrictType = RestrictModule | RestrictExtension | RestrictFlag | RestrictFunction deriving (Show,Eq,Ord) data Restrict = Restrict {restrictType :: RestrictType ,restrictDefault :: Bool ,restrictName :: [String] ,restrictAs :: [String] -- for RestrictModule only, what module names you can import it as ,restrictWithin :: [(String, String)] ,restrictBadIdents :: [String] ,restrictMessage :: Maybe String } deriving Show data SmellType = SmellLongFunctions | SmellLongTypeLists | SmellManyArgFunctions | SmellManyImports deriving (Show,Eq,Ord) getSmellType :: String -> Maybe SmellType getSmellType "long functions" = Just SmellLongFunctions getSmellType "long type lists" = Just SmellLongTypeLists getSmellType "many arg functions" = Just SmellManyArgFunctions getSmellType "many imports" = Just SmellManyImports getSmellType _ = Nothing data Setting = SettingClassify Classify | SettingMatchExp HintRule | SettingRestrict Restrict | SettingArgument String -- ^ Extra command-line argument | SettingSmell SmellType Int | Builtin String -- use a builtin hint set | Infix FixityInfo deriving Show hlint-3.1.6/src/Config/Read.hs0000644000000000000000000000145213632352456014243 0ustar0000000000000000 module Config.Read(readFilesConfig) where import Config.Type import Control.Monad import Control.Exception.Extra import Config.Yaml import Data.List.Extra import System.FilePath readFilesConfig :: [(FilePath, Maybe String)] -> IO [Setting] readFilesConfig files = do let (yaml, haskell) = partition (\(x,_) -> lower (takeExtension x) `elem` [".yml",".yaml"]) files unless (null haskell) $ errorIO $ "HLint 2.3 and beyond cannot use Haskell configuration files.\n" ++ "Tried to use: " ++ show haskell ++ "\n" ++ "Convert it to .yaml file format, following the example at\n" ++ " " yaml <- mapM (uncurry readFileConfigYaml) yaml pure $ settingsFromConfigYaml yaml hlint-3.1.6/src/Config/Haskell.hs0000644000000000000000000000574413663430505014757 0ustar0000000000000000{-# LANGUAGE PatternGuards, ViewPatterns, TupleSections #-} module Config.Haskell( readPragma, readComment ) where import Data.Char import Data.List.Extra import Text.Read import Data.Tuple.Extra import Data.Maybe import Config.Type import Util import Prelude import GHC.Util import SrcLoc import GHC.Hs.Extension import GHC.Hs.Decls hiding (SpliceDecl) import GHC.Hs.Expr hiding (Match) import GHC.Hs.Lit import FastString import ApiAnnotation import Outputable import Language.Haskell.GhclibParserEx.GHC.Utils.Outputable import Language.Haskell.GhclibParserEx.GHC.Types.Name.Reader -- | Read an {-# ANN #-} pragma and determine if it is intended for HLint. -- Return Nothing if it is not an HLint pragma, otherwise what it means. readPragma :: AnnDecl GhcPs -> Maybe Classify readPragma (HsAnnotation _ _ provenance expr) = f expr where name = case provenance of ValueAnnProvenance (L _ x) -> occNameStr x TypeAnnProvenance (L _ x) -> occNameStr x ModuleAnnProvenance -> "" f (L _ (HsLit _ (HsString _ (unpackFS -> s)))) | "hlint:" `isPrefixOf` lower s = case getSeverity a of Nothing -> errorOn expr "bad classify pragma" Just severity -> Just $ Classify severity (trimStart b) "" name where (a,b) = break isSpace $ trimStart $ drop 6 s f (L _ (HsPar _ x)) = f x f (L _ (ExprWithTySig _ x _)) = f x f _ = Nothing readPragma _ = Nothing readComment :: Located AnnotationComment -> [Classify] readComment c@(L pos AnnBlockComment{}) | (hash, x) <- maybe (False, x) (True,) $ stripPrefix "#" x , x <- trim x , (hlint, x) <- word1 x , lower hlint == "hlint" = f hash x where x = commentText c f hash x | Just x <- if hash then stripSuffix "#" x else Just x , (sev, x) <- word1 x , Just sev <- getSeverity sev , (things, x) <- g x , Just hint <- if x == "" then Just "" else readMaybe x = map (Classify sev hint "") $ ["" | null things] ++ things f hash _ = errorOnComment c $ "bad HLINT pragma, expected:\n {-" ++ h ++ " HLINT \"Hint name\" " ++ h ++ "-}" where h = ['#' | hash] g x | (s, x) <- word1 x , s /= "" , not $ "\"" `isPrefixOf` s = first ((if s == "module" then "" else s):) $ g x g x = ([], x) readComment _ = [] errorOn :: Outputable a => Located a -> String -> b errorOn (L pos val) msg = exitMessageImpure $ showSrcSpan pos ++ ": Error while reading hint file, " ++ msg ++ "\n" ++ unsafePrettyPrint val errorOnComment :: Located AnnotationComment -> String -> b errorOnComment c@(L s _) msg = exitMessageImpure $ let isMultiline = isCommentMultiline c in showSrcSpan s ++ ": Error while reading hint file, " ++ msg ++ "\n" ++ (if isMultiline then "{-" else "--") ++ commentText c ++ (if isMultiline then "-}" else "") hlint-3.1.6/src/Config/Compute.hs0000644000000000000000000000651213671470061015002 0ustar0000000000000000{-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE RecordWildCards #-} -- | Given a file, guess settings from it by looking at the hints. module Config.Compute(computeSettings) where import GHC.All import GHC.Util import Config.Type import Fixity import Data.Generics.Uniplate.DataOnly import GHC.Hs hiding (Warning) import RdrName import Name import Bag import SrcLoc import Language.Haskell.GhclibParserEx.GHC.Hs.ExtendInstances import Language.Haskell.GhclibParserEx.GHC.Hs.Expr import Language.Haskell.GhclibParserEx.GHC.Utils.Outputable import Language.Haskell.GhclibParserEx.GHC.Types.Name.Reader import Prelude -- | Given a source file, guess some hints that might apply. -- Returns the text of the hints (if you want to save it down) along with the settings to be used. computeSettings :: ParseFlags -> FilePath -> IO (String, [Setting]) computeSettings flags file = do x <- parseModuleEx flags file Nothing case x of Left (ParseError sl msg _) -> pure ("# Parse error " ++ showSrcSpan sl ++ ": " ++ msg, []) Right ModuleEx{ghcModule=m} -> do let xs = concatMap findSetting (hsmodDecls $ unLoc m) s = unlines $ ["# hints found in " ++ file] ++ concatMap renderSetting xs ++ ["# no hints found" | null xs] pure (s,xs) renderSetting :: Setting -> [String] -- Only need to convert the subset of Setting we generate renderSetting (SettingMatchExp HintRule{..}) = ["- warn: {lhs: " ++ show (unsafePrettyPrint hintRuleLHS) ++ ", rhs: " ++ show (unsafePrettyPrint hintRuleRHS) ++ "}"] renderSetting (Infix x) = ["- fixity: " ++ show (unsafePrettyPrint $ toFixitySig x)] renderSetting _ = [] findSetting :: LHsDecl GhcPs -> [Setting] findSetting (L _ (ValD _ x)) = findBind x findSetting (L _ (InstD _ (ClsInstD _ ClsInstDecl{cid_binds}))) = concatMap (findBind . unLoc) $ bagToList cid_binds findSetting (L _ (SigD _ (FixSig _ x))) = map Infix $ fromFixitySig x findSetting x = [] findBind :: HsBind GhcPs -> [Setting] findBind VarBind{var_id, var_rhs} = findExp var_id [] $ unLoc var_rhs findBind FunBind{fun_id, fun_matches} = findExp (unLoc fun_id) [] $ HsLam noExtField fun_matches findBind _ = [] findExp :: IdP GhcPs -> [String] -> HsExpr GhcPs -> [Setting] findExp name vs (HsLam _ MG{mg_alts=L _ [L _ Match{m_pats, m_grhss=GRHSs{grhssGRHSs=[L _ (GRHS _ [] x)], grhssLocalBinds=L _ (EmptyLocalBinds _)}}]}) = if length m_pats == length ps then findExp name (vs++ps) $ unLoc x else [] where ps = [rdrNameStr x | L _ (VarPat _ x) <- m_pats] findExp name vs HsLam{} = [] findExp name vs HsVar{} = [] findExp name vs (OpApp _ x dot y) | isDot dot = findExp name (vs++["_hlint"]) $ HsApp noExtField x $ noLoc $ HsPar noExtField $ noLoc $ HsApp noExtField y $ noLoc $ mkVar "_hlint" findExp name vs bod = [SettingMatchExp $ HintRule Warning defaultHintName [] mempty (extendInstances lhs) (extendInstances $ fromParen rhs) Nothing] where lhs = fromParen $ noLoc $ transform f bod rhs = apps $ map noLoc $ HsVar noExtField (noLoc name) : map snd rep rep = zip vs $ map (mkVar . pure) ['a'..] f (HsVar _ x) | Just y <- lookup (rdrNameStr x) rep = y f (OpApp _ x dol y) | isDol dol = HsApp noExtField x $ noLoc $ HsPar noExtField y f x = x mkVar :: String -> HsExpr GhcPs mkVar = HsVar noExtField . noLoc . Unqual . mkVarOcc hlint-3.1.6/data/0000755000000000000000000000000013674744766011766 5ustar0000000000000000hlint-3.1.6/data/Test.hs0000644000000000000000000000575013605323475013230 0ustar0000000000000000-- These hints are for test purposes, and are not intended to -- be used for real. -- FIXME: Should make this module modules in one file, so can easily test lots of -- things without them overlapping module HLint.Test where error = Prelude.readFile ==> bad error = (x :: Int) ==> (x :: Int32) where _ = noTypeCheck error "Test1" = scanr ==> scanr error "Test2" = filter ==> filter error "Test3" = foldr ==> foldr error "Test4" = foldl ==> foldl ignore "Test1" = "" ignore "Test3" ignore "Test2" = ignoreTest warn = ignoreTest3 suggest = ignoreTest4 ignore = Ignore_Test {-# ANN module "HLint: ignore Test4" #-} {-# ANN annTest2 "HLint: error" #-} {-# ANN annTest3 ("HLint: warn" :: String) #-} {-# ANN annTest4 ("HLint: suggest" :: String) #-} {-# ANN type Ann_Test ("HLint: ignore") #-} error = concat (map f x) ==> Data.List.concatMap f x infix 9 + error = a * (b+c) ==> undefined error = Array.head ==> head error = tail ==> Array.tail warn = id Control.Arrow.*** id ==> id error = zip [1..length x] x ==> zipFrom 1 x error = before a ==> after a warn "noop" = a ? 0 ==> a {- <--! TEST (temporarily disabled see issue https://github.com/ndmitchell/hlint/issues/809) !--> main = readFile "foo" >>= putStr \ -- bad import Prelude hiding(readFile) \ import Data.ByteString.Char8(readFile) \ test = readFile "foo" >>= putStr import Prelude as Prelude2 \ yes = Prelude2.readFile "foo" >>= putStr \ -- bad yes = 32 :: Int -- 32 :: Int32 yes = before 12 -- after 12 ignoreTest = filter -- @Ignore ??? ignoreTest2 = filter -- @Error ??? ignoreTest3 = filter -- @Warning ??? ignoreTest4 = filter -- @Suggestion ??? ignoreAny = scanr -- @Ignore ??? ignoreNew = foldr -- @Ignore ??? type Ignore_Test = Int -- @Ignore ??? annTest = foldl -- @Ignore ??? annTest2 = foldl -- @Error ??? annTest3 = scanr -- @Warning ??? annTest4 = scanr -- @Suggestion ??? type Ann_Test = Int -- @Ignore ??? concatMap f x = concat (map f x) concatMop f x = concat (map f x) -- Data.List.concatMap f x yes = 1 * 2+3 -- undefined import Foo; test = Foo.id 1 test = head import Array; test = Array.head -- head test = Array.head -- head test = head import qualified Array; test = head import Array(tail); test = head import Array(head); test = head -- head import Array as A; test = A.head -- head test = tail -- Array.tail import qualified Array as B; test = tail -- B.tail import Control.Arrow; test = id *** id -- id test = id Control.Arrow.*** id -- id import Control.Arrow as Q; test = id Q.*** id -- id zip [1..length x] zip [1..length x] x -- zipFrom 1 x test = 5 + 0 -- 5 {-# ANN module "HLint: ignore Unused LANGUAGE pragma" #-} \ {-# LANGUAGE RecordWildCards #-} -- @Ignore ??? {-# ANN module "HLint: ignore Unused LANGUAGE pragma" #-} \ {-# LANGUAGE RecordWildCards #-} -- @Ignore ??? {-# ANN lam "HLint: ignore Redundant lambda" #-} \ lam = \x -> x x x -- @Ignore ??? {-# ANN module "HLint: ignore Reduce duplication" #-} \ dup = do a; a; a; a; a; a -- @Ignore ??? -} hlint-3.1.6/data/report_template.html0000644000000000000000000001007213474042413016034 0ustar0000000000000000 HLint Report

    All hints

      $HINTS

    All files

      $FILES

    Report generated by HLint $VERSION - a tool to suggest improvements to your Haskell code.

    $CONTENT
    hlint-3.1.6/data/hs-lint.el0000644000000000000000000000762313311534446013652 0ustar0000000000000000;;; hs-lint.el --- minor mode for HLint code checking ;; Copyright 2009 (C) Alex Ott ;; ;; Author: Alex Ott ;; Keywords: haskell, lint, HLint ;; Requirements: ;; Status: distributed under terms of GPL2 or above ;; Typical message from HLint looks like: ;; ;; /Users/ott/projects/lang-exp/haskell/test.hs:52:1: Eta reduce ;; Found: ;; count1 p l = length (filter p l) ;; Perhaps: ;; count1 p = length . filter p (require 'compile) (defgroup hs-lint nil "Run HLint as inferior of Emacs, parse error messages." :group 'tools :group 'haskell) (defcustom hs-lint-command "hlint" "The default hs-lint command for \\[hlint]." :type 'string :group 'hs-lint) (defcustom hs-lint-save-files t "Save modified files when run HLint or no (ask user)" :type 'boolean :group 'hs-lint) (defcustom hs-lint-replace-with-suggestions nil "Replace user's code with suggested replacements" :type 'boolean :group 'hs-lint) (defcustom hs-lint-replace-without-ask nil "Replace user's code with suggested replacements automatically" :type 'boolean :group 'hs-lint) (defun hs-lint-process-setup () "Setup compilation variables and buffer for `hlint'." (run-hooks 'hs-lint-setup-hook)) ;; regex for replace suggestions ;; ;; ^\(.*?\):\([0-9]+\):\([0-9]+\): .* ;; Found: ;; \s +\(.*\) ;; Perhaps: ;; \s +\(.*\) (defvar hs-lint-regex "^\\(.*?\\):\\([0-9]+\\):\\([0-9]+\\): .*[\n\C-m]Found:[\n\C-m]\\s +\\(.*\\)[\n\C-m]Perhaps:[\n\C-m]\\s +\\(.*\\)[\n\C-m]" "Regex for HLint messages") (defun make-short-string (str maxlen) (if (< (length str) maxlen) str (concat (substring str 0 (- maxlen 3)) "..."))) (defun hs-lint-replace-suggestions () "Perform actual replacement of suggestions" (goto-char (point-min)) (while (re-search-forward hs-lint-regex nil t) (let* ((fname (match-string 1)) (fline (string-to-number (match-string 2))) (old-code (match-string 4)) (new-code (match-string 5)) (msg (concat "Replace '" (make-short-string old-code 30) "' with '" (make-short-string new-code 30) "'")) (bline 0) (eline 0) (spos 0) (new-old-code "")) (save-excursion (switch-to-buffer (get-file-buffer fname)) (goto-line fline) (beginning-of-line) (setf bline (point)) (when (or hs-lint-replace-without-ask (yes-or-no-p msg)) (end-of-line) (setf eline (point)) (beginning-of-line) (setf old-code (regexp-quote old-code)) (while (string-match "\\\\ " old-code spos) (setf new-old-code (concat new-old-code (substring old-code spos (match-beginning 0)) "\\ *")) (setf spos (match-end 0))) (setf new-old-code (concat new-old-code (substring old-code spos))) (remove-text-properties bline eline '(composition nil)) (when (re-search-forward new-old-code eline t) (replace-match new-code nil t))))))) (defun hs-lint-finish-hook (buf msg) "Function, that is executed at the end of HLint execution" (if hs-lint-replace-with-suggestions (hs-lint-replace-suggestions) (next-error 1 t))) (define-compilation-mode hs-lint-mode "HLint" "Mode for check Haskell source code." (set (make-local-variable 'compilation-process-setup-function) 'hs-lint-process-setup) (set (make-local-variable 'compilation-disable-input) t) (set (make-local-variable 'compilation-scroll-output) nil) (set (make-local-variable 'compilation-finish-functions) (list 'hs-lint-finish-hook)) ) (defun hs-lint () "Run HLint for current buffer with haskell source" (interactive) (save-some-buffers hs-lint-save-files) (compilation-start (concat hs-lint-command " \"" buffer-file-name "\"") 'hs-lint-mode)) (provide 'hs-lint) ;;; hs-lint.el ends here hlint-3.1.6/data/HLint_TypeCheck.hs0000644000000000000000000000047113637726522015266 0ustar0000000000000000 -- Used with --typecheck module HLint_TypeCheck where (==>) :: a -> a -> a (==>) = undefined _noParen_ = id --------------------------------------------------------------------- -- EXAMPLES main :: IO () main = return () {-# LINE 116 "data\\Default.hs" #-} _test64 = \ p x -> (and (map p x)) ==> (all p x) hlint-3.1.6/data/HLint_QuickCheck.hs0000644000000000000000000001123413637726537015426 0ustar0000000000000000{-# LANGUAGE NoMonomorphismRestriction, ExtendedDefaultRules, ScopedTypeVariables, DeriveDataTypeable, ViewPatterns #-} {-# LANGUAGE FlexibleInstances, UndecidableInstances, OverlappingInstances, GeneralizedNewtypeDeriving #-} {-# LANGUAGE StandaloneDeriving #-} -- | Used with --quickcheck module HLint_QuickCheck(module HLint_QuickCheck, module X) where import System.IO.Unsafe import Data.Typeable import Data.List import Data.Maybe import Data.IORef import Control.Exception import Control.Monad import System.IO import Control.Concurrent.Chan import System.Mem.Weak(Weak) import Test.QuickCheck hiding ((==>)) import Test.QuickCheck.Test hiding (test) import Test.QuickCheck.Modifiers as X default(Maybe Bool,[Bool],Int,Dbl) -- We need a Show instance that nails down the sides, so defaulting works. -- The one from Text.Show.Functions is insufficient. instance (Show a, Show b) => Show (a -> b) where show _ = "" newtype Dbl = Dbl Double deriving (Enum,Floating,Fractional,Num,Read,Real,RealFloat,RealFrac,Show,Typeable,Arbitrary,CoArbitrary) instance Eq Dbl where Dbl a == Dbl b | isNaN a && isNaN b = True | otherwise = abs (a - b) < 1e-4 || let s = a+b in s /= 0 && abs ((a-b)/s) < 1e-8 instance Ord Dbl where compare a b | a == b = EQ compare (Dbl a) (Dbl b) = compare a b newtype NegZero a = NegZero a deriving (Typeable, Show) instance (Num a, Arbitrary a) => Arbitrary (NegZero a) where arbitrary = fmap (NegZero . negate . abs) arbitrary newtype Nat a = Nat a deriving (Typeable, Show) instance (Num a, Arbitrary a) => Arbitrary (Nat a) where arbitrary = fmap (Nat . abs) arbitrary newtype Compare a = Compare (a -> a -> Ordering) deriving (Typeable, Show) instance (Ord a, Arbitrary a) => Arbitrary (Compare a) where arbitrary = fmap (\b -> Compare $ (if b then flip else id) compare) arbitrary instance Show a => Show (IO a) where show _ = "" instance Show a => Show (Weak a) where show _ = "" instance Show a => Show (Chan a) where show _ = "" instance Eq (IO a) where _ == _ = True instance Eq SomeException where a == b = show a == show b deriving instance Typeable IOMode instance Arbitrary Handle where arbitrary = elements [stdin, stdout, stderr] instance CoArbitrary Handle where coarbitrary _ = variant 0 instance Arbitrary IOMode where arbitrary = elements [ReadMode,WriteMode,AppendMode,ReadWriteMode] instance Arbitrary a => Arbitrary (IO a) where arbitrary = fmap return arbitrary instance Arbitrary (Chan a) where arbitrary = return $ unsafePerformIO newChan instance Exception (Maybe Bool) data Test a = Test Bool a a deriving (Show, Typeable) instance Functor Test where fmap f (Test a b c) = Test a (f b) (f c) a ==> b = Test False a b a ?==> b = Test True a b class Testable2 a where property2 :: Test a -> Property instance Testable2 a => Testable (Test a) where property = property2 instance Eq a => Testable2 a where property2 (Test bx (catcher -> x) (catcher -> y)) = property $ (bx && isNothing x) || x == y instance (Arbitrary a, Show a, Testable2 b) => Testable2 (a -> b) where property2 x = property $ \a -> fmap ($ a) x {-# NOINLINE bad #-} bad :: IORef Int bad = unsafePerformIO $ newIORef 0 test :: (Show p, Testable p, Typeable p) => FilePath -> Int -> String -> p -> IO () test file line hint p = do res <- quickCheckWithResult stdArgs{chatty=False} p unless (isSuccess res) $ do putStrLn $ "\n" ++ file ++ ":" ++ show line ++ ": " ++ hint print $ typeOf p putStr $ output res modifyIORef bad (+1) catcher :: a -> Maybe a catcher x = unsafePerformIO $ do res <- try $ evaluate x return $ case res of Left (_ :: SomeException) -> Nothing Right v -> Just v _noParen_ = id withMain :: IO () -> IO () withMain act = do act bad <- readIORef bad when (bad > 0) $ error $ "Failed " ++ show bad ++ " tests" --------------------------------------------------------------------- -- EXAMPLES main :: IO () main = withMain $ do let t = \ a -> (findIndex ((==) a)) ==> (elemIndex a) in test "data\\Default.hs" 144 "findIndex ((==) a) ==> elemIndex a" t let t = ((foldr1 (&&)) ?==> (and)) in test "data\\Default.hs" 179 "foldr1 (&&) ==> and" t let t = \ x -> (sqrt x) ==> (x ** 0.5) in test "data\\Default.hs" 407 "sinh x / cosh x ==> tanh x" t let t = \ (NegZero i) x -> (take i x) ==> ([]) in test "data\\Default.hs" 154 "take i x ==> []" t let t = \ (Compare f) x -> (head (sortBy f x)) ==> (minimumBy f x) in test "data\\Default.hs" 70 "head (sortBy f x) ==> minimumBy f x" t let t = \ f -> ((f $)) ==> (f) in test "data\\Default.hs" 218 "(f $) ==> f" t hlint-3.1.6/data/hlint.yaml0000644000000000000000000017047613674736772014005 0ustar0000000000000000# hlint configuration file # ================================== # The hlint tool is mainly automatic, but some hints/restrictions can be specified here. - package: name: base modules: - import Prelude - import Control.Arrow - import Control.Exception - import Control.Monad - import Control.Monad.Trans.State - import qualified Data.Foldable - import Data.Foldable(asum, sequenceA_, traverse_, for_) - import Data.Traversable(traverse, for) - import Control.Applicative - import Data.Bifunctor - import Data.Function - import Data.Int - import Data.Char - import Data.List as Data.List - import Data.List as X - import Data.Maybe - import Data.Monoid - import System.IO - import Control.Concurrent.Chan - import System.Mem.Weak - import Control.Exception.Base - import System.Exit - import Data.Either - import Numeric - import IO as System.IO - import List as Data.List - import Maybe as Data.Maybe - import Monad as Control.Monad - import Char as Data.Char - package: name: lens modules: - import Control.Lens - import Control.Lens.Operators - import Control.Monad.Reader - package: name: attoparsec modules: - import Data.Attoparsec.Text - import Data.Attoparsec.ByteString - package: name: codeworld-api modules: - import CodeWorld - group: name: default enabled: true imports: - package base rules: # I/O - warn: {lhs: putStrLn (show x), rhs: print x} - warn: {lhs: mapM_ putChar, rhs: putStr} - warn: {lhs: hGetChar stdin, rhs: getChar} - warn: {lhs: hGetLine stdin, rhs: getLine} - warn: {lhs: hGetContents stdin, rhs: getContents} - warn: {lhs: hPutChar stdout, rhs: putChar} - warn: {lhs: hPutStr stdout, rhs: putStr} - warn: {lhs: hPutStrLn stdout, rhs: putStrLn} - warn: {lhs: hPrint stdout, rhs: print} - warn: {lhs: hWaitForInput a 0, rhs: hReady a} - warn: {lhs: hPutStrLn a (show b), rhs: hPrint a b} - warn: {lhs: hIsEOF stdin, rhs: isEOF} - warn: {lhs: withFile f WriteMode (\h -> hPutStr h x), rhs: writeFile f x} - warn: {lhs: withFile f WriteMode (\h -> hPutStrLn h x), rhs: writeFile f (x ++ "\n")} - warn: {lhs: withFile f AppendMode (\h -> hPutStr h x), rhs: appendFile f x} - warn: {lhs: withFile f AppendMode (\h -> hPutStrLn h x), rhs: appendFile f (x ++ "\n")} # EXIT - warn: {lhs: exitWith ExitSuccess, rhs: exitSuccess} # ORD - warn: {lhs: not (a == b), rhs: a /= b, note: incorrect if either value is NaN} - warn: {lhs: not (a /= b), rhs: a == b, note: incorrect if either value is NaN} - warn: {lhs: not (a > b), rhs: a <= b, note: incorrect if either value is NaN} - warn: {lhs: not (a >= b), rhs: a < b, note: incorrect if either value is NaN} - warn: {lhs: not (a < b), rhs: a >= b, note: incorrect if either value is NaN} - warn: {lhs: not (a <= b), rhs: a > b, note: incorrect if either value is NaN} - warn: {lhs: compare x y /= GT, rhs: x <= y} - warn: {lhs: compare x y == LT, rhs: x < y} - warn: {lhs: compare x y /= LT, rhs: x >= y} - warn: {lhs: compare x y == GT, rhs: x > y} - warn: {lhs: compare x y == EQ, rhs: x == y} - warn: {lhs: compare x y /= EQ, rhs: x /= y} - warn: {lhs: head (sort x), rhs: minimum x} - warn: {lhs: last (sort x), rhs: maximum x} - warn: {lhs: head (sortBy f x), rhs: minimumBy f x, side: isCompare f} - warn: {lhs: last (sortBy f x), rhs: maximumBy f x, side: isCompare f} - warn: {lhs: reverse (sortBy f x), rhs: sortBy (flip f) x, name: Avoid reverse, side: isCompare f, note: Stabilizes sort order} - warn: {lhs: sortBy (flip (comparing f)), rhs: sortOn (Down . f)} - warn: {lhs: sortBy (comparing f), rhs: sortOn f, side: notEq f fst && notEq f snd} - warn: {lhs: reverse (sortOn f x), rhs: sortOn (Data.Ord.Down . f) x, name: Avoid reverse, note: Stabilizes sort order} # This suggestion likely costs performance, see https://github.com/ndmitchell/hlint/issues/669#issuecomment-607154496 # - warn: {lhs: reverse (sort x), rhs: sortOn Data.Ord.Down x, name: Avoid reverse, note: Stabilizes sort order} - hint: {lhs: flip (g `on` h), rhs: flip g `on` h, name: Move flip} - hint: {lhs: (f `on` g) `on` h, rhs: f `on` (g . h), name: Fuse on/on} # READ/SHOW - warn: {lhs: showsPrec 0 x "", rhs: show x} - warn: {lhs: readsPrec 0, rhs: reads} - warn: {lhs: showsPrec 0, rhs: shows} - hint: {lhs: showIntAtBase 16 intToDigit, rhs: showHex} - hint: {lhs: showIntAtBase 8 intToDigit, rhs: showOct} # LIST - warn: {lhs: concat (map f x), rhs: concatMap f x} - warn: {lhs: concat (fmap f x), rhs: concatMap f x} - hint: {lhs: "concat [a, b]", rhs: a ++ b} - hint: {lhs: map f (map g x), rhs: map (f . g) x, name: Use map once} - hint: {lhs: concatMap f (map g x), rhs: concatMap (f . g) x, name: Fuse concatMap/map} - hint: {lhs: x !! 0, rhs: head x} - warn: {lhs: take n (repeat x), rhs: replicate n x} - warn: {lhs: map f (replicate n x), rhs: replicate n (f x)} - warn: {lhs: map f (repeat x), rhs: repeat (f x)} - warn: {lhs: "cycle [x]", rhs: repeat x} - warn: {lhs: head (reverse x), rhs: last x} - warn: {lhs: head (drop n x), rhs: x !! n, side: isNat n} - warn: {lhs: head (drop n x), rhs: x !! max 0 n, side: not (isNat n) && not (isNeg n)} - warn: {lhs: reverse (tail (reverse x)), rhs: init x, note: IncreasesLaziness} - warn: {lhs: reverse (reverse x), rhs: x, note: IncreasesLaziness, name: Avoid reverse} - warn: {lhs: isPrefixOf (reverse x) (reverse y), rhs: isSuffixOf x y} - warn: {lhs: "foldr (++) []", rhs: concat} - warn: {lhs: foldr (++) "", rhs: concat} - warn: {lhs: "foldr ((++) . f) []", rhs: concatMap f} - warn: {lhs: foldr ((++) . f) "", rhs: concatMap f} - warn: {lhs: "foldl (++) []", rhs: concat, note: IncreasesLaziness} - warn: {lhs: foldl (++) "", rhs: concat, note: IncreasesLaziness} - warn: {lhs: foldl f (head x) (tail x), rhs: foldl1 f x} - warn: {lhs: foldr f (last x) (init x), rhs: foldr1 f x} - warn: {lhs: "foldr (\\c a -> x : a) []", rhs: "map (\\c -> x)"} - warn: {lhs: foldr (.) id l z, rhs: foldr ($) z l} - warn: {lhs: span (not . p), rhs: break p} - warn: {lhs: break (not . p), rhs: span p} - warn: {lhs: "(takeWhile p x, dropWhile p x)", rhs: span p x, note: DecreasesLaziness} - warn: {lhs: fst (span p x), rhs: takeWhile p x} - warn: {lhs: snd (span p x), rhs: dropWhile p x} - warn: {lhs: fst (break p x), rhs: takeWhile (not . p) x} - warn: {lhs: snd (break p x), rhs: dropWhile (not . p) x} - warn: {lhs: "(take n x, drop n x)", rhs: splitAt n x, note: DecreasesLaziness} - warn: {lhs: fst (splitAt p x), rhs: take p x} - warn: {lhs: snd (splitAt p x), rhs: drop p x} - warn: {lhs: concatMap (++ "\n"), rhs: unlines} - warn: {lhs: map id, rhs: id} - warn: {lhs: concatMap id, rhs: concat} - warn: {lhs: or (map p x), rhs: any p x} - warn: {lhs: and (map p x), rhs: all p x} - warn: {lhs: any f (map g x), rhs: any (f . g) x} - warn: {lhs: all f (map g x), rhs: all (f . g) x} - warn: {lhs: "zipWith (,)", rhs: zip} - warn: {lhs: "zipWith3 (,,)", rhs: zip3} - hint: {lhs: length x == 0, rhs: null x, note: IncreasesLaziness} - hint: {lhs: 0 == length x, rhs: null x, note: IncreasesLaziness} - hint: {lhs: length x < 1, rhs: null x, note: IncreasesLaziness} - hint: {lhs: 1 > length x, rhs: null x, note: IncreasesLaziness} - hint: {lhs: length x <= 0, rhs: null x, note: IncreasesLaziness} - hint: {lhs: 0 >= length x, rhs: null x, note: IncreasesLaziness} - hint: {lhs: "x == []", rhs: null x} - hint: {lhs: "[] == x", rhs: null x} - hint: {lhs: all (const False), rhs: "null"} - hint: {lhs: any (const True) x, rhs: not (null x), name: Use null} - hint: {lhs: length x /= 0, rhs: not (null x), note: IncreasesLaziness, name: Use null} - hint: {lhs: 0 /= length x, rhs: not (null x), note: IncreasesLaziness, name: Use null} - hint: {lhs: "\\x -> [x]", rhs: "(:[])", name: "Use :"} - hint: {lhs: map f (zip x y), rhs: zipWith (curry f) x y, side: not (isApp f)} - hint: {lhs: "map f (fromMaybe [] x)", rhs: "maybe [] (map f) x"} - warn: {lhs: not (elem x y), rhs: notElem x y} - hint: {lhs: foldr f z (map g x), rhs: foldr (f . g) z x, name: Fuse foldr/map} - warn: {lhs: "x ++ concatMap (' ':) y", rhs: "unwords (x:y)"} - warn: {lhs: intercalate " ", rhs: unwords} - hint: {lhs: concat (intersperse x y), rhs: intercalate x y, side: notEq x " "} - hint: {lhs: concat (intersperse " " x), rhs: unwords x} - warn: {lhs: null (filter f x), rhs: not (any f x), name: Use any} - warn: {lhs: "filter f x == []", rhs: not (any f x), name: Use any} - warn: {lhs: "filter f x /= []", rhs: any f x} - warn: {lhs: any id, rhs: or} - warn: {lhs: all id, rhs: and} - warn: {lhs: any (not . f) x, rhs: not (all f x), name: Hoist not} - warn: {lhs: all (not . f) x, rhs: not (any f x), name: Hoist not} - warn: {lhs: any ((==) a), rhs: elem a, note: ValidInstance Eq a} - warn: {lhs: any (== a), rhs: elem a} - warn: {lhs: any (a ==), rhs: elem a, note: ValidInstance Eq a} - warn: {lhs: all ((/=) a), rhs: notElem a, note: ValidInstance Eq a} - warn: {lhs: all (/= a), rhs: notElem a, note: ValidInstance Eq a} - warn: {lhs: all (a /=), rhs: notElem a, note: ValidInstance Eq a} - warn: {lhs: elem True, rhs: or} - warn: {lhs: notElem False, rhs: and} - warn: {lhs: True `elem` l, rhs: or l} - warn: {lhs: False `notElem` l, rhs: and l} - warn: {lhs: findIndex ((==) a), rhs: elemIndex a} - warn: {lhs: findIndex (a ==), rhs: elemIndex a} - warn: {lhs: findIndex (== a), rhs: elemIndex a} - warn: {lhs: findIndices ((==) a), rhs: elemIndices a} - warn: {lhs: findIndices (a ==), rhs: elemIndices a} - warn: {lhs: findIndices (== a), rhs: elemIndices a} - warn: {lhs: "lookup b (zip l [0..])", rhs: elemIndex b l} - hint: {lhs: "elem x [y]", rhs: x == y, note: ValidInstance Eq a} - hint: {lhs: "notElem x [y]", rhs: x /= y, note: ValidInstance Eq a} - hint: {lhs: length x >= 0, rhs: "True", name: Length always non-negative} - hint: {lhs: 0 <= length x, rhs: "True", name: Length always non-negative} - hint: {lhs: length x > 0, rhs: not (null x), note: IncreasesLaziness, name: Use null} - hint: {lhs: 0 < length x, rhs: not (null x), note: IncreasesLaziness, name: Use null} - hint: {lhs: length x >= 1, rhs: not (null x), note: IncreasesLaziness, name: Use null} - hint: {lhs: 1 <= length x, rhs: not (null x), note: IncreasesLaziness, name: Use null} - warn: {lhs: take i x, rhs: "[]", side: isNegZero i, name: Take on a non-positive} - warn: {lhs: drop i x, rhs: x, side: isNegZero i, name: Drop on a non-positive} - warn: {lhs: last (scanl f z x), rhs: foldl f z x} - warn: {lhs: head (scanr f z x), rhs: foldr f z x} - warn: {lhs: iterate id, rhs: repeat} - warn: {lhs: zipWith f (repeat x), rhs: map (f x)} - warn: {lhs: zipWith f y (repeat z), rhs: map (\x -> f x z) y} - warn: {lhs: listToMaybe (filter p x), rhs: find p x} - warn: {lhs: zip (take n x) (take n y), rhs: take n (zip x y)} - warn: {lhs: zip (take n x) (take m y), rhs: take (min n m) (zip x y), side: notEq n m, note: IncreasesLaziness, name: Redundant take} # MONOIDS - warn: {lhs: mempty <> x, rhs: x, name: "Monoid law, left identity"} - warn: {lhs: mempty `mappend` x, rhs: x, name: "Monoid law, left identity"} - warn: {lhs: x <> mempty, rhs: x, name: "Monoid law, right identity"} - warn: {lhs: x `mappend` mempty, rhs: x, name: "Monoid law, right identity"} - warn: {lhs: foldr (<>) mempty, rhs: mconcat} - warn: {lhs: foldr mappend mempty, rhs: mconcat} # TRAVERSABLES - warn: {lhs: sequenceA (map f x), rhs: traverse f x} - warn: {lhs: sequenceA (fmap f x), rhs: traverse f x} - warn: {lhs: sequenceA_ (map f x), rhs: traverse_ f x} - warn: {lhs: sequenceA_ (fmap f x), rhs: traverse_ f x} - warn: {lhs: foldMap id, rhs: fold} - warn: {lhs: fold (fmap f x), rhs: foldMap f x} - warn: {lhs: fold (map f x), rhs: foldMap f x} - warn: {lhs: foldMap f (fmap g x), rhs: foldMap (f . g) x, name: Fuse foldMap/fmap} - warn: {lhs: foldMap f (map g x), rhs: foldMap (f . g) x, name: Fuse foldMap/map} # BY - warn: {lhs: deleteBy (==), rhs: delete} - warn: {lhs: groupBy (==), rhs: group} - warn: {lhs: insertBy compare, rhs: insert} - warn: {lhs: intersectBy (==), rhs: intersect} - warn: {lhs: maximumBy compare, rhs: maximum} - warn: {lhs: minimumBy compare, rhs: minimum} - warn: {lhs: nubBy (==), rhs: nub} - warn: {lhs: sortBy compare, rhs: sort} - warn: {lhs: unionBy (==), rhs: union} # FOLDS - warn: {lhs: foldr (>>) (return ()), rhs: sequence_} - warn: {lhs: foldr (&&) True, rhs: and} - warn: {lhs: foldl (&&) True, rhs: and, note: IncreasesLaziness} - warn: {lhs: foldr1 (&&) , rhs: and, note: "RemovesError on `[]`"} - warn: {lhs: foldl1 (&&) , rhs: and, note: "RemovesError on `[]`"} - warn: {lhs: foldr (||) False, rhs: or} - warn: {lhs: foldl (||) False, rhs: or, note: IncreasesLaziness} - warn: {lhs: foldr1 (||) , rhs: or, note: "RemovesError on `[]`"} - warn: {lhs: foldl1 (||) , rhs: or, note: "RemovesError on `[]`"} - warn: {lhs: foldl (+) 0, rhs: sum} - warn: {lhs: foldr (+) 0, rhs: sum} - warn: {lhs: foldl1 (+) , rhs: sum, note: "RemovesError on `[]`"} - warn: {lhs: foldr1 (+) , rhs: sum, note: "RemovesError on `[]`"} - warn: {lhs: foldl (*) 1, rhs: product} - warn: {lhs: foldr (*) 1, rhs: product} - warn: {lhs: foldl1 (*) , rhs: product, note: "RemovesError on `[]`"} - warn: {lhs: foldr1 (*) , rhs: product, note: "RemovesError on `[]`"} - warn: {lhs: foldl1 max , rhs: maximum} - warn: {lhs: foldr1 max , rhs: maximum} - warn: {lhs: foldl1 min , rhs: minimum} - warn: {lhs: foldr1 min , rhs: minimum} - warn: {lhs: foldr mplus mzero, rhs: msum} # FUNCTION - warn: {lhs: \x -> x, rhs: id} - warn: {lhs: \x y -> x, rhs: const} - warn: {lhs: "\\(x,y) -> y", rhs: snd} - warn: {lhs: "\\(x,y) -> x", rhs: fst} - hint: {lhs: "\\x y -> f (x,y)", rhs: curry f} - hint: {lhs: "\\(x,y) -> f x y", rhs: uncurry f, note: IncreasesLaziness} - warn: {lhs: f (fst p) (snd p), rhs: uncurry f p} - warn: {lhs: "uncurry (\\x y -> z)", rhs: "\\(x,y) -> z"} - warn: {lhs: "curry (\\(x,y) -> z)", rhs: "\\x y -> z"} - warn: {lhs: uncurry (curry f), rhs: f} - warn: {lhs: curry (uncurry f), rhs: f} - warn: {lhs: ($) (f x), rhs: f x, name: Redundant $} - warn: {lhs: (f $), rhs: f, name: Redundant $} - warn: {lhs: (Data.Function.& f), rhs: f, name: Redundant Data.Function.&} - hint: {lhs: \x -> y, rhs: const y, side: isAtom y && not (isWildcard y)} # If any isWildcard recursively then x may be used but not mentioned explicitly - warn: {lhs: flip f x y, rhs: f y x, side: isApp original && isAtom y} - warn: {lhs: id x, rhs: x} - warn: {lhs: id . x, rhs: x, name: Redundant id} - warn: {lhs: x . id, rhs: x, name: Redundant id} - warn: {lhs: "((,) x)", rhs: "(_noParen_ x,)", name: Use tuple-section, note: RequiresExtension TupleSections} - warn: {lhs: "flip (,) x", rhs: "(,_noParen_ x)", name: Use tuple-section, note: RequiresExtension TupleSections} # CHAR - warn: {lhs: a >= 'a' && a <= 'z', rhs: isAsciiLower a} - warn: {lhs: a >= 'A' && a <= 'Z', rhs: isAsciiUpper a} - warn: {lhs: a >= '0' && a <= '9', rhs: isDigit a} - warn: {lhs: a >= '0' && a <= '7', rhs: isOctDigit a} - warn: {lhs: isLower a || isUpper a, rhs: isAlpha a} - warn: {lhs: isUpper a || isLower a, rhs: isAlpha a} # BOOL - warn: {lhs: x == True, rhs: x, name: Redundant ==} - hint: {lhs: x == False, rhs: not x, name: Redundant ==} - warn: {lhs: True == a, rhs: a, name: Redundant ==} - hint: {lhs: False == a, rhs: not a, name: Redundant ==} - warn: {lhs: a /= True, rhs: not a, name: Redundant /=} - hint: {lhs: a /= False, rhs: a, name: Redundant /=} - warn: {lhs: True /= a, rhs: not a, name: Redundant /=} - hint: {lhs: False /= a, rhs: a, name: Redundant /=} - warn: {lhs: if a then x else x, rhs: x, note: IncreasesLaziness, name: Redundant if} - warn: {lhs: if a then True else False, rhs: a, name: Redundant if} - warn: {lhs: if a then False else True, rhs: not a, name: Redundant if} - warn: {lhs: if a then t else (if b then t else f), rhs: if a || b then t else f, name: Redundant if} - warn: {lhs: if a then (if b then t else f) else f, rhs: if a && b then t else f, name: Redundant if} - warn: {lhs: if x then True else y, rhs: x || y, side: notEq y False, name: Redundant if} - warn: {lhs: if x then y else False, rhs: x && y, side: notEq y True, name: Redundant if} - warn: {lhs: if | b -> t | otherwise -> f, rhs: if b then t else f, name: Redundant multi-way if} - hint: {lhs: "case a of {True -> t; False -> f}", rhs: if a then t else f, name: Use if} - hint: {lhs: "case a of {False -> f; True -> t}", rhs: if a then t else f, name: Use if} - hint: {lhs: "case a of {True -> t; _ -> f}", rhs: if a then t else f, name: Use if} - hint: {lhs: "case a of {False -> f; _ -> t}", rhs: if a then t else f, name: Use if} - hint: {lhs: "if c then (True, x) else (False, x)", rhs: "(c, x)", note: IncreasesLaziness, name: Redundant if} - hint: {lhs: "if c then (False, x) else (True, x)", rhs: "(not c, x)", note: IncreasesLaziness, name: Redundant if} - hint: {lhs: "or [x, y]", rhs: x || y} - hint: {lhs: "or [x, y, z]", rhs: x || y || z} - hint: {lhs: "and [x, y]", rhs: x && y} - hint: {lhs: "and [x, y, z]", rhs: x && y && z} - warn: {lhs: if x then False else y, rhs: not x && y, side: notEq y True, name: Redundant if} - warn: {lhs: if x then y else True, rhs: not x || y, side: notEq y False, name: Redundant if} - warn: {lhs: not (not x), rhs: x, name: Redundant not} # ARROW - warn: {lhs: id *** g, rhs: second g} - warn: {lhs: f *** id, rhs: first f} - ignore: {lhs: zip (map f x) (map g x), rhs: map (f Control.Arrow.&&& g) x} - ignore: {lhs: "\\x -> (f x, g x)", rhs: f Control.Arrow.&&& g} - hint: {lhs: "(fst x, snd x)", rhs: x, note: DecreasesLaziness, name: Redundant pair} # BIFUNCTOR - warn: {lhs: bimap id g, rhs: second g} - warn: {lhs: bimap f id, rhs: first f} - warn: {lhs: first id, rhs: id} - warn: {lhs: second id, rhs: id} - warn: {lhs: bimap id id, rhs: id} - warn: {lhs: first f (second g x), rhs: bimap f g x} - warn: {lhs: second g (first f x), rhs: bimap f g x} - warn: {lhs: first f (first g x), rhs: first (f . g) x} - warn: {lhs: second f (second g x), rhs: second (f . g) x} - warn: {lhs: bimap f h (bimap g i x), rhs: bimap (f . g) (h . i) x} - warn: {lhs: first f (bimap g h x), rhs: bimap (f . g) h x} - warn: {lhs: second g (bimap f h x), rhs: bimap f (g . h) x} - warn: {lhs: bimap f h (first g x), rhs: bimap (f . g) h x} - warn: {lhs: bimap f g (second h x), rhs: bimap f (g . h) x} - hint: {lhs: "\\(x,y) -> (f x, g y)", rhs: Data.Bifunctor.bimap f g, note: IncreasesLaziness} - hint: {lhs: "\\(x,y) -> (f x,y)", rhs: Data.Bifunctor.first f, note: IncreasesLaziness} - hint: {lhs: "\\(x,y) -> (x,f y)", rhs: Data.Bifunctor.second f, note: IncreasesLaziness} - hint: {lhs: "(f (fst x), g (snd x))", rhs: Data.Bifunctor.bimap f g x} - hint: {lhs: "(f (fst x), snd x)", rhs: Data.Bifunctor.first f x} - hint: {lhs: "(fst x, g (snd x))", rhs: Data.Bifunctor.second g x} # FUNCTOR - warn: {lhs: fmap f (fmap g x), rhs: fmap (f . g) x, name: Functor law} - warn: {lhs: f <$> g <$> x, rhs: f . g <$> x, name: Functor law} - warn: {lhs: fmap id, rhs: id, name: Functor law} - warn: {lhs: id <$> x, rhs: x, name: Functor law} - hint: {lhs: fmap f $ x, rhs: f <$> x, side: isApp x || isAtom x} - hint: {lhs: \x -> a <$> b x, rhs: fmap a . b} - hint: {lhs: x *> pure y, rhs: x Data.Functor.$> y} - hint: {lhs: x *> return y, rhs: x Data.Functor.$> y} - hint: {lhs: pure x <* y, rhs: x Data.Functor.<$ y} - hint: {lhs: return x <* y, rhs: x Data.Functor.<$ y} - hint: {lhs: const x <$> y, rhs: x <$ y} - hint: {lhs: pure x <$> y, rhs: x <$ y} - hint: {lhs: return x <$> y, rhs: x <$ y} - hint: {lhs: x <&> const y, rhs: x Data.Functor.$> y} - hint: {lhs: x <&> pure y, rhs: x Data.Functor.$> y} - hint: {lhs: x <&> return y, rhs: x Data.Functor.$> y} # APPLICATIVE - hint: {lhs: return x <*> y, rhs: x <$> y} - hint: {lhs: pure x <*> y, rhs: x <$> y} - warn: {lhs: x <* pure y, rhs: x} - warn: {lhs: pure x *> y, rhs: "y"} # MONAD - warn: {lhs: return a >>= f, rhs: f a, name: "Monad law, left identity"} - warn: {lhs: f =<< return a, rhs: f a, name: "Monad law, left identity"} - warn: {lhs: m >>= return, rhs: m, name: "Monad law, right identity"} - warn: {lhs: return =<< m, rhs: m, name: "Monad law, right identity"} - warn: {lhs: liftM, rhs: fmap} - warn: {lhs: liftA, rhs: fmap} - hint: {lhs: m >>= return . f, rhs: f <$> m} - hint: {lhs: return . f =<< m, rhs: f <$> m} - warn: {lhs: fmap f x >>= g, rhs: x >>= g . f} - warn: {lhs: f <$> x >>= g, rhs: x >>= g . f} - warn: {lhs: x Data.Functor.<&> f >>= g, rhs: x >>= g . f} - warn: {lhs: g =<< fmap f x, rhs: g . f =<< x} - warn: {lhs: g =<< f <$> x, rhs: g . f =<< x} - warn: {lhs: g =<< (x Data.Functor.<&> f), rhs: g . f =<< x} - warn: {lhs: if x then y else return (), rhs: Control.Monad.when x $ _noParen_ y, side: not (isAtom y)} - warn: {lhs: if x then y else return (), rhs: Control.Monad.when x y, side: isAtom y} - warn: {lhs: if x then return () else y, rhs: Control.Monad.unless x $ _noParen_ y, side: isAtom y} - warn: {lhs: if x then return () else y, rhs: Control.Monad.unless x y, side: isAtom y} - warn: {lhs: sequence (map f x), rhs: mapM f x} - warn: {lhs: sequence_ (map f x), rhs: mapM_ f x} - warn: {lhs: sequence (fmap f x), rhs: mapM f x} - warn: {lhs: sequence_ (fmap f x), rhs: mapM_ f x} - hint: {lhs: flip mapM, rhs: Control.Monad.forM} - hint: {lhs: flip mapM_, rhs: Control.Monad.forM_} - hint: {lhs: flip forM, rhs: mapM} - hint: {lhs: flip forM_, rhs: mapM_} - warn: {lhs: when (not x), rhs: unless x} - warn: {lhs: unless (not x), rhs: when x} - warn: {lhs: x >>= id, rhs: Control.Monad.join x} - warn: {lhs: id =<< x, rhs: Control.Monad.join x} - warn: {lhs: id =<< x, rhs: Control.Monad.join x} - warn: {lhs: id =<< x, rhs: Control.Monad.join x} - hint: {lhs: join (f <$> x), rhs: f =<< x} - hint: {lhs: join (fmap f x), rhs: f =<< x} - hint: {lhs: a >> return (), rhs: Control.Monad.void a, side: isAtom a || isApp a} - warn: {lhs: fmap (const ()), rhs: Control.Monad.void} - warn: {lhs: const () <$> x, rhs: Control.Monad.void x} - warn: {lhs: flip (>=>), rhs: (<=<)} - warn: {lhs: flip (<=<), rhs: (>=>)} - warn: {lhs: flip (>>=), rhs: (=<<)} - warn: {lhs: flip (=<<), rhs: (>>=)} - hint: {lhs: \x -> f x >>= g, rhs: f Control.Monad.>=> g} - hint: {lhs: \x -> f =<< g x, rhs: f Control.Monad.<=< g} - hint: {lhs: (>>= f) . g, rhs: f Control.Monad.<=< g} - hint: {lhs: (f =<<) . g, rhs: f Control.Monad.<=< g} - warn: {lhs: a >> forever a, rhs: forever a} - hint: {lhs: liftM2 id, rhs: ap} - warn: {lhs: liftA2 f (return x), rhs: fmap (f x)} - warn: {lhs: liftM2 f (pure x), rhs: fmap (f x)} - warn: {lhs: liftM2 f (return x), rhs: fmap (f x)} - warn: {lhs: fmap f (return x), rhs: return (f x)} - warn: {lhs: f <$> return x, rhs: return (f x)} - warn: {lhs: mapM (uncurry f) (zip l m), rhs: zipWithM f l m} - warn: {lhs: mapM_ (void . f), rhs: mapM_ f} - warn: {lhs: forM_ x (void . f), rhs: forM_ x f} - warn: {lhs: a >>= \_ -> b, rhs: a >> b} - warn: {lhs: m <* return x, rhs: m} - warn: {lhs: return x *> m, rhs: m} - warn: {lhs: pure x >> m, rhs: m} - warn: {lhs: return x >> m, rhs: m} # STATE MONAD - warn: {lhs: fst (runState x y), rhs: evalState x y} - warn: {lhs: snd (runState x y), rhs: execState x y} # MONAD LIST - warn: {lhs: fmap unzip (mapM f x), rhs: Control.Monad.mapAndUnzipM f x} - warn: {lhs: sequence (zipWith f x y), rhs: Control.Monad.zipWithM f x y} - warn: {lhs: sequence_ (zipWith f x y), rhs: Control.Monad.zipWithM_ f x y} - warn: {lhs: sequence (replicate n x), rhs: Control.Monad.replicateM n x} - warn: {lhs: sequence_ (replicate n x), rhs: Control.Monad.replicateM_ n x} - warn: {lhs: sequenceA (zipWith f x y), rhs: Control.Monad.zipWithM f x y} - warn: {lhs: sequenceA_ (zipWith f x y), rhs: Control.Monad.zipWithM_ f x y} - warn: {lhs: sequenceA (replicate n x), rhs: Control.Monad.replicateM n x} - warn: {lhs: sequenceA_ (replicate n x), rhs: Control.Monad.replicateM_ n x} - warn: {lhs: mapM f (replicate n x), rhs: Control.Monad.replicateM n (f x)} - warn: {lhs: mapM_ f (replicate n x), rhs: Control.Monad.replicateM_ n (f x)} - warn: {lhs: mapM f (map g x), rhs: mapM (f . g) x, name: Fuse mapM/map} - warn: {lhs: mapM_ f (map g x), rhs: mapM_ (f . g) x, name: Fuse mapM_/map} - warn: {lhs: traverse f (map g x), rhs: traverse (f . g) x, name: Fuse traverse/map} - warn: {lhs: traverse_ f (map g x), rhs: traverse_ (f . g) x, name: Fuse traverse_/map} - warn: {lhs: mapM id, rhs: sequence} - warn: {lhs: mapM_ id, rhs: sequence_} # APPLICATIVE / TRAVERSABLE - warn: {lhs: flip traverse, rhs: for} - warn: {lhs: flip for, rhs: traverse} - warn: {lhs: flip traverse_, rhs: for_} - warn: {lhs: flip for_, rhs: traverse_} - warn: {lhs: foldr (*>) (pure ()), rhs: sequenceA_} - warn: {lhs: foldr (<|>) empty, rhs: asum} - warn: {lhs: liftA2 (flip ($)), rhs: (<**>)} - warn: {lhs: liftA2 f (pure x), rhs: fmap (f x)} - warn: {lhs: fmap f (pure x), rhs: pure (f x)} - warn: {lhs: f <$> pure x, rhs: pure (f x)} - warn: {lhs: Just <$> a <|> pure Nothing, rhs: optional a} - hint: {lhs: m >>= pure . f, rhs: f <$> m} - hint: {lhs: pure . f =<< m, rhs: f <$> m} - warn: {lhs: empty <|> x, rhs: x, name: "Alternative law, left identity"} - warn: {lhs: x <|> empty, rhs: x, name: "Alternative law, right identity"} - warn: {lhs: traverse id, rhs: sequenceA} - warn: {lhs: traverse_ id, rhs: sequenceA_} # LIST COMP - hint: {lhs: "if b then [x] else []", rhs: "[x | b]", name: Use list comprehension} - hint: {lhs: "if b then [] else [x]", rhs: "[x | not b]", name: Use list comprehension} - hint: {lhs: "[x | x <- y]", rhs: "y", side: isVar x, name: Redundant list comprehension} # SEQ - warn: {lhs: seq x x, rhs: x, name: Redundant seq} - warn: {lhs: join seq, rhs: id, name: Redundant seq} - warn: {lhs: id $! x, rhs: x, name: Redundant $!} - warn: {lhs: seq x y, rhs: "y", side: isWHNF x, name: Redundant seq} - warn: {lhs: f $! x, rhs: f x, side: isWHNF x, name: Redundant $!} - warn: {lhs: evaluate x, rhs: return x, side: isWHNF x, name: Redundant evaluate} - warn: {lhs: seq (rnf x) (), rhs: rnf x, name: Redundant seq} # TUPLE - warn: {lhs: fst (unzip x), rhs: map fst x} - warn: {lhs: snd (unzip x), rhs: map snd x} # MAYBE - warn: {lhs: maybe x id, rhs: Data.Maybe.fromMaybe x} - warn: {lhs: maybe Nothing Just, rhs: id, name: Redundant maybe} - warn: {lhs: maybe False (const True), rhs: Data.Maybe.isJust} - warn: {lhs: maybe True (const False), rhs: Data.Maybe.isNothing} - warn: {lhs: maybe False (x ==), rhs: (Just x ==)} - warn: {lhs: maybe True (x /=), rhs: (Just x /=)} - warn: {lhs: maybe False (== x), rhs: (Just x ==), note: ValidInstance Eq x} - warn: {lhs: maybe True (/= x), rhs: (Just x /=), note: ValidInstance Eq x} - warn: {lhs: fromMaybe False x, rhs: Just True == x} # Eta expanded, see https://github.com/ndmitchell/hlint/issues/970#issuecomment-643645053 - warn: {lhs: fromMaybe True x, rhs: Just False /= x} - warn: {lhs: not (isNothing x), rhs: isJust x} - warn: {lhs: not (isJust x), rhs: isNothing x} - warn: {lhs: "maybe [] (:[])", rhs: maybeToList} - warn: {lhs: catMaybes (map f x), rhs: mapMaybe f x} - warn: {lhs: catMaybes (fmap f x), rhs: mapMaybe f x} - hint: {lhs: case x of Nothing -> y; Just a -> a , rhs: Data.Maybe.fromMaybe y x, side: isAtom y, name: Replace case with fromMaybe} - hint: {lhs: case x of Just a -> a; Nothing -> y, rhs: Data.Maybe.fromMaybe y x, side: isAtom y, name: Replace case with fromMaybe} - hint: {lhs: case x of Nothing -> y; Just a -> f a , rhs: maybe y f x, side: isAtom y && isAtom f, name: Replace case with maybe} - hint: {lhs: case x of Just a -> f a; Nothing -> y, rhs: maybe y f x, side: isAtom y && isAtom f, name: Replace case with maybe} - warn: {lhs: if isNothing x then y else f (fromJust x), rhs: maybe y f x} - warn: {lhs: if isJust x then f (fromJust x) else y, rhs: maybe y f x} - warn: {lhs: maybe Nothing (Just . f), rhs: fmap f} - hint: {lhs: map fromJust (filter isJust x), rhs: Data.Maybe.catMaybes x} - warn: {lhs: x == Nothing , rhs: isNothing x} - warn: {lhs: Nothing == x , rhs: isNothing x} - warn: {lhs: x /= Nothing , rhs: Data.Maybe.isJust x} - warn: {lhs: Nothing /= x , rhs: Data.Maybe.isJust x} - warn: {lhs: concatMap (maybeToList . f), rhs: Data.Maybe.mapMaybe f} - warn: {lhs: concatMap maybeToList, rhs: catMaybes} - warn: {lhs: maybe n Just x, rhs: x Control.Applicative.<|> n} - warn: {lhs: if isNothing x then y else fromJust x, rhs: fromMaybe y x} - warn: {lhs: if isJust x then fromJust x else y, rhs: fromMaybe y x} - warn: {lhs: isJust x && (fromJust x == y), rhs: x == Just y} - warn: {lhs: mapMaybe f (map g x), rhs: mapMaybe (f . g) x, name: Fuse mapMaybe/map} - warn: {lhs: fromMaybe a (fmap f x), rhs: maybe a f x} - warn: {lhs: fromMaybe a (f <$> x), rhs: maybe a f x} - warn: {lhs: mapMaybe id, rhs: catMaybes} - hint: {lhs: "[x | Just x <- a]", rhs: Data.Maybe.catMaybes a} - hint: {lhs: case m of Nothing -> Nothing; Just x -> x, rhs: Control.Monad.join m} - hint: {lhs: maybe Nothing id, rhs: join} - hint: {lhs: maybe Nothing f x, rhs: f =<< x} - warn: {lhs: maybe x f (fmap g y), rhs: maybe x (f . g) y, name: Redundant fmap} - warn: {lhs: isJust (fmap f x), rhs: isJust x} - warn: {lhs: isNothing (fmap f x), rhs: isNothing x} - warn: {lhs: fromJust (fmap f x), rhs: f (fromJust x), note: IncreasesLaziness} - warn: {lhs: mapMaybe f (fmap g x), rhs: mapMaybe (f . g) x, name: Redundant fmap} # EITHER - warn: {lhs: "[a | Left a <- a]", rhs: lefts a} - warn: {lhs: "[a | Right a <- a]", rhs: rights a} - warn: {lhs: either Left (Right . f), rhs: fmap f} - warn: {lhs: either f g (fmap h x), rhs: either f (g . h) x, name: Redundant fmap} - warn: {lhs: isLeft (fmap f x), rhs: isLeft x} - warn: {lhs: isRight (fmap f x), rhs: isRight x} - warn: {lhs: fromLeft x (fmap f y), rhs: fromLeft x y} - warn: {lhs: fromRight x (fmap f y), rhs: either (const x) f y} # INFIX - hint: {lhs: elem x y, rhs: x `elem` y, side: not (isInfixApp original) && not (isParen result), name: Use infix} - hint: {lhs: notElem x y, rhs: x `notElem` y, side: not (isInfixApp original) && not (isParen result), name: Use infix} - hint: {lhs: isInfixOf x y, rhs: x `isInfixOf` y, side: not (isInfixApp original) && not (isParen result), name: Use infix} - hint: {lhs: isSuffixOf x y, rhs: x `isSuffixOf` y, side: not (isInfixApp original) && not (isParen result), name: Use infix} - hint: {lhs: isPrefixOf x y, rhs: x `isPrefixOf` y, side: not (isInfixApp original) && not (isParen result), name: Use infix} - hint: {lhs: union x y, rhs: x `union` y, side: not (isInfixApp original) && not (isParen result), name: Use infix} - hint: {lhs: intersect x y, rhs: x `intersect` y, side: not (isInfixApp original) && not (isParen result), name: Use infix} # MATHS - warn: {lhs: fromIntegral x, rhs: x, side: isLitInt x, name: Redundant fromIntegral} - warn: {lhs: fromInteger x, rhs: x, side: isLitInt x, name: Redundant fromInteger} - hint: {lhs: x + negate y, rhs: x - y} - hint: {lhs: 0 - x, rhs: negate x} - warn: {lhs: negate (negate x), rhs: x, name: Redundant negate} - hint: {lhs: log y / log x, rhs: logBase x y} - hint: {lhs: sin x / cos x, rhs: tan x} - hint: {lhs: rem n 2 == 0, rhs: even n} - hint: {lhs: 0 == rem n 2, rhs: even n} - hint: {lhs: rem n 2 /= 0, rhs: odd n} - hint: {lhs: 0 /= rem n 2, rhs: odd n} - hint: {lhs: mod n 2 == 0, rhs: even n} - hint: {lhs: 0 == mod n 2, rhs: even n} - hint: {lhs: mod n 2 /= 0, rhs: odd n} - hint: {lhs: 0 /= mod n 2, rhs: odd n} - hint: {lhs: not (even x), rhs: odd x} - hint: {lhs: not (odd x), rhs: even x} - hint: {lhs: x ** 0.5, rhs: sqrt x} - hint: {lhs: x ^ 0, rhs: "1", name: Use 1} - hint: {lhs: round (x - 0.5), rhs: floor x} # CONCURRENT - hint: {lhs: mapM_ (writeChan a), rhs: writeList2Chan a} - error: {lhs: atomically (readTVar x), rhs: readTVarIO x} # TYPEABLE - hint: {lhs: "typeOf (a :: b)", rhs: "typeRep (Proxy :: Proxy b)"} # EXCEPTION - hint: {lhs: flip Control.Exception.catch, rhs: handle} - hint: {lhs: flip handle, rhs: Control.Exception.catch} - hint: {lhs: flip (catchJust p), rhs: handleJust p} - hint: {lhs: flip (handleJust p), rhs: catchJust p} - hint: {lhs: Control.Exception.bracket b (const a) (const t), rhs: Control.Exception.bracket_ b a t} - hint: {lhs: Control.Exception.bracket (openFile x y) hClose, rhs: withFile x y} - hint: {lhs: Control.Exception.bracket (openBinaryFile x y) hClose, rhs: withBinaryFile x y} - hint: {lhs: throw (ErrorCall a), rhs: error a} - warn: {lhs: toException NonTermination, rhs: nonTermination} - warn: {lhs: toException NestedAtomically, rhs: nestedAtomically} # IOREF - hint: {lhs: modifyIORef r (const x), rhs: writeIORef r x} - hint: {lhs: modifyIORef r (\v -> x), rhs: writeIORef r x} # STOREABLE/PTR - hint: {lhs: castPtr nullPtr, rhs: nullPtr} - hint: {lhs: castPtr (castPtr x), rhs: castPtr x} - hint: {lhs: plusPtr (castPtr x), rhs: plusPtr x} - hint: {lhs: minusPtr (castPtr x), rhs: minusPtr x} - hint: {lhs: minusPtr x (castPtr y), rhs: minusPtr x y} - hint: {lhs: peekByteOff (castPtr x), rhs: peekByteOff x} - hint: {lhs: pokeByteOff (castPtr x), rhs: pokeByteOff x} # WEAK POINTERS - warn: {lhs: mkWeak a a b, rhs: mkWeakPtr a b} - warn: {lhs: "mkWeak a (a, b) c", rhs: mkWeakPair a b c} # FOLDABLE - warn: {lhs: case m of Nothing -> return (); Just x -> f x, rhs: Data.Foldable.forM_ m f} - warn: {lhs: case m of Just x -> f x; Nothing -> return (), rhs: Data.Foldable.forM_ m f} - warn: {lhs: case m of Just x -> f x; _ -> return (), rhs: Data.Foldable.forM_ m f} - warn: {lhs: when (isJust m) (f (fromJust m)), rhs: Data.Foldable.forM_ m f} # STATE MONAD - warn: {lhs: f <$> Control.Monad.State.get, rhs: gets f} - warn: {lhs: fmap f Control.Monad.State.get, rhs: gets f} - warn: {lhs: f <$> Control.Monad.State.gets g, rhs: gets (f . g)} - warn: {lhs: fmap f (Control.Monad.State.gets g), rhs: gets (f . g)} - warn: {lhs: f <$> Control.Monad.Reader.ask, rhs: asks f} - warn: {lhs: fmap f Control.Monad.Reader.ask, rhs: asks f} - warn: {lhs: f <$> Control.Monad.Reader.asks g, rhs: asks (f . g)} - warn: {lhs: fmap f (Control.Monad.Reader.asks g), rhs: asks (f . g)} - warn: {lhs: fst (runState m s), rhs: evalState m s} - warn: {lhs: snd (runState m s), rhs: execState m s} # EVALUATE - warn: {lhs: True && x, rhs: x, name: Evaluate} - warn: {lhs: False && x, rhs: "False", name: Evaluate} - warn: {lhs: True || x, rhs: "True", name: Evaluate} - warn: {lhs: False || x, rhs: x, name: Evaluate} - warn: {lhs: not True, rhs: "False", name: Evaluate} - warn: {lhs: not False, rhs: "True", name: Evaluate} - warn: {lhs: Nothing >>= k, rhs: Nothing, name: Evaluate} - warn: {lhs: k =<< Nothing, rhs: Nothing, name: Evaluate} - warn: {lhs: either f g (Left x), rhs: f x, name: Evaluate} - warn: {lhs: either f g (Right y), rhs: g y, name: Evaluate} - warn: {lhs: "fst (x,y)", rhs: x, name: Evaluate} - warn: {lhs: "snd (x,y)", rhs: "y", name: Evaluate} - warn: {lhs: "init [x]", rhs: "[]", name: Evaluate} - warn: {lhs: "null []", rhs: "True", name: Evaluate} - warn: {lhs: "length []", rhs: "0", name: Evaluate} - warn: {lhs: "foldl f z []", rhs: z, name: Evaluate} - warn: {lhs: "foldr f z []", rhs: z, name: Evaluate} - warn: {lhs: "foldr1 f [x]", rhs: x, name: Evaluate} - warn: {lhs: "scanr f z []", rhs: "[z]", name: Evaluate} - warn: {lhs: "scanr1 f []", rhs: "[]", name: Evaluate} - warn: {lhs: "scanr1 f [x]", rhs: "[x]", name: Evaluate} - warn: {lhs: "take n []", rhs: "[]", note: IncreasesLaziness, name: Evaluate} - warn: {lhs: "drop n []", rhs: "[]", note: IncreasesLaziness, name: Evaluate} - warn: {lhs: "takeWhile p []", rhs: "[]", name: Evaluate} - warn: {lhs: "dropWhile p []", rhs: "[]", name: Evaluate} - warn: {lhs: "span p []", rhs: "([],[])", name: Evaluate} - warn: {lhs: lines "", rhs: "[]", name: Evaluate} - warn: {lhs: "unwords []", rhs: "\"\"", name: Evaluate} - warn: {lhs: x - 0, rhs: x, name: Evaluate} - warn: {lhs: x * 1, rhs: x, name: Evaluate} - warn: {lhs: x / 1, rhs: x, name: Evaluate} - warn: {lhs: "concat [a]", rhs: a, name: Evaluate} - warn: {lhs: "concat []", rhs: "[]", name: Evaluate} - warn: {lhs: "zip [] []", rhs: "[]", name: Evaluate} - warn: {lhs: const x y, rhs: x, name: Evaluate} - warn: {lhs: any (const False), rhs: const False, note: IncreasesLaziness, name: Evaluate} - warn: {lhs: all (const True), rhs: const True, note: IncreasesLaziness, name: Evaluate} - warn: {lhs: "[] ++ x", rhs: x, name: Evaluate} - warn: {lhs: "x ++ []", rhs: x, name: Evaluate} # FOLDABLE + TUPLES - warn: {lhs: "foldr f z (x,b)", rhs: f b z, name: Using foldr on tuple} - warn: {lhs: "foldr' f z (x,b)", rhs: f b z, name: Using foldr' on tuple} - warn: {lhs: "foldl f z (x,b)", rhs: f z b, name: Using foldl on tuple} - warn: {lhs: "foldl' f z (x,b)", rhs: f z b, name: Using foldl' on tuple} - warn: {lhs: "foldMap f (x,b)", rhs: f b, name: Using foldMap on tuple} - warn: {lhs: "foldr1 f (x,b)", rhs: b, name: Using foldr1 on tuple} - warn: {lhs: "foldl1 f (x,b)", rhs: b, name: Using foldl1 on tuple} - warn: {lhs: "elem e (x,b)", rhs: e == b, name: Using elem on tuple} - warn: {lhs: "fold (x,b)", rhs: b, name: Using fold on tuple} - warn: {lhs: "toList (x,b)", rhs: b, name: Using toList on tuple} - warn: {lhs: "maximum (x,b)", rhs: b, name: Using maximum on tuple} - warn: {lhs: "minimum (x,b)", rhs: b, name: Using minimum on tuple} - warn: {lhs: "sum (x,b)", rhs: b, name: Using sum on tuple} - warn: {lhs: "product (x,b)", rhs: b, name: Using product on tuple} - warn: {lhs: "concat (x,b)", rhs: b, name: Using concat on tuple} - warn: {lhs: "and (x,b)", rhs: b, name: Using and on tuple} - warn: {lhs: "or (x,b)", rhs: b, name: Using or on tuple} - warn: {lhs: "any f (x,b)", rhs: f b, name: Using any on tuple} - warn: {lhs: "all f (x,b)", rhs: f b, name: Using all on tuple} - warn: {lhs: "foldr f z (x,y,b)", rhs: f b z, name: Using foldr on tuple} - warn: {lhs: "foldr' f z (x,y,b)", rhs: f b z, name: Using foldr' on tuple} - warn: {lhs: "foldl f z (x,y,b)", rhs: f z b, name: Using foldl on tuple} - warn: {lhs: "foldl' f z (x,y,b)", rhs: f z b, name: Using foldl' on tuple} - warn: {lhs: "foldMap f (x,y,b)", rhs: f b, name: Using foldMap on tuple} - warn: {lhs: "foldr1 f (x,y,b)", rhs: b, name: Using foldr1 on tuple} - warn: {lhs: "foldl1 f (x,y,b)", rhs: b, name: Using foldl1 on tuple} - warn: {lhs: "elem e (x,y,b)", rhs: e == b, name: Using elem on tuple} - warn: {lhs: "fold (x,y,b)", rhs: b, name: Using fold on tuple} - warn: {lhs: "toList (x,y,b)", rhs: b, name: Using toList on tuple} - warn: {lhs: "maximum (x,y,b)", rhs: b, name: Using maximum on tuple} - warn: {lhs: "minimum (x,y,b)", rhs: b, name: Using minimum on tuple} - warn: {lhs: "sum (x,y,b)", rhs: b, name: Using sum on tuple} - warn: {lhs: "product (x,y,b)", rhs: b, name: Using product on tuple} - warn: {lhs: "concat (x,y,b)", rhs: b, name: Using concat on tuple} - warn: {lhs: "and (x,y,b)", rhs: b, name: Using and on tuple} - warn: {lhs: "or (x,y,b)", rhs: b, name: Using or on tuple} - warn: {lhs: "any f (x,y,b)", rhs: f b, name: Using any on tuple} - warn: {lhs: "all f (x,y,b)", rhs: f b, name: Using all on tuple} - warn: {lhs: null x , rhs: "False", side: isTuple x, name: Using null on tuple} - warn: {lhs: length x, rhs: "1" , side: isTuple x, name: Using length on tuple} # MAP - warn: {lhs: "Data.Map.fromList []", rhs: Data.Map.empty} - warn: {lhs: "Data.Map.Lazy.fromList []", rhs: Data.Map.Lazy.empty} - warn: {lhs: "Data.Map.Strict.fromList []", rhs: Data.Map.Strict.empty} - group: name: lens enabled: true imports: - package base - package lens rules: - warn: {lhs: "(a ^. b) ^. c", rhs: "a ^. (b . c)"} - warn: {lhs: "fromJust (a ^? b)", rhs: "a ^?! b"} - warn: {lhs: "a .~ Just b", rhs: "a ?~ b"} - warn: {lhs: "a & (mapped %~ b)", rhs: "a <&> b"} - warn: {lhs: "a & ((mapped . b) %~ c)", rhs: "a <&> b %~ c"} - warn: {lhs: "a & (mapped .~ b)", rhs: "b <$ a"} - warn: {lhs: "ask <&> (^. a)", rhs: "view a"} - warn: {lhs: "view a <&> (^. b)", rhs: "view (a . b)"} # `at` pitfalls: - warn: {lhs: "Control.Lens.at a . Control.Lens._Just", rhs: "Control.Lens.ix a"} - error: {lhs: "Control.Lens.has (Control.Lens.at a)", rhs: "True"} - error: {lhs: "Control.Lens.has (a . Control.Lens.at b)", rhs: "Control.Lens.has a"} - error: {lhs: "Control.Lens.nullOf (Control.Lens.at a)", rhs: "False"} - error: {lhs: "Control.Lens.nullOf (a . Control.Lens.at b)", rhs: "Control.Lens.nullOf a"} - group: name: use-lens enabled: false imports: - package base - package lens rules: - warn: {lhs: "either Just (const Nothing)", rhs: preview _Left} - warn: {lhs: "either (const Nothing) Just", rhs: preview _Right} - group: name: attoparsec enabled: true imports: - package base - package attoparsec rules: - warn: {lhs: Data.Attoparsec.Text.option Nothing (Just <$> p), rhs: optional p} - warn: {lhs: Data.Attoparsec.ByteString.option Nothing (Just <$> p), rhs: optional p} - group: name: generalise enabled: false imports: - package base rules: - warn: {lhs: map, rhs: fmap} - warn: {lhs: a ++ b, rhs: a <> b} - warn: {lhs: "sequence [a]", rhs: "pure <$> a"} - warn: {lhs: "x /= []", rhs: not (null x), name: Use null} - warn: {lhs: "[] /= x", rhs: not (null x), name: Use null} - group: name: generalise-for-conciseness enabled: false imports: - package base rules: - warn: {lhs: maybe mempty, rhs: foldMap} - warn: {lhs: maybe False, rhs: any} - warn: {lhs: maybe True, rhs: all} - warn: {lhs: either (const mempty), rhs: foldMap} - warn: {lhs: either (const False), rhs: any} - warn: {lhs: either (const True), rhs: all} - warn: {lhs: Data.Maybe.fromMaybe mempty, rhs: Data.Foldable.fold} - warn: {lhs: Data.Maybe.fromMaybe 0, rhs: sum} - warn: {lhs: Data.Maybe.fromMaybe 1, rhs: product} - warn: {lhs: Data.Maybe.fromMaybe empty, rhs: Data.Foldable.asum} - warn: {lhs: Data.Maybe.fromMaybe mzero, rhs: Data.Foldable.msum} - warn: {lhs: Data.Either.fromRight mempty, rhs: Data.Foldable.fold} - warn: {lhs: Data.Either.fromRight False, rhs: or} - warn: {lhs: Data.Either.fromRight True, rhs: and} - warn: {lhs: Data.Either.fromRight 0, rhs: sum} - warn: {lhs: Data.Either.fromRight 1, rhs: product} - warn: {lhs: Data.Either.fromRight empty, rhs: Data.Foldable.asum} - warn: {lhs: Data.Either.fromRight mzero, rhs: Data.Foldable.msum} - warn: {lhs: if f x then Just x else Nothing, rhs: mfilter f (Just x)} - hint: {lhs: maybe (pure ()), rhs: traverse_, note: IncreasesLaziness} - hint: {lhs: fromMaybe (pure ()), rhs: sequenceA_, note: IncreasesLaziness} - hint: {lhs: fromRight (pure ()), rhs: sequenceA_, note: IncreasesLaziness} - hint: {lhs: "[fst x, snd x]", rhs: Data.Bifoldable.biList x} - hint: {lhs: "\\(x, y) -> [x, y]", rhs: Data.Bifoldable.biList, note: IncreasesLaziness} # hints that use the 'extra' library - group: name: extra enabled: false rules: - warn: {lhs: fmap concat (forM a b), rhs: concatForM a b} - warn: {lhs: concat <$> forM a b, rhs: concatForM a b} - warn: {lhs: fmap concat (forM_ a b), rhs: concatForM_ a b} - warn: {lhs: concat <$> forM_ a b, rhs: concatForM_ a b} - warn: {lhs: "maybe (pure ()) b a", rhs: "whenJust a b"} - warn: {lhs: "maybe (return ()) b a", rhs: "whenJust a b"} - warn: {lhs: "maybeM (pure ()) b a", rhs: "whenJustM a b"} - warn: {lhs: "maybeM (return ()) b a", rhs: "whenJustM a b"} - warn: {lhs: "if a then Just <$> b else pure Nothing", rhs: "whenMaybe a b"} - warn: {lhs: "maybe a b =<< c", rhs: "maybeM a b c"} - warn: {lhs: "maybeM a pure x", rhs: "fromMaybeM a b"} - warn: {lhs: "maybeM a return x", rhs: "fromMaybeM a b"} - warn: {lhs: "either a b =<< c", rhs: "eitherM a b c"} - warn: {lhs: "fold1M a b >> return ()", rhs: "fold1M_ a b"} - warn: {lhs: "fold1M a b >> pure ()", rhs: "fold1M_ a b"} - warn: {lhs: "flip concatMapM", rhs: "concatForM"} - warn: {lhs: "liftM mconcat (mapM a b)", rhs: "mconcatMapM a b"} - warn: {lhs: "ifM a b (return ())", rhs: "whenM a b"} - warn: {lhs: "ifM a (return ()) b", rhs: "unlessM a b"} - warn: {lhs: "ifM a (return True) b", rhs: "(||^) a b"} - warn: {lhs: "ifM a b (return False)", rhs: "(&&^) a b"} - warn: {lhs: "anyM id", rhs: "orM"} - warn: {lhs: "allM id", rhs: "andM"} - warn: {lhs: "either id id", rhs: "fromEither"} - warn: {lhs: "either (const Nothing) Just", rhs: "eitherToMaybe"} - warn: {lhs: "either (Left . a) Right", rhs: "mapLeft a"} - warn: {lhs: "atomicModifyIORef a (\\ v -> (b v, ()))", rhs: "atomicModifyIORef_ a b"} - warn: {lhs: "atomicModifyIORef' a (\\ v -> (b v, ()))", rhs: "atomicModifyIORef'_ a b"} - warn: {lhs: "null (intersect a b)", rhs: "disjoint a b"} - warn: {lhs: "[minBound .. maxBound]", rhs: "enumerate"} - warn: {lhs: "zipWithFrom (,)", rhs: "zipFrom"} - warn: {lhs: "zip [i..]", rhs: "zipFrom i"} - warn: {lhs: "zipWith f [i..]", rhs: "zipWithFrom f i"} - warn: {lhs: "dropWhile isSpace", rhs: "trimStart"} - warn: {lhs: "dropWhileEnd isSpace", rhs: "trimEnd"} - warn: {lhs: "trimEnd (trimStart a)", rhs: "trim a"} - warn: {lhs: "map toLower", rhs: "lower"} - warn: {lhs: "map toUpper", rhs: "upper"} - warn: {lhs: "mergeBy compare", rhs: "merge"} - warn: {lhs: "breakEnd (not . a)", rhs: "spanEnd a"} - warn: {lhs: "spanEnd (not . a)", rhs: "breakEnd a"} - warn: {lhs: "mconcat (map a b)", rhs: "mconcatMap a b"} - warn: {lhs: "fromMaybe b (stripPrefix a b)", rhs: "dropPrefix a b"} - warn: {lhs: "fromMaybe b (stripSuffix a b)", rhs: "dropSuffix a b"} - warn: {lhs: "nubSortBy compare", rhs: "nubSort"} - warn: {lhs: "nubSortBy (compare `on` a)", rhs: "nubSortOn a"} - warn: {lhs: "nubOrdBy compare", rhs: "nubOrd"} - warn: {lhs: "\\a -> (a, a)", rhs: "dupe"} - warn: {lhs: "showFFloat (Just a) b \"\"", rhs: "showDP a b"} - warn: {lhs: "readFileEncoding utf8", rhs: "readFileUTF8"} - warn: {lhs: "withFile a ReadMode hGetContents'", rhs: "readFile' a"} - warn: {lhs: "readFileEncoding' utf8", rhs: "readFileUTF8'"} - warn: {lhs: "withBinaryFile a ReadMode hGetContents'", rhs: "readFileBinary' a"} - warn: {lhs: "writeFileEncoding utf8", rhs: "writeFileUTF8"} - warn: {lhs: "head $ x ++ [y]", rhs: "headDef y x"} - warn: {lhs: "last $ x : y", rhs: "lastDef x y"} - warn: {lhs: "drop 1", rhs: "drop1"} - warn: {lhs: "dropEnd 1", rhs: "dropEnd1"} # hints that will be enabled in future - group: name: future enabled: false rules: - warn: {lhs: return, rhs: pure} - group: name: dollar enabled: false imports: - package base rules: - warn: {lhs: a $ b $ c, rhs: a . b $ c} - group: # These hints are same if all matched functions are monomorphic, or polymorphic, but don't have adhoc polymorphism name: monomorphic enabled: false imports: - package base rules: - warn: {lhs: if c then f x else f y, rhs: f (if c then x else y), note: IncreasesLaziness, name: Too strict if} - hint: {lhs: maybe (f x) (f . g), rhs: f . maybe x g, note: IncreasesLaziness, name: Too strict maybe} - hint: {lhs: maybe (f x) f y, rhs: f (Data.Maybe.fromMaybe x y), note: IncreasesLaziness, name: Too strict maybe} - group: name: codeworld enabled: false imports: - package base - package codeworld-api rules: - warn: {lhs: "pictures [ p ]", rhs: p, name: Evaluate} - warn: {lhs: "pictures [ p, q ]", rhs: p & q, name: Evaluate} - hint: {lhs: foldl1 (&), rhs: pictures} - hint: {lhs: foldr (&) blank, rhs: pictures} - hint: {lhs: scaled x x, rhs: dilated x} - hint: {lhs: scaledPoint x x, rhs: dilatedPoint x} - warn: {lhs: "brighter (- a)", rhs: "duller a"} - warn: {lhs: "lighter (- a)", rhs: "darker a"} - warn: {lhs: "duller (- a)", rhs: "brighter a"} - warn: {lhs: "darker (- a)", rhs: "lighter a"} - group: name: teaching enabled: false imports: - package base rules: - hint: {lhs: "x /= []", rhs: not (null x), name: Use null} - hint: {lhs: "[] /= x", rhs: not (null x), name: Use null} - hint: {lhs: "not (x || y)", rhs: "not x && not y", name: Apply De Morgan law} - hint: {lhs: "not (x && y)", rhs: "not x || not y", name: Apply De Morgan law} - hint: {lhs: "[ f x | x <- l ]", rhs: map f l} - group: # used for tests, enabled when testing this file name: testing enabled: false rules: - warn: {lhs: "[issue766| |]", rhs: "mempty", name: "Use mempty"} # # yes = concat . map f -- concatMap f # yes = foo . bar . concat . map f . baz . bar -- concatMap f . baz . bar # yes = map f (map g x) -- map (f . g) x # yes = concat.map (\x->if x==e then l' else [x]) -- concatMap (\x->if x==e then l' else [x]) # yes = f x where f x = concat . map head -- concatMap head # yes = concat . map f . g -- concatMap f . g # yes = concat $ map f x -- concatMap f x # yes = "test" ++ concatMap (' ':) ["of","this"] -- unwords ("test":["of","this"]) # yes = if f a then True else b -- f a || b # yes = not (a == b) -- a /= b # yes = not (a /= b) -- a == b # yes = not . (a ==) -- (a /=) # yes = not . (== a) -- (/= a) # yes = not . (a /=) -- (a ==) # yes = not . (/= a) -- (== a) # yes = if a then 1 else if b then 1 else 2 -- if a || b then 1 else 2 # no = if a then 1 else if b then 3 else 2 # yes = a >>= return . bob -- bob <$> a # yes = return . bob =<< a -- bob <$> a # yes = m alice >>= pure . b -- b <$> m alice # yes = pure .b =<< m alice -- b <$> m alice # yes = asciiCI "hi" *> pure Hi -- asciiCI "hi" Data.Functor.$> Hi # yes = asciiCI "bye" *> return Bye -- asciiCI "bye" Data.Functor.$> Bye # yes = pure x <* y -- x Data.Functor.<$ y # yes = return x <* y -- x Data.Functor.<$ y # yes = const x <$> y -- x <$ y # yes = pure alice <$> [1, 2] -- alice <$ [1, 2] # yes = return alice <$> "Bob" -- alice <$ "Bob" # yes = Just a <&> const b -- Just a Data.Functor.$> b # yes = [a,b] <&> pure c -- [a,b] Data.Functor.$> c # yes = Hi <&> return bye -- Hi Data.Functor.$> bye # yes = (x !! 0) + (x !! 2) -- head x # yes = if b < 42 then [a] else [] -- [a | b < 42] # no = take n (foo xs) == "hello" # yes = head (reverse xs) -- last xs # yes = reverse xs `isPrefixOf` reverse ys -- isSuffixOf xs ys # no = putStrLn $ show (length xs) ++ "Test" # yes = ftable ++ map (\ (c, x) -> (toUpper c, urlEncode x)) ftable -- Data.Bifunctor.bimap toUpper urlEncode # yes = map (\(a,b) -> a) xs -- fst # yes = map (\(a,_) -> a) xs -- fst # yes = readFile $ args !! 0 -- head args # yes = if Debug `elem` opts then ["--debug"] else [] -- ["--debug" | Debug `elem` opts] # yes = if nullPS s then return False else if headPS s /= '\n' then return False else alter_input tailPS >> return True \ # -- if nullPS s || (headPS s /= '\n') then return False else alter_input tailPS >> return True # yes = if foo then do stuff; moreStuff; lastOfTheStuff else return () \ # -- Control.Monad.when foo $ do stuff ; moreStuff ; lastOfTheStuff # yes = if foo then stuff else return () -- Control.Monad.when foo stuff # yes = foo $ \(a, b) -> (a, y + b) -- Data.Bifunctor.second ((+) y) # no = foo $ \(a, b) -> (a, a + b) # yes = map (uncurry (+)) $ zip [1 .. 5] [6 .. 10] -- zipWith (curry (uncurry (+))) [1 .. 5] [6 .. 10] # yes = curry (uncurry (+)) -- (+) # yes = fst foo .= snd foo -- uncurry (.=) foo # yes = fst foo `_ba__'r''` snd foo -- uncurry _ba__'r'' foo # no = do iter <- textBufferGetTextIter tb ; textBufferSelectRange tb iter iter # no = flip f x $ \y -> y*y+y # no = \x -> f x (g x) # no = foo (\ v -> f v . g) # yes = concat . intersperse " " -- unwords # yes = Prelude.concat $ intersperse " " xs -- unwords xs # yes = concat $ Data.List.intersperse " " xs -- unwords xs # yes = if a then True else False -- a # yes = if x then true else False -- x && true # yes = elem x y -- x `elem` y # yes = foo (elem x y) -- x `elem` y # no = x `elem` y # no = elem 1 [] : [] # test a = foo (\x -> True) -- const True # test a = foo (\_ -> True) -- const True # test a = foo (\x -> x) -- id # h a = flip f x (y z) -- f (y z) x # h a = flip f x $ y z # yes x = case x of {True -> a ; False -> b} -- if x then a else b # yes x = case x of {False -> a ; _ -> b} -- if x then b else a # no = const . ok . toResponse $ "saved" # yes = case x z of Nothing -> y; Just pat -> pat -- Data.Maybe.fromMaybe y (x z) # yes = if p then s else return () -- Control.Monad.when p s # warn = a $$$$ b $$$$ c ==> a . b $$$$$ c # yes = when (not . null $ asdf) -- unless (null asdf) # yes = (foo . bar . when) (not . null $ asdf) -- (foo . bar) (unless (null asdf)) # yes = id 1 -- 1 # yes = case concat (map f x) of [] -> [] -- concatMap f x # yes = [v | v <- xs] -- xs # no = [Left x | Left x <- xs] # when p s = if p then s else return () # no = x ^^ 18.5 # instance Arrow (->) where first f = f *** id # yes = fromInteger 12 -- 12 # import Prelude hiding (catch); no = catch # import Control.Exception as E; no = E.catch # main = do f; putStrLn $ show x -- print x # main = map (writer,) $ map arcObj $ filter (rdfPredEq (Res dctreferences)) ts -- map ((writer,) . arcObj) (filter (rdfPredEq (Res dctreferences)) ts) # h x y = return $! (x, y) -- return (x, y) # h x y = return $! x # getInt = do { x <- readIO "0"; return $! (x :: Int) } # foo = evaluate [12] -- return [12] # test = \ a -> f a >>= \ b -> return (a, b) # fooer input = catMaybes . map Just $ input -- mapMaybe Just # yes = mapMaybe id -- catMaybes # foo = magic . isLeft $ fmap f x -- magic (isLeft x) # foo = (bar . baz . magic . isRight) (fmap f x) -- (bar . baz . magic) (isRight x) # main = print $ map (\_->5) [2,3,5] -- const 5 # main = head $ drop n x -- x !! max 0 n # main = head $ drop (-3) x -- x # main = head $ drop 2 x -- x !! 2 # main = foo . bar . baz . head $ drop 2 x -- (foo . bar . baz) (x !! 2) # main = drop 0 x -- x # main = take 0 x -- [] # main = take (-5) x -- [] # main = take (-y) x # main = take 4 x # main = let (first, rest) = (takeWhile p l, dropWhile p l) in rest -- span p l # main = let (first, rest) = (take n l, drop n l) in rest -- splitAt n l # main = fst (splitAt n l) -- take n l # main = snd $ splitAt n l -- drop n l # main = map $ \ d -> ([| $d |], [| $d |]) # pairs (x:xs) = map (x,) xs ++ pairs xs # {-# ANN foo "HLint: ignore" #-};foo = map f (map g x) -- @Ignore ??? # {-# HLINT ignore foo #-};foo = map f (map g x) -- @Ignore ??? # yes = fmap lines $ abc 123 -- lines <$> abc 123 # no = fmap lines $ abc $ def 123 # test = foo . not . not -- id # test = map (not . not) xs -- id # used = not . not . any (`notElem` special) . fst . derives -- any (`notElem` special) . fst . derives # test = foo . id . map -- map # test = food id xs # yes = baz baz >> return () -- Control.Monad.void (baz baz) # no = foo >>= bar >>= something >>= elsee >> return () # no = f (#) x # data Pair = P {a :: !Int}; foo = return $! P{a=undefined} # data Pair = P {a :: !Int}; foo = return $! P undefined # foo = return $! Just undefined -- return (Just undefined) # foo = return $! (a,b) -- return (a,b) # foo = return $! 1 # foo = return $! "test" # bar = [x | (x,_) <- pts] # return' x = x `seq` return x # foo = last (sortBy (compare `on` fst) xs) -- maximumBy (compare `on` fst) xs # g = \ f -> parseFile f >>= (\ cu -> return (f, cu)) # foo = bar $ \(x,y) -> x x y # foo = (\x -> f x >>= g) -- f Control.Monad.>=> g # foo = (\f -> h f >>= g) -- h Control.Monad.>=> g # foo = (\f -> h f >>= f) # foo = bar $ \x -> [x,y] # foo = bar $ \x -> [z,y] -- const [z,y] # f condition tChar tBool = if condition then _monoField tChar else _monoField tBool # foo = maybe Bar{..} id -- Data.Maybe.fromMaybe Bar{..} # foo = (\a -> Foo {..}) 1 # foo = zipWith SymInfo [0 ..] (repeat ty) -- map (\ x -> SymInfo x ty) [0 ..] # f rec = rec # mean x = fst $ foldl (\(m, n) x' -> (m+(x'-m)/(n+1),n+1)) (0,0) x # {-# LANGUAGE TypeApplications #-} \ # foo = id @Int # {-# LANGUAGE TypeApplications #-} \ # foo = const @_ @SomeException # foo = id 12 -- 12 # yes = foldr (\ curr acc -> (+ 1) curr : acc) [] -- map (\ curr -> (+ 1) curr) # yes = foldr (\ curr acc -> curr + curr : acc) [] -- map (\ curr -> curr + curr) # no = foo $ (,) x $ do {this is a test; and another test} # no = sequence (return x) # no = sequenceA (pure a) # {-# LANGUAGE QuasiQuotes #-}; no = f (\url -> [hamlet|foo @{url}|]) # yes = f ((,) x) -- (x,) # yes = f ((,) (2 + 3)) -- (2 + 3,) # instance Class X where method = map f (map g x) -- map (f . g) x # instance Eq X where x == y = compare x y == EQ # issue1055 = map f ((sort . map g) xs) # issue1049 = True `elem` xs -- or xs # issue1049 = elem True -- or # issue1062 = bar (\(f, x) -> baz () () . f $ x) -- uncurry ((.) (baz () ())) # issue1058 n = [] ++ issue1058 (n+1) -- issue1058 (n+1) # import Prelude \ # yes = flip mapM -- Control.Monad.forM # import Control.Monad \ # yes = flip mapM -- forM # import Control.Monad(forM) \ # yes = flip mapM -- forM # import Control.Monad(forM_) \ # yes = flip mapM -- Control.Monad.forM # import qualified Control.Monad \ # yes = flip mapM -- Control.Monad.forM # import qualified Control.Monad as CM \ # yes = flip mapM -- CM.forM # import qualified Control.Monad as CM(forM,filterM) \ # yes = flip mapM -- CM.forM # import Control.Monad as CM(forM,filterM) \ # yes = flip mapM -- forM # import Control.Monad hiding (forM) \ # yes = flip mapM -- Control.Monad.forM # import Control.Monad hiding (filterM) \ # yes = flip mapM -- forM # import qualified Data.Text.Lazy as DTL \ # main = DTL.concat $ map (`DTL.snoc` '-') [DTL.pack "one", DTL.pack "two", DTL.pack "three"] # import Text.Blaze.Html5.Attributes as A \ # main = A.id (stringValue id') # import Prelude((==)) \ # import qualified Prelude as P \ # main = P.length xs == 0 -- P.null xs # main = hello .~ Just 12 -- hello ?~ 12 # foo = liftIO $ window `on` deleteEvent $ do a; b # no = sort <$> f input `shouldBe` sort <$> x # sortBy (comparing length) -- sortOn length # myJoin = on $ child ^. ChildParentId ==. parent ^. ParentId # foo = typeOf (undefined :: Foo Int) -- typeRep (Proxy :: Proxy (Foo Int)) # foo = typeOf (undefined :: a) -- typeRep (Proxy :: Proxy a) # {-# RULES "Id-fmap-id" forall (x :: Id a). fmap id x = x #-} # import Data.Map (fromList) \ # fromList [] -- Data.Map.empty # import Data.Map.Lazy (fromList) \ # fromList [] -- Data.Map.Lazy.empty # import Data.Map.Strict (fromList) \ # fromList [] -- Data.Map.Strict.empty # test953 = for [] $ \n -> bar n >>= \case {Just n -> pure (); Nothing -> baz n} # f = map (flip (,) "a") "123" -- (,"a") # f = map ((,) "a") "123" -- ("a",) # test979 = flip Map.traverseWithKey blocks \k v -> lots_of_code_goes_here # infixl 4 <*! \ # test993 = f =<< g <$> x <*! y # {-# LANGUAGE QuasiQuotes #-} \ # test = [issue766| |] -- mempty # {-# LANGUAGE QuasiQuotes #-} \ # test = [issue766| x |] # hlint-3.1.6/data/hlint.ghci0000644000000000000000000000244412725731243013722 0ustar0000000000000000-- -*- mode: haskell; -*- -- Begin copied material. -- :{ :def redir \varcmd -> return $ case break Data.Char.isSpace varcmd of (var,_:cmd) -> unlines [":set -fno-print-bind-result" ,"tmp <- System.Directory.getTemporaryDirectory" ,"(f,h) <- System.IO.openTempFile tmp \"ghci\"" ,"sto <- GHC.IO.Handle.hDuplicate System.IO.stdout" ,"GHC.IO.Handle.hDuplicateTo h System.IO.stdout" ,"System.IO.hClose h" ,cmd ,"GHC.IO.Handle.hDuplicateTo sto System.IO.stdout" ,"let readFileNow f = readFile f >>= \\t->Data.List.length t `seq` return t" ,var++" <- readFileNow f" ,"System.Directory.removeFile f" ] _ -> "putStrLn \"usage: :redir \"" :} --- Integration with the hlint code style tool :{ :def hlint \extra -> return $ unlines [":unset +t +s" ,":set -w" ,":redir hlintvar1 :show modules" ,":cmd return $ \":! hlint \" ++ unwords (map (takeWhile (/=',') . drop 2 . dropWhile (/= '(')) $ lines hlintvar1) ++ \" \" ++ " ++ show extra ,":set +t +s -Wall" ] :} hlint-3.1.6/data/hlint.10000644000000000000000000000242413472452374013153 0ustar0000000000000000.TH HLINT "1" "July 2009" "HLint (C) Neil Mitchell 2006-2009" "User Commands" .SH NAME HLint \- haskell source code suggestions .SH SYNOPSIS .B hlint [\fIfiles/directories\fR] [\fIoptions\fR] .SH DESCRIPTION \fIHLint\fR is a tool for suggesting possible improvements to Haskell code. These suggestions include ideas such as using alternative functions, simplifying code and spotting redundancies. .SH OPTIONS .TP \fB\-?\fR \fB\-\-help\fR Display help message .TP \fB\-v\fR \fB\-\-version\fR Display version information .TP \fB\-r[file]\fR \fB\-\-report\fR[=\fIfile\fR] Generate a report in HTML .TP \fB\-h\fR \fIfile\fR \fB\-\-hint\fR=\fIfile\fR Hint/ignore file to use .TP \fB\-c\fR \fB\-\-color\fR, \fB\-\-colour\fR Color the output (requires ANSI terminal) .TP \fB\-i\fR \fImessage\fR \fB\-\-ignore\fR=\fImessage\fR Ignore a particular hint .TP \fB\-s\fR \fB\-\-show\fR Show all ignored ideas .TP \fB\-t\fR \fB\-\-test\fR Run in test mode .SH EXAMPLE "To check all Haskell files in 'src' and generate a report type:" .IP hlint src \fB\-\-report\fR .SH "SEE ALSO" The full documentation for .B HLint is available in \fI/usr/share/doc/hlint/hlint.html\fI. .SH AUTHOR This manual page was written by Joachim Breitner for the Debian system (but may be used by others). hlint-3.1.6/data/default.yaml0000644000000000000000000000334413075153430014253 0ustar0000000000000000# HLint configuration file # https://github.com/ndmitchell/hlint ########################## # This file contains a template configuration file, which is typically # placed as .hlint.yaml in the root of your project # Specify additional command line arguments # # - arguments: [--color, --cpp-simple, -XQuasiQuotes] # Control which extensions/flags/modules/functions can be used # # - extensions: # - default: false # all extension are banned by default # - name: [PatternGuards, ViewPatterns] # only these listed extensions can be used # - {name: CPP, within: CrossPlatform} # CPP can only be used in a given module # # - flags: # - {name: -w, within: []} # -w is allowed nowhere # # - modules: # - {name: [Data.Set, Data.HashSet], as: Set} # if you import Data.Set qualified, it must be as 'Set' # - {name: Control.Arrow, within: []} # Certain modules are banned entirely # # - functions: # - {name: unsafePerformIO, within: []} # unsafePerformIO can only appear in no modules # Add custom hints for this project # # Will suggest replacing "wibbleMany [myvar]" with "wibbleOne myvar" # - error: {lhs: "wibbleMany [x]", rhs: wibbleOne x} # Turn on hints that are off by default # # Ban "module X(module X) where", to require a real export list # - warn: {name: Use explicit module export list} # # Replace a $ b $ c with a . b $ c # - group: {name: dollar, enabled: true} # # Generalise map to fmap, ++ to <> # - group: {name: generalise, enabled: true} # Ignore some builtin hints # - ignore: {name: Use let} # - ignore: {name: Use const, within: SpecialModule} # Only within certain modules # Define some custom infix operators # - fixity: infixr 3 ~^#^~ # To generate a suitable file for HLint do: # $ hlint --default > .hlint.yaml