packages feed

smuggler2 (empty) → 0.3.2.1

raw patch · 40 files changed

+1784/−0 lines, 40 filesdep +basedep +containersdep +directorysetup-changed

Dependencies added: base, containers, directory, filepath, ghc, ghc-exactprint, ghc-paths, smuggler2, syb, tasty, tasty-golden, text, typed-process

Files

+ CHANGELOG.md view
@@ -0,0 +1,16 @@+# Changelog++Notable changes to `Smuggler2`++`smuggler2` uses [PVP Versioning][1].+The change log is available [on GitHub][2].++## [Unreleased]+- Nothing to report++##  [0.3.2.1]: --  30 May 2020+- Provisional version for Hackage+++[1]: https://pvp.haskell.org+[2]: https://github.com/jrp2014/smuggler2/releases
+ LICENSE view
@@ -0,0 +1,373 @@+Mozilla Public License Version 2.0+==================================++1. Definitions+--------------++1.1. "Contributor"+    means each individual or legal entity that creates, contributes to+    the creation of, or owns Covered Software.++1.2. "Contributor Version"+    means the combination of the Contributions of others (if any) used+    by a Contributor and that particular Contributor's Contribution.++1.3. "Contribution"+    means Covered Software of a particular Contributor.++1.4. "Covered Software"+    means Source Code Form to which the initial Contributor has attached+    the notice in Exhibit A, the Executable Form of such Source Code+    Form, and Modifications of such Source Code Form, in each case+    including portions thereof.++1.5. "Incompatible With Secondary Licenses"+    means++    (a) that the initial Contributor has attached the notice described+        in Exhibit B to the Covered Software; or++    (b) that the Covered Software was made available under the terms of+        version 1.1 or earlier of the License, but not also under the+        terms of a Secondary License.++1.6. "Executable Form"+    means any form of the work other than Source Code Form.++1.7. "Larger Work"+    means a work that combines Covered Software with other material, in+    a separate file or files, that is not Covered Software.++1.8. "License"+    means this document.++1.9. "Licensable"+    means having the right to grant, to the maximum extent possible,+    whether at the time of the initial grant or subsequently, any and+    all of the rights conveyed by this License.++1.10. "Modifications"+    means any of the following:++    (a) any file in Source Code Form that results from an addition to,+        deletion from, or modification of the contents of Covered+        Software; or++    (b) any new file in Source Code Form that contains any Covered+        Software.++1.11. "Patent Claims" of a Contributor+    means any patent claim(s), including without limitation, method,+    process, and apparatus claims, in any patent Licensable by such+    Contributor that would be infringed, but for the grant of the+    License, by the making, using, selling, offering for sale, having+    made, import, or transfer of either its Contributions or its+    Contributor Version.++1.12. "Secondary License"+    means either the GNU General Public License, Version 2.0, the GNU+    Lesser General Public License, Version 2.1, the GNU Affero General+    Public License, Version 3.0, or any later versions of those+    licenses.++1.13. "Source Code Form"+    means the form of the work preferred for making modifications.++1.14. "You" (or "Your")+    means an individual or a legal entity exercising rights under this+    License. For legal entities, "You" includes any entity that+    controls, is controlled by, or is under common control with You. For+    purposes of this definition, "control" means (a) the power, direct+    or indirect, to cause the direction or management of such entity,+    whether by contract or otherwise, or (b) ownership of more than+    fifty percent (50%) of the outstanding shares or beneficial+    ownership of such entity.++2. License Grants and Conditions+--------------------------------++2.1. Grants++Each Contributor hereby grants You a world-wide, royalty-free,+non-exclusive license:++(a) under intellectual property rights (other than patent or trademark)+    Licensable by such Contributor to use, reproduce, make available,+    modify, display, perform, distribute, and otherwise exploit its+    Contributions, either on an unmodified basis, with Modifications, or+    as part of a Larger Work; and++(b) under Patent Claims of such Contributor to make, use, sell, offer+    for sale, have made, import, and otherwise transfer either its+    Contributions or its Contributor Version.++2.2. Effective Date++The licenses granted in Section 2.1 with respect to any Contribution+become effective for each Contribution on the date the Contributor first+distributes such Contribution.++2.3. Limitations on Grant Scope++The licenses granted in this Section 2 are the only rights granted under+this License. No additional rights or licenses will be implied from the+distribution or licensing of Covered Software under this License.+Notwithstanding Section 2.1(b) above, no patent license is granted by a+Contributor:++(a) for any code that a Contributor has removed from Covered Software;+    or++(b) for infringements caused by: (i) Your and any other third party's+    modifications of Covered Software, or (ii) the combination of its+    Contributions with other software (except as part of its Contributor+    Version); or++(c) under Patent Claims infringed by Covered Software in the absence of+    its Contributions.++This License does not grant any rights in the trademarks, service marks,+or logos of any Contributor (except as may be necessary to comply with+the notice requirements in Section 3.4).++2.4. Subsequent Licenses++No Contributor makes additional grants as a result of Your choice to+distribute the Covered Software under a subsequent version of this+License (see Section 10.2) or under the terms of a Secondary License (if+permitted under the terms of Section 3.3).++2.5. Representation++Each Contributor represents that the Contributor believes its+Contributions are its original creation(s) or it has sufficient rights+to grant the rights to its Contributions conveyed by this License.++2.6. Fair Use++This License is not intended to limit any rights You have under+applicable copyright doctrines of fair use, fair dealing, or other+equivalents.++2.7. Conditions++Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted+in Section 2.1.++3. Responsibilities+-------------------++3.1. Distribution of Source Form++All distribution of Covered Software in Source Code Form, including any+Modifications that You create or to which You contribute, must be under+the terms of this License. You must inform recipients that the Source+Code Form of the Covered Software is governed by the terms of this+License, and how they can obtain a copy of this License. You may not+attempt to alter or restrict the recipients' rights in the Source Code+Form.++3.2. Distribution of Executable Form++If You distribute Covered Software in Executable Form then:++(a) such Covered Software must also be made available in Source Code+    Form, as described in Section 3.1, and You must inform recipients of+    the Executable Form how they can obtain a copy of such Source Code+    Form by reasonable means in a timely manner, at a charge no more+    than the cost of distribution to the recipient; and++(b) You may distribute such Executable Form under the terms of this+    License, or sublicense it under different terms, provided that the+    license for the Executable Form does not attempt to limit or alter+    the recipients' rights in the Source Code Form under this License.++3.3. Distribution of a Larger Work++You may create and distribute a Larger Work under terms of Your choice,+provided that You also comply with the requirements of this License for+the Covered Software. If the Larger Work is a combination of Covered+Software with a work governed by one or more Secondary Licenses, and the+Covered Software is not Incompatible With Secondary Licenses, this+License permits You to additionally distribute such Covered Software+under the terms of such Secondary License(s), so that the recipient of+the Larger Work may, at their option, further distribute the Covered+Software under the terms of either this License or such Secondary+License(s).++3.4. Notices++You may not remove or alter the substance of any license notices+(including copyright notices, patent notices, disclaimers of warranty,+or limitations of liability) contained within the Source Code Form of+the Covered Software, except that You may alter any license notices to+the extent required to remedy known factual inaccuracies.++3.5. Application of Additional Terms++You may choose to offer, and to charge a fee for, warranty, support,+indemnity or liability obligations to one or more recipients of Covered+Software. However, You may do so only on Your own behalf, and not on+behalf of any Contributor. You must make it absolutely clear that any+such warranty, support, indemnity, or liability obligation is offered by+You alone, and You hereby agree to indemnify every Contributor for any+liability incurred by such Contributor as a result of warranty, support,+indemnity or liability terms You offer. You may include additional+disclaimers of warranty and limitations of liability specific to any+jurisdiction.++4. Inability to Comply Due to Statute or Regulation+---------------------------------------------------++If it is impossible for You to comply with any of the terms of this+License with respect to some or all of the Covered Software due to+statute, judicial order, or regulation then You must: (a) comply with+the terms of this License to the maximum extent possible; and (b)+describe the limitations and the code they affect. Such description must+be placed in a text file included with all distributions of the Covered+Software under this License. Except to the extent prohibited by statute+or regulation, such description must be sufficiently detailed for a+recipient of ordinary skill to be able to understand it.++5. Termination+--------------++5.1. The rights granted under this License will terminate automatically+if You fail to comply with any of its terms. However, if You become+compliant, then the rights granted under this License from a particular+Contributor are reinstated (a) provisionally, unless and until such+Contributor explicitly and finally terminates Your grants, and (b) on an+ongoing basis, if such Contributor fails to notify You of the+non-compliance by some reasonable means prior to 60 days after You have+come back into compliance. Moreover, Your grants from a particular+Contributor are reinstated on an ongoing basis if such Contributor+notifies You of the non-compliance by some reasonable means, this is the+first time You have received notice of non-compliance with this License+from such Contributor, and You become compliant prior to 30 days after+Your receipt of the notice.++5.2. If You initiate litigation against any entity by asserting a patent+infringement claim (excluding declaratory judgment actions,+counter-claims, and cross-claims) alleging that a Contributor Version+directly or indirectly infringes any patent, then the rights granted to+You by any and all Contributors for the Covered Software under Section+2.1 of this License shall terminate.++5.3. In the event of termination under Sections 5.1 or 5.2 above, all+end user license agreements (excluding distributors and resellers) which+have been validly granted by You or Your distributors under this License+prior to termination shall survive termination.++************************************************************************+*                                                                      *+*  6. Disclaimer of Warranty                                           *+*  -------------------------                                           *+*                                                                      *+*  Covered Software is provided under this License on an "as is"       *+*  basis, without warranty of any kind, either expressed, implied, or  *+*  statutory, including, without limitation, warranties that the       *+*  Covered Software is free of defects, merchantable, fit for a        *+*  particular purpose or non-infringing. The entire risk as to the     *+*  quality and performance of the Covered Software is with You.        *+*  Should any Covered Software prove defective in any respect, You     *+*  (not any Contributor) assume the cost of any necessary servicing,   *+*  repair, or correction. This disclaimer of warranty constitutes an   *+*  essential part of this License. No use of any Covered Software is   *+*  authorized under this License except under this disclaimer.         *+*                                                                      *+************************************************************************++************************************************************************+*                                                                      *+*  7. Limitation of Liability                                          *+*  --------------------------                                          *+*                                                                      *+*  Under no circumstances and under no legal theory, whether tort      *+*  (including negligence), contract, or otherwise, shall any           *+*  Contributor, or anyone who distributes Covered Software as          *+*  permitted above, be liable to You for any direct, indirect,         *+*  special, incidental, or consequential damages of any character      *+*  including, without limitation, damages for lost profits, loss of    *+*  goodwill, work stoppage, computer failure or malfunction, or any    *+*  and all other commercial damages or losses, even if such party      *+*  shall have been informed of the possibility of such damages. This   *+*  limitation of liability shall not apply to liability for death or   *+*  personal injury resulting from such party's negligence to the       *+*  extent applicable law prohibits such limitation. Some               *+*  jurisdictions do not allow the exclusion or limitation of           *+*  incidental or consequential damages, so this exclusion and          *+*  limitation may not apply to You.                                    *+*                                                                      *+************************************************************************++8. Litigation+-------------++Any litigation relating to this License may be brought only in the+courts of a jurisdiction where the defendant maintains its principal+place of business and such litigation shall be governed by laws of that+jurisdiction, without reference to its conflict-of-law provisions.+Nothing in this Section shall prevent a party's ability to bring+cross-claims or counter-claims.++9. Miscellaneous+----------------++This License represents the complete agreement concerning the subject+matter hereof. If any provision of this License is held to be+unenforceable, such provision shall be reformed only to the extent+necessary to make it enforceable. Any law or regulation which provides+that the language of a contract shall be construed against the drafter+shall not be used to construe this License against a Contributor.++10. Versions of the License+---------------------------++10.1. New Versions++Mozilla Foundation is the license steward. Except as provided in Section+10.3, no one other than the license steward has the right to modify or+publish new versions of this License. Each version will be given a+distinguishing version number.++10.2. Effect of New Versions++You may distribute the Covered Software under the terms of the version+of the License under which You originally received the Covered Software,+or under the terms of any subsequent version published by the license+steward.++10.3. Modified Versions++If you create software not governed by this License, and you want to+create a new license for such software, you may create and use a+modified version of this License if you rename the license and remove+any references to the name of the license steward (except to note that+such modified license differs from this License).++10.4. Distributing Source Code Form that is Incompatible With Secondary+Licenses++If You choose to distribute Source Code Form that is Incompatible With+Secondary Licenses under the terms of this version of the License, the+notice described in Exhibit B of this License must be attached.++Exhibit A - Source Code Form License Notice+-------------------------------------------++  This Source Code Form is subject to the terms of the Mozilla Public+  License, v. 2.0. If a copy of the MPL was not distributed with this+  file, You can obtain one at http://mozilla.org/MPL/2.0/.++If it is not possible or desirable to put the notice in a particular+file, then You may include the notice in a location (such as a LICENSE+file in a relevant directory) where a recipient would be likely to look+for such a notice.++You may add additional accurate notices of copyright ownership.++Exhibit B - "Incompatible With Secondary Licenses" Notice+---------------------------------------------------------++  This Source Code Form is "Incompatible With Secondary Licenses", as+  defined by the Mozilla Public License, v. 2.0.
+ Makefile view
@@ -0,0 +1,28 @@+# For convenience+#++.PHONY: test clean accept doc hlint ghcid weed++all: test doc++test:+	 cabal test --test-show-details=streaming++clean:+	cabal clean++accept:+	cabal run smuggler2-test -- --accept++doc:+	cabal haddock++hlint:+	hlint src test/Test.hs++ghcid:+	ghcid++weed:+	cabal build+	weeder
+ README.md view
@@ -0,0 +1,309 @@+# smuggler2++<!--+[![Hackage](https://img.shields.io/hackage/v/smuggler.svg?logo=haskell)](https://hackage.haskell.org/package/smuggler2)+[![Build](https://img.shields.io/travis/kowainik/smuggler.svg?logo=travis)](http://travis-ci.org/kowainik/smuggler)+-->++[![MPL-2.0 license](https://img.shields.io/badge/license-MPL--2.0-blue.svg)](https://github.com/jrp2014/smuggler2/blob/master/LICENSE)+![Smuggler2](https://github.com/jrp2014/smuggler2/workflows/Smuggler2/badge.svg)+[![Build Status](https://travis-ci.com/jrp2014/smuggler2.svg?branch=master)](https://travis-ci.com/jrp2014/smuggler2)++Smuggler2 is a Haskell GHC Source Plugin that rewrites module imports (to produce a+minimal set) and adds or replaces explicit exports automatically.++This may make code easier to read because the provenance of imported+names is explcit. While writing code, it may be convenient to import a complete+module (by not specifiying what is to be imported from it) and then get+Smuggler2 to limit the import to include only the names that are used.++By default, all values, types and classes defined in a module+are exported (excluding those that are imported). Smuggler2 can generate+the code for that maximalist export list, for hand pruning. (It does not check+whether an exported name is used.) Limiting exports may make it easier for+`ghc` to optimise some code.++## How to use++### Adding Smuggler2 to your dependencies++Add `smuggler2` to the dependencies of your project and to your compiler+flags. For example, you could include in your project `cabal` file something+like++```Cabal+flag smuggler2+  description: Rewrite sources to cleanp imports, and create explicit exports+  exports+  default:     False+  manual:      True++common smuggler-options+  if flag(smuggler2)+    ghc-options: -fplugin:Smuggler2.Plugin+    build-depends: smuggler2 >= 0.3+```++and the `import: smuggler-options` in the appropriate `library` or `executable` sections.++The use of the flag allows you to build with or without source processing. Eg,++```bash+$ cabal build -fsmuggler2+```++using the example above.++You might use this approach to refine your imports or get a starting point for your+exports, but not rewrite them every time you compile. The use of a flag means+that you can also exclude `smuggler2` dependencies from your final builds.++### Alternatively, using a local version++If you have installed `smuggler2` from a local copy of this repository,+you may only need to add `-package smuggler2` to your `ghc-options`.++```Cabal+common smuggler-options+  if flag(smuggler2)+    ghc-options: -fplugin:Smuggler2.Plugin --package smuggler22+```++(You may need to install from a local copy using `cabal v1-install` for `smuggler2`+to be recognised.)++### Options++`Smuggler2` has several (case-insensitive) options, which can be set by adding a+`-fplugin-opt=Smuggler2.Plugin:` to your `ghc-options`++- `NoImportProcessing` - do no import processing+- `PreserveInstanceImports` - remove unused imports, but preserve a library import stub.+  such as `import Mod ()`, to import only instances of typeclasses from it. (The default.)+- `MinimiseImports` - remove unused imports, including any that may be needed+  only to import typeclass instances. This may, therefore, stop the module from compiling.++- `NoExportProcessing` - do no export processing+- `AddExplicitExports` - add an explicit list of all available exports (excluding+  those that are imported) if there is no existing export list. (The default.)+  You may want to edit it to keep specific values, types or classes local to the module.+  At present, a typeclass and its class methods are exported individually. You may want to+  replace those exports with an abbreviation such as `C(..)`.+- `ReplaceExports` - replace any existing module export list with one containing all+  available exports (which, again, you can, of course, then prune to your requirements).++Any other option value is used to generate a source file with a new extension of+the option value (`new` in the following example) rather than replacing the original file.++```Cabal+    ghc-options: -fplugin:Smuggler2.Plugin -fplugin-opt=Smuggler2.Plugin:new+```++This will create output files with a `.new` suffix rather the overwriting the originals.++Smuggler2 tries not to perform file changes when+there are no unused imports or exports to be added or replaced.+So you can just run `ghcid` as usual:++```bash+ghcid --command='cabal repl'+```++If you add `-v` to your `ghc-options`++## Caveats++`Smugggler2` is robust -- it can chew through the+[agda](https://github.com/agda/agda) codebase of over 370 modules with complex+interdependencies and be tripped over by only+- a handful of pattern synonym imports,+- a couple of ambiguous exports (are we trying to export something+defined in the current module or something with the same name from an imported+module)+- and a couple of imports where both qualifed and unqualifed version of the+  module are imported and there are references to both qualified and unqualifed+  version of the same names++But there are some caveats, most of which are either easy enough to work around+(and still benefit from a great reduction in keyboard work):+- `Smuggler2` rewrites the existing imports, rather than attempting to prune+  them. (This is a more aggressive approach than `smuggler` which focuses on+  removing redundant imports.) It has advantages and disadvantages. The+  advantage is that a minimal set of imports is generated in a reproducable format.+  The disdvantage is that imports may be reordered, comments and blankdropped, external+  imports mixed with external, etc.++- By default `Smuggler2` does not remove imports completely because an import may be being+  used to only import instances of typeclasses, So it will leave stubs like++  ```haskell+  import Mod ()+  ```++  that you may want to remove manually. Alternatively use the `MinimiseImports` option to+  remove them anyway, at the risk of producing code that fails to compile.++- CPP files will not be processed correctly: the imports will be generated for+  current CPP settings and any CPP annotations in the import block will be+  discarded. This may be a particular problem if you are writing code for+  several generations of `ghc` and `base` for example.+  [retrie](https://github.com/facebookincubator/retrie/blob/master/Retrie/CPP.hs)+  solves this problem generating all possible versions of the module+   (exponential in the number of `#if` directives), operating on each version+   individually, and splicing results back into the original file. A tour de+   force!++- `smuggler2` depends on the current `ghc` compiler and `base` library to check+  whether an import is redundant. Earlier versions of the compiler may, of+  course, need it. The [base library+  changelog](https://hackage.haskell.org/package/base/changelog) provides some+  details of what was made available when.++- Multiple separate import lines referring to the same library are not+  consolidated++- Literate Haskell `lhs` files are not supported++- `hiding` clauses may not be properly analysed.  So hiding things that are not+  used may not be spotted.++- Certain syntax pattern imports may not be imported correctly (the `pattern`+  keyword is missing)++- The test suite does not seem to run reliably on Windows. This is probably+  more of an issue with the way that the tests are run, than `Smuggler2` itself.++- Currently `cabal` does not have a particular way of specifying plugins.+  (See, eg, https://gitlab.haskell.org/ghc/ghc/issues/11244 and+  https://github.com/haskell/cabal/issues/2965) which would allow cleaner+  separation of user code and plugin-code++## For contributors++Requirements:++- `ghc-8.6.5`, `ghc-8.8.3` and `ghc-8.10.1`: `Smuggler2` will not compile with earlier versions.+- The test golden values are for `ghc-8.10.1` and `ghc-8.8.3`. Some of them fail on `ghc-8.6.5`+  because it seems to need to import `Data.Bool` whereas later versions of GHC don't. The results+  compile on `ghc-8.6.5` and later anyway, but the imports are not as minimal+  for later versions as they could be.+- `cabal >= 3.0` (ideally `3.2`)++### How to build++```shell+$ cabal update+$ cabal build+```++To build with debugging:++```shell+$ cabal bulid -fdebug+```++Curently this just adds an `-fdump-minimal-imports` parameter to GHC+compilation.++### How to run tests++There is a `tasty-golden`-based test suite that can be run by++```shell+$ cabal test smuggler-test --enable-tests+```++Further help can be found by++```shell+$ cabal run smuggler-test -- --help+```++(note the extra `--`)++For example, if you are running on `ghc-8.6.5` you can++```shell+$ cabal run smuggler2-test -- --accept+```++to update the golden outputs to the current results of (failing) tests.++It is sometimes necessary to run `cabal clean` before running tests to ensure+that old build artefacts do not lead to misleading results.++`smuggler-test` uses `cabal exec ghc` internally to run a test. The `cabal` command+that is to be used to do that can be set using the `CABAL` environment variable.+This may be helpful for certain workflows where `cabal` is not in the current+path, or you want to add extra flags to the `cabal` command.++The test suite does not run reliably on Windows++## Implementation approach++`smuggler2` uses the `ghc-exactprint`+[library](https://hackage.haskell.org/package/ghc-exactprint) to modiify the+source code. The documentation for the library is fairly spartan, and the+library is not widely used, so the use here can, no doubt, be optimised.++The library is needed because the annotated AST that GHC generates does not have enough+information to reconstitute the original source.+Some parts of the renamed syntax tree (for example, imports) are+not found in the typechecked one. `ghc-exactprint` provides parsers that+preserve this information, which is stored in a separate+`Anns` `Map` used to generate properly formatted source text.++To make manipulation of GHC's AST and `ghc-exactprint`'s `Anns` easier,+`ghc-exactprint` provides a set of Transform functions. These are intended to facilitate+making changes to the AST and adjusting the `Anns` to suit the changes.++> These functions are [said to be under heavy development](https://hackage.haskell.org/package/ghc-exactprint-0.6.3/docs/Language-Haskell-GHC-ExactPrint-Transform.html).+> It is not entirely obvious how they are intended to be used or composed. The+> approach provided by [`retrie`](https://hackage.haskell.org/package/retrie)+> wraps an AST and `Anns` into a single type that seems to make AST+> transformations easier to compose and reduces the risk of the `Anns` and AST getting+> out of sync as it is being transformed, something with which the type system doesn't+> help you since the `Anns` are stored as a `Map`.++### Imports++`smuggler2` uses GHC to generate a set of minimal imports. It++- parses the original file+- dumps the minimal exports that GHC generates and parses them back in (to pick+  up the annotations needed for printing)+- drops implicit imports (such as Prelude) and, optionally, imports that are+  for instances only+- replaces the original imports with minimal ones+- `exactPrint`s the result back over the original file (or one with a different+  suffix, if that was specified as option to `smuggler2`)++This round tripping is needed because the AST provided by GHC to `smuggler2`+is of a different type from the AST that `ghc-exactprint` uses. (It is the+product of the renaming phase of the compiler, while `ghc-exactprint` uses+a parse phase AST.)++### Exports++Exports are simpler to deal with as GHC's `exports_from_avail` does the work.++## Other projects++- Smuggler2 was is a rewrite of [`smuggler`](https://hackage.haskell.org/package/smuggler)+- `retrie` a [code modding tool](https://hackage.haskell.org/package/retrie)+  that works with GHC 8.10.1+- `refact-global-hse` an ambitious [import refactoring tool](https://github.com/ddssff/refact-global-hse).+  This uses `haskell-src-exts` rather than `ghc-exactprint` and so may not work with current versions of GHC.+- These blog posts contain some fragments on the topic of using `ghc-exactprint` to manipulate import lists+  [Terser import declarations](https://www.machinesung.com/scribbles/terser-import-declarations.html) and+  [GHC API](https://www.machinesung.com/scribbles/ghc-api.html) (The site+  doesn't always seem to be up.)++## Acknowledgements++Thanks to++- Dmitrii Kovanikov and Veronika Romashkina who wrote [`smuggler`](https://hackage.haskell.org/package/smuggler)+- Alan Zimmerman and Matthew Pickering for+  [`ghc-exactprint`](https://hackage.haskell.org/package/ghc-exactprint)+- The ghc authors who have made the compiler internals available through an API.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ TODO.md view
@@ -0,0 +1,14 @@+# Things to be done++- [ ] does hiding work?++- [ ] does CPP work? (should do)++- [ ] Script to generate new test (from template?)++- [ ] get tasty-golden to delete successful results and update tests where+  smuggler makes no changes++- [ ] refactor the much more sophisticated https://github.com/ddssff/refact-global-hse++- [ ] Do a better job of preserving comments
+ app/Main.hs view
@@ -0,0 +1,13 @@+module Main (+  main ) where++import Data.Bool ( bool )+import Data.List ()+import Data.Maybe ( fromMaybe )++main :: IO ()+main = do+    let foo = fromMaybe bool Nothing+    print (foo undefined undefined undefined :: Int)+    print True+-- main = parseFile
+ smuggler2.cabal view
@@ -0,0 +1,145 @@+cabal-version:      3.0+name:               smuggler2+version:            0.3.2.1+synopsis:+  GHC Source Plugin that helps to minimise imports and generate explicit exports++description:+  == Usage+  Add @smuggler2@ to the build dependencies of your project.+  .+  Then add the following to ghc-options: @-fplugin=Smuggler2.Plugin@++homepage:           https://github.com/jrp2014/smuggler2+bug-reports:        https://github.com/jrp2014/smuggler2+license:            MPL-2.0+license-file:       LICENSE+author:             jrp2014, Dmitrii Kovanikov, Veronika Romashkina+maintainer:         jrp2014+copyright:          2020 jrp2014, Dmitrii Kovanikov+category:           Development, Refactoring, Compiler Plugin+build-type:         Simple+extra-doc-files:+  README.md+  CHANGELOG.md+  TODO.md++extra-source-files:+  test/tests/*.hs+  Makefile+  Setup.hs+  weeder.dhall++-- wildcards are allowed only in the basename+--test/tests/*.*-golden++tested-with:        GHC ==8.6.5 || ==8.8.3 || ==8.10.1++flag debug+  description: Enable debugging support+  default:     False+  manual:      True++flag threaded+  description: Build with support for multithreaded execution+  default:     True+  manual:      True++source-repository head+  type:     git+  location: https://github.com/jrp2014/smuggler++common common-options+  -- test these bounds+  build-depends:      base >=4.9 && <4.16++  -- -O2 gives a nearly 10% speed improvement+  ghc-options:+    -O2 -Wall -Wextra -Wincomplete-uni-patterns+    -Wincomplete-record-updates -Wcompat -Widentities+    -Wredundant-constraints -fhide-source-paths++  if impl(ghc >=8.10.0)+    ghc-options: -Wunused-packages -fwrite-ide-info++  if flag(debug)+    ghc-options: -ddump-minimal-imports+    cpp-options: -DDEBUG++  default-language:   Haskell2010++  -- Most of these are unnecessary+  default-extensions:+    DeriveGeneric+    GeneralizedNewtypeDeriving+    InstanceSigs+    LambdaCase+    OverloadedStrings+    RecordWildCards+    ScopedTypeVariables+    StandaloneDeriving+    TypeApplications++common executable-options+  ghc-options: -rtsopts++  if flag(debug)+    ghc-options: -debug++  if flag(threaded)+    ghc-options: -threaded -with-rtsopts=-N++library+  import:          common-options+  hs-source-dirs:  src+  exposed-modules:+    Smuggler2.Options+    Smuggler2.Anns+    Smuggler2.Parser+    Smuggler2.Plugin++  other-modules:+    Paths_smuggler2++  autogen-modules: Paths_smuggler2++  build-depends:+    , containers      ^>=0.6.0+    , directory       ^>=1.3.3+    , filepath        ^>=1.4.2+    , ghc             >=8.6.5 && <8.11+    , ghc-exactprint  ^>=0.6.3+    , syb             ^>=0.7.1++  if flag(debug)+    build-depends: text++-- Currently doesn't do anything much+executable play-smuggler2+  import:         common-options+  import:         executable-options+  hs-source-dirs: app+  main-is:        Main.hs++  if flag(debug)+    ghc-options: -fplugin=Smuggler2.Plugin++  build-depends:  smuggler2++test-suite smuggler2-test+  import:         common-options+  type:           exitcode-stdio-1.0+  hs-source-dirs: test+  build-depends:+    , filepath+    , ghc-paths+    , smuggler2+    , tasty+    , tasty-golden+    , typed-process++  main-is:        Test.hs+  ghc-options:    -rtsopts++  if flag(threaded)+    ghc-options: -threaded -with-rtsopts=-N
+ src/Smuggler2/Anns.hs view
@@ -0,0 +1,171 @@+{-|+ Description: Utility functions for transforming and manipulating 'ghc' AST elements+              and their associated 'ghc-exactprint' 'Language.Haskell.GHC.ExactPrint.Anns'+ -}+module Smuggler2.Anns+  ( mkExportAnnT,+    mkLoc,+    mkParenT,+    setAnnsForT,+    swapEntryDPT,+  )+where++import Avail (AvailInfo (..))+import Data.Generics as SYB (Data)+import qualified Data.Map.Strict as Map (alter, fromList, insert, lookup, toList, union)+import Data.Maybe (fromMaybe)+import GHC+  ( AnnKeywordId (AnnCloseP, AnnDotdot, AnnOpenP, AnnVal),+    GhcPs,+    IE (..),+    IEWrappedName (..),+    LIEWrappedName,+    Name,+    RdrName,+  )+import GhcPlugins (GenLocated (L), Located, getOccFS, getOccString, mkVarUnqual)+import Language.Haskell.GHC.ExactPrint+  ( Annotation (annEntryDelta, annPriorComments, annsDP),+    TransformT,+    modifyAnnsT,+    uniqueSrcSpanT,+  )+import Language.Haskell.GHC.ExactPrint.Types+  ( DeltaPos (..),+    KeywordId (G),+    annNone,+    mkAnnKey,+    noExt,+  )+import Lexeme (isLexSym)++-- Generates the annotations for a name, wrapping () around symbollic names+mkLIEName :: Monad m => Name -> TransformT m (LIEWrappedName RdrName)+mkLIEName name = do+  let nameFS = getOccFS name+  let ann =+        if isLexSym nameFS -- infix type or data constructor / identifier+          then [(G AnnOpenP, DP (0, 0)), (G AnnVal, DP (0, 0)), (G AnnCloseP, DP (0, 0))]+          else [(G AnnVal, DP (0, 0))]+  lname <-+    mkLocWithAnns+      (mkVarUnqual nameFS)+      (DP (1, 2)) -- drop downn a line and indent 2 spaces+      ann+  mkLoc (IEName lname)++-- | Uses 'AvailInfo' about an exportable thing to generate the corresponding+-- piece of (annotated) AST+mkExportAnnT :: Monad m => AvailInfo -> TransformT m (Located (IE GhcPs))+-- Ordinary identifier+mkExportAnnT (Avail name) = do+  liename <- mkLIEName name+  mkLoc (IEVar noExt liename)++-- A type or class.  Since we expect @name@ to be in scope, it should be the head+-- of @names@+mkExportAnnT (AvailTC name names fieldlabels) = do+  liename <- mkLIEName name++  -- Could export pieces explicitly, but this becomes ugly;+  -- operators need to be wrapped in (), etc, so just export things+  -- with pieces by wildcard+  let lienameWithWildcard =+        mkLocWithAnns+          (IEThingAll noExt liename)+          (DP (0, 0))+          [(G AnnOpenP, DP (0, 0)), (G AnnDotdot, DP (0, 0)), (G AnnCloseP, DP (0, 0))]++  case (names, fieldlabels) of+    -- This case implies that the type or class is not to be in scope+    -- which should not happen as we should only be processing exportable things+    -- Alternativey, could just: mkLoc (IEThingAbs noExt liename)+    ([], _) ->+      error $+        "smuggler: trying to export type class that is not to be in scope "+          ++ getOccString name+    -- A type class with no pieces+    ([_typeclass], []) -> mkLoc (IEThingAbs noExt liename)+    -- A type class with no pieces, but with field selectors.  A record type?+    ([_typeclass], _fl) -> lienameWithWildcard+    -- A type class with pieces+    (typeorclass : _pieces, _fl) ->+      if name == typeorclass -- check AvailTC invariant+        then+            lienameWithWildcard+        else+          error $+            "smuggler: broken AvailTC invariant: "+              ++ getOccString name+              ++ "/="+              ++ getOccString typeorclass+++-------------------------------------------------------------------------------+-- Inspired by retrie++-- | Generates a unique location and wraps the given ast chunk with that location+-- Also adds a DP and an annotation at that location+mkLocWithAnns :: (Data e, Monad m) => e -> DeltaPos -> [(KeywordId, DeltaPos)] -> TransformT m (Located e)+mkLocWithAnns e dp anns = do+  le <- L <$> uniqueSrcSpanT <*> pure e+  setAnnsForT le dp anns++-- | `mkLoc` generates a unique location and wraps the given ast chunk with that location+-- Also adds an empty annotation at that location+mkLoc :: (Data e, Monad m) => e -> TransformT m (Located e)+mkLoc e = mkLocWithAnns e (DP (0, 0)) []++-- | Add an open and close paren annotation to a located thing+mkParenT ::+  (Data x, Monad m) =>+  (Located x -> x) ->+  Located x ->+  TransformT m (Located x)+mkParenT k e = do+  pe <- mkLoc (k e)+  _ <- setAnnsForT pe (DP (0, 0)) [(G GHC.AnnOpenP, DP (0, 1)), (G GHC.AnnCloseP, DP (0, 1))]+  swapEntryDPT e pe+  return pe++-- | Set the `ghc-exactprint` annotations for a 'Located' thing+setAnnsForT ::+  (Data e, Monad m) =>+  Located e ->+  DeltaPos ->+  [(KeywordId, DeltaPos)] ->+  TransformT m (Located e)+setAnnsForT e dp anns = modifyAnnsT (Map.alter f (mkAnnKey e)) >> return e+  where+    f Nothing = Just annNone {annEntryDelta = dp, annsDP = anns}+    f (Just a) =+      Just+        a+          { annEntryDelta = dp,+            annsDP =+              Map.toList $+                Map.union (Map.fromList anns) (Map.fromList (annsDP a))+          }++-- | Swap two 'Located' things' relative position tage ('DeltaPos')+swapEntryDPT ::+  (Data a, Data b, Monad m) => Located a -> Located b -> TransformT m ()+swapEntryDPT a b = modifyAnnsT $ \anns ->+  let akey = mkAnnKey a+      bkey = mkAnnKey b+      aann = fromMaybe annNone $ Map.lookup akey anns+      bann = fromMaybe annNone $ Map.lookup bkey anns+   in Map.insert+        akey+        aann+          { annEntryDelta = annEntryDelta bann,+            annPriorComments = annPriorComments bann+          }+        $ Map.insert+          bkey+          bann+            { annEntryDelta = annEntryDelta aann,+              annPriorComments = annPriorComments aann+            }+          anns
+ src/Smuggler2/Options.hs view
@@ -0,0 +1,50 @@+{- |+ Description: handling of command line options++ -}+module Smuggler2.Options+  ( Options (..),+    parseCommandLineOptions,+    ImportAction (..),+    ExportAction (..),+  )+where++import Data.Char (toLower)+import Data.List (foldl')+import Plugins (CommandLineOption)++-- | Ways of performing import processing+data ImportAction = NoImportProcessing | PreserveInstanceImports | MinimiseImports+  deriving (Eq, Show)++-- | Ways of performing emport processing+data ExportAction = NoExportProcessing | AddExplicitExports | ReplaceExports+  deriving (Eq, Show)++-- | Internal representation of the plugin's command line options+data Options = Options+  { importAction :: ImportAction,+    exportAction :: ExportAction,+    newExtension :: Maybe String+  }+  deriving (Show)++-- | The default is to retain instance-only imports (eg, Data.List () )+-- and add explict exports only if they are not already present+defaultOptions :: Options+defaultOptions = Options PreserveInstanceImports AddExplicitExports Nothing++-- | Simple command line option parser.  Last occurrence wins.+parseCommandLineOptions :: [CommandLineOption] -> Options+parseCommandLineOptions = foldl' parseCommandLineOption defaultOptions+  where+    parseCommandLineOption :: Options -> CommandLineOption -> Options+    parseCommandLineOption opts clo = case toLower <$> clo of+      "noimportprocessing" -> opts {importAction = NoImportProcessing}+      "preserveinstanceimports" -> opts {importAction = PreserveInstanceImports}+      "minimiseimports" -> opts {importAction = MinimiseImports}+      "noexportprocessing" -> opts {exportAction = NoExportProcessing}+      "addexplicitexports" -> opts {exportAction = AddExplicitExports}+      "replaceexports" -> opts {exportAction = ReplaceExports}+      _ -> opts {newExtension = Just clo}
+ src/Smuggler2/Parser.hs view
@@ -0,0 +1,48 @@+{-|+ Description: provides a wrapper around the 'ghc-exactprint' parser+ -}+{-# LANGUAGE CPP #-}++module Smuggler2.Parser+  ( runParser,+  )+where++import Control.Monad.IO.Class (MonadIO (liftIO))+import DynFlags (DynFlags, GeneralFlag (Opt_KeepRawTokenStream), gopt_set)+import GHC (ParsedSource)+import Language.Haskell.GHC.ExactPrint (Anns)+import Language.Haskell.GHC.ExactPrint.Parsers (parseModuleFromStringInternal)+#if MIN_VERSION_GLASGOW_HASKELL(8,10,1,0)+import ErrUtils (fatalErrorMsg, printBagOfErrors)+import Outputable (text)+#else+import ErrUtils (fatalErrorMsg)+import Outputable (ppr, showSDoc, text)+#endif+import TcRnTypes (RnM)++-- | Wrapper around the 'ghc-exactprint' parser.  Prints diagnostics for failed parses+-- (which should never happen). We need to use+-- 'Language.Haskell.GHC.ExactPrint.parseModuleFromStringInternal'+-- because 'Language.Haskell.GHC.ExactPrintparseModuleFromString'+-- doesn't pick up the correct 'DynFlags' in -- some cases.+runParser ::+  DynFlags -> FilePath -> String -> RnM (Either () (Anns, ParsedSource))+runParser dflags fileName fileContents = do+  -- Withoout the following, comments are stripped (see #10942)+  -- It would be more efficient, but less visible to apply this tweak at the+  -- outset, in the main plugin function, but keep it here for visibility+  let dflags' = dflags `gopt_set` Opt_KeepRawTokenStream++  case parseModuleFromStringInternal dflags' fileName fileContents of+    Left msg -> liftIO $ do+#if MIN_VERSION_GLASGOW_HASKELL(8,10,1,0)+      fatalErrorMsg dflags (text "smuggler parse failure:")+      printBagOfErrors dflags msg+#else+      fatalErrorMsg dflags (text $ "smuggler parse failure: " +++                            showSDoc dflags (ppr $ fst msg) ++ ": " ++ snd msg)+#endif+      return $ Left ()+    Right x -> return $ Right x
+ src/Smuggler2/Plugin.hs view
@@ -0,0 +1,300 @@+{-|+ Description: the core of 'smuggler2'+ -}+{-# LANGUAGE LambdaCase #-}++module Smuggler2.Plugin+  ( plugin,+  )+where++import Avail (AvailInfo, Avails)+import Control.Monad (unless)+import Data.Maybe (fromMaybe, isNothing)+import Data.Version (showVersion)+import DynFlags (DynFlags (dumpDir), HasDynFlags (getDynFlags))+import ErrUtils (compilationProgressMsg, fatalErrorMsg)+import GHC+  ( GenLocated (L),+    GhcPs,+    HsModule (hsmodExports, hsmodImports),+    ImportDecl (ideclHiding, ideclImplicit),+    LIE,+    LImportDecl,+    Located,+    ModSummary (ms_hspp_buf, ms_hspp_file, ms_mod),+    Module (moduleName),+    ParsedSource,+    moduleNameString,+    unLoc,+  )+import GHC.IO.Encoding (setLocaleEncoding, utf8)+import IOEnv (MonadIO (liftIO), readMutVar)+import Language.Haskell.GHC.ExactPrint+  ( Anns,+    TransformT,+    addTrailingCommaT,+    exactPrint,+    graftT,+    runTransform,+    setEntryDPT,+  )+import Language.Haskell.GHC.ExactPrint.Types (DeltaPos (DP))+import Outputable (Outputable (ppr),  neverQualify, printForUser, text, vcat)+import Paths_smuggler2 (version)+import Plugins+  ( CommandLineOption,+    Plugin (pluginRecompile, typeCheckResultAction),+    defaultPlugin,+    purePlugin,+  )+import RnNames (ImportDeclUsage, findImportUsage, getMinimalImports)+import Smuggler2.Anns (mkExportAnnT, mkLoc, mkParenT)+import Smuggler2.Options+  ( ExportAction (AddExplicitExports, NoExportProcessing, ReplaceExports),+    ImportAction (MinimiseImports, NoImportProcessing),+    Options (exportAction, importAction, newExtension),+    parseCommandLineOptions,+  )+import Smuggler2.Parser (runParser)+import StringBuffer (StringBuffer (StringBuffer), lexemeToString)+import System.Directory (removeFile)+import System.FilePath ((-<.>), (</>))+import System.IO (IOMode (WriteMode), withFile)+import TcRnExports (exports_from_avail)+import TcRnTypes+  ( RnM,+    TcGblEnv (tcg_exports, tcg_imports, tcg_mod, tcg_rdr_env, tcg_rn_exports, tcg_rn_imports, tcg_used_gres),+    TcM,+  )++-- | 'Plugin' interface to GHC+plugin :: Plugin+plugin =+  defaultPlugin+    { typeCheckResultAction = smugglerPlugin,+      pluginRecompile = purePlugin -- Don't force recompilation.  [Is this the right approach?]+    }++-- | The plugin itself+smugglerPlugin :: [CommandLineOption] -> ModSummary -> TcGblEnv -> TcM TcGblEnv+smugglerPlugin clopts modSummary tcEnv+  -- short circuit, if nothing to do+  | (importAction options == NoImportProcessing)+      && (exportAction options == NoExportProcessing) =+    return tcEnv+  | otherwise = do+    -- Get the imports and their usage+    let imports = tcg_rn_imports tcEnv+    uses <- readMutVar $ tcg_used_gres tcEnv+    let usage = findImportUsage imports uses++    -- This ensures that the source file is not touched if there are no unused+    -- imports, or exports already exist and we are not replacing them+    let noUnusedImports = all (\(_decl, _used, unused) -> null unused) usage+    let hasExplicitExports = case tcg_rn_exports tcEnv of+          Nothing -> False -- There is not even a module header+          (Just []) -> False+          (Just _) -> True+    -- ... so short circuit if:+    -- - we are skipping import processing or there are no unused imports, and+    -- - we are skipping export processing or there are explict exports and we are not replacing them+    -- (This is not a complete check; ideally, that the new imp/exports are+    -- different from the existing ones, etc)+    if (importAction options == NoImportProcessing || noUnusedImports) &&+       (     exportAction options == NoExportProcessing+         || (hasExplicitExports && exportAction options /= ReplaceExports)+       )+      then return tcEnv+      else do+        dflags <- getDynFlags+        liftIO $ compilationProgressMsg dflags ("smuggler: " ++ showVersion version)++        -- Dump GHC's view of what the minimal imports are for the current+        -- module, so that they can be annotated when parsed back in+        -- This is needed because 'getMinimalImports` returns a list of import+        -- declarations that are `GhcRn` but `ghc-exactPrint` operates in+        -- `GhcPs`+        let minImpFilePath = mkMinimalImportsPath dflags (ms_mod modSummary)+        printMinimalImports' dflags minImpFilePath usage++        -- Run smuggling only for its side effects+        tcEnv <$ smuggling dflags minImpFilePath++  where++    -- Does all the work+    smuggling :: DynFlags -> FilePath -> RnM ()+    smuggling dflags minImpFilePath = do++      let modulePath = ms_hspp_file modSummary++      -- Read files as UTF-8 strings (GHC accepts only ASCII or UTF-8)+      liftIO $ setLocaleEncoding utf8++      -- Get the pre-processed module source code+      let modFileContents = case ms_hspp_buf modSummary of+            -- Not clear under what circumstances this could happen+            Nothing -> error $ "smuggler: missing source file: " ++ modulePath+            Just contents -> strBufToStr contents++      -- Parse the whole module+      runParser dflags modulePath modFileContents >>= \case+        Left () -> return () -- do nothing if file is invalid Haskell+        Right (annsHsMod, astHsMod) -> do+          -- Read the dumped file of minimal imports+          minImpFileContents <- liftIO $ readFile minImpFilePath++          -- Parse the minimal imports file -- gets the annnotations too+          runParser dflags minImpFilePath minImpFileContents >>= \case+            Left () ->+              liftIO $+                fatalErrorMsg dflags (text $ "smuggler: failed to parse minimal imports from " ++ minImpFilePath)+            Right (annsImpMod, L _ impMod) -> do++              -- The actual minimal imports themselves, as generated by GHC+              let minImports = hsmodImports impMod++              -- What is exported by the module+              exports <-+                if exportAction options == ReplaceExports+                  then exportable+                  else return $ tcg_exports tcEnv -- what is currently exported++              -- Bringing it all together: generate a new ast and annotations for it+              let (astHsMod', (annsHsMod', _locIndex), _log) =+                    runTransform annsHsMod $+                      replaceImports annsImpMod minImports astHsMod+                        >>= addExplicitExports exports++              -- Print the result+              let newContent = exactPrint astHsMod' annsHsMod'+              liftIO $ case newExtension options of+                Nothing -> writeFile modulePath newContent+                Just ext -> writeFile (modulePath -<.> ext) newContent++            -- Clean up: delete the GHC-generated imports file+              liftIO $ removeFile minImpFilePath++          where++            -- Generates the things that would be exportabe if there were no+            -- explict export header, so suitable for replacing one+            exportable :: RnM [AvailInfo]+            exportable = do+              let rdr_env = tcg_rdr_env tcEnv+              let imports = tcg_imports tcEnv --  actually not needed for the Nothing case+              let this_mod = tcg_mod tcEnv+              exports <- exports_from_avail Nothing rdr_env imports this_mod+              return (snd exports)++            --  | Replace a target module's imports+            --  See <https://github.com/facebookincubator/retrie/blob/master/Retrie/CPP.hs>+            replaceImports ::+              Monad m =>+              -- | annotations for the replacement imports+              Anns ->+              -- | the replacement imports+              [LImportDecl GhcPs] ->+              -- | target module+              ParsedSource ->+              TransformT m ParsedSource+            replaceImports anns minImports t@(L l m) =+              case importAction options of+                NoImportProcessing -> return t+                _ -> do+                  -- This does all the work+                  -- retrie has a neat `insertImports' function that also+                  -- deduplicates+                  imps <- graftT anns minImports+                  -- nudge down the imports list onto a new line+                  unless (null imps) $ setEntryDPT (head imps) (DP (2, 0))+                  return $ L l m {hsmodImports = imps}++            -- | Add explict exports to the target module+            addExplicitExports ::+              Monad m =>+              -- | The list of exports to be added+              Avails ->+              -- | target module+              ParsedSource ->+              TransformT m ParsedSource+            addExplicitExports exports t@(L astLoc hsMod) =+              case exportAction options of+                NoExportProcessing -> return t+                AddExplicitExports ->+                  -- only add explicit exports if there are none+                  -- seems to work even if there is no explict module declaration+                  -- presumably because the annotations that we generate are just+                  -- unused by exactPrint+                  if isNothing currentExplicitExports then result else return t+                ReplaceExports -> result+              where+                currentExplicitExports :: Maybe (Located [LIE GhcPs])+                currentExplicitExports = hsmodExports hsMod++                -- This does all the export replacement work+                result :: Monad m => TransformT m ParsedSource+                result+                  | null exports = return t -- there is nothing exportable+                  | otherwise = do+                    -- Generate the exports list+                    exportsList <- mapM mkExportAnnT exports+                    -- add commas in between and parens around+                    mapM_ addTrailingCommaT (init exportsList)+                    lExportsList <- mkLoc exportsList >>= mkParenT unLoc++                    -- No need to do any graftTing here as we have been modifying the+                    -- annotations in the current transformation+                    return $ L astLoc hsMod {hsmodExports = Just lExportsList}++    -- | This version of the GHC function ignores implicit imports, as they+    -- cannot be parsed back in.  (There is an extraneous (implicit))+    -- It also provides for leaving out instance-only imports (eg, Data.List() )+    printMinimalImports' :: DynFlags -> FilePath -> [ImportDeclUsage] -> RnM ()+    printMinimalImports' dflags filename imports_w_usage =+      do+        imports' <- getMinimalImports imports_w_usage+        liftIO $+          withFile+            filename+            WriteMode+            ( \h ->+                -- The neverQualify is important.  We are printing Names+                -- but they are in the context of an 'import' decl, and+                -- we never qualify things inside there+                -- E.g.   import Blag( f, b )+                -- not    import Blag( Blag.f, Blag.g )!+                printForUser dflags h neverQualify (vcat (map ppr (filter (letThrough . unLoc) imports')))+            )+      where+        notImplicit :: ImportDecl pass -> Bool+        notImplicit = not . ideclImplicit++        notInstancesOnly :: ImportDecl pass -> Bool+        notInstancesOnly i = case ideclHiding i of+          Just (False, L _ []) -> False+          _ -> True++        keepInstanceOnlyImports :: Bool+        keepInstanceOnlyImports = importAction options /= MinimiseImports++        letThrough :: ImportDecl pass -> Bool+        letThrough i = notImplicit i && (keepInstanceOnlyImports || notInstancesOnly i)++    -- Construct the path into which GHC's version of minimal imports is dumped+    mkMinimalImportsPath :: DynFlags -> Module -> FilePath+    mkMinimalImportsPath dflags this_mod+      | Just d <- dumpDir dflags = d </> basefn+      | otherwise = basefn+      where+        basefn =+          "smuggler-" ++ moduleNameString (moduleName this_mod) ++ "."+            ++ fromMaybe "smuggler" (newExtension options)+            ++ ".imports"++    options :: Options+    options = parseCommandLineOptions clopts++    strBufToStr :: StringBuffer -> String+    strBufToStr sb@(StringBuffer _ len _) = lexemeToString sb len
+ test/Test.hs view
@@ -0,0 +1,113 @@+{-# LANGUAGE OverloadedStrings #-}++module Main where++import Data.Maybe (fromMaybe)+import GHC.Paths (ghc)+import Smuggler2.Options (ExportAction (..), ImportAction (..), Options (..))+import System.Environment (lookupEnv)+import System.FilePath ((-<.>), (</>), takeBaseName)+import System.Process.Typed+  ( ProcessConfig,+    proc,+    runProcess_,+    setEnvInherit,+    setWorkingDirInherit,+  )+import Test.Tasty (TestTree, defaultMain, testGroup)+import Test.Tasty.Golden (findByExtension, goldenVsFileDiff, writeBinaryFile)++-- | Where the tests are, relative to the project level cabal file+testDir :: FilePath+testDir = "test" </> "tests"++-- | Combinations of import and export action options to be tested+optionsList :: [Options]+optionsList =+  [ mkOptions PreserveInstanceImports NoExportProcessing,+    mkOptions MinimiseImports ReplaceExports,+    mkOptions MinimiseImports NoExportProcessing,+    mkOptions NoImportProcessing AddExplicitExports,+    mkOptions NoImportProcessing NoExportProcessing,+    mkOptions NoImportProcessing ReplaceExports+  ]+  where+    mkOptions :: ImportAction -> ExportAction -> Options+    mkOptions ia ea = Options ia ea (Just $ mkExt ia ea)++-- | Make an extention for an output file+mkExt :: ImportAction -> ExportAction -> String+mkExt ia ea = show ia ++ show ea -- ++ "-" ++ takeFileName ghc++-- | Generate test for a list of 'Options' each of which specify what action to+-- take on imports and exports+testOptions :: [Options] -> IO TestTree+testOptions opts =+  testGroup "All" <$> sequenceA (goldenTests <$> opts)++-- | Generate tests for a set of 'Options' (that specifies what to do to+-- imports and exports)+goldenTests :: Options -> IO TestTree+goldenTests opts = do+  testFiles <- findByExtension [".hs"] testDir+  return $+    testGroup+      testName+      [ goldenVsFileDiff+          (takeBaseName testFile) -- test name+          (\ref new -> ["git", "diff", "--no-index", ref, new]) -- how to display diffs+          (testFile -<.> testName ++ "-golden") -- golden file+          outputFilename+          ( do+              -- Write a default output file for those tests where smuggler+              -- (deliberately) does not generate a new one+              writeBinaryFile outputFilename "Source file was not touched\r\n"+              compile testFile opts+          )+        | testFile <- testFiles,+          let outputFilename = testFile -<.> testName+      ]+  where+    testName = fromMaybe "NoNewExtension" (newExtension opts)++-- | Just run all the tests+main :: IO ()+main = defaultMain =<< testOptions optionsList++-- | Use `cabal exec` to run the compilation, so that the smuggler plugin is+-- picked up from the local database.  GHC alone would use the global one.+compile :: FilePath -> Options -> IO ()+compile testcase opts = do+  cabalPath <- lookupEnv "CABAL" -- find, eg, @/opt/ghc/bin/cabal@ or @cabal -vnormal+nowrap@+  let cabalCmd = words $ fromMaybe "cabal" cabalPath -- default to @cabal@ if @CABAL@ is not set+  let cabalConfig =+        setWorkingDirInherit . setEnvInherit $+          proc (head cabalCmd) (tail cabalCmd ++ cabalArgs) :: ProcessConfig () () ()+  runProcess_ cabalConfig+  where+    cabalArgs :: [String]+    cabalArgs =+      -- - not sure why it is necessary to mention the smuggler2 package explicitly,+      --   but it appears to be hidden otherwise.+      -- - This puts the .imports files that smuggler generates somewhere they+      --   can easily be found+      [ "--with-compiler=" ++ ghc,+        "exec",+        ghc,+        "--",+        "-package smuggler2",+        "-v0",+        "-dumpdir=" ++ testDir,+        "-fno-code",+        "-fplugin=Smuggler2.Plugin"+      ]+        ++ map+          ("-fplugin-opt=Smuggler2.Plugin:" ++)+          ( let ia = importAction opts+                ea = exportAction opts+                p = [show ia, show ea]+             in case newExtension opts of+                  Nothing -> mkExt ia ea : p -- provide a default extension+                  Just e -> e : p+          )+        ++ [testcase]
+ test/tests/Bare.hs view
@@ -0,0 +1,4 @@++import Data.List++main = undefined
+ test/tests/BareHello.hs view
@@ -0,0 +1,3 @@+module BareHello where++main = putStrLn "Hello, World"
+ test/tests/BoolBoolUnused.hs view
@@ -0,0 +1,3 @@+module Test.BoolBoolUnused where++import Data.Bool (bool)
+ test/tests/BoolBoolUsed.hs view
@@ -0,0 +1,5 @@+module Test.BoolBoolUsed where++import Data.Bool (bool)++func = bool
+ test/tests/BoolPartlyUsedFirst.hs view
@@ -0,0 +1,5 @@+module Test.BoolPartlyUsedFirst where++import Data.Bool (bool, not)++foo = bool
+ test/tests/BoolPartlyUsedLast.hs view
@@ -0,0 +1,5 @@+module Test.BoolPartlyUsedLast where++import Data.Bool (bool, not)++foo = not
+ test/tests/Class.hs view
@@ -0,0 +1,42 @@+module Class where+++data Record a = Record { aa :: a,  b :: String }++data Impossible++  = Impossible  String Integer+    -- ^ We reached a program point which should be unreachable.++  | Unreachable String Integer+    -- ^ @Impossible@ with a different error message.+    --   Used when we reach a program point which can in principle+    --   be reached, but not for a certain run.++  | ImpMissingDefinitions [String] String+    -- ^ We reached a program point without all the required+    -- primitives or BUILTIN to proceed forward.+    -- @ImpMissingDefinitions neededDefs forThis@+++class CatchImpossible m where++  -- | Catch any 'Impossible' exception.+  catchImpossible :: m a -> (Impossible -> m a) -> m a+  catchImpossible = catchImpossibleJust Just++  -- | Catch only 'Impossible' exceptions selected by the filter.+  catchImpossibleJust :: (Impossible -> Maybe b) -> m a -> (b -> m a) -> m a+  catchImpossibleJust = flip . handleImpossibleJust++  -- | Version of 'catchImpossible' with argument order suiting short handlers.+  handleImpossible :: (Impossible -> m a) -> m a -> m a+  handleImpossible = flip catchImpossible++  -- | Version of 'catchImpossibleJust' with argument order suiting short handlers.+  handleImpossibleJust :: (Impossible -> Maybe b) -> (b -> m a) -> m a -> m a+  handleImpossibleJust = flip . catchImpossibleJust++  {-# MINIMAL catchImpossibleJust | handleImpossibleJust #-}++
+ test/tests/ConstructorsUnused.hs view
@@ -0,0 +1,5 @@+module Test.ConstructorsUnused where++import Data.Bool (Bool (True, False), not)++foo = not
+ test/tests/Dummy.hs view
@@ -0,0 +1,4 @@+module Test.Dummy where++main :: IO ()+main = pure ()
+ test/tests/Duplicated.hs view
@@ -0,0 +1,8 @@+module Duplicated where++import Data.Char (isDigit, isSpace)+import Data.Char (isPrint, isSpace)+import Data.Char (isLetter, isSpace)+++test x = isDigit x || isLetter x
+ test/tests/ExportExplicit.hs view
@@ -0,0 +1,10 @@+module Test.ExportExplicit (isExported, anotherExport) where++isExported = (+1)++anotherExport = True++andAnother = 27++main :: IO ()+main = pure ()
+ test/tests/ExportImplicit.hs view
@@ -0,0 +1,10 @@+module Test.ExportImplicit where++import Data.Char++isExported = (+1)++alsoExported = map toLower "AbC"++main :: IO ()+main = print $ isExported 1
+ test/tests/ImplicitImportUnused.hs view
@@ -0,0 +1,9 @@+module Test.ImplicitImportUnused where++import Data.List+import Data.Bool (not, (&&))+import Data.Maybe (fromMaybe, isJust, isNothing)++foo = isJust++bar = 27
+ test/tests/ImplicitImportUsed.hs view
@@ -0,0 +1,5 @@+module Test.ImplicitImportUsed where++import Data.List++foo = intercalate ","
+ test/tests/ImportEmpty.hs view
@@ -0,0 +1,3 @@+module Test.ImportEmpty where++import Data.Bool ()
+ test/tests/MoreExports.hs view
@@ -0,0 +1,20 @@+module MoreExports where++--Another comment+--++newtype N a = Int a++data D a = A a  | B | C a++data R = R { a :: Int, b :: Bool }++data H = I | J | K deriving Show+++foo = (+1)++-- A further  Comment++main :: IO ()+main = print J
+ test/tests/MultilineUnused.hs view
@@ -0,0 +1,10 @@+module Test.MultilineUnused where++import Data.Bool (bool)+import Data.List (break, cycle, drop, dropWhile, filter, genericDrop, genericLength,+                  genericReplicate, genericSplitAt, genericTake, group, inits, intercalate,+                  intersperse, isPrefixOf, iterate, map, permutations, repeat, replicate, reverse,+                  scanl, scanr, sort, sortBy, sortOn, splitAt, subsequences, tails, take, takeWhile,+                  transpose, unfoldr, unzip, unzip3, zip, zip3, zipWith, (++))++foo = bool
+ test/tests/Operator.hs view
@@ -0,0 +1,8 @@+module Operator where++infixl 5 $+$++($+$) :: [a] -> [a] -> [a]+[] $+$ d = d+d $+$ [] = d+d $+$ d' = d ++ d'
+ test/tests/QualUnqual.hs view
@@ -0,0 +1,6 @@+module QualUnqual where++import qualified Data.Char as C (isDigit, isLetter)+import Data.Char (isDigit, isLetter)++test x = C.isDigit x || isDigit x
+ test/tests/TypeConstructorUnused.hs view
@@ -0,0 +1,5 @@+module Test.TypeConstructorUnused where++import Data.Bool (Bool (False, True))++true = True
+ test/tests/TypeConstructorUnusedComma.hs view
@@ -0,0 +1,5 @@+module Test.TypeConstructorUnusedComma where++import Data.Bool (Bool (False, True))++false = False
+ test/tests/TypeConstructorUsed.hs view
@@ -0,0 +1,5 @@+module Test.TypeConstructorUsed where++import Data.Bool (Bool (True))++true = True
+ test/tests/TypeUnused.hs view
@@ -0,0 +1,5 @@+module Test.TypeUnused where++import Data.Bool (Bool, not)++foo = not
+ test/tests/TypeUsed.hs view
@@ -0,0 +1,6 @@+module Test.TypeUsed where++import Data.Bool (Bool, not)++foo :: Bool+foo = undefined
+ test/tests/WildcardUnused.hs view
@@ -0,0 +1,5 @@+module Test.WildcardUnused where++import Data.Bool (Bool (..), not)++foo = not
+ test/tests/WildcardUsed.hs view
@@ -0,0 +1,5 @@+module Test.WildcardUsed where++import Data.Bool (Bool (..))++foo = False
+ weeder.dhall view
@@ -0,0 +1,1 @@+{ roots = [ "^Smuggler2.Plugin.plugin$", "^Main.main$", "^Paths_smuggler2" ], type-class-roots = True }