packages feed

hls-module-name-plugin 1.0.2.0 → 1.1.0.0

raw patch · 8 files changed

+128/−68 lines, 8 filesdep ~ghcidedep ~hls-plugin-apidep ~hls-test-utilsPVP ok

version bump matches the API change (PVP)

Dependency ranges changed: ghcide, hls-plugin-api, hls-test-utils

API changes (from Hackage documentation)

+ Ide.Plugin.ModuleName: instance GHC.Show.Show Ide.Plugin.ModuleName.Log
+ Ide.Plugin.ModuleName: instance Prettyprinter.Internal.Pretty Ide.Plugin.ModuleName.Log
- Ide.Plugin.ModuleName: descriptor :: PluginId -> PluginDescriptor IdeState
+ Ide.Plugin.ModuleName: descriptor :: Recorder (WithPriority Log) -> PluginId -> PluginDescriptor IdeState

Files

hls-module-name-plugin.cabal view
@@ -1,6 +1,6 @@ cabal-version:      2.4 name:               hls-module-name-plugin-version:            1.0.2.0+version:            1.1.0.0 synopsis:           Module name plugin for Haskell Language Server description:   Please see the README on GitHub at <https://github.com/haskell/haskell-language-server#readme>@@ -16,8 +16,11 @@   LICENSE   test/testdata/**/*.yaml   test/testdata/**/*.hs+  test/testdata/**/*.cabal+  test/testdata/**/*.project  library+  buildable: True   exposed-modules:  Ide.Plugin.ModuleName   hs-source-dirs:   src   build-depends:@@ -25,8 +28,8 @@     , base                  >=4.12 && <5     , directory     , filepath-    , ghcide                ^>=1.7-    , hls-plugin-api        ^>=1.4+    , ghcide                ^>=1.8+    , hls-plugin-api        ^>=1.5     , lsp     , text     , transformers@@ -35,6 +38,7 @@   default-language: Haskell2010  test-suite tests+  buildable: True   type:             exitcode-stdio-1.0   default-language: Haskell2010   hs-source-dirs:   test@@ -44,4 +48,4 @@     , base     , filepath     , hls-module-name-plugin-    , hls-test-utils          ^>=1.3+    , hls-test-utils          ^>=1.4
src/Ide/Plugin/ModuleName.hs view
@@ -4,6 +4,7 @@ {-# LANGUAGE RecordWildCards   #-} {-# LANGUAGE ViewPatterns      #-} {-# OPTIONS_GHC -Wall -Wwarn -fno-warn-type-defaults #-}+{-# OPTIONS_GHC -Wno-name-shadowing #-}  {- | Keep the module name in sync with its file path. @@ -15,54 +16,62 @@     descriptor, ) where -import           Control.Monad              (forM_, void)-import           Control.Monad.IO.Class     (liftIO)-import           Control.Monad.Trans.Class  (lift)+import           Control.Monad                (forM_, void)+import           Control.Monad.IO.Class       (liftIO)+import           Control.Monad.Trans.Class    (lift) import           Control.Monad.Trans.Maybe-import           Data.Aeson                 (Value (Null), toJSON)-import           Data.Char                  (isLower)-import qualified Data.HashMap.Strict        as HashMap-import           Data.List                  (intercalate, isPrefixOf, minimumBy)-import qualified Data.List.NonEmpty         as NE-import           Data.Maybe                 (maybeToList)-import           Data.Ord                   (comparing)-import           Data.String                (IsString)-import qualified Data.Text                  as T-import           Development.IDE            (GetParsedModule (GetParsedModule),-                                             GhcSession (GhcSession), IdeState,-                                             evalGhcEnv, hscEnvWithImportPaths,-                                             realSrcSpanToRange, runAction,-                                             uriToFilePath', use, use_)-import           Development.IDE.GHC.Compat (GenLocated (L), getSessionDynFlags,-                                             hsmodName, importPaths, locA,-                                             pattern RealSrcSpan,-                                             pm_parsed_source, unLoc, moduleNameString)+import           Data.Aeson                   (Value (Null), toJSON)+import           Data.Char                    (isLower)+import qualified Data.HashMap.Strict          as HashMap+import           Data.List                    (intercalate, isPrefixOf,+                                               minimumBy)+import qualified Data.List.NonEmpty           as NE+import           Data.Maybe                   (maybeToList)+import           Data.Ord                     (comparing)+import           Data.String                  (IsString)+import qualified Data.Text                    as T+import           Development.IDE              (GetParsedModule (GetParsedModule),+                                               GhcSession (GhcSession),+                                               IdeState, Pretty,+                                               Priority (Debug, Info), Recorder,+                                               WithPriority, colon, evalGhcEnv,+                                               hscEnvWithImportPaths, logWith,+                                               realSrcSpanToRange, runAction,+                                               uriToFilePath', use, use_, (<+>))+import           Development.IDE.GHC.Compat   (GenLocated (L),+                                               getSessionDynFlags, hsmodName,+                                               importPaths, locA,+                                               moduleNameString,+                                               pattern RealSrcSpan,+                                               pm_parsed_source, unLoc)+import           Development.IDE.Types.Logger (Pretty (..)) import           Ide.Types import           Language.LSP.Server-import           Language.LSP.Types         hiding-                                            (SemanticTokenAbsolute (length, line),-                                             SemanticTokenRelative (length),-                                             SemanticTokensEdit (_start))-import           Language.LSP.VFS           (virtualFileText)-import           System.Directory           (makeAbsolute)-import           System.FilePath            (dropExtension, splitDirectories,-                                             takeFileName)+import           Language.LSP.Types           hiding+                                              (SemanticTokenAbsolute (length, line),+                                               SemanticTokenRelative (length),+                                               SemanticTokensEdit (_start))+import           Language.LSP.VFS             (virtualFileText)+import           System.Directory             (makeAbsolute)+import           System.FilePath              (dropExtension, normalise,+                                               pathSeparator, splitDirectories,+                                               takeFileName)  -- |Plugin descriptor-descriptor :: PluginId -> PluginDescriptor IdeState-descriptor plId =+descriptor :: Recorder (WithPriority Log) -> PluginId -> PluginDescriptor IdeState+descriptor recorder plId =     (defaultPluginDescriptor plId)-        { pluginHandlers = mkPluginHandler STextDocumentCodeLens codeLens-        , pluginCommands = [PluginCommand updateModuleNameCommand "set name of module to match with file path" command]+        { pluginHandlers = mkPluginHandler STextDocumentCodeLens (codeLens recorder)+        , pluginCommands = [PluginCommand updateModuleNameCommand "set name of module to match with file path" (command recorder)]         }  updateModuleNameCommand :: IsString p => p updateModuleNameCommand = "updateModuleName"  -- | Generate code lenses-codeLens :: PluginMethodHandler IdeState 'TextDocumentCodeLens-codeLens state pluginId CodeLensParams{_textDocument=TextDocumentIdentifier uri} =-  Right . List . maybeToList . (asCodeLens <$>) <$> action state uri+codeLens :: Recorder (WithPriority Log) -> PluginMethodHandler IdeState 'TextDocumentCodeLens+codeLens recorder state pluginId CodeLensParams{_textDocument=TextDocumentIdentifier uri} =+  Right . List . maybeToList . (asCodeLens <$>) <$> action recorder state uri   where     asCodeLens :: Action -> CodeLens     asCodeLens Replace{..} = CodeLens aRange (Just cmd) Nothing@@ -70,9 +79,9 @@         cmd = mkLspCommand pluginId updateModuleNameCommand aTitle (Just [toJSON aUri])  -- | (Quasi) Idempotent command execution: recalculate action to execute on command request-command :: CommandFunction IdeState Uri-command state uri = do-  actMaybe <- action state uri+command :: Recorder (WithPriority Log) -> CommandFunction IdeState Uri+command recorder state uri = do+  actMaybe <- action recorder state uri   forM_ actMaybe $ \Replace{..} ->     let       -- | Convert an Action to the corresponding edit operation@@ -91,19 +100,22 @@   deriving (Show)  -- | Required action (that can be converted to either CodeLenses or CodeActions)-action :: IdeState -> Uri -> LspM c (Maybe Action)-action state uri =-  traceAs "action" <$> runMaybeT $ do+action :: Recorder (WithPriority Log) -> IdeState -> Uri -> LspM c (Maybe Action)+action recorder state uri =+  runMaybeT $ do     nfp <- MaybeT . pure . uriToNormalizedFilePath $ toNormalizedUri uri     fp <- MaybeT . pure $ uriToFilePath' uri      contents <- lift . getVirtualFile $ toNormalizedUri uri     let emptyModule = maybe True (T.null . T.strip . virtualFileText) contents -    correctNames <- liftIO $ traceAs "correctNames" <$> pathModuleNames state nfp fp+    correctNames <- liftIO $ pathModuleNames recorder state nfp fp+    logWith recorder Debug (CorrectNames correctNames)     bestName <- minimumBy (comparing T.length) <$> (MaybeT . pure $ NE.nonEmpty correctNames)+    logWith recorder Info (BestName bestName) -    statedNameMaybe <- liftIO $ traceAs "statedName" <$> codeModuleName state nfp+    statedNameMaybe <- liftIO $ codeModuleName state nfp+    logWith recorder Debug (ModuleName $ snd <$> statedNameMaybe)     case statedNameMaybe of       Just (nameRange, statedName)         | statedName `notElem` correctNames ->@@ -117,14 +129,23 @@ -- | Possible module names, as derived by the position of the module in the -- source directories.  There may be more than one possible name, if the source -- directories are nested inside each other.-pathModuleNames :: IdeState -> NormalizedFilePath -> String -> IO [T.Text]-pathModuleNames state normFilePath filePath+pathModuleNames :: Recorder (WithPriority Log) -> IdeState -> NormalizedFilePath -> FilePath -> IO [T.Text]+pathModuleNames recorder state normFilePath filePath   | isLower . head $ takeFileName filePath = return ["Main"]   | otherwise = do       session <- runAction "ModuleName.ghcSession" state $ use_ GhcSession normFilePath       srcPaths <- evalGhcEnv (hscEnvWithImportPaths session) $ importPaths <$> getSessionDynFlags-      paths <- mapM makeAbsolute srcPaths+      logWith recorder Debug (SrcPaths srcPaths)++      -- Append a `pathSeparator` to make the path looks like a directory,+      --   and then we can drop it uniformly.+      -- See https://github.com/haskell/haskell-language-server/pull/3092 for details.+      let paths = map (normalise . (<> pure pathSeparator)) srcPaths+      logWith recorder Debug (NormalisedPaths paths)+       mdlPath <- makeAbsolute filePath+      logWith recorder Debug (AbsoluteFilePath mdlPath)+       let prefixes = filter (`isPrefixOf` mdlPath) paths       pure (map (moduleNameFrom mdlPath) prefixes)   where@@ -132,7 +153,7 @@       T.pack         . intercalate "."         . splitDirectories-        . drop (length prefix + 1)+        . drop (length prefix)         $ dropExtension mdlPath  -- | The module name, as stated in the module@@ -142,8 +163,20 @@   L (locA -> (RealSrcSpan l _)) m <- MaybeT . pure . hsmodName . unLoc $ pm_parsed_source pm   pure (realSrcSpanToRange l, T.pack $ moduleNameString m) --- traceAs :: Show a => String -> a -> a--- traceAs lbl a = trace (lbl ++ " = " ++ show a) a+data Log =+    CorrectNames [T.Text]+  | BestName T.Text+  | ModuleName (Maybe T.Text)+  | SrcPaths [FilePath]+  | NormalisedPaths [FilePath]+  | AbsoluteFilePath FilePath+  deriving Show -traceAs :: b -> a -> a-traceAs _ a = a+instance Pretty Log where+  pretty log = "ModuleName." <> case log of+    CorrectNames log     -> "CorrectNames" <> colon <+> pretty log+    BestName log         -> "BestName" <> colon <+> pretty log+    ModuleName log       -> "StatedNameMaybe" <> colon <+> pretty log+    SrcPaths log         -> "SrcPaths" <> colon <+> pretty log+    NormalisedPaths log  -> "NormalisedPaths" <> colon <+> pretty log+    AbsoluteFilePath log -> "AbsoluteFilePath" <> colon <+> pretty log
test/Main.hs view
@@ -13,7 +13,7 @@ main = defaultTestRunner tests  moduleNamePlugin :: PluginDescriptor IdeState-moduleNamePlugin = ModuleName.descriptor "moduleName"+moduleNamePlugin = ModuleName.descriptor mempty "moduleName"  tests :: TestTree tests =@@ -39,10 +39,15 @@       void $ skipManyTill anyMessage (message SWorkspaceApplyEdit)   , testCase "Should not show code lens if the module name is correct" $       runSessionWithServer moduleNamePlugin testDataDir $ do-          doc <- openDoc "CorrectName.hs" "haskell"-          lenses <- getCodeLenses doc-          liftIO $ lenses @?= []-          closeDoc doc+        doc <- openDoc "CorrectName.hs" "haskell"+        lenses <- getCodeLenses doc+        liftIO $ lenses @?= []+        closeDoc doc+  -- https://github.com/haskell/haskell-language-server/issues/3047+  , goldenWithModuleName "Fix#3047" "canonicalize/Lib/A" $ \doc -> do+      [CodeLens { _command = Just c }] <- getCodeLenses doc+      executeCommand c+      void $ skipManyTill anyMessage (message SWorkspaceApplyEdit)   ]  goldenWithModuleName :: TestName -> FilePath -> (TextDocumentIdentifier -> Session ()) -> TestTree
+ test/testdata/cabal.project view
@@ -0,0 +1,1 @@+packages: ./canonicalize
+ test/testdata/canonicalize/Lib/A.expected.hs view
@@ -0,0 +1,1 @@+module Lib.A where
+ test/testdata/canonicalize/Lib/A.hs view
+ test/testdata/canonicalize/canonicalize.cabal view
@@ -0,0 +1,7 @@+cabal-version: 2.4+name: canonicalize+version: 0.1.0.0++library+  build-depends: base+  hs-source-dirs: ./
test/testdata/hie.yaml view
@@ -1,8 +1,17 @@ cradle:-  direct:-    arguments:-      - "-isubdir"-      - "TEmptyModule"-      - "TWrongModuleName"-      - "mainlike"-      - "CorrectName"+  multi:+    - path: "./"+      config:+        cradle:+          direct:+            arguments:+              - "-isubdir"+              - "TEmptyModule"+              - "TWrongModuleName"+              - "CorrectName"+    - path: "./canonicalize"+      config:+        cradle:+          cabal:+            - path: "./"+              component: "lib:canonicalize"