Skip to content

[#1958] Fix placement of language pragmas #2043

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

Merged
merged 2 commits into from
Jul 31, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 27 additions & 10 deletions plugins/hls-pragmas-plugin/src/Ide/Plugin/Pragmas.hs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import Control.Applicative ((<|>))
import Control.Lens hiding (List)
import Control.Monad (join)
import Control.Monad.IO.Class (MonadIO (liftIO))
import Data.Char (isSpace)
import qualified Data.HashMap.Strict as H
import Data.List
import Data.List.Extra (nubOrdOn)
Expand Down Expand Up @@ -51,7 +52,7 @@ codeActionProvider state _plId (J.CodeActionParams _ _ docId _ (J.CodeActionCont
pm <- liftIO $ fmap join $ runAction "Pragmas.GetParsedModule" state $ getParsedModule `traverse` mFile
mbContents <- liftIO $ fmap (snd =<<) $ runAction "Pragmas.GetFileContents" state $ getFileContents `traverse` mFile
let dflags = ms_hspp_opts . pm_mod_summary <$> pm
insertRange = maybe (Range (Position 0 0) (Position 0 0)) endOfModuleHeader mbContents
insertRange = maybe (Range (Position 0 0) (Position 0 0)) findNextPragmaPosition mbContents
pedits = nubOrdOn snd . concat $ suggest dflags <$> diags
return $ Right $ List $ pragmaEditToAction uri insertRange <$> pedits

Expand Down Expand Up @@ -181,13 +182,29 @@ completion _ide _ complParams = do
}
_ -> return $ J.List []

-- ---------------------------------------------------------------------
-----------------------------------------------------------------------

-- | Find first line after (last pragma / last shebang / beginning of file).
-- Useful for inserting pragmas.
endOfModuleHeader :: T.Text -> Range
endOfModuleHeader contents = Range loc loc
where
loc = Position line 0
line = maybe 0 succ (lastLineWithPrefix "{-#" <|> lastLineWithPrefix "#!")
lastLineWithPrefix pre = listToMaybe $ reverse $ findIndices (T.isPrefixOf pre) $ T.lines contents
-- | Find first line after the last LANGUAGE pragma
-- Defaults to line 0 if the file contains no shebang(s), OPTIONS_GHC pragma(s), or other LANGUAGE pragma(s)
-- Otherwise it will be one after the count of line numbers, with order: Shebangs -> OPTIONS_GHC -> LANGUAGE
findNextPragmaPosition :: T.Text -> Range
findNextPragmaPosition contents = Range loc loc
where
loc = Position line 0
line = afterLangPragma . afterOptsGhc $ afterShebang 0
afterLangPragma = afterPragma "LANGUAGE" contents
afterOptsGhc = afterPragma "OPTIONS_GHC" contents
afterShebang = afterPragma "" contents

afterPragma :: T.Text -> T.Text -> Int -> Int
afterPragma name contents lineNum = maybe lineNum succ $ lastLineWithPrefix (checkPragma name) contents
where
lastLineWithPrefix p contents = listToMaybe . reverse $ findIndices p $ T.lines contents

checkPragma :: T.Text -> T.Text -> Bool
checkPragma name = check
where
check l = (isPragma l || isShebang l) && getName l == name
getName l = T.take (T.length name) $ T.dropWhile isSpace $ T.drop 3 l
isPragma = T.isPrefixOf "{-#"
isShebang = T.isPrefixOf "#!"
48 changes: 48 additions & 0 deletions plugins/hls-pragmas-plugin/test/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,54 @@ codeActionTests =
liftIO $ "Add \"FlexibleInstances\"" `elem` map (^. L.title) cas @? "Contains FlexibleInstances code action"
executeCodeAction $ head cas

, goldenWithPragmas "adds LANGUAGE pragma after shebang and last language pragma" "AfterShebangAndPragma" $ \doc -> do
_ <- waitForDiagnosticsFrom doc
cas <- map fromAction <$> getAllCodeActions doc
liftIO $ "Add \"NamedFieldPuns\"" `elem` map (^. L.title) cas @? "Contains NamedFieldPuns code action"
executeCodeAction $ head cas

, goldenWithPragmas "adds above module keyword on first line" "ModuleOnFirstLine" $ \doc -> do
_ <- waitForDiagnosticsFrom doc
cas <- map fromAction <$> getAllCodeActions doc
liftIO $ "Add \"TupleSections\"" `elem` map (^. L.title) cas @? "Contains TupleSections code action"
executeCodeAction $ head cas

, goldenWithPragmas "adds LANGUAGE pragma after GHC_OPTIONS" "AfterGhcOptions" $ \doc -> do
_ <- waitForDiagnosticsFrom doc
cas <- map fromAction <$> getAllCodeActions doc
liftIO $ "Add \"TupleSections\"" `elem` map (^. L.title) cas @? "Contains TupleSections code action"
executeCodeAction $ head cas

, goldenWithPragmas "adds LANGUAGE pragma after shebang and GHC_OPTIONS" "AfterShebangAndOpts" $ \doc -> do
_ <- waitForDiagnosticsFrom doc
cas <- map fromAction <$> getAllCodeActions doc
liftIO $ "Add \"TupleSections\"" `elem` map (^. L.title) cas @? "Contains TupleSections code action"
executeCodeAction $ head cas

, goldenWithPragmas "adds LANGUAGE pragma after shebang, GHC_OPTIONS and language pragma" "AfterShebangAndOptionsAndPragma" $ \doc -> do
_ <- waitForDiagnosticsFrom doc
cas <- map fromAction <$> getAllCodeActions doc
liftIO $ "Add \"TupleSections\"" `elem` map (^. L.title) cas @? "Contains TupleSections code action"
executeCodeAction $ head cas

, goldenWithPragmas "adds LANGUAGE pragma after all others ignoring later INLINE pragma" "AfterShebangAndOptionsAndPragmasIgnoreInline" $ \doc -> do
_ <- waitForDiagnosticsFrom doc
cas <- map fromAction <$> getAllCodeActions doc
liftIO $ "Add \"TupleSections\"" `elem` map (^. L.title) cas @? "Contains TupleSections code action"
executeCodeAction $ head cas

, goldenWithPragmas "adds LANGUAGE pragma after all others ignoring multiple later INLINE pragma" "AfterAllWithMultipleInlines" $ \doc -> do
_ <- waitForDiagnosticsFrom doc
cas <- map fromAction <$> getAllCodeActions doc
liftIO $ "Add \"TupleSections\"" `elem` map (^. L.title) cas @? "Contains TupleSections code action"
executeCodeAction $ head cas

, goldenWithPragmas "adds LANGUAGE pragma correctly ignoring later INLINE pragma" "AddLanguagePragma" $ \doc -> do
_ <- waitForDiagnosticsFrom doc
cas <- map fromAction <$> getAllCodeActions doc
liftIO $ "Add \"TupleSections\"" `elem` map (^. L.title) cas @? "Contains TupleSections code action"
executeCodeAction $ head cas

, goldenWithPragmas "adds TypeApplications pragma" "TypeApplications" $ \doc -> do
_ <- waitForDiagnosticsFrom doc
cas <- map fromAction <$> getAllCodeActions doc
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TupleSections #-}

module NeedsLanguagePragma where

tupleSection = (1,) <$> Just 2

{-# INLINE addOne #-}
addOne :: Int -> Int
addOne x = x + 1
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{-# LANGUAGE OverloadedStrings #-}

module NeedsLanguagePragma where

tupleSection = (1,) <$> Just 2

{-# INLINE addOne #-}
addOne :: Int -> Int
addOne x = x + 1
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#! /usr/bin/env nix-shell
#! nix-shell --pure -i runghc -p "haskellPackages.ghcWithPackages (hp: with hp; [ turtle ])"
{-# OPTIONS_GHC -Wall #-}
{-# OPTIONS_GHC -Wno-unused-imports #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TupleSections #-}

data Something = Something {
foo :: !String,
bar :: !Int
}

tupleSection = (1, ) <$> Just 2

{-# INLINE addOne #-}
addOne :: Int -> Int
addOne x = x + 1

{-# INLINE subOne #-}
subOne :: Int -> Int
subOne x = x - 1
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#! /usr/bin/env nix-shell
#! nix-shell --pure -i runghc -p "haskellPackages.ghcWithPackages (hp: with hp; [ turtle ])"
{-# OPTIONS_GHC -Wall #-}
{-# OPTIONS_GHC -Wno-unused-imports #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}

data Something = Something {
foo :: !String,
bar :: !Int
}

tupleSection = (1, ) <$> Just 2

{-# INLINE addOne #-}
addOne :: Int -> Int
addOne x = x + 1

{-# INLINE subOne #-}
subOne :: Int -> Int
subOne x = x - 1
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{-# OPTIONS_GHC -Wall #-}
{-# OPTIONS_GHC -Wno-unused-imports #-}
{-# LANGUAGE TupleSections #-}

data Something = Something {
foo :: !String,
bar :: !Int
}

tupleSection = (1, ) <$> Just 2

{-# INLINE addOne #-}
addOne :: Int -> Int
addOne x = x + 1

{-# INLINE subOne #-}
subOne :: Int -> Int
subOne x = x - 1
17 changes: 17 additions & 0 deletions plugins/hls-pragmas-plugin/test/testdata/AfterGhcOptions.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{-# OPTIONS_GHC -Wall #-}
{-# OPTIONS_GHC -Wno-unused-imports #-}

data Something = Something {
foo :: !String,
bar :: !Int
}

tupleSection = (1, ) <$> Just 2

{-# INLINE addOne #-}
addOne :: Int -> Int
addOne x = x + 1

{-# INLINE subOne #-}
subOne :: Int -> Int
subOne x = x - 1
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#! /usr/bin/env nix-shell
#! nix-shell --pure -i runghc -p "haskellPackages.ghcWithPackages (hp: with hp; [ turtle ])"
{-# OPTIONS_GHC -Wall #-}
{-# OPTIONS_GHC -Wno-unused-imports #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TupleSections #-}

data Something = Something {
foo :: !String,
bar :: !Int
}

tupleSection = (1, ) <$> Just 2
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#! /usr/bin/env nix-shell
#! nix-shell --pure -i runghc -p "haskellPackages.ghcWithPackages (hp: with hp; [ turtle ])"
{-# OPTIONS_GHC -Wall #-}
{-# OPTIONS_GHC -Wno-unused-imports #-}
{-# LANGUAGE OverloadedStrings #-}

data Something = Something {
foo :: !String,
bar :: !Int
}

tupleSection = (1, ) <$> Just 2
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#! /usr/bin/env nix-shell
#! nix-shell --pure -i runghc -p "haskellPackages.ghcWithPackages (hp: with hp; [ turtle ])"
{-# OPTIONS_GHC -Wall #-}
{-# OPTIONS_GHC -Wno-unused-imports #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TupleSections #-}

data Something = Something {
foo :: !String,
bar :: !Int
}

tupleSection = (1, ) <$> Just 2

{-# INLINE addOne #-}
addOne :: Int -> Int
addOne x = x + 1
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#! /usr/bin/env nix-shell
#! nix-shell --pure -i runghc -p "haskellPackages.ghcWithPackages (hp: with hp; [ turtle ])"
{-# OPTIONS_GHC -Wall #-}
{-# OPTIONS_GHC -Wno-unused-imports #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}

data Something = Something {
foo :: !String,
bar :: !Int
}

tupleSection = (1, ) <$> Just 2

{-# INLINE addOne #-}
addOne :: Int -> Int
addOne x = x + 1
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#! /usr/bin/env nix-shell
#! nix-shell --pure -i runghc -p "haskellPackages.ghcWithPackages (hp: with hp; [ turtle ])"
{-# OPTIONS_GHC -Wall #-}
{-# OPTIONS_GHC -Wno-unused-imports #-}
{-# LANGUAGE TupleSections #-}

data Something = Something {
foo :: !String,
bar :: !Int
}

tupleSection = (1, ) <$> Just 2
11 changes: 11 additions & 0 deletions plugins/hls-pragmas-plugin/test/testdata/AfterShebangAndOpts.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#! /usr/bin/env nix-shell
#! nix-shell --pure -i runghc -p "haskellPackages.ghcWithPackages (hp: with hp; [ turtle ])"
{-# OPTIONS_GHC -Wall #-}
{-# OPTIONS_GHC -Wno-unused-imports #-}

data Something = Something {
foo :: !String,
bar :: !Int
}

tupleSection = (1, ) <$> Just 2
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#! /usr/bin/env nix-shell
#! nix-shell --pure -i runghc -p "haskellPackages.ghcWithPackages (hp: with hp; [ turtle ])"
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE NamedFieldPuns #-}
-- | Doc Comment
{- Block -}

module BeforeDocComment where

data Record = Record
{ a :: Int,
b :: Double,
c :: String
}

f Record{a, b} = a
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#! /usr/bin/env nix-shell
#! nix-shell --pure -i runghc -p "haskellPackages.ghcWithPackages (hp: with hp; [ turtle ])"
{-# LANGUAGE OverloadedStrings #-}
-- | Doc Comment
{- Block -}

module BeforeDocComment where

data Record = Record
{ a :: Int,
b :: Double,
c :: String
}

f Record{a, b} = a
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{-# LANGUAGE TupleSections #-}
module Main where

tupleSection = (1,) <$> Just 2
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module Main where

tupleSection = (1,) <$> Just 2