Skip to content

Exactprint-based import list modification introduces extra newline when there is at least one newline between the module header and the topmost import #1274

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
konn opened this issue Jan 30, 2021 · 8 comments · Fixed by #1264
Labels
component: ghcide type: bug Something isn't right: doesn't work as intended, documentation is missing/outdated, etc..

Comments

@konn
Copy link
Collaborator

konn commented Jan 30, 2021

As found in PR #1264, it seems that exactprint-based import list modification seems to introduce an extra newline in certain situation.
Bisecting it in the master branch, this behaviour is first introduced in eb557b3.

Steps to reproduce

  1. Checkout eb557b3 and build ghcide the binary and make sure your LSP client will use it as a language server executable.

  2. Save the following as Lib.hs:

    module Lib where
    
    import Data.Vector ()
    
    theFun = fromList

    Make sure there is at least one newline between the module header and the import declaration; otherwise this bug won't occur.

  3. Open Lib.hs and wait for the module to be loaded.

  4. Apply Add fromList to Data.Vector code action.

Expected behaviour

fromList is added to the import list, without any modification to the lines except for import list itself, resulting in:

module Lib where

import Data.Vector (fromList)

theFun = fromList

Actual behaviour

An extra newline added between the module header and the import declaration:

module Lib where


import Data.Vector (fromList)

theFun = fromList

Note that there are two blank lines in-between instead of just one.

@berberman
Copy link
Collaborator

Thanks for your report. I extracted extendImport into a standalone project to check it without the help of ghcide, using exactPrint function to print the result.

I had a module A to be imported in module Test:

module A where

class C a where
  m1 :: a
  m2 :: a

I tried running extendImportViaParent df "C" "m1" in several scenarios:

  1. no empty lines between module declaration and the target import declaration
module Test where
import A ()

resulted in "\nimport A (C (m1))".

  1. one empty line
module Test where

import A ()

resulted in "\n\nimport A (C (m1))".

  1. two empty lines
module Test where


import A ()

resulted in "\n\n\nimport A (C (m1))".

  1. no empty lines, but there is another adjoining import declaration above
module Test where

import Data.Text
import A ()

resulted in "\nimport A (C (m1))".

It's possible to conclude that the number of unexpected line feeds in the result of exact print depends on how many empty lines between the previous declaration and this import declaration. I think tests didn't expose this error was because in ghcide's procedure, the first line feed was "eaten" somehow; for example, in this test case:

 testSession "extend single line import with value" $ template
            [("ModuleA.hs", T.unlines
                    [ "module ModuleA where"
                    , "stuffA :: Double"
                    , "stuffA = 0.00750"
                    , "stuffB :: Integer"
                    , "stuffB = 123"
                    ])]
            ("ModuleB.hs", T.unlines
                    [ "module ModuleB where"
                    , "import ModuleA as A (stuffB)"
                    , "main = print (stuffA, stuffB)"
                    ])
            (Range (Position 3 17) (Position 3 18))
            ["Add stuffA to the import list of ModuleA"]
            (T.unlines
                    [ "module ModuleB where"
                    , "import ModuleA as A (stuffB, stuffA)"
                    , "main = print (stuffA, stuffB)"
                    ])

there are no empty lines between module ModuleB where and import ModuleA as A (stuffB), so the result is exactly what we expect.

If you change your example to:

module Lib where
import Data.Vector ()

theFun = fromList

I think the result would be:

module Lib where
import Data.Vector (fromList)

theFun = fromList

Actually, I have encountered this issue when I was implementing haddock comments plugin, but unfortunately, I didn't figure out the real reason, and didn't know what was happening under the hood. So my workaround is to use T.strip :(

result <- T.strip . T.pack $ exactPrint locDecl anns' =

@konn
Copy link
Collaborator Author

konn commented Jan 30, 2021

Thank you for your detailed explanation!
So, for a tentative workaround, it seems that your T.strip usage seems reasonable to me. Though, in some cases in import lists, it might also eat proper indentation as well. So, perhaps T.dropWhile (`elem` "\r\n") would be more appropriate.

@konn
Copy link
Collaborator Author

konn commented Jan 30, 2021

I want to keep this issue open until the real reason is discovered. Any objections?

@pepeiborra
Copy link
Collaborator

I haven't checked, but I think the real reason is that the annotated source contains annotations that record the pre and post white space, whereas the SrcSpan in the non annotated AST does not include the surrounding white space.

Therefore the real solution will be to remove the outermost white space annotations before asking exactprint to prettyprint the rewrite.

@berberman
Copy link
Collaborator

I just tried setEntryDPT importDecl dp00, where importDecl is the top-level AST we want to rewrite. It solved the issue.

@konn
Copy link
Collaborator Author

konn commented Jan 30, 2021

I confirmed @berberman's approach applying setEntryDPT to the original import declaration (and drops tail in rewriteToEdit) works also in import disambiguation and even indented case! Thank you @pepeiborra and @berberman for pointing it out!

@konn
Copy link
Collaborator Author

konn commented Jan 30, 2021

So, what should be done next?
Should I also address this issue also in PR #1264?
Or, another PR must be filed first (possibly by @berberman?), merge them into master, and then fix #1264 based on it?

@pepeiborra
Copy link
Collaborator

Imho the call to setEntryDPT needs to be made by rewrite.

@jneira jneira added component: ghcide type: bug Something isn't right: doesn't work as intended, documentation is missing/outdated, etc.. labels Jan 30, 2021
@mergify mergify bot closed this as completed in #1264 Jan 31, 2021
mergify bot pushed a commit that referenced this issue Jan 31, 2021
)

* wip

* Draft completed

* Removes Unuseds

* Redundant extension

* linting

* Makes HLint happy

* tweak for transfer annotation logic (not working)

* Initial implementation done

* Import list reorder

* Remove redundant fmt

* lint

* Missing import

* Excludes false-positive qualified imports

* A first test (not enough though)

* fmt.sh

* Some more test cases

* More test cases

* Ah! CRLF have bitten me!

* Tentative workaround for #1274

* Wait much to ensure rewrite suggestion to be collected

* Tests for type suggestion

* Slightly more wait

* A little smarter waiting strartegy for actions

* Import list slim up

* Adjusted to the master

* Update ghcide/src/Development/IDE/Plugin/CodeAction.hs

Co-authored-by: Pepe Iborra <[email protected]>

* Rewrote using `expectDiagnostics`

* Case for Prelude.++

* Corrects test name

* Renames `rawIEWrapName` to `unqualIEWrapName`, and moved to the appropriate module

* Rewrote qualifying strategy with `Rewrite`

* Use exactprint also for hideImplicitPreludeSymbol

* Unify exact actions and `suggestImportDisambiguation`

* Moves a comment to the right place

* Won't panic on errornous input, let HLS keep going

* No, each errornous rewrite must not be dropped seprately, but discarded as a whole

* Update ghcide/src/Development/IDE/Spans/Common.hs

Co-authored-by: Potato Hatsue <[email protected]>

* ieNames

* Makes Splice plugin compiles

* Stop using nfp

* Since there is global `setEntryDPT dp00`, we don't add offset here

* Removes redundant (why warned here?)

* Made `hideImplicitPreludeSymbol` total

Co-authored-by: Pepe Iborra <[email protected]>
Co-authored-by: Potato Hatsue <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
component: ghcide type: bug Something isn't right: doesn't work as intended, documentation is missing/outdated, etc..
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants