ormolu 0.6.0.1 → 0.7.0.0
raw patch · 71 files changed
+1922/−1366 lines, 71 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
- Ormolu: getFixityOverridesForSourceFile :: MonadIO m => CabalInfo -> m FixityMap
- Ormolu: type FixityMap = Map OpName FixityInfo
- Ormolu.Fixity: [fiMaxPrecedence] :: FixityInfo -> Int
- Ormolu.Fixity: [fiMinPrecedence] :: FixityInfo -> Int
- Ormolu.Fixity: bootPackages :: Set PackageName
- Ormolu.Fixity: buildFixityMap :: Float -> Set PackageName -> LazyFixityMap
- Ormolu.Fixity: buildFixityMap' :: Map PackageName FixityMap -> Map PackageName Int -> Set PackageName -> Float -> Set PackageName -> LazyFixityMap
- Ormolu.Fixity: data HackageInfo
- Ormolu.Fixity: data LazyFixityMap
- Ormolu.Fixity: defaultStrategyThreshold :: Float
- Ormolu.Fixity: lookupFixity :: OpName -> LazyFixityMap -> Maybe FixityInfo
- Ormolu.Fixity: packageToOps :: Map PackageName FixityMap
- Ormolu.Fixity: packageToPopularity :: Map PackageName Int
- Ormolu.Fixity: type FixityMap = Map OpName FixityInfo
- Ormolu.Fixity.Internal: LazyFixityMap :: [FixityMap] -> LazyFixityMap
- Ormolu.Fixity.Internal: [fiMaxPrecedence] :: FixityInfo -> Int
- Ormolu.Fixity.Internal: [fiMinPrecedence] :: FixityInfo -> Int
- Ormolu.Fixity.Internal: data HackageInfo
- Ormolu.Fixity.Internal: instance GHC.Base.Semigroup Ormolu.Fixity.Internal.FixityInfo
- Ormolu.Fixity.Internal: instance GHC.Show.Show Ormolu.Fixity.Internal.LazyFixityMap
- Ormolu.Fixity.Internal: lookupFixity :: OpName -> LazyFixityMap -> Maybe FixityInfo
- Ormolu.Fixity.Internal: newtype LazyFixityMap
- Ormolu.Fixity.Internal: type FixityMap = Map OpName FixityInfo
- Ormolu.Fixity.Parser: parseFixityMap :: FilePath -> Text -> Either (ParseErrorBundle Text Void) FixityMap
- Ormolu.Fixity.Printer: printFixityMap :: FixityMap -> Text
- Ormolu.Parser.Result: [prFixityMap] :: ParseResult -> LazyFixityMap
- Ormolu.Parser.Result: [prFixityOverrides] :: ParseResult -> FixityMap
- Ormolu.Printer.Combinators: askFixityMap :: R LazyFixityMap
- Ormolu.Printer.Combinators: askFixityOverrides :: R FixityMap
- Ormolu.Printer.Combinators: inciHalf :: R () -> R ()
- Ormolu.Printer.Internal: askFixityMap :: R LazyFixityMap
- Ormolu.Printer.Internal: askFixityOverrides :: R FixityMap
- Ormolu.Printer.Internal: inciHalf :: R () -> R ()
- Ormolu.Printer.Operators: [opiFix] :: OpInfo op -> FixityInfo
- Ormolu.Utils.Fixity: getFixityOverridesForSourceFile :: MonadIO m => CabalInfo -> m FixityMap
+ Ormolu: [cfgModuleReexports] :: Config region -> !ModuleReexports
+ Ormolu: data FixityOverrides
+ Ormolu: data ModuleReexports
+ Ormolu: defaultFixityOverrides :: FixityOverrides
+ Ormolu: defaultModuleReexports :: ModuleReexports
+ Ormolu: getDotOrmoluForSourceFile :: MonadIO m => FilePath -> m (FixityOverrides, ModuleReexports)
+ Ormolu.Config: [cfgModuleReexports] :: Config region -> !ModuleReexports
+ Ormolu.Fixity: FixityApproximation :: Maybe FixityDirection -> Int -> Int -> FixityApproximation
+ Ormolu.Fixity: FixityOverrides :: Map OpName FixityInfo -> FixityOverrides
+ Ormolu.Fixity: ModuleFixityMap :: Map OpName FixityProvenance -> ModuleFixityMap
+ Ormolu.Fixity: ModuleReexports :: Map ModuleName (NonEmpty ModuleName) -> ModuleReexports
+ Ormolu.Fixity: PackageFixityMap :: Map OpName (NonEmpty (PackageName, ModuleName, FixityInfo)) -> PackageFixityMap
+ Ormolu.Fixity: [faDirection] :: FixityApproximation -> Maybe FixityDirection
+ Ormolu.Fixity: [faMaxPrecedence] :: FixityApproximation -> Int
+ Ormolu.Fixity: [faMinPrecedence] :: FixityApproximation -> Int
+ Ormolu.Fixity: [fiPrecedence] :: FixityInfo -> Int
+ Ormolu.Fixity: [unFixityOverrides] :: FixityOverrides -> Map OpName FixityInfo
+ Ormolu.Fixity: [unModuleReexports] :: ModuleReexports -> Map ModuleName (NonEmpty ModuleName)
+ Ormolu.Fixity: applyFixityOverrides :: FixityOverrides -> ModuleFixityMap -> ModuleFixityMap
+ Ormolu.Fixity: data FixityApproximation
+ Ormolu.Fixity: defaultDependencies :: Set PackageName
+ Ormolu.Fixity: defaultFixityApproximation :: FixityApproximation
+ Ormolu.Fixity: defaultFixityOverrides :: FixityOverrides
+ Ormolu.Fixity: defaultModuleReexports :: ModuleReexports
+ Ormolu.Fixity: hackageInfo :: HackageInfo
+ Ormolu.Fixity: inferFixity :: RdrName -> ModuleFixityMap -> FixityApproximation
+ Ormolu.Fixity: moduleFixityMap :: PackageFixityMap -> [FixityImport] -> ModuleFixityMap
+ Ormolu.Fixity: newtype FixityOverrides
+ Ormolu.Fixity: newtype HackageInfo
+ Ormolu.Fixity: newtype ModuleFixityMap
+ Ormolu.Fixity: newtype ModuleReexports
+ Ormolu.Fixity: newtype PackageFixityMap
+ Ormolu.Fixity: packageFixityMap :: Set PackageName -> PackageFixityMap
+ Ormolu.Fixity: packageFixityMap' :: HackageInfo -> Set PackageName -> PackageFixityMap
+ Ormolu.Fixity.Imports: FixityImport :: !Maybe PackageName -> !ModuleName -> !FixityQualification -> !Maybe (ImportListInterpretation, [OpName]) -> FixityImport
+ Ormolu.Fixity.Imports: [fimportList] :: FixityImport -> !Maybe (ImportListInterpretation, [OpName])
+ Ormolu.Fixity.Imports: [fimportModule] :: FixityImport -> !ModuleName
+ Ormolu.Fixity.Imports: [fimportPackage] :: FixityImport -> !Maybe PackageName
+ Ormolu.Fixity.Imports: [fimportQualified] :: FixityImport -> !FixityQualification
+ Ormolu.Fixity.Imports: applyModuleReexports :: ModuleReexports -> [FixityImport] -> [FixityImport]
+ Ormolu.Fixity.Imports: data FixityImport
+ Ormolu.Fixity.Imports: extractFixityImports :: [LImportDecl GhcPs] -> [FixityImport]
+ Ormolu.Fixity.Internal: FixityApproximation :: Maybe FixityDirection -> Int -> Int -> FixityApproximation
+ Ormolu.Fixity.Internal: FixityOverrides :: Map OpName FixityInfo -> FixityOverrides
+ Ormolu.Fixity.Internal: FromModuleImports :: NonEmpty (FixityQualification, FixityInfo) -> FixityProvenance
+ Ormolu.Fixity.Internal: Given :: FixityInfo -> FixityProvenance
+ Ormolu.Fixity.Internal: ModuleFixityMap :: Map OpName FixityProvenance -> ModuleFixityMap
+ Ormolu.Fixity.Internal: ModuleReexports :: Map ModuleName (NonEmpty ModuleName) -> ModuleReexports
+ Ormolu.Fixity.Internal: OnlyQualified :: ModuleName -> FixityQualification
+ Ormolu.Fixity.Internal: PackageFixityMap :: Map OpName (NonEmpty (PackageName, ModuleName, FixityInfo)) -> PackageFixityMap
+ Ormolu.Fixity.Internal: UnqualifiedAndQualified :: ModuleName -> FixityQualification
+ Ormolu.Fixity.Internal: [faDirection] :: FixityApproximation -> Maybe FixityDirection
+ Ormolu.Fixity.Internal: [faMaxPrecedence] :: FixityApproximation -> Int
+ Ormolu.Fixity.Internal: [faMinPrecedence] :: FixityApproximation -> Int
+ Ormolu.Fixity.Internal: [fiPrecedence] :: FixityInfo -> Int
+ Ormolu.Fixity.Internal: [unFixityOverrides] :: FixityOverrides -> Map OpName FixityInfo
+ Ormolu.Fixity.Internal: [unModuleReexports] :: ModuleReexports -> Map ModuleName (NonEmpty ModuleName)
+ Ormolu.Fixity.Internal: data FixityApproximation
+ Ormolu.Fixity.Internal: data FixityProvenance
+ Ormolu.Fixity.Internal: data FixityQualification
+ Ormolu.Fixity.Internal: defaultFixityApproximation :: FixityApproximation
+ Ormolu.Fixity.Internal: defaultFixityOverrides :: FixityOverrides
+ Ormolu.Fixity.Internal: defaultModuleReexports :: ModuleReexports
+ Ormolu.Fixity.Internal: inferFixity :: RdrName -> ModuleFixityMap -> FixityApproximation
+ Ormolu.Fixity.Internal: instance Control.DeepSeq.NFData Ormolu.Fixity.Internal.FixityApproximation
+ Ormolu.Fixity.Internal: instance Control.DeepSeq.NFData Ormolu.Fixity.Internal.HackageInfo
+ Ormolu.Fixity.Internal: instance Data.Binary.Class.Binary Ormolu.Fixity.Internal.FixityApproximation
+ Ormolu.Fixity.Internal: instance GHC.Base.Semigroup Ormolu.Fixity.Internal.FixityApproximation
+ Ormolu.Fixity.Internal: instance GHC.Classes.Eq Ormolu.Fixity.Internal.FixityApproximation
+ Ormolu.Fixity.Internal: instance GHC.Classes.Eq Ormolu.Fixity.Internal.FixityOverrides
+ Ormolu.Fixity.Internal: instance GHC.Classes.Eq Ormolu.Fixity.Internal.FixityProvenance
+ Ormolu.Fixity.Internal: instance GHC.Classes.Eq Ormolu.Fixity.Internal.FixityQualification
+ Ormolu.Fixity.Internal: instance GHC.Classes.Eq Ormolu.Fixity.Internal.ModuleFixityMap
+ Ormolu.Fixity.Internal: instance GHC.Classes.Eq Ormolu.Fixity.Internal.ModuleReexports
+ Ormolu.Fixity.Internal: instance GHC.Classes.Eq Ormolu.Fixity.Internal.PackageFixityMap
+ Ormolu.Fixity.Internal: instance GHC.Classes.Ord Ormolu.Fixity.Internal.FixityApproximation
+ Ormolu.Fixity.Internal: instance GHC.Generics.Generic Ormolu.Fixity.Internal.FixityApproximation
+ Ormolu.Fixity.Internal: instance GHC.Show.Show Ormolu.Fixity.Internal.FixityApproximation
+ Ormolu.Fixity.Internal: instance GHC.Show.Show Ormolu.Fixity.Internal.FixityOverrides
+ Ormolu.Fixity.Internal: instance GHC.Show.Show Ormolu.Fixity.Internal.FixityProvenance
+ Ormolu.Fixity.Internal: instance GHC.Show.Show Ormolu.Fixity.Internal.FixityQualification
+ Ormolu.Fixity.Internal: instance GHC.Show.Show Ormolu.Fixity.Internal.ModuleFixityMap
+ Ormolu.Fixity.Internal: instance GHC.Show.Show Ormolu.Fixity.Internal.ModuleReexports
+ Ormolu.Fixity.Internal: instance GHC.Show.Show Ormolu.Fixity.Internal.PackageFixityMap
+ Ormolu.Fixity.Internal: newtype FixityOverrides
+ Ormolu.Fixity.Internal: newtype HackageInfo
+ Ormolu.Fixity.Internal: newtype ModuleFixityMap
+ Ormolu.Fixity.Internal: newtype ModuleReexports
+ Ormolu.Fixity.Internal: newtype PackageFixityMap
+ Ormolu.Fixity.Parser: isIdentifierConstituent :: Char -> Bool
+ Ormolu.Fixity.Parser: isIdentifierFirstChar :: Char -> Bool
+ Ormolu.Fixity.Parser: isModuleSegmentConstituent :: Char -> Bool
+ Ormolu.Fixity.Parser: isModuleSegmentFirstChar :: Char -> Bool
+ Ormolu.Fixity.Parser: isOperatorConstituent :: Char -> Bool
+ Ormolu.Fixity.Parser: isPackageNameConstituent :: Char -> Bool
+ Ormolu.Fixity.Parser: pModuleName :: Parser ModuleName
+ Ormolu.Fixity.Parser: pPackageName :: Parser PackageName
+ Ormolu.Fixity.Parser: parseDotOrmolu :: FilePath -> Text -> Either (ParseErrorBundle Text Void) (FixityOverrides, ModuleReexports)
+ Ormolu.Fixity.Parser: parseModuleReexportDeclaration :: Text -> Either (ParseErrorBundle Text Void) (ModuleName, NonEmpty ModuleName)
+ Ormolu.Fixity.Printer: printDotOrmolu :: FixityOverrides -> ModuleReexports -> Text
+ Ormolu.Parser.CommentStream: type LComment = RealLocated Comment
+ Ormolu.Parser.Result: [prModuleFixityMap] :: ParseResult -> ModuleFixityMap
+ Ormolu.Printer.Combinators: askModuleFixityMap :: R ModuleFixityMap
+ Ormolu.Printer.Combinators: encloseLocated :: HasSrcSpan l => GenLocated l [a] -> ([a] -> R ()) -> R ()
+ Ormolu.Printer.Combinators: getEnclosingSpanWhere :: (RealSrcSpan -> Bool) -> R (Maybe RealSrcSpan)
+ Ormolu.Printer.Internal: askModuleFixityMap :: R ModuleFixityMap
+ Ormolu.Printer.Internal: getEnclosingSpanWhere :: (RealSrcSpan -> Bool) -> R (Maybe RealSrcSpan)
+ Ormolu.Printer.Meat.Declaration.Value: Applicand :: IsApplicand
+ Ormolu.Printer.Meat.Declaration.Value: NotApplicand :: IsApplicand
+ Ormolu.Printer.Meat.Declaration.Value: data IsApplicand
+ Ormolu.Printer.Operators: [opiFixityApproximation] :: OpInfo op -> FixityApproximation
+ Ormolu.Utils: ghcModuleNameToCabal :: ModuleName -> ModuleName
+ Ormolu.Utils.Fixity: getDotOrmoluForSourceFile :: MonadIO m => FilePath -> m (FixityOverrides, ModuleReexports)
+ Ormolu.Utils.Fixity: parseModuleReexportDeclarationStr :: String -> Either String (ModuleName, NonEmpty ModuleName)
+ Ormolu.Utils.IO: findClosestFileSatisfying :: MonadIO m => (FilePath -> Bool) -> FilePath -> m (Maybe FilePath)
- Ormolu: Config :: ![DynOption] -> FixityMap -> !Set PackageName -> !Bool -> !Bool -> !Bool -> !SourceType -> !ColorMode -> !region -> Config region
+ Ormolu: Config :: ![DynOption] -> !FixityOverrides -> !ModuleReexports -> !Set PackageName -> !Bool -> !Bool -> !Bool -> !SourceType -> !ColorMode -> !region -> Config region
- Ormolu: [cfgFixityOverrides] :: Config region -> FixityMap
+ Ormolu: [cfgFixityOverrides] :: Config region -> !FixityOverrides
- Ormolu: refineConfig :: SourceType -> Maybe CabalInfo -> Maybe FixityMap -> Config region -> Config region
+ Ormolu: refineConfig :: SourceType -> Maybe CabalInfo -> Maybe FixityOverrides -> Maybe ModuleReexports -> Config region -> Config region
- Ormolu.Config: Config :: ![DynOption] -> FixityMap -> !Set PackageName -> !Bool -> !Bool -> !Bool -> !SourceType -> !ColorMode -> !region -> Config region
+ Ormolu.Config: Config :: ![DynOption] -> !FixityOverrides -> !ModuleReexports -> !Set PackageName -> !Bool -> !Bool -> !Bool -> !SourceType -> !ColorMode -> !region -> Config region
- Ormolu.Config: [cfgFixityOverrides] :: Config region -> FixityMap
+ Ormolu.Config: [cfgFixityOverrides] :: Config region -> !FixityOverrides
- Ormolu.Fixity: FixityInfo :: Maybe FixityDirection -> Int -> Int -> FixityInfo
+ Ormolu.Fixity: FixityInfo :: FixityDirection -> Int -> FixityInfo
- Ormolu.Fixity: HackageInfo :: Map PackageName FixityMap -> Map PackageName Int -> HackageInfo
+ Ormolu.Fixity: HackageInfo :: Map PackageName (Map ModuleName (Map OpName FixityInfo)) -> HackageInfo
- Ormolu.Fixity: [fiDirection] :: FixityInfo -> Maybe FixityDirection
+ Ormolu.Fixity: [fiDirection] :: FixityInfo -> FixityDirection
- Ormolu.Fixity.Internal: FixityInfo :: Maybe FixityDirection -> Int -> Int -> FixityInfo
+ Ormolu.Fixity.Internal: FixityInfo :: FixityDirection -> Int -> FixityInfo
- Ormolu.Fixity.Internal: HackageInfo :: Map PackageName FixityMap -> Map PackageName Int -> HackageInfo
+ Ormolu.Fixity.Internal: HackageInfo :: Map PackageName (Map ModuleName (Map OpName FixityInfo)) -> HackageInfo
- Ormolu.Fixity.Internal: [fiDirection] :: FixityInfo -> Maybe FixityDirection
+ Ormolu.Fixity.Internal: [fiDirection] :: FixityInfo -> FixityDirection
- Ormolu.Parser: parseModule :: MonadIO m => Config RegionDeltas -> LazyFixityMap -> FilePath -> Text -> m ([Warn], Either (SrcSpan, String) [SourceSnippet])
+ Ormolu.Parser: parseModule :: MonadIO m => Config RegionDeltas -> PackageFixityMap -> FilePath -> Text -> m ([Warn], Either (SrcSpan, String) [SourceSnippet])
- Ormolu.Parser.CommentStream: CommentStream :: [RealLocated Comment] -> CommentStream
+ Ormolu.Parser.CommentStream: CommentStream :: [LComment] -> CommentStream
- Ormolu.Parser.CommentStream: mkCommentStream :: Text -> HsModule GhcPs -> (Maybe (RealLocated Comment), [([RealLocated Comment], Pragma)], CommentStream)
+ Ormolu.Parser.CommentStream: mkCommentStream :: Text -> HsModule GhcPs -> (Maybe LComment, [([LComment], Pragma)], CommentStream)
- Ormolu.Parser.Result: ParseResult :: HsModule GhcPs -> SourceType -> Maybe (RealLocated Comment) -> [([RealLocated Comment], Pragma)] -> CommentStream -> EnumSet Extension -> FixityMap -> LazyFixityMap -> Int -> ParseResult
+ Ormolu.Parser.Result: ParseResult :: HsModule GhcPs -> SourceType -> Maybe LComment -> [([LComment], Pragma)] -> CommentStream -> EnumSet Extension -> ModuleFixityMap -> Int -> ParseResult
- Ormolu.Parser.Result: [prPragmas] :: ParseResult -> [([RealLocated Comment], Pragma)]
+ Ormolu.Parser.Result: [prPragmas] :: ParseResult -> [([LComment], Pragma)]
- Ormolu.Parser.Result: [prStackHeader] :: ParseResult -> Maybe (RealLocated Comment)
+ Ormolu.Parser.Result: [prStackHeader] :: ParseResult -> Maybe LComment
- Ormolu.Printer.Combinators: getEnclosingSpan :: (RealSrcSpan -> Bool) -> R (Maybe RealSrcSpan)
+ Ormolu.Printer.Combinators: getEnclosingSpan :: R (Maybe RealSrcSpan)
- Ormolu.Printer.Combinators: runR :: R () -> SpanStream -> CommentStream -> SourceType -> EnumSet Extension -> FixityMap -> LazyFixityMap -> Text
+ Ormolu.Printer.Combinators: runR :: R () -> SpanStream -> CommentStream -> SourceType -> EnumSet Extension -> ModuleFixityMap -> Text
- Ormolu.Printer.Internal: getEnclosingSpan :: (RealSrcSpan -> Bool) -> R (Maybe RealSrcSpan)
+ Ormolu.Printer.Internal: getEnclosingSpan :: R (Maybe RealSrcSpan)
- Ormolu.Printer.Internal: popComment :: (RealLocated Comment -> Bool) -> R (Maybe (RealLocated Comment))
+ Ormolu.Printer.Internal: popComment :: (LComment -> Bool) -> R (Maybe LComment)
- Ormolu.Printer.Internal: runR :: R () -> SpanStream -> CommentStream -> SourceType -> EnumSet Extension -> FixityMap -> LazyFixityMap -> Text
+ Ormolu.Printer.Internal: runR :: R () -> SpanStream -> CommentStream -> SourceType -> EnumSet Extension -> ModuleFixityMap -> Text
- Ormolu.Printer.Meat.Declaration.Value: p_hsExpr' :: BracketStyle -> HsExpr GhcPs -> R ()
+ Ormolu.Printer.Meat.Declaration.Value: p_hsExpr' :: IsApplicand -> BracketStyle -> HsExpr GhcPs -> R ()
- Ormolu.Printer.Meat.Module: p_hsModule :: Maybe (RealLocated Comment) -> [([RealLocated Comment], Pragma)] -> HsModule GhcPs -> R ()
+ Ormolu.Printer.Meat.Module: p_hsModule :: Maybe LComment -> [([LComment], Pragma)] -> HsModule GhcPs -> R ()
- Ormolu.Printer.Meat.Pragma: p_pragmas :: [([RealLocated Comment], Pragma)] -> R ()
+ Ormolu.Printer.Meat.Pragma: p_pragmas :: [([LComment], Pragma)] -> R ()
- Ormolu.Printer.Operators: OpInfo :: op -> Maybe OpName -> FixityInfo -> OpInfo op
+ Ormolu.Printer.Operators: OpInfo :: op -> Maybe RdrName -> FixityApproximation -> OpInfo op
- Ormolu.Printer.Operators: [opiName] :: OpInfo op -> Maybe OpName
+ Ormolu.Printer.Operators: [opiName] :: OpInfo op -> Maybe RdrName
- Ormolu.Printer.Operators: isHardSplitterOp :: FixityInfo -> Bool
+ Ormolu.Printer.Operators: isHardSplitterOp :: FixityApproximation -> Bool
- Ormolu.Printer.Operators: reassociateOpTree :: (op -> Maybe RdrName) -> FixityMap -> LazyFixityMap -> OpTree ty op -> OpTree ty (OpInfo op)
+ Ormolu.Printer.Operators: reassociateOpTree :: (op -> Maybe RdrName) -> ModuleFixityMap -> OpTree ty op -> OpTree ty (OpInfo op)
Files
- CHANGELOG.md +30/−0
- README.md +30/−10
- app/Main.hs +74/−40
- data/examples/declaration/data/datatype-contexts-out.hs +11/−0
- data/examples/declaration/data/datatype-contexts.hs +8/−0
- data/examples/declaration/type-synonyms/multi-line-out.hs +2/−0
- data/examples/declaration/type-synonyms/multi-line.hs +2/−0
- data/examples/declaration/value/function/application-1-out.hs +11/−11
- data/examples/declaration/value/function/application-2-out.hs +6/−6
- data/examples/declaration/value/function/arrow/proc-applications2-out.hs +2/−1
- data/examples/declaration/value/function/arrow/proc-do-simple1-out.hs +12/−0
- data/examples/declaration/value/function/arrow/proc-do-simple1.hs +10/−0
- data/examples/declaration/value/function/arrow/proc-form-do-indent-out.hs +2/−1
- data/examples/declaration/value/function/infix/esqueleto-0-out.hs +12/−0
- data/examples/declaration/value/function/infix/esqueleto-0.hs +10/−0
- data/examples/declaration/value/function/infix/esqueleto-1-out.hs +9/−0
- data/examples/declaration/value/function/infix/esqueleto-1.hs +9/−0
- data/examples/declaration/value/function/infix/lenses-out.hs +2/−0
- data/examples/declaration/value/function/infix/lenses.hs +2/−0
- data/examples/declaration/value/function/infix/qualified-prelude-out.hs +12/−0
- data/examples/declaration/value/function/infix/qualified-prelude.hs +12/−0
- data/examples/declaration/value/function/lambda-case-out.hs +6/−0
- data/examples/declaration/value/function/lambda-case.hs +6/−0
- data/examples/declaration/value/function/multi-way-if-out.hs +4/−4
- data/examples/declaration/value/function/operators-0-out.hs +2/−0
- data/examples/declaration/value/function/operators-0.hs +2/−0
- data/examples/declaration/value/function/operators-3-out.hs +2/−0
- data/examples/declaration/value/function/operators-3.hs +2/−0
- data/examples/declaration/value/function/operators-4-out.hs +2/−0
- data/examples/declaration/value/function/operators-4.hs +2/−0
- data/examples/declaration/value/function/operators-6-out.hs +2/−0
- data/examples/declaration/value/function/operators-6.hs +2/−0
- data/examples/module-header/multiline-empty-comment-out.hs +4/−0
- data/examples/module-header/multiline-empty-comment.hs +2/−0
- extract-hackage-info/hackage-info.bin binary
- ormolu.cabal +8/−5
- src/Ormolu.hs +35/−20
- src/Ormolu/Config.hs +6/−4
- src/Ormolu/Fixity.hs +133/−207
- src/Ormolu/Fixity/Imports.hs +91/−0
- src/Ormolu/Fixity/Internal.hs +201/−77
- src/Ormolu/Fixity/Parser.hs +113/−17
- src/Ormolu/Fixity/Printer.hs +50/−34
- src/Ormolu/Parser.hs +85/−30
- src/Ormolu/Parser/CommentStream.hs +9/−6
- src/Ormolu/Parser/Result.hs +4/−7
- src/Ormolu/Printer.hs +1/−2
- src/Ormolu/Printer/Combinators.hs +21/−4
- src/Ormolu/Printer/Comments.hs +25/−25
- src/Ormolu/Printer/Internal.hs +26/−46
- src/Ormolu/Printer/Meat/Common.hs +1/−1
- src/Ormolu/Printer/Meat/Declaration/Data.hs +9/−1
- src/Ormolu/Printer/Meat/Declaration/OpTree.hs +4/−3
- src/Ormolu/Printer/Meat/Declaration/Value.hs +59/−52
- src/Ormolu/Printer/Meat/Declaration/Value.hs-boot +4/−1
- src/Ormolu/Printer/Meat/ImportExport.hs +0/−4
- src/Ormolu/Printer/Meat/Module.hs +3/−3
- src/Ormolu/Printer/Meat/Pragma.hs +2/−2
- src/Ormolu/Printer/Meat/Type.hs +2/−3
- src/Ormolu/Printer/Operators.hs +34/−45
- src/Ormolu/Utils.hs +9/−1
- src/Ormolu/Utils/Cabal.hs +16/−35
- src/Ormolu/Utils/Fixity.hs +50/−29
- src/Ormolu/Utils/IO.hs +40/−1
- tests/Ormolu/CabalInfoSpec.hs +1/−1
- tests/Ormolu/Fixity/ParserSpec.hs +166/−72
- tests/Ormolu/Fixity/PrinterSpec.hs +43/−20
- tests/Ormolu/FixitySpec.hs +327/−0
- tests/Ormolu/HackageInfoSpec.hs +0/−507
- tests/Ormolu/OpTreeSpec.hs +20/−20
- tests/Ormolu/PrinterSpec.hs +18/−8
CHANGELOG.md view
@@ -1,3 +1,33 @@+## Ormolu 0.7.0.0++* Inference of operator fixity information is now more precise and takes+ into account the import section of the module being formatted. [Issue+ 892](https://github.com/tweag/ormolu/issues/892) and [issue+ 929](https://github.com/tweag/ormolu/issues/929).++* Ormolu can now be made aware of module re-exports through either special+ declarations in `.ormolu` files (see the readme for a description of the+ syntax), or on the command line with the `--reexport`/`-r` option. [Issue+ 1017](https://github.com/tweag/ormolu/issues/1017).++* Ormolu now looks for `.ormolu` files independently of `.cabal` files. This+ means that it is now possible to have one `.ormolu` file for multiple+ Cabal packages. [Issue 1019](https://github.com/tweag/ormolu/issues/1019).++* Consistently format `do` blocks/`case`s/`MultiWayIf`s with 4 spaces if and+ only if they occur as the applicand. [Issue+ 1002](https://github.com/tweag/ormolu/issues/1002) and [issue+ 730](https://github.com/tweag/ormolu/issues/730).++* Support the (deprecated) `DatatypeContexts` extension to avoid surprises.+ [Issue 1012](https://github.com/tweag/ormolu/issues/1012).++* Don't let comments escape from empty export lists. [Issue+ 906](https://github.com/tweag/ormolu/issues/906).++* Format `\cases` with multiple patterns across multiple lines correctly. [Issue+ 1025](https://github.com/tweag/ormolu/issues/1025).+ ## Ormolu 0.6.0.1 * Fix false positives in AST diffing related to `UnicodeSyntax`. [PR
README.md view
@@ -162,15 +162,15 @@ ### Language extensions, dependencies, and fixities Ormolu automatically locates the Cabal file that corresponds to a given-source code file. When input comes from stdin, one can pass-`--stdin-input-file` which will give Ormolu the location of the Haskell-source file that should be used as the starting point for searching for a-suitable Cabal file. Cabal files are used to extract both default extensions+source code file. Cabal files are used to extract both default extensions and dependencies. Default extensions directly affect behavior of the GHC parser, while dependencies are used to figure out fixities of operators that-appear in the source code. Fixities can also be overridden if `.ormolu` file-is found next to the corresponding Cabal file, i.e. they should be siblings-in the same directory.+appear in the source code. Fixities can also be overridden via an `.ormolu`+file which should be located at a higher level in the file system hierarchy+than the source file that is being formatted. When the input comes from+stdin, one can pass `--stdin-input-file` which will give Ormolu the location+that should be used as the starting point for searching for `.cabal` and+`.ormolu` files. Here is an example of `.ormolu` file: @@ -187,15 +187,35 @@ It uses exactly the same syntax as usual Haskell fixity declarations to make it easier for Haskellers to edit and maintain. -Besides, all of the above-mentioned parameters can be controlled from the+As of Ormolu 0.7.0.0, `.ormolu` files can also contain instructions about+module re-exports that Ormolu should be aware of. This might be desirable+because at the moment Ormolu cannot know about all possible module+re-exports in the ecosystem and only few of them are actually important when+it comes to fixity deduction. In 99% of cases the user won't have to do+anything, especially since most common re-exports are already programmed+into Ormolu. (You are welcome to open PRs to make Ormolu aware of more+re-exports by default.) However, when the fixity of an operator is not+inferred correctly, making Ormolu aware of a re-export may come in handy.+Here is an example:++```haskell+module Control.Lens exports Control.Lens.At+module Control.Lens exports Control.Lens.Lens+```++Module re-export declarations can be mixed freely with fixity overrides, as+long as each declaration is on its own line.++Finally, all of the above-mentioned parameters can be controlled from the command line: * Language extensions can be specified with the `-o` or `--ghc-opt` flag. * Dependencies can be specified with the `-p` or `--package` flag. * Fixities can be specified with the `-f` or `--fixity` flag.+* Re-exports can be specified with the `-r` or `--reexport` flag. -Searching for both `.cabal` and `.ormolu` files can be disabled by passing-`--no-cabal`.+Searching for `.cabal` and `.ormolu` files can be disabled by passing+`--no-cabal` and `--no-dot-ormolu` respectively. ### Magic comments
app/Main.hs view
@@ -11,20 +11,22 @@ import Control.Monad import Data.Bool (bool) import Data.List (intercalate, sort)+import Data.List.NonEmpty (NonEmpty) import Data.Map.Strict qualified as Map import Data.Maybe (fromMaybe, mapMaybe, maybeToList) import Data.Set qualified as Set import Data.Text.IO qualified as TIO import Data.Version (showVersion)+import Distribution.ModuleName (ModuleName) import Language.Haskell.TH.Env (envQ) import Options.Applicative import Ormolu import Ormolu.Diff.Text (diffText, printTextDiff)-import Ormolu.Fixity (FixityInfo, OpName)+import Ormolu.Fixity import Ormolu.Parser (manualExts) import Ormolu.Terminal import Ormolu.Utils (showOutputable)-import Ormolu.Utils.Fixity (parseFixityDeclarationStr)+import Ormolu.Utils.Fixity import Ormolu.Utils.IO import Paths_ormolu (version) import System.Directory@@ -38,7 +40,7 @@ Opts {..} <- execParser optsParserInfo let formatOne' = formatOne- optCabal+ optConfigFileOpts optMode optSourceType optConfig@@ -65,7 +67,7 @@ -- | Format a single input. formatOne :: -- | How to use .cabal files- CabalOpts ->+ ConfigFileOpts -> -- | Mode of operation Mode -> -- | The 'SourceType' requested by the user@@ -75,7 +77,7 @@ -- | File to format or stdin as 'Nothing' Maybe FilePath -> IO ExitCode-formatOne CabalOpts {..} mode reqSourceType rawConfig mpath =+formatOne ConfigFileOpts {..} mode reqSourceType rawConfig mpath = withPrettyOrmoluExceptions (cfgColorMode rawConfig) $ do let getCabalInfoForSourceFile' sourceFile = do cabalSearchResult <- getCabalInfoForSourceFile sourceFile@@ -97,21 +99,24 @@ <> sourceFile return (Just cabalInfo) CabalFound cabalInfo -> return (Just cabalInfo)+ getDotOrmoluForSourceFile' sourceFile = do+ if optDoNotUseDotOrmolu+ then return Nothing+ else Just <$> getDotOrmoluForSourceFile sourceFile case FP.normalise <$> mpath of -- input source = STDIN Nothing -> do- resultConfig <-- ( if optDoNotUseCabal- then pure Nothing- else case optStdinInputFile of- Just stdinInputFile ->- getCabalInfoForSourceFile' stdinInputFile- Nothing -> throwIO OrmoluMissingStdinInputFile- )- >>= patchConfig Nothing+ mcabalInfo <- case (optStdinInputFile, optDoNotUseCabal) of+ (_, True) -> return Nothing+ (Nothing, False) -> throwIO OrmoluMissingStdinInputFile+ (Just inputFile, False) -> getCabalInfoForSourceFile' inputFile+ mdotOrmolu <- case optStdinInputFile of+ Nothing -> return Nothing+ Just inputFile -> getDotOrmoluForSourceFile' inputFile+ config <- patchConfig Nothing mcabalInfo mdotOrmolu case mode of Stdout -> do- ormoluStdin resultConfig >>= TIO.putStr+ ormoluStdin config >>= TIO.putStr return ExitSuccess InPlace -> do hPutStrLn@@ -124,25 +129,29 @@ originalInput <- getContentsUtf8 let stdinRepr = "<stdin>" formattedInput <-- ormolu resultConfig stdinRepr originalInput+ ormolu config stdinRepr originalInput handleDiff originalInput formattedInput stdinRepr -- input source = a file Just inputFile -> do- resultConfig <-- ( if optDoNotUseCabal- then pure Nothing- else getCabalInfoForSourceFile' inputFile- )- >>= patchConfig (Just (detectSourceType inputFile))+ mcabalInfo <-+ if optDoNotUseCabal+ then return Nothing+ else getCabalInfoForSourceFile' inputFile+ mdotOrmolu <- getDotOrmoluForSourceFile' inputFile+ config <-+ patchConfig+ (Just (detectSourceType inputFile))+ mcabalInfo+ mdotOrmolu case mode of Stdout -> do- ormoluFile resultConfig inputFile >>= TIO.putStr+ ormoluFile config inputFile >>= TIO.putStr return ExitSuccess InPlace -> do -- ormoluFile is not used because we need originalInput originalInput <- readFileUtf8 inputFile formattedInput <-- ormolu resultConfig inputFile originalInput+ ormolu config inputFile originalInput when (formattedInput /= originalInput) $ writeFileUtf8 inputFile formattedInput return ExitSuccess@@ -150,16 +159,23 @@ -- ormoluFile is not used because we need originalInput originalInput <- readFileUtf8 inputFile formattedInput <-- ormolu resultConfig inputFile originalInput+ ormolu config inputFile originalInput handleDiff originalInput formattedInput inputFile where- patchConfig mdetectedSourceType mcabalInfo = do+ patchConfig mdetectedSourceType mcabalInfo mdotOrmolu = do let sourceType = fromMaybe ModuleSource (reqSourceType <|> mdetectedSourceType)- mfixityOverrides <- traverse getFixityOverridesForSourceFile mcabalInfo- return (refineConfig sourceType mcabalInfo mfixityOverrides rawConfig)+ let mfixityOverrides = fst <$> mdotOrmolu+ mmoduleReexports = snd <$> mdotOrmolu+ return $+ refineConfig+ sourceType+ mcabalInfo+ mfixityOverrides+ mmoduleReexports+ rawConfig handleDiff originalInput formattedInput fileRepr = case diffText originalInput formattedInput fileRepr of Nothing -> return ExitSuccess@@ -179,8 +195,8 @@ optMode :: !Mode, -- | Ormolu 'Config' optConfig :: !(Config RegionIndices),- -- | Options related to info extracted from .cabal files- optCabal :: CabalOpts,+ -- | Options related to info extracted from files+ optConfigFileOpts :: ConfigFileOpts, -- | Source type option, where 'Nothing' means autodetection optSourceType :: !(Maybe SourceType), -- | Haskell source files to format or stdin (when the list is empty)@@ -198,10 +214,12 @@ Check deriving (Eq, Show) --- | Configuration related to .cabal files.-data CabalOpts = CabalOpts+-- | Options related to configuration stored in the file system.+data ConfigFileOpts = ConfigFileOpts { -- | DO NOT extract default-extensions and dependencies from .cabal files optDoNotUseCabal :: Bool,+ -- | DO NOT look for @.ormolu@ files+ optDoNotUseDotOrmolu :: Bool, -- | Optional path to a file which will be used to find a .cabal file -- when using input from stdin optStdinInputFile :: Maybe FilePath@@ -252,22 +270,24 @@ ] ) <*> configParser- <*> cabalOptsParser+ <*> configFileOptsParser <*> sourceTypeParser <*> (many . strArgument . mconcat) [ metavar "FILE", help "Haskell source files to format or stdin (the default)" ] -cabalOptsParser :: Parser CabalOpts-cabalOptsParser =- CabalOpts+configFileOptsParser :: Parser ConfigFileOpts+configFileOptsParser =+ ConfigFileOpts <$> (switch . mconcat) [ long "no-cabal",- help $- "Do not extract default-extensions and dependencies from .cabal files"- ++ ", do not look for .ormolu files"+ help "Do not extract default-extensions and dependencies from .cabal files" ]+ <*> (switch . mconcat)+ [ long "no-dot-ormolu",+ help "Do not look for .ormolu files"+ ] <*> (optional . strOption . mconcat) [ long "stdin-input-file", help "Path which will be used to find the .cabal file when using input from stdin"@@ -282,7 +302,7 @@ metavar "OPT", help "GHC options to enable (e.g. language extensions)" ]- <*> ( fmap (Map.fromListWith (<>) . mconcat)+ <*> ( fmap (FixityOverrides . Map.fromList . mconcat) . many . option parseFixityDeclaration . mconcat@@ -292,6 +312,16 @@ metavar "FIXITY", help "Fixity declaration to use (an override)" ]+ <*> ( fmap (ModuleReexports . Map.fromListWith (<>) . mconcat . pure)+ . many+ . option parseModuleReexportDeclaration+ . mconcat+ )+ [ long "reexport",+ short 'r',+ metavar "REEXPORT",+ help "Module re-export that Ormolu should know about"+ ] <*> (fmap Set.fromList . many . strOption . mconcat) [ long "package", short 'p',@@ -360,6 +390,10 @@ -- | Parse a fixity declaration. parseFixityDeclaration :: ReadM [(OpName, FixityInfo)] parseFixityDeclaration = eitherReader parseFixityDeclarationStr++-- | Parse a module reexport declaration.+parseModuleReexportDeclaration :: ReadM (ModuleName, NonEmpty ModuleName)+parseModuleReexportDeclaration = eitherReader parseModuleReexportDeclarationStr -- | Parse 'ColorMode'. parseColorMode :: ReadM ColorMode
+ data/examples/declaration/data/datatype-contexts-out.hs view
@@ -0,0 +1,11 @@+data (IsString s) => T s = T++data+ (IsString s) =>+ T s = T++data+ ( IsString s,+ IsString s+ ) =>+ T s = T
+ data/examples/declaration/data/datatype-contexts.hs view
@@ -0,0 +1,8 @@+data IsString s => T s = T++data IsString s =>+ T s = T++data+ ( IsString s+ , IsString s ) => T s = T
data/examples/declaration/type-synonyms/multi-line-out.hs view
@@ -1,3 +1,5 @@+import Servant.API+ type Foo a b c = Bar c a b
data/examples/declaration/type-synonyms/multi-line.hs view
@@ -1,3 +1,5 @@+import Servant.API+ type Foo a b c = Bar c a b
data/examples/declaration/value/function/application-1-out.hs view
@@ -1,19 +1,19 @@ main = do- x- y- z+ x+ y+ z main = case foo of- x -> a- foo- a- b+ x -> a+ foo+ a+ b main = do- if x then y else z- foo- a- b+ if x then y else z+ foo+ a+ b
data/examples/declaration/value/function/application-2-out.hs view
@@ -16,8 +16,8 @@ foo = do do- (+ 1)- 2+ (+ 1)+ 2 foo = do case () of () -> (+ 1)@@ -25,8 +25,8 @@ foo = do case () of- () -> (+ 1)- 2+ () -> (+ 1)+ 2 foo = do \case 2 -> 3@@ -34,5 +34,5 @@ foo = do \case- 2 -> 3- 2+ 2 -> 3+ 2
data/examples/declaration/value/function/arrow/proc-applications2-out.hs view
@@ -8,4 +8,5 @@ LT -> \a -> returnA -< x + a EQ -> \b -> returnA -< y + z + b GT -> \c -> returnA -< z + x- ) 1+ )+ 1
data/examples/declaration/value/function/arrow/proc-do-simple1-out.hs view
@@ -15,3 +15,15 @@ f -< a++foo =+ proc x ->+ do+ returnA -< x+ 1++foo a =+ proc x ->+ case Left x of+ Left x -> returnA -< x+ a
data/examples/declaration/value/function/arrow/proc-do-simple1.hs view
@@ -18,3 +18,13 @@ -< a +foo =+ proc x -> do+ returnA -< x+ 1++foo a =+ proc x ->+ case Left x of+ Left x -> returnA -< x+ a
data/examples/declaration/value/function/arrow/proc-form-do-indent-out.hs view
@@ -10,4 +10,5 @@ (| bar (bindA -< y)- |) z+ |)+ z
+ data/examples/declaration/value/function/infix/esqueleto-0-out.hs view
@@ -0,0 +1,12 @@+import Database.Esqueleto.Experimental++foo = select $ do+ t <-+ from $+ table @Bar+ `innerJoin` table @Baz+ `on` do+ \(br :& bz) -> whatever+ where_ $+ t ^. BarInt ==. val 3+ &&. t ^. BarName `in_` valList ["hello", "world"]
+ data/examples/declaration/value/function/infix/esqueleto-0.hs view
@@ -0,0 +1,10 @@+import Database.Esqueleto.Experimental++foo = select $ do+ t <- from $ table @Bar+ `innerJoin` table @Baz+ `on` do+ \(br :& bz) -> whatever+ where_ $+ t ^. BarInt ==. val 3+ &&. t ^. BarName `in_` valList ["hello", "world"]
+ data/examples/declaration/value/function/infix/esqueleto-1-out.hs view
@@ -0,0 +1,9 @@+import qualified Database.Esqueleto.Experimental as E++foo =+ E.from $+ E.table+ `E.innerJoin` E.table+ `E.on` ( \(a :& b) ->+ a E.^. AField E.==. b E.^. BField+ )
+ data/examples/declaration/value/function/infix/esqueleto-1.hs view
@@ -0,0 +1,9 @@+import qualified Database.Esqueleto.Experimental as E++foo =+ E.from $+ E.table+ `E.innerJoin` E.table+ `E.on` ( \(a :& b) ->+ a E.^. AField E.==. b E.^. BField+ )
data/examples/declaration/value/function/infix/lenses-out.hs view
@@ -1,3 +1,5 @@+import Control.Lens+ lenses = Just $ M.fromList $
data/examples/declaration/value/function/infix/lenses.hs view
@@ -1,3 +1,5 @@+import Control.Lens+ lenses = Just $ M.fromList $ "type" .= ("user.connection" :: Text) # "connection" .= uc
+ data/examples/declaration/value/function/infix/qualified-prelude-out.hs view
@@ -0,0 +1,12 @@+module StreamSpec where++import Prelude (($))+import qualified Prelude++spec :: Spec+spec = do+ describe "Comparing list function to" $ do+ qit "yieldMany" $+ \(mono :: Seq Int) ->+ yieldMany mono+ `checkProducer` otoList mono
+ data/examples/declaration/value/function/infix/qualified-prelude.hs view
@@ -0,0 +1,12 @@+module StreamSpec where++import qualified Prelude+import Prelude (($))++spec :: Spec+spec = do+ describe "Comparing list function to" $ do+ qit "yieldMany" $+ \(mono :: Seq Int) ->+ yieldMany mono `checkProducer`+ otoList mono
data/examples/declaration/value/function/lambda-case-out.hs view
@@ -15,3 +15,9 @@ foo = \cases (Just a) (Just a) -> a + a _ _ -> 0++foo :: Bool -> Bool -> Bool+foo = \cases+ True+ True -> True+ _ _ -> False
data/examples/declaration/value/function/lambda-case.hs view
@@ -15,3 +15,9 @@ foo = \cases (Just a) (Just a) -> a + a _ _ -> 0++foo :: Bool -> Bool -> Bool+foo = \cases+ True+ True -> True+ _ _ -> False
data/examples/declaration/value/function/multi-way-if-out.hs view
@@ -4,10 +4,10 @@ bar x y = if- | x > y -> x- | x < y ->- y- | otherwise -> x+ | x > y -> x+ | x < y ->+ y+ | otherwise -> x baz = if
data/examples/declaration/value/function/operators-0-out.hs view
@@ -1,3 +1,5 @@+import Control.Lens.Operators+ a = b & c .~ d
data/examples/declaration/value/function/operators-0.hs view
@@ -1,3 +1,5 @@+import Control.Lens.Operators+ a = b & c .~ d & e %~ f
data/examples/declaration/value/function/operators-3-out.hs view
@@ -1,3 +1,5 @@+import Control.Arrow+ foo = op <> n <+> colon
data/examples/declaration/value/function/operators-3.hs view
@@ -1,3 +1,5 @@+import Control.Arrow+ foo = op <> n <+> colon <+> prettySe <+> text "=" <+> prettySe <> text sc
data/examples/declaration/value/function/operators-4-out.hs view
@@ -1,3 +1,5 @@+import Control.Arrow+ foo = line <> bindingOf <+> text "="
data/examples/declaration/value/function/operators-4.hs view
@@ -1,3 +1,5 @@+import Control.Arrow+ foo = line <> bindingOf <+> text "=" <+> tPretty <+> colon <+> align <> prettyPs
data/examples/declaration/value/function/operators-6-out.hs view
@@ -1,3 +1,5 @@+import Servant.API+ type PermuteRef = "a" :> ( "b" :> "c" :> End
data/examples/declaration/value/function/operators-6.hs view
@@ -1,3 +1,5 @@+import Servant.API+ type PermuteRef = "a" :> ( "b" :> "c" :> End :<|> "c" :> "b" :> End
+ data/examples/module-header/multiline-empty-comment-out.hs view
@@ -0,0 +1,4 @@+module Foo+ ( -- test+ )+where
+ data/examples/module-header/multiline-empty-comment.hs view
@@ -0,0 +1,2 @@+module Foo ( -- test+ ) where
extract-hackage-info/hackage-info.bin view
binary file changed (1292137 → 952236 bytes)
ormolu.cabal view
@@ -1,6 +1,6 @@ cabal-version: 2.4 name: ormolu-version: 0.6.0.1+version: 0.7.0.0 license: BSD-3-Clause license-file: LICENSE.md maintainer: Mark Karpov <mark.karpov@tweag.io>@@ -78,6 +78,7 @@ Ormolu.Printer.Meat.Type Ormolu.Printer.Operators Ormolu.Fixity+ Ormolu.Fixity.Imports Ormolu.Fixity.Internal Ormolu.Fixity.Parser Ormolu.Fixity.Printer@@ -99,7 +100,7 @@ Cabal-syntax >=3.10 && <3.11, Diff >=0.4 && <1.0, MemoTrie >=0.6 && <0.7,- ansi-terminal >=0.10 && <1.0,+ ansi-terminal >=0.10 && <1.1, array >=0.5 && <0.6, base >=4.14 && <5.0, binary >=0.8 && <0.9,@@ -133,15 +134,16 @@ autogen-modules: Paths_ormolu default-language: GHC2021 build-depends:+ Cabal-syntax >=3.10 && <3.11, base >=4.12 && <5.0, containers >=0.5 && <0.7, directory ^>=1.3, filepath >=1.2 && <1.5, ghc-lib-parser >=9.6 && <9.7,- th-env >=0.1.1 && <0.2, optparse-applicative >=0.14 && <0.18, ormolu,- text >=2.0 && <3.0+ text >=2.0 && <3.0,+ th-env >=0.1.1 && <0.2 if flag(dev) ghc-options:@@ -161,7 +163,7 @@ Ormolu.Diff.TextSpec Ormolu.Fixity.ParserSpec Ormolu.Fixity.PrinterSpec- Ormolu.HackageInfoSpec+ Ormolu.FixitySpec Ormolu.OpTreeSpec Ormolu.Parser.OptionsSpec Ormolu.Parser.ParseFailureSpec@@ -179,6 +181,7 @@ ghc-lib-parser >=9.6 && <9.7, hspec >=2.0 && <3.0, hspec-megaparsec >=2.2,+ megaparsec >=9.0, ormolu, path >=0.6 && <0.10, path-io >=1.4.2 && <2.0,
src/Ormolu.hs view
@@ -24,9 +24,12 @@ CabalUtils.CabalInfo (..), CabalUtils.getCabalInfoForSourceFile, - -- * Fixity overrides- FixityMap,- getFixityOverridesForSourceFile,+ -- * Fixity overrides and module re-exports+ FixityOverrides,+ defaultFixityOverrides,+ ModuleReexports,+ defaultModuleReexports,+ getDotOrmoluForSourceFile, -- * Working with exceptions OrmoluException (..),@@ -38,6 +41,7 @@ import Control.Monad import Control.Monad.IO.Class (MonadIO (..)) import Data.Map.Strict qualified as Map+import Data.Maybe (fromMaybe) import Data.Set qualified as Set import Data.Text (Text) import Data.Text qualified as T@@ -55,7 +59,7 @@ import Ormolu.Printer import Ormolu.Utils (showOutputable) import Ormolu.Utils.Cabal qualified as CabalUtils-import Ormolu.Utils.Fixity (getFixityOverridesForSourceFile)+import Ormolu.Utils.Fixity (getDotOrmoluForSourceFile) import Ormolu.Utils.IO import System.FilePath @@ -85,11 +89,7 @@ let totalLines = length (T.lines originalInput) cfg = regionIndicesToDeltas totalLines <$> cfgWithIndices fixityMap =- -- It is important to keep all arguments (but last) of- -- 'buildFixityMap' constant (such as 'defaultStrategyThreshold'),- -- otherwise it is going to break memoization.- buildFixityMap- defaultStrategyThreshold+ packageFixityMap (cfgDependencies cfg) -- memoized on the set of dependencies (warnings, result0) <- parseModule' cfg fixityMap OrmoluParsingFailed path originalInput@@ -180,32 +180,47 @@ -- | Cabal info for the file, if available Maybe CabalUtils.CabalInfo -> -- | Fixity overrides, if available- Maybe FixityMap ->+ Maybe FixityOverrides ->+ -- | Module re-exports, if available+ Maybe ModuleReexports -> -- | 'Config' to refine Config region -> -- | Refined 'Config' Config region-refineConfig sourceType mcabalInfo mfixityOverrides rawConfig =+refineConfig sourceType mcabalInfo mfixityOverrides mreexports rawConfig = rawConfig { cfgDynOptions = cfgDynOptions rawConfig ++ dynOptsFromCabal, cfgFixityOverrides =- Map.unionWith (<>) (cfgFixityOverrides rawConfig) fixityOverrides,+ FixityOverrides $+ Map.unions+ [ unFixityOverrides fixityOverrides,+ unFixityOverrides (cfgFixityOverrides rawConfig),+ unFixityOverrides defaultFixityOverrides+ ],+ cfgModuleReexports =+ ModuleReexports $+ Map.unionsWith+ (<>)+ [ unModuleReexports reexports,+ unModuleReexports (cfgModuleReexports rawConfig),+ unModuleReexports defaultModuleReexports+ ], cfgDependencies = Set.union (cfgDependencies rawConfig) depsFromCabal, cfgSourceType = sourceType } where- fixityOverrides =- case mfixityOverrides of- Nothing -> Map.empty- Just x -> x+ fixityOverrides = fromMaybe defaultFixityOverrides mfixityOverrides+ reexports = fromMaybe defaultModuleReexports mreexports (dynOptsFromCabal, depsFromCabal) = case mcabalInfo of- Nothing -> ([], Set.empty)+ Nothing ->+ -- If no cabal info is provided, assume base as a dependency by+ -- default.+ ([], defaultDependencies) Just CabalUtils.CabalInfo {..} -> -- It makes sense to take into account the operator info for the- -- package itself if we know it, as if it were its own- -- dependency.+ -- package itself if we know it, as if it were its own dependency. (ciDynOpts, Set.insert ciPackageName ciDependencies) ----------------------------------------------------------------------------@@ -217,7 +232,7 @@ -- | Ormolu configuration Config RegionDeltas -> -- | Fixity Map for operators- LazyFixityMap ->+ PackageFixityMap -> -- | How to obtain 'OrmoluException' to throw when parsing fails (SrcSpan -> String -> OrmoluException) -> -- | File name to use in errors
src/Ormolu/Config.hs view
@@ -14,13 +14,12 @@ ) where -import Data.Map.Strict qualified as Map import Data.Set (Set) import Data.Set qualified as Set import Distribution.Types.PackageName (PackageName) import GHC.Generics (Generic) import GHC.Types.SrcLoc qualified as GHC-import Ormolu.Fixity (FixityMap)+import Ormolu.Fixity import Ormolu.Terminal (ColorMode (..)) -- | Type of sources that can be formatted by Ormolu.@@ -36,7 +35,9 @@ { -- | Dynamic options to pass to GHC parser cfgDynOptions :: ![DynOption], -- | Fixity overrides- cfgFixityOverrides :: FixityMap,+ cfgFixityOverrides :: !FixityOverrides,+ -- | Module reexports to take into account when doing fixity resolution+ cfgModuleReexports :: !ModuleReexports, -- | Known dependencies, if any cfgDependencies :: !(Set PackageName), -- | Do formatting faster but without automatic detection of defects@@ -78,7 +79,8 @@ defaultConfig = Config { cfgDynOptions = [],- cfgFixityOverrides = Map.empty,+ cfgFixityOverrides = defaultFixityOverrides,+ cfgModuleReexports = defaultModuleReexports, cfgDependencies = Set.empty, cfgUnsafe = False, cfgDebug = False,
src/Ormolu/Fixity.hs view
@@ -1,7 +1,7 @@ {-# LANGUAGE CPP #-}-{-# LANGUAGE MultiWayIf #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE PatternSynonyms #-}+{-# LANGUAGE RecordWildCards #-} {-# LANGUAGE TemplateHaskell #-} -- | Definitions for fixity analysis.@@ -12,34 +12,39 @@ occOpName, FixityDirection (..), FixityInfo (..),- FixityMap,- LazyFixityMap,- lookupFixity,- HackageInfo (..),- defaultStrategyThreshold, defaultFixityInfo,- buildFixityMap,- buildFixityMap',- bootPackages,- packageToOps,- packageToPopularity,+ FixityApproximation (..),+ defaultFixityApproximation,+ FixityOverrides (..),+ defaultFixityOverrides,+ ModuleReexports (..),+ defaultModuleReexports,+ PackageFixityMap (..),+ ModuleFixityMap (..),+ inferFixity,+ HackageInfo (..),+ hackageInfo,+ defaultDependencies,+ packageFixityMap,+ packageFixityMap',+ moduleFixityMap,+ applyFixityOverrides, ) where import Data.Binary qualified as Binary import Data.Binary.Get qualified as Binary import Data.ByteString.Lazy qualified as BL-import Data.Foldable (foldl')-import Data.List.NonEmpty (NonEmpty ((:|)))+import Data.List.NonEmpty (NonEmpty) import Data.List.NonEmpty qualified as NE-import Data.Map.Strict (Map) import Data.Map.Strict qualified as Map-import Data.Maybe (fromMaybe) import Data.MemoTrie (memo)-import Data.Semigroup (sconcat) import Data.Set (Set) import Data.Set qualified as Set+import Distribution.ModuleName (ModuleName) import Distribution.Types.PackageName (PackageName, mkPackageName, unPackageName)+import Language.Haskell.Syntax.ImpExp (ImportListInterpretation (..))+import Ormolu.Fixity.Imports (FixityImport (..)) import Ormolu.Fixity.Internal #if BUNDLE_FIXITIES import Data.FileEmbed (embedFile)@@ -48,210 +53,131 @@ import System.IO.Unsafe (unsafePerformIO) #endif -packageToOps :: Map PackageName FixityMap-packageToPopularity :: Map PackageName Int+-- | The built-in 'HackageInfo' used by Ormolu.+hackageInfo :: HackageInfo #if BUNDLE_FIXITIES-HackageInfo packageToOps packageToPopularity =+hackageInfo = Binary.runGet Binary.get $ BL.fromStrict $(embedFile "extract-hackage-info/hackage-info.bin") #else -- The GHC WASM backend does not yet support Template Haskell, so we instead -- pass in the encoded fixity DB via pre-initialization with Wizer.-HackageInfo packageToOps packageToPopularity =+hackageInfo = unsafePerformIO $ Binary.runGet Binary.get . BL.fromStrict <$> B.readFile "hackage-info.bin"-{-# NOINLINE packageToOps #-}-{-# NOINLINE packageToPopularity #-}+{-# NOINLINE hackageInfo #-} #endif --- | List of packages shipped with GHC, for which the download count from--- Hackage does not reflect their high popularity.--- See https://github.com/tweag/ormolu/pull/830#issuecomment-986609572.--- "base" is not is this list, because it is already whitelisted--- by buildFixityMap'.-bootPackages :: Set PackageName-bootPackages =- Set.fromList- [ "array",- "binary",- "bytestring",- "containers",- "deepseq",- "directory",- "exceptions",- "filepath",- "ghc-binary",- "mtl",- "parsec",- "process",- "stm",- "template-haskell",- "terminfo",- "text",- "time",- "transformers",- "unix",- "Win32"- ]---- | The default value for the popularity ratio threshold, after which a--- very popular definition from packageToOps will completely rule out--- conflicting definitions instead of being merged with them.-defaultStrategyThreshold :: Float-defaultStrategyThreshold = 0.9+-- | Default set of packages to assume as dependencies e.g. when no Cabal+-- file is found or taken into consideration.+defaultDependencies :: Set PackageName+defaultDependencies = Set.singleton (mkPackageName "base") --- | Build a fixity map using the given popularity threshold and a list of--- cabal dependencies. Dependencies from the list have higher priority than--- other packages.-buildFixityMap ::- -- | Popularity ratio threshold, after which a very popular package will- -- completely rule out conflicting definitions coming from other packages- -- instead of being merged with them- Float ->- -- | Explicitly known dependencies+-- | Compute the fixity map that is specific to the package we are formatting.+packageFixityMap ::+ -- | Set of packages to select Set PackageName ->- -- | Resulting map- LazyFixityMap-buildFixityMap = buildFixityMap' packageToOps packageToPopularity bootPackages+ -- | Package fixity map+ PackageFixityMap+packageFixityMap = packageFixityMap' hackageInfo --- | Build a fixity map using the given popularity threshold and a list of--- cabal dependencies. Dependencies from the list have higher priority than--- other packages. This specific version of the function allows the user to--- specify the package databases used to build the final fixity map.-buildFixityMap' ::- -- | Map from package to fixity map for operators defined in this package- Map PackageName FixityMap ->- -- | Map from package to popularity- Map PackageName Int ->- -- | Higher priority packages- Set PackageName ->- -- | Popularity ratio threshold, after which a very popular package will- -- completely rule out conflicting definitions coming from other packages- -- instead of being merged with them- Float ->- -- | Explicitly known dependencies+-- | The same as 'packageFixityMap', except this specific version of the+-- function allows the user to specify 'HackageInfo' used to build the final+-- fixity map.+packageFixityMap' ::+ -- | Hackage info+ HackageInfo ->+ -- | Set of packages to select Set PackageName ->- -- | Resulting map- LazyFixityMap-buildFixityMap'- operatorMap- popularityMap- higherPriorityPackages- strategyThreshold = memoSet $ \dependencies ->- let baseFixityMap =- Map.insert ":" colonFixityInfo $- fromMaybe Map.empty $- Map.lookup "base" operatorMap- cabalFixityMap =- mergeAll (buildPackageFixityMap <$> Set.toList dependencies)- higherPriorityFixityMap =- mergeAll (buildPackageFixityMap <$> Set.toList higherPriorityPackages)- remainingFixityMap =- mergeFixityMaps- popularityMap- strategyThreshold- (buildPackageFixityMap <$> Set.toList remainingPackages)- remainingPackages =- Map.keysSet operatorMap- `Set.difference` Set.union dependencies higherPriorityPackages- buildPackageFixityMap packageName =- ( packageName,- fromMaybe Map.empty $- Map.lookup packageName operatorMap- )- -- we need a threshold > 1.0 so that no dependency can reach the- -- threshold- mergeAll = mergeFixityMaps Map.empty 10.0- in LazyFixityMap- [ baseFixityMap,- cabalFixityMap,- higherPriorityFixityMap,- remainingFixityMap- ]--memoSet :: (Set PackageName -> v) -> Set PackageName -> v-memoSet f = memo (f . Set.fromAscList . fmap mkPackageName) . fmap unPackageName . Set.toAscList+ -- | Package fixity map+ PackageFixityMap+packageFixityMap' (HackageInfo m) = memoSet $ \dependencies ->+ -- The core idea here is to transform:+ --+ -- Map PackageName (Map ModuleName (Map OpName FixityInfo))+ --+ -- into+ --+ -- Map OpName [(PackageName, ModuleName, FixityInfo)]+ --+ -- which we accomplish by turning 'Map's into tuples with 'Map.toList' and+ -- then flattening them with 'flatten :: [(a, [b])] -> [(a, b)]'.+ --+ -- The target type results from the need to be able to quickly index by+ -- the operator name when we do fixity resolution later.+ PackageFixityMap+ . Map.mapMaybe NE.nonEmpty+ . Map.fromListWith (<>)+ . fmap rearrange+ . flatten+ . Map.toList+ . Map.map (flatten . Map.toList . Map.map Map.toList)+ $ Map.restrictKeys m dependencies+ where+ rearrange (packageName, (moduleName, (opName, fixityInfo))) =+ (opName, [(packageName, moduleName, fixityInfo)])+ flatten xs = do+ (k, vs) <- xs+ v <- vs+ return (k, v) --- | Merge a list of individual fixity maps, coming from different packages.--- Package popularities and the given threshold are used to choose between--- the "keep best only" (>= threshold) and "merge all" (< threshold)--- strategies when conflicting definitions are encountered for an operator.-mergeFixityMaps ::- -- | Map from package name to 30-days download count- Map PackageName Int ->- -- | Popularity ratio threshold- Float ->- -- | List of (package name, package fixity map) to merge- [(PackageName, FixityMap)] ->- -- | Resulting fixity map- FixityMap-mergeFixityMaps popularityMap threshold packageMaps =- Map.map- (useThreshold threshold . NE.fromList . Map.toList)- scoredMap+-- | Compute the fixity map that is specific to the module we are formatting.+moduleFixityMap ::+ -- | Fixity information selected from dependencies of this package+ PackageFixityMap ->+ -- | A simplified representation of the import list in this module+ [FixityImport] ->+ -- | Fixity map specific to this module+ ModuleFixityMap+moduleFixityMap (PackageFixityMap m) imports =+ ModuleFixityMap $+ Map.insert+ ":"+ (Given colonFixityInfo)+ (Map.map FromModuleImports (Map.mapMaybeWithKey select m)) where- scoredMap = Map.map getScores opFixityMap- -- when we encounter a duplicate key (op1) in the unionsWith operation,- -- we have- -- op1 -map-> {definitions1 -map-> originPackages}- -- op1 -map-> {definitions2 -map-> originPackages}- -- so we merge the keys (which have the type:- -- Map FixityInfo (NonEmpty PackageName))- -- using 'Map.unionWith (<>)', to "concatenate" the list of- -- definitions for this operator, and to also "concatenate" origin- -- packages if a same definition is found in both maps- opFixityMap =- Map.unionsWith- (Map.unionWith (<>))- (opFixityMapFrom <$> packageMaps)- useThreshold ::- -- Threshold- Float ->- -- List of conflicting (definition, score) for a given operator- NonEmpty (FixityInfo, Int) ->- -- Resulting fixity, using the specified threshold to choose between- -- strategy "keep best only" and "merge all"- FixityInfo- useThreshold t fixScores =- if toFloat maxScore / toFloat sumScores >= t- then sconcat . fmap fst $ maxs -- merge potential ex-aequo winners- else sconcat . fmap fst $ fixScores- where- toFloat x = fromIntegral x :: Float- maxs = maxWith snd fixScores- maxScore = snd $ NE.head maxs- sumScores = foldl' (+) 0 (snd <$> fixScores)- getScores ::- -- Map for a given operator associating each of its conflicting- -- definitions with the packages that define it- Map FixityInfo (NonEmpty PackageName) ->- -- Map for a given operator associating each of its conflicting- -- definitions with their score (= sum of the popularity of the- -- packages that define it)- Map FixityInfo Int- getScores =- Map.map- (sum . fmap (fromMaybe 0 . flip Map.lookup popularityMap))- opFixityMapFrom ::- -- (packageName, package fixity map)- (PackageName, FixityMap) ->- -- Map associating each operator of the package with a- -- {map for a given operator associating each of its definitions with- -- the list of packages that define it}- -- (this list can only be == [packageName] in the context of this- -- function)- Map OpName (Map FixityInfo (NonEmpty PackageName))- opFixityMapFrom (packageName, opsMap) =- Map.map- (flip Map.singleton (packageName :| []))- opsMap- maxWith :: (Ord b) => (a -> b) -> NonEmpty a -> NonEmpty a- maxWith f xs = snd $ foldl' comp (f h, h :| []) t- where- h :| t = xs- comp (fMax, maxs) x =- let fX = f x- in if- | fMax < fX -> (fX, x :| [])- | fMax == fX -> (fMax, NE.cons x maxs)- | otherwise -> (fMax, maxs)+ select ::+ OpName ->+ NonEmpty (PackageName, ModuleName, FixityInfo) ->+ Maybe (NonEmpty (FixityQualification, FixityInfo))+ select opName =+ let f (packageName, moduleName, fixityInfo) =+ (,fixityInfo)+ <$> resolveThroughImports packageName moduleName opName+ in NE.nonEmpty . concatMap f+ resolveThroughImports ::+ PackageName ->+ ModuleName ->+ OpName ->+ [FixityQualification]+ resolveThroughImports packageName moduleName opName =+ let doesImportMatch FixityImport {..} =+ let packageMatches =+ case fimportPackage of+ Nothing -> True+ Just p -> p == packageName+ moduleMatches =+ fimportModule == moduleName+ opMatches = case fimportList of+ Nothing -> True+ Just (Exactly, xs) -> opName `elem` xs+ Just (EverythingBut, xs) -> opName `notElem` xs+ in packageMatches && moduleMatches && opMatches+ in fimportQualified <$> filter doesImportMatch imports++-- | Apply fixity overrides.+applyFixityOverrides ::+ -- | User overrides+ FixityOverrides ->+ -- | Module fixity map+ ModuleFixityMap ->+ -- | Module fixity map with overrides applied+ ModuleFixityMap+applyFixityOverrides (FixityOverrides o) (ModuleFixityMap m) =+ ModuleFixityMap (Map.union (Map.map Given o) m)++memoSet :: (Set PackageName -> v) -> Set PackageName -> v+memoSet f =+ memo (f . Set.fromAscList . fmap mkPackageName)+ . fmap unPackageName+ . Set.toAscList
+ src/Ormolu/Fixity/Imports.hs view
@@ -0,0 +1,91 @@+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE RecordWildCards #-}++-- | Simplified representation of the import list for the purposes of fixity+-- inference.+module Ormolu.Fixity.Imports+ ( FixityImport (..),+ extractFixityImports,+ applyModuleReexports,+ )+where++import Data.Bifunctor (second)+import Data.List.NonEmpty qualified as NE+import Data.Map.Strict qualified as Map+import Distribution.ModuleName (ModuleName)+import Distribution.Types.PackageName+import GHC.Data.FastString qualified as GHC+import GHC.Hs hiding (ModuleName)+import GHC.Types.Name.Occurrence+import GHC.Types.PkgQual (RawPkgQual (..))+import GHC.Types.SourceText (StringLiteral (..))+import GHC.Types.SrcLoc+import Ormolu.Fixity.Internal+import Ormolu.Utils (ghcModuleNameToCabal)++-- | Simplified info about an import.+data FixityImport = FixityImport+ { fimportPackage :: !(Maybe PackageName),+ fimportModule :: !ModuleName,+ fimportQualified :: !FixityQualification,+ fimportList :: !(Maybe (ImportListInterpretation, [OpName]))+ }++-- | Extract 'FixityImport's from the AST.+extractFixityImports ::+ [LImportDecl GhcPs] ->+ [FixityImport]+extractFixityImports = fmap (extractFixityImport . unLoc)++-- | Extract an individual 'FixityImport'.+extractFixityImport :: ImportDecl GhcPs -> FixityImport+extractFixityImport ImportDecl {..} =+ FixityImport+ { fimportPackage = case ideclPkgQual of+ NoRawPkgQual -> Nothing+ RawPkgQual strLiteral ->+ Just . mkPackageName . GHC.unpackFS . sl_fs $ strLiteral,+ fimportModule = ideclName',+ fimportQualified = case (ideclQualified, ideclAs') of+ (QualifiedPre, Nothing) ->+ OnlyQualified ideclName'+ (QualifiedPost, Nothing) ->+ OnlyQualified ideclName'+ (QualifiedPre, Just m) -> OnlyQualified m+ (QualifiedPost, Just m) -> OnlyQualified m+ (NotQualified, Nothing) ->+ UnqualifiedAndQualified ideclName'+ (NotQualified, Just m) ->+ UnqualifiedAndQualified m,+ fimportList =+ fmap+ (second (concatMap (fmap occOpName . ieToOccNames . unLoc) . unLoc))+ ideclImportList+ }+ where+ ideclName' = ghcModuleNameToCabal (unLoc ideclName)+ ideclAs' = ghcModuleNameToCabal . unLoc <$> ideclAs++ieToOccNames :: IE GhcPs -> [OccName]+ieToOccNames = \case+ IEVar _ (L _ x) -> [occName x]+ IEThingAbs _ (L _ x) -> [occName x]+ IEThingAll _ (L _ x) -> [occName x] -- TODO not quite correct, but how to do better?+ IEThingWith _ (L _ x) _ xs -> occName x : fmap (occName . unLoc) xs+ _ -> []++-- | Apply given module re-exports.+applyModuleReexports :: ModuleReexports -> [FixityImport] -> [FixityImport]+applyModuleReexports (ModuleReexports reexports) imports = imports >>= expand+ where+ expand i = do+ case Map.lookup (fimportModule i) reexports of+ Nothing -> pure i+ Just exports ->+ let exportToImport mmodule =+ i+ { fimportPackage = Nothing,+ fimportModule = mmodule+ }+ in NE.toList exports >>= expand . exportToImport
src/Ormolu/Fixity/Internal.hs view
@@ -1,6 +1,8 @@ {-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE PatternSynonyms #-}+{-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ViewPatterns #-} module Ormolu.Fixity.Internal@@ -10,12 +12,20 @@ occOpName, FixityDirection (..), FixityInfo (..),- defaultFixityInfo, colonFixityInfo,+ defaultFixityInfo,+ FixityApproximation (..),+ defaultFixityApproximation, HackageInfo (..),- FixityMap,- LazyFixityMap (..),- lookupFixity,+ FixityOverrides (..),+ defaultFixityOverrides,+ ModuleReexports (..),+ defaultModuleReexports,+ PackageFixityMap (..),+ ModuleFixityMap (..),+ FixityProvenance (..),+ FixityQualification (..),+ inferFixity, ) where @@ -23,18 +33,51 @@ import Data.Binary (Binary) import Data.ByteString.Short (ShortByteString) import Data.ByteString.Short qualified as SBS-import Data.Foldable (asum)+import Data.List.NonEmpty (NonEmpty (..))+import Data.List.NonEmpty qualified as NE import Data.Map.Strict (Map) import Data.Map.Strict qualified as Map+import Data.Maybe (fromMaybe) import Data.String (IsString (..)) import Data.Text (Text) import Data.Text qualified as T import Data.Text.Encoding qualified as T-import Distribution.Types.PackageName (PackageName)+import Distribution.ModuleName (ModuleName)+import Distribution.Types.PackageName import GHC.Data.FastString (fs_sbs) import GHC.Generics (Generic) import GHC.Types.Name (OccName (occNameFS))+import GHC.Types.Name.Reader (RdrName (..), rdrNameOcc)+import Ormolu.Utils (ghcModuleNameToCabal) +-- | An operator name.+newtype OpName = MkOpName+ { -- | Invariant: UTF-8 encoded+ getOpName :: ShortByteString+ }+ deriving newtype (Eq, Ord, Binary, NFData)++-- | Convert an 'OpName' to 'Text'.+unOpName :: OpName -> Text+unOpName = T.decodeUtf8 . SBS.fromShort . getOpName++pattern OpName :: Text -> OpName+pattern OpName opName <- (unOpName -> opName)+ where+ OpName = MkOpName . SBS.toShort . T.encodeUtf8++{-# COMPLETE OpName #-}++-- | Convert an 'OccName to an 'OpName'.+occOpName :: OccName -> OpName+occOpName = MkOpName . fs_sbs . occNameFS++instance Show OpName where+ show = T.unpack . unOpName++instance IsString OpName where+ fromString = OpName . T.pack+ -- | Fixity direction. data FixityDirection = InfixL@@ -43,102 +86,183 @@ deriving stock (Eq, Ord, Show, Generic) deriving anyclass (Binary, NFData) --- | Fixity information about an infix operator that takes the uncertainty--- that can arise from conflicting definitions into account.+-- | Fixity information about an infix operator. This type provides precise+-- information as opposed to 'FixityApproximation'. data FixityInfo = FixityInfo+ { -- | Fixity direction+ fiDirection :: FixityDirection,+ -- | Precedence+ fiPrecedence :: Int+ }+ deriving stock (Eq, Ord, Show, Generic)+ deriving anyclass (Binary, NFData)++-- | Fixity info of the built-in colon data constructor.+colonFixityInfo :: FixityInfo+colonFixityInfo = FixityInfo InfixR 5++-- | Fixity that is implicitly assumed if no fixity declaration is present.+defaultFixityInfo :: FixityInfo+defaultFixityInfo = FixityInfo InfixL 9++-- | Approximation of fixity information that takes the uncertainty that can+-- arise from conflicting definitions into account.+data FixityApproximation = FixityApproximation { -- | Fixity direction if it is known- fiDirection :: Maybe FixityDirection,+ faDirection :: Maybe FixityDirection, -- | Minimum precedence level found in the (maybe conflicting) -- definitions for the operator (inclusive)- fiMinPrecedence :: Int,+ faMinPrecedence :: Int, -- | Maximum precedence level found in the (maybe conflicting) -- definitions for the operator (inclusive)- fiMaxPrecedence :: Int+ faMaxPrecedence :: Int } deriving stock (Eq, Ord, Show, Generic) deriving anyclass (Binary, NFData) --- | The lowest level of information we can have about an operator.-defaultFixityInfo :: FixityInfo-defaultFixityInfo =- FixityInfo- { fiDirection = Just InfixL,- fiMinPrecedence = 9,- fiMaxPrecedence = 9- }---- | Fixity info of the built-in colon data constructor.-colonFixityInfo :: FixityInfo-colonFixityInfo =- FixityInfo- { fiDirection = Just InfixR,- fiMinPrecedence = 5,- fiMaxPrecedence = 5- }- -- | Gives the ability to merge two (maybe conflicting) definitions for an -- operator, keeping the higher level of compatible information from both.-instance Semigroup FixityInfo where- FixityInfo {fiDirection = dir1, fiMinPrecedence = min1, fiMaxPrecedence = max1}- <> FixityInfo {fiDirection = dir2, fiMinPrecedence = min2, fiMaxPrecedence = max2} =- FixityInfo- { fiDirection = dir',- fiMinPrecedence = min min1 min2,- fiMaxPrecedence = max max1 max2+instance Semigroup FixityApproximation where+ FixityApproximation {faDirection = dir1, faMinPrecedence = min1, faMaxPrecedence = max1}+ <> FixityApproximation {faDirection = dir2, faMinPrecedence = min2, faMaxPrecedence = max2} =+ FixityApproximation+ { faDirection = dir',+ faMinPrecedence = min min1 min2,+ faMaxPrecedence = max max1 max2 } where dir' = case (dir1, dir2) of (Just a, Just b) | a == b -> Just a _ -> Nothing --- | An operator name.-newtype OpName = MkOpName- { -- | Invariant: UTF-8 encoded- getOpName :: ShortByteString- }- deriving newtype (Eq, Ord, Binary, NFData)+-- | The lowest level of information we can have about an operator.+defaultFixityApproximation :: FixityApproximation+defaultFixityApproximation = fixityInfoToApproximation defaultFixityInfo --- | Convert an 'OpName' to 'Text'.-unOpName :: OpName -> Text-unOpName = T.decodeUtf8 . SBS.fromShort . getOpName+-- | Convert from 'FixityInfo' to 'FixityApproximation'.+fixityInfoToApproximation :: FixityInfo -> FixityApproximation+fixityInfoToApproximation FixityInfo {..} =+ FixityApproximation+ { faDirection = Just fiDirection,+ faMinPrecedence = fiPrecedence,+ faMaxPrecedence = fiPrecedence+ } -pattern OpName :: Text -> OpName-pattern OpName opName <- (unOpName -> opName)- where- OpName = MkOpName . SBS.toShort . T.encodeUtf8+-- | The map of operators declared by each package grouped by module name.+newtype HackageInfo+ = HackageInfo (Map PackageName (Map ModuleName (Map OpName FixityInfo)))+ deriving stock (Generic)+ deriving anyclass (Binary, NFData) -{-# COMPLETE OpName #-}+-- | Map from the operator name to its 'FixityInfo'.+newtype FixityOverrides = FixityOverrides+ { unFixityOverrides :: Map OpName FixityInfo+ }+ deriving stock (Eq, Show) --- | Convert an 'OccName to an 'OpName'.-occOpName :: OccName -> OpName-occOpName = MkOpName . fs_sbs . occNameFS+-- | Fixity overrides to use by default.+defaultFixityOverrides :: FixityOverrides+defaultFixityOverrides = FixityOverrides Map.empty -instance Show OpName where- show = T.unpack . unOpName+-- | Module re-exports+newtype ModuleReexports = ModuleReexports+ { unModuleReexports :: Map ModuleName (NonEmpty ModuleName)+ }+ deriving stock (Eq, Show) -instance IsString OpName where- fromString = OpName . T.pack+-- | Module re-exports to apply by default.+defaultModuleReexports :: ModuleReexports+defaultModuleReexports =+ ModuleReexports . Map.fromList $+ [ ( "Control.Lens",+ NE.fromList+ [ "Control.Lens.At",+ "Control.Lens.Cons",+ "Control.Lens.Each",+ "Control.Lens.Empty",+ "Control.Lens.Equality",+ "Control.Lens.Fold",+ "Control.Lens.Getter",+ "Control.Lens.Indexed",+ "Control.Lens.Iso",+ "Control.Lens.Lens",+ "Control.Lens.Level",+ "Control.Lens.Plated",+ "Control.Lens.Prism",+ "Control.Lens.Reified",+ "Control.Lens.Review",+ "Control.Lens.Setter",+ "Control.Lens.TH",+ "Control.Lens.Traversal",+ "Control.Lens.Tuple",+ "Control.Lens.Type",+ "Control.Lens.Wrapped",+ "Control.Lens.Zoom"+ ]+ ),+ ( "Servant",+ NE.fromList+ [ "Servant.API"+ ]+ ),+ ( "Optics",+ NE.fromList+ [ "Optics.Fold",+ "Optics.Operators",+ "Optics.IxAffineFold",+ "Optics.IxFold",+ "Optics.IxTraversal",+ "Optics.Traversal"+ ]+ )+ ] --- | Map from the operator name to its 'FixityInfo'.-type FixityMap = Map OpName FixityInfo+-- | Fixity information that is specific to a package being formatted. It+-- requires module-specific imports in order to be usable.+newtype PackageFixityMap+ = PackageFixityMap (Map OpName (NonEmpty (PackageName, ModuleName, FixityInfo)))+ deriving stock (Eq, Show) --- | A variant of 'FixityMap', represented as a lazy union of several--- 'FixityMap's.-newtype LazyFixityMap = LazyFixityMap [FixityMap]- deriving (Show)+-- | Fixity map that takes into account imports in a particular module.+newtype ModuleFixityMap+ = ModuleFixityMap (Map OpName FixityProvenance)+ deriving stock (Eq, Show) --- | Lookup a 'FixityInfo' of an operator. This might have drastically--- different performance depending on whether this is an "unusual" operator.-lookupFixity :: OpName -> LazyFixityMap -> Maybe FixityInfo-lookupFixity op (LazyFixityMap maps) = asum (Map.lookup op <$> maps)+-- | Provenance of fixity info.+data FixityProvenance+ = -- | 'FixityInfo' of a built-in operator or provided by a user override.+ Given FixityInfo+ | -- | 'FixityInfo' to be inferred from module imports.+ FromModuleImports (NonEmpty (FixityQualification, FixityInfo))+ deriving stock (Eq, Show) --- | The map of operators declared by each package and the popularity of--- each package, if available.-data HackageInfo- = HackageInfo- -- | Map from package name to a map from operator name to its fixity- (Map PackageName FixityMap)- -- | Map from package name to its 30-days download count from Hackage- (Map PackageName Int)- deriving stock (Generic)- deriving anyclass (Binary)+-- | Fixity qualification that determines how 'FixityInfo' matches a+-- particular use of an operator, given whether it is qualified or+-- unqualified and the module name used.+data FixityQualification+ = UnqualifiedAndQualified ModuleName+ | OnlyQualified ModuleName+ deriving stock (Eq, Show)++-- | Get a 'FixityApproximation' of an operator.+inferFixity :: RdrName -> ModuleFixityMap -> FixityApproximation+inferFixity rdrName (ModuleFixityMap m) =+ case Map.lookup opName m of+ Nothing -> defaultFixityApproximation+ Just (Given fixityInfo) ->+ fixityInfoToApproximation fixityInfo+ Just (FromModuleImports xs) ->+ let isMatching (provenance, _fixityInfo) =+ case provenance of+ UnqualifiedAndQualified mn ->+ maybe True (== mn) moduleName+ OnlyQualified mn ->+ maybe False (== mn) moduleName+ in fromMaybe defaultFixityApproximation+ . foldMap (Just . fixityInfoToApproximation . snd)+ $ NE.filter isMatching xs+ where+ opName = occOpName (rdrNameOcc rdrName)+ moduleName = case rdrName of+ Qual x _ -> Just (ghcModuleNameToCabal x)+ _ -> Nothing
src/Ormolu/Fixity/Parser.hs view
@@ -3,20 +3,41 @@ -- | Parser for fixity maps. module Ormolu.Fixity.Parser- ( parseFixityMap,+ ( parseDotOrmolu, parseFixityDeclaration,+ parseModuleReexportDeclaration, -- * Raw parsers pFixity, pOperator,+ pModuleName,+ pPackageName,++ -- * Internal+ isIdentifierFirstChar,+ isIdentifierConstituent,+ isOperatorConstituent,+ isPackageNameConstituent,+ isModuleSegmentFirstChar,+ isModuleSegmentConstituent, ) where +import Control.Monad (void, when)+import Data.Bifunctor (bimap)+import Data.Char (isAlphaNum, isUpper) import Data.Char qualified as Char+import Data.Either (partitionEithers)+import Data.List (intercalate)+import Data.List.NonEmpty (NonEmpty (..))+import Data.List.NonEmpty qualified as NE import Data.Map.Strict qualified as Map import Data.Text (Text) import Data.Text qualified as T import Data.Void (Void)+import Distribution.ModuleName (ModuleName)+import Distribution.ModuleName qualified as ModuleName+import Distribution.Types.PackageName (PackageName, mkPackageName) import Ormolu.Fixity import Text.Megaparsec import Text.Megaparsec.Char@@ -24,39 +45,62 @@ type Parser = Parsec Void Text --- | Parse textual representation of a 'FixityMap'.-parseFixityMap ::+-- | Parse textual representation of 'FixityOverrides'.+parseDotOrmolu :: -- | Location of the file we are parsing (only for parse errors) FilePath -> -- | File contents to parse Text -> -- | Parse result- Either (ParseErrorBundle Text Void) FixityMap-parseFixityMap = runParser pFixityMap+ Either (ParseErrorBundle Text Void) (FixityOverrides, ModuleReexports)+parseDotOrmolu = runParser pDotOrmolu -- | Parse a single self-contained fixity declaration. parseFixityDeclaration ::- -- | Expression to parse+ -- | Text to parse Text -> -- | Parse result Either (ParseErrorBundle Text Void) [(OpName, FixityInfo)] parseFixityDeclaration = runParser (pFixity <* eof) "" -pFixityMap :: Parser FixityMap-pFixityMap =- Map.fromListWith (<>) . mconcat- <$> many (pFixity <* eol <* hidden space)+-- | Parse a single self-contained module re-export declaration.+parseModuleReexportDeclaration ::+ -- | Text to parse+ Text ->+ -- | Parse result+ Either+ (ParseErrorBundle Text Void)+ (ModuleName, NonEmpty ModuleName)+parseModuleReexportDeclaration = runParser (pModuleReexport <* eof) ""++pDotOrmolu :: Parser (FixityOverrides, ModuleReexports)+pDotOrmolu =+ bimap+ (FixityOverrides . Map.fromList . mconcat)+ (ModuleReexports . Map.map NE.sort . Map.fromListWith (<>))+ . partitionEithers+ <$> many configLine <* eof+ where+ configLine = do+ x <- eitherP pFixity pModuleReexport+ void eol+ hidden space+ return x -- | Parse a single fixity declaration, such as -- -- > infixr 4 +++, >>> pFixity :: Parser [(OpName, FixityInfo)] pFixity = do- fiDirection <- Just <$> pFixityDirection+ fiDirection <- pFixityDirection hidden hspace1- fiMinPrecedence <- L.decimal- let fiMaxPrecedence = fiMinPrecedence+ offsetAtPrecedence <- getOffset+ fiPrecedence <- L.decimal+ when (fiPrecedence > 9) $+ region+ (setErrorOffset offsetAtPrecedence)+ (fail "precedence should not be greater than 9") hidden hspace1 ops <- sepBy1 pOperator (char ',' >> hidden hspace) hidden hspace@@ -80,8 +124,60 @@ haskellIdentifier = T.cons <$> letterChar- <*> takeWhileP Nothing (\x -> Char.isAlphaNum x || x == '_' || x == '\'')+ <*> takeWhileP Nothing isIdentifierConstituent normalOperator =- takeWhile1P (Just "operator character") $ \x ->- (Char.isSymbol x || Char.isPunctuation x)- && (x /= ',' && x /= '`' && x /= '(' && x /= ')')+ takeWhile1P (Just "operator character") isOperatorConstituent++pModuleReexport :: Parser (ModuleName, NonEmpty ModuleName)+pModuleReexport = do+ void (string "module")+ hidden hspace1+ exportingModule <- pModuleName+ hidden hspace1+ void (string "exports")+ hidden hspace1+ exportedModule <- pModuleName+ hidden hspace+ return (exportingModule, NE.singleton exportedModule)++pModuleName :: Parser ModuleName+pModuleName =+ ModuleName.fromString . intercalate "."+ <$> sepBy1 pModuleSegment (char '.')+ <?> "module name"+ where+ pModuleSegment = do+ x <- satisfy isModuleSegmentFirstChar <?> "capital letter"+ xs <-+ many+ ( satisfy isModuleSegmentConstituent+ <?> "module segment continuation"+ )+ return (x : xs)++pPackageName :: Parser PackageName+pPackageName =+ mkPackageName <$> some (satisfy isPackageNameConstituent) <?> "package name"++-- Internal predicates (exposed for testing)++isIdentifierFirstChar :: Char -> Bool+isIdentifierFirstChar = Char.isLetter++isIdentifierConstituent :: Char -> Bool+isIdentifierConstituent x = Char.isAlphaNum x || x == '_' || x == '\''++isOperatorConstituent :: Char -> Bool+isOperatorConstituent x =+ (Char.isSymbol x || Char.isPunctuation x)+ && (x /= ',' && x /= '`' && x /= '(' && x /= ')')++isPackageNameConstituent :: Char -> Bool+isPackageNameConstituent x = x == '-' || isAlphaNum x++isModuleSegmentFirstChar :: Char -> Bool+isModuleSegmentFirstChar x = isAlphaNum x && isUpper x++isModuleSegmentConstituent :: Char -> Bool+isModuleSegmentConstituent x =+ x == '_' || x == '\'' || isAlphaNum x
src/Ormolu/Fixity/Printer.hs view
@@ -1,55 +1,71 @@ {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} --- | Printer for fixity maps.+-- | Printer for fixity overrides. module Ormolu.Fixity.Printer- ( printFixityMap,+ ( printDotOrmolu, ) where import Data.Char qualified as Char+import Data.List (intercalate)+import Data.List.NonEmpty (NonEmpty) import Data.Map.Strict qualified as Map+import Data.Semigroup (sconcat) import Data.Text (Text) import Data.Text qualified as T import Data.Text.Lazy qualified as TL import Data.Text.Lazy.Builder (Builder) import Data.Text.Lazy.Builder qualified as B import Data.Text.Lazy.Builder.Int qualified as B+import Distribution.ModuleName (ModuleName)+import Distribution.ModuleName qualified as ModuleName import Ormolu.Fixity --- | Print out a textual representation of a 'FixityMap'.-printFixityMap :: FixityMap -> Text-printFixityMap =- TL.toStrict- . B.toLazyText- . mconcat- . fmap renderOne- . concatMap decompose- . Map.toList+-- | Print out a textual representation of an @.ormolu@ file.+printDotOrmolu ::+ FixityOverrides ->+ ModuleReexports ->+ Text+printDotOrmolu+ (FixityOverrides fixityOverrides)+ (ModuleReexports moduleReexports) =+ TL.toStrict . B.toLazyText $+ (mconcat . fmap renderSingleFixityOverride . Map.toList) fixityOverrides+ <> (mconcat . fmap renderSingleModuleReexport . Map.toList) moduleReexports++renderSingleFixityOverride :: (OpName, FixityInfo) -> Builder+renderSingleFixityOverride (OpName operator, FixityInfo {..}) =+ mconcat+ [ case fiDirection of+ InfixL -> "infixl"+ InfixR -> "infixr"+ InfixN -> "infix",+ " ",+ B.decimal fiPrecedence,+ " ",+ if isTickedOperator operator+ then "`" <> B.fromText operator <> "`"+ else B.fromText operator,+ "\n"+ ] where- decompose :: (OpName, FixityInfo) -> [(FixityDirection, Int, OpName)]- decompose (operator, FixityInfo {..}) =- let forDirection dir =- (dir, fiMinPrecedence, operator)- : [ (dir, fiMaxPrecedence, operator)- | fiMinPrecedence /= fiMaxPrecedence- ]- in case fiDirection of- Nothing -> concatMap forDirection [InfixL, InfixR]- Just dir -> forDirection dir- renderOne :: (FixityDirection, Int, OpName) -> Builder- renderOne (fixityDirection, n, OpName operator) =+ isTickedOperator = maybe True (Char.isLetter . fst) . T.uncons++renderSingleModuleReexport ::+ (ModuleName, NonEmpty ModuleName) ->+ Builder+renderSingleModuleReexport (exportingModule, exports) =+ sconcat (renderSingle <$> exports)+ where+ renderSingle exportedModule = mconcat- [ case fixityDirection of- InfixL -> "infixl"- InfixR -> "infixr"- InfixN -> "infix",- " ",- B.decimal n,- " ",- if isTickedOperator operator- then "`" <> B.fromText operator <> "`"- else B.fromText operator,+ [ "module ",+ renderModuleName exportingModule,+ " exports ",+ renderModuleName exportedModule, "\n" ]- isTickedOperator = maybe True (Char.isLetter . fst) . T.uncons++renderModuleName :: ModuleName -> Builder+renderModuleName = B.fromString . intercalate "." . ModuleName.components
src/Ormolu/Parser.hs view
@@ -18,31 +18,38 @@ import Control.Monad.IO.Class import Data.Char (isSpace) import Data.Functor-import Data.Generics+import Data.Generics hiding (orElse) import Data.List qualified as L import Data.List.NonEmpty qualified as NE import Data.Text (Text)+import GHC.Builtin.Names (mAIN_NAME) import GHC.Data.Bag (bagToList) import GHC.Data.EnumSet qualified as EnumSet import GHC.Data.FastString qualified as GHC+import GHC.Data.Maybe (orElse)+import GHC.Data.StringBuffer (StringBuffer) import GHC.Driver.CmdLine qualified as GHC import GHC.Driver.Config.Parser (initParserOpts)+import GHC.Driver.Errors.Types qualified as GHC import GHC.Driver.Session as GHC import GHC.DynFlags (baseDynFlags) import GHC.Hs hiding (UnicodeSyntax) import GHC.LanguageExtensions.Type (Extension (..)) import GHC.Parser qualified as GHC+import GHC.Parser.Annotation qualified as GHC import GHC.Parser.Header qualified as GHC import GHC.Parser.Lexer qualified as GHC-import GHC.Types.Error (NoDiagnosticOpts (..), getMessages)-import GHC.Types.SourceError qualified as GHC (handleSourceError)+import GHC.Types.Error qualified as GHC+import GHC.Types.SourceError qualified as GHC import GHC.Types.SrcLoc import GHC.Utils.Error+import GHC.Utils.Exception (ExceptionMonad) import GHC.Utils.Outputable (defaultSDocContext) import GHC.Utils.Panic qualified as GHC import Ormolu.Config import Ormolu.Exception-import Ormolu.Fixity (LazyFixityMap)+import Ormolu.Fixity hiding (packageFixityMap)+import Ormolu.Fixity.Imports (applyModuleReexports, extractFixityImports) import Ormolu.Imports (normalizeImports) import Ormolu.Parser.CommentStream import Ormolu.Parser.Result@@ -50,13 +57,13 @@ import Ormolu.Processing.Preprocess import Ormolu.Utils (incSpanLine, showOutputable, textToStringBuffer) --- | Parse a complete module from string.+-- | Parse a complete module from 'Text'. parseModule :: (MonadIO m) => -- | Ormolu configuration Config RegionDeltas ->- -- | Fixity map to include in the resulting 'ParseResult's- LazyFixityMap ->+ -- | Package fixity map+ PackageFixityMap -> -- | File name (only for source location annotations) FilePath -> -- | Input for parser@@ -65,7 +72,7 @@ ( [GHC.Warn], Either (SrcSpan, String) [SourceSnippet] )-parseModule config@Config {..} fixityMap path rawInput = liftIO $ do+parseModule config@Config {..} packageFixityMap path rawInput = liftIO $ do -- It's important that 'setDefaultExts' is done before -- 'parsePragmasIntoDynFlags', because otherwise we might enable an -- extension that was explicitly disabled in the file.@@ -74,35 +81,46 @@ GHC.Opt_Haddock (setDefaultExts baseDynFlags) extraOpts = dynOptionToLocatedStr <$> cfgDynOptions+ rawInputStringBuffer = textToStringBuffer rawInput+ beginningLoc =+ mkSrcSpan+ (mkSrcLoc (GHC.mkFastString path) 1 1)+ (mkSrcLoc (GHC.mkFastString path) 1 1) (warnings, dynFlags) <-- parsePragmasIntoDynFlags baseFlags extraOpts path rawInput >>= \case+ parsePragmasIntoDynFlags baseFlags extraOpts path rawInputStringBuffer >>= \case Right res -> pure res- Left err ->- let loc =- mkSrcSpan- (mkSrcLoc (GHC.mkFastString path) 1 1)- (mkSrcLoc (GHC.mkFastString path) 1 1)- in throwIO (OrmoluParsingFailed loc err)+ Left err -> throwIO (OrmoluParsingFailed beginningLoc err) let cppEnabled = EnumSet.member Cpp (GHC.extensionFlags dynFlags)+ implicitPrelude = EnumSet.member ImplicitPrelude (GHC.extensionFlags dynFlags)+ fixityImports <-+ parseImports dynFlags implicitPrelude path rawInputStringBuffer >>= \case+ Right res ->+ pure (applyModuleReexports cfgModuleReexports (extractFixityImports res))+ Left err ->+ throwIO (OrmoluParsingFailed beginningLoc err)+ let modFixityMap =+ applyFixityOverrides+ cfgFixityOverrides+ (moduleFixityMap packageFixityMap fixityImports) snippets <- runExceptT . forM (preprocess cppEnabled cfgRegion rawInput) $ \case Right region -> fmap ParsedSnippet . ExceptT $- parseModuleSnippet (config $> region) fixityMap dynFlags path rawInput+ parseModuleSnippet (config $> region) modFixityMap dynFlags path rawInput Left raw -> pure $ RawSnippet raw pure (warnings, snippets) parseModuleSnippet :: (MonadIO m) => Config RegionDeltas ->- LazyFixityMap ->+ ModuleFixityMap -> DynFlags -> FilePath -> Text -> m (Either (SrcSpan, String) ParseResult)-parseModuleSnippet Config {..} fixityMap dynFlags path rawInput = liftIO $ do+parseModuleSnippet Config {..} modFixityMap dynFlags path rawInput = liftIO $ do let (input, indent) = removeIndentation . linesInRegion cfgRegion $ rawInput let pStateErrors pstate =- let errs = bagToList . getMessages $ GHC.getPsErrorMessages pstate+ let errs = bagToList . GHC.getMessages $ GHC.getPsErrorMessages pstate fixupErrSpan = incSpanLine (regionPrefixLength cfgRegion) rateSeverity = \case SevError -> 1 :: Int@@ -116,7 +134,7 @@ msg = showOutputable . formatBulleted defaultSDocContext- . diagnosticMessage NoDiagnosticOpts+ . diagnosticMessage GHC.NoDiagnosticOpts $ err in case L.sortOn (rateSeverity . errMsgSeverity) errs of [] -> Nothing@@ -148,8 +166,7 @@ prPragmas = pragmas, prCommentStream = comments, prExtensions = GHC.extensionFlags dynFlags,- prFixityOverrides = cfgFixityOverrides,- prFixityMap = fixityMap,+ prModuleFixityMap = modFixityMap, prIndent = indent } return r@@ -253,6 +270,8 @@ ---------------------------------------------------------------------------- -- Helpers taken from HLint +-- | Detect pragmas in the given input and return them as a collection of+-- 'DynFlags'. parsePragmasIntoDynFlags :: -- | Pre-set 'DynFlags' DynFlags ->@@ -261,14 +280,14 @@ -- | File name (only for source location annotations) FilePath -> -- | Input for parser- Text ->+ StringBuffer -> IO (Either String ([GHC.Warn], DynFlags))-parsePragmasIntoDynFlags flags extraOpts filepath str =- catchErrors $ do+parsePragmasIntoDynFlags flags extraOpts filepath input =+ catchGhcErrors $ do let (_warnings, fileOpts) = GHC.getOptions (initParserOpts flags)- (textToStringBuffer str)+ input filepath (flags', leftovers, warnings) <- parseDynamicFilePragma flags (extraOpts <> fileOpts)@@ -278,9 +297,45 @@ throwIO (OrmoluUnrecognizedOpts (unLoc <$> unrecognizedOpts)) let flags'' = flags' `gopt_set` Opt_KeepRawTokenStream return $ Right (warnings, flags'')++-- | Detect the collection of imports used in the given input.+parseImports ::+ -- | Pre-set 'DynFlags'+ DynFlags ->+ -- | Implicit Prelude?+ Bool ->+ -- | File name (only for source location annotations)+ FilePath ->+ -- | Input for the parser+ StringBuffer ->+ IO (Either String [LImportDecl GhcPs])+parseImports flags implicitPrelude filepath input =+ case GHC.unP GHC.parseHeader (GHC.initParserState popts input loc) of+ GHC.PFailed pst ->+ return $ Left (showOutputable (GHC.getPsErrorMessages pst))+ GHC.POk pst rdr_module ->+ return $+ let (_warnings, errors) = GHC.getPsMessages pst+ in if not (isEmptyMessages errors)+ then Left (showOutputable (GHC.GhcPsMessage <$> errors))+ else+ let hsmod = unLoc rdr_module+ mmoduleName = hsmodName hsmod+ main_loc = srcLocSpan (mkSrcLoc (GHC.mkFastString filepath) 1 1)+ mod' = mmoduleName `orElse` L (GHC.noAnnSrcSpan main_loc) mAIN_NAME+ explicitImports = hsmodImports hsmod+ implicitImports =+ GHC.mkPrelImports (unLoc mod') main_loc implicitPrelude explicitImports+ in Right (explicitImports ++ implicitImports) where- catchErrors act =- GHC.handleGhcException- reportErr- (GHC.handleSourceError reportErr act)+ popts = initParserOpts flags+ loc = mkRealSrcLoc (GHC.mkFastString filepath) 1 1++-- | Catch and report GHC errors.+catchGhcErrors :: (ExceptionMonad m) => m (Either String a) -> m (Either String a)+catchGhcErrors m =+ GHC.handleGhcException+ reportErr+ (GHC.handleSourceError reportErr m)+ where reportErr e = return $ Left (show e)
src/Ormolu/Parser/CommentStream.hs view
@@ -10,6 +10,7 @@ showCommentStream, -- * Comment+ LComment, Comment (..), unComment, hasAtomsBefore,@@ -44,7 +45,7 @@ -- | A stream of 'RealLocated' 'Comment's in ascending order with respect to -- beginning of corresponding spans.-newtype CommentStream = CommentStream [RealLocated Comment]+newtype CommentStream = CommentStream [LComment] deriving (Eq, Data, Semigroup, Monoid) -- | Create 'CommentStream' from 'HsModule'. The pragmas are@@ -55,8 +56,8 @@ -- | Module to use for comment extraction HsModule GhcPs -> -- | Stack header, pragmas, and comment stream- ( Maybe (RealLocated Comment),- [([RealLocated Comment], Pragma)],+ ( Maybe LComment,+ [([LComment], Pragma)], CommentStream ) mkCommentStream input hsModule =@@ -115,6 +116,8 @@ ---------------------------------------------------------------------------- -- Comment +type LComment = RealLocated Comment+ -- | A wrapper for a single comment. The 'Bool' indicates whether there were -- atoms before beginning of the comment in the original input. The -- 'NonEmpty' list inside contains lines of multiline comment @{- … -}@ or@@ -131,7 +134,7 @@ -- | Raw comment string RealLocated Text -> -- | Remaining lines of original input and the constructed 'Comment'- ([(Int, Text)], RealLocated Comment)+ ([(Int, Text)], LComment) mkComment ls (L l s) = (ls', comment) where comment =@@ -185,7 +188,7 @@ extractStackHeader :: -- | Comment stream to analyze [RealLocated Text] ->- ([RealLocated Text], Maybe (RealLocated Comment))+ ([RealLocated Text], Maybe LComment) extractStackHeader = \case [] -> ([], Nothing) (x : xs) ->@@ -203,7 +206,7 @@ Text -> -- | Comment stream to analyze [RealLocated Text] ->- ([RealLocated Comment], [([RealLocated Comment], Pragma)])+ ([LComment], [([LComment], Pragma)]) extractPragmas input = go initialLs id id where initialLs = zip [1 ..] (T.lines input)
src/Ormolu/Parser/Result.hs view
@@ -9,9 +9,8 @@ import GHC.Data.EnumSet (EnumSet) import GHC.Hs import GHC.LanguageExtensions.Type-import GHC.Types.SrcLoc import Ormolu.Config (SourceType)-import Ormolu.Fixity (FixityMap, LazyFixityMap)+import Ormolu.Fixity (ModuleFixityMap) import Ormolu.Parser.CommentStream import Ormolu.Parser.Pragma (Pragma) @@ -25,17 +24,15 @@ -- | Either regular module or signature file prSourceType :: SourceType, -- | Stack header- prStackHeader :: Maybe (RealLocated Comment),+ prStackHeader :: Maybe LComment, -- | Pragmas and the associated comments- prPragmas :: [([RealLocated Comment], Pragma)],+ prPragmas :: [([LComment], Pragma)], -- | Comment stream prCommentStream :: CommentStream, -- | Enabled extensions prExtensions :: EnumSet Extension,- -- | Fixity overrides- prFixityOverrides :: FixityMap, -- | Fixity map for operators- prFixityMap :: LazyFixityMap,+ prModuleFixityMap :: ModuleFixityMap, -- | Indentation level, can be non-zero in case of region formatting prIndent :: Int }
src/Ormolu/Printer.hs view
@@ -36,6 +36,5 @@ prCommentStream prSourceType prExtensions- prFixityOverrides- prFixityMap+ prModuleFixityMap RawSnippet r -> r
src/Ormolu/Printer/Combinators.hs view
@@ -10,6 +10,7 @@ R, runR, getEnclosingSpan,+ getEnclosingSpanWhere, isExtensionEnabled, -- * Combinators@@ -21,11 +22,10 @@ newline, inci, inciIf,- inciHalf, askSourceType,- askFixityOverrides,- askFixityMap,+ askModuleFixityMap, located,+ encloseLocated, located', switchLayout, Layout (..),@@ -78,7 +78,7 @@ import GHC.Types.SrcLoc import Ormolu.Printer.Comments import Ormolu.Printer.Internal-import Ormolu.Utils (HasSrcSpan (..))+import Ormolu.Utils (HasSrcSpan (..), getLoc') ---------------------------------------------------------------------------- -- Basic@@ -111,6 +111,23 @@ withEnclosingSpan l $ switchLayout [RealSrcSpan l Strict.Nothing] (f a) spitFollowingComments l++-- | Similar to 'located', but when the "payload" is an empty list, print+-- virtual elements at the start and end of the source span to prevent comments+-- from "floating out".+encloseLocated ::+ (HasSrcSpan l) =>+ GenLocated l [a] ->+ ([a] -> R ()) ->+ R ()+encloseLocated la f = located la $ \a -> do+ when (null a) $ located (L startSpan ()) pure+ f a+ when (null a) $ located (L endSpan ()) pure+ where+ l = getLoc' la+ (startLoc, endLoc) = (srcSpanStart l, srcSpanEnd l)+ (startSpan, endSpan) = (mkSrcSpan startLoc startLoc, mkSrcSpan endLoc endLoc) -- | A version of 'located' with arguments flipped. located' ::
src/Ormolu/Printer/Comments.hs view
@@ -27,8 +27,8 @@ RealSrcSpan -> R () spitPrecedingComments ref = do- gotSome <- handleCommentSeries (spitPrecedingComment ref)- when gotSome $ do+ comments <- handleCommentSeries (spitPrecedingComment ref)+ when (not $ null comments) $ do lastMark <- getSpanMark -- Insert a blank line between the preceding comments and the thing -- after them if there was a blank line in the input.@@ -58,8 +58,8 @@ spitPrecedingComment :: -- | Span of the element to attach comments to RealSrcSpan ->- -- | Are we done?- R Bool+ -- | The comment that was output, if any+ R (Maybe LComment) spitPrecedingComment ref = do mlastMark <- getSpanMark let p (L l _) = realSrcSpanEnd l <= realSrcSpanStart ref@@ -81,14 +81,14 @@ spitFollowingComment :: -- | AST element to attach comments to RealSrcSpan ->- -- | Are we done?- R Bool+ -- | The comment that was output, if any+ R (Maybe LComment) spitFollowingComment ref = do mlastMark <- getSpanMark mnSpn <- nextEltSpan -- Get first enclosing span that is not equal to reference span, i.e. it's -- truly something enclosing the AST element.- meSpn <- getEnclosingSpan (/= ref)+ meSpn <- getEnclosingSpanWhere (/= ref) withPoppedComment (commentFollowsElt ref mnSpn meSpn mlastMark) $ \l comment -> if theSameLinePost l ref then@@ -102,8 +102,8 @@ -- | Output a single remaining comment from the comment stream. spitRemainingComment ::- -- | Are we done?- R Bool+ -- | The comment that was output, if any+ R (Maybe LComment) spitRemainingComment = do mlastMark <- getSpanMark withPoppedComment (const True) $ \l comment -> do@@ -116,33 +116,33 @@ -- | Output series of comments. handleCommentSeries ::- -- | Given location of previous comment, output the next comment- -- returning 'True' if we're done- R Bool ->- -- | Whether we printed any comments- R Bool-handleCommentSeries f = go False+ -- | Output and return the next comment, if any+ R (Maybe LComment) ->+ -- | The comments outputted+ R [LComment]+handleCommentSeries f = go where- go gotSome = do- done <- f- if done- then return gotSome- else go True+ go = do+ mComment <- f+ case mComment of+ Nothing -> return []+ Just comment -> (comment :) <$> go -- | Try to pop a comment using given predicate and if there is a comment -- matching the predicate, print it out. withPoppedComment :: -- | Comment predicate- (RealLocated Comment -> Bool) ->+ (LComment -> Bool) -> -- | Printing function (RealSrcSpan -> Comment -> R ()) -> -- | Are we done?- R Bool+ R (Maybe LComment) withPoppedComment p f = do r <- popComment p case r of- Nothing -> return True- Just (L l comment) -> False <$ f l comment+ Nothing -> return ()+ Just (L l comment) -> f l comment+ return r -- | Determine if we need to insert a newline between current comment and -- last printed comment.@@ -190,7 +190,7 @@ -- | Location of last comment in the series Maybe SpanMark -> -- | Comment to test- RealLocated Comment ->+ LComment -> Bool commentFollowsElt ref mnSpn meSpn mlastMark (L l comment) = -- A comment follows a AST element if all 4 conditions are satisfied:
src/Ormolu/Printer/Internal.hs view
@@ -17,10 +17,8 @@ space, newline, askSourceType,- askFixityOverrides,- askFixityMap,+ askModuleFixityMap, inci,- inciHalf, sitcc, Layout (..), enterLayout,@@ -39,6 +37,7 @@ nextEltSpan, popComment, getEnclosingSpan,+ getEnclosingSpanWhere, withEnclosingSpan, thisLineSpans, @@ -59,6 +58,7 @@ import Control.Monad.State.Strict import Data.Bool (bool) import Data.Coerce+import Data.List (find) import Data.Maybe (listToMaybe) import Data.Text (Text) import Data.Text qualified as T@@ -70,7 +70,7 @@ import GHC.Types.SrcLoc import GHC.Utils.Outputable (Outputable) import Ormolu.Config (SourceType (..))-import Ormolu.Fixity (FixityMap, LazyFixityMap)+import Ormolu.Fixity (ModuleFixityMap) import Ormolu.Parser.CommentStream import Ormolu.Printer.SpanStream import Ormolu.Utils (showOutputable)@@ -99,12 +99,8 @@ rcExtensions :: EnumSet Extension, -- | Whether the source is a signature or a regular module rcSourceType :: SourceType,- -- | Fixity map overrides, kept separately because if we parametrized- -- 'Ormolu.Fixity.buildFixityMap' by fixity overrides it would break- -- memoization- rcFixityOverrides :: FixityMap,- -- | Fixity map for operators- rcFixityMap :: LazyFixityMap+ -- | Module fixity map+ rcModuleFixityMap :: ModuleFixityMap } -- | State context of 'R'.@@ -172,13 +168,11 @@ SourceType -> -- | Enabled extensions EnumSet Extension ->- -- | Fixity overrides- FixityMap ->- -- | Fixity map- LazyFixityMap ->+ -- | Module fixity map+ ModuleFixityMap -> -- | Resulting rendition Text-runR (R m) sstream cstream sourceType extensions fixityOverrides fixityMap =+runR (R m) sstream cstream sourceType extensions moduleFixityMap = TL.toStrict . toLazyText . scBuilder $ execState (runReaderT m rc) sc where rc =@@ -189,8 +183,7 @@ rcCanUseBraces = False, rcExtensions = extensions, rcSourceType = sourceType,- rcFixityOverrides = fixityOverrides,- rcFixityMap = fixityMap+ rcModuleFixityMap = moduleFixityMap } sc = SC@@ -387,13 +380,9 @@ askSourceType :: R SourceType askSourceType = R (asks rcSourceType) --- | Retrieve fixity overrides map.-askFixityOverrides :: R FixityMap-askFixityOverrides = R (asks rcFixityOverrides)---- | Retrieve the lazy fixity map.-askFixityMap :: R LazyFixityMap-askFixityMap = R (asks rcFixityMap)+-- | Retrieve the module fixity map.+askModuleFixityMap :: R ModuleFixityMap+askModuleFixityMap = R (asks rcModuleFixityMap) inciBy :: Int -> R () -> R () inciBy step (R m) = R (local modRC m)@@ -411,11 +400,6 @@ inci :: R () -> R () inci = inciBy indentStep --- | In rare cases, we have to indent by a positive amount smaller--- than 'indentStep'.-inciHalf :: R () -> R ()-inciHalf = inciBy $ (indentStep `div` 2) `max` 1- -- | Set indentation level for the inner computation equal to current -- column. This makes sure that the entire inner block is uniformly -- \"shifted\" to the right.@@ -496,31 +480,27 @@ -- | Pop a 'Comment' from the 'CommentStream' if given predicate is -- satisfied and there are comments in the stream. popComment ::- (RealLocated Comment -> Bool) ->- R (Maybe (RealLocated Comment))+ (LComment -> Bool) ->+ R (Maybe LComment) popComment f = R $ do CommentStream cstream <- gets scCommentStream case cstream of- [] -> return Nothing- (x : xs) ->- if f x- then- Just x- <$ modify- ( \sc ->- sc- { scCommentStream = CommentStream xs- }- )- else return Nothing+ (x : xs) | f x -> do+ modify $ \sc -> sc {scCommentStream = CommentStream xs}+ return $ Just x+ _ -> return Nothing +-- | Get the immediately enclosing 'RealSrcSpan'.+getEnclosingSpan :: R (Maybe RealSrcSpan)+getEnclosingSpan = getEnclosingSpanWhere (const True)+ -- | Get the first enclosing 'RealSrcSpan' that satisfies given predicate.-getEnclosingSpan ::+getEnclosingSpanWhere :: -- | Predicate to use (RealSrcSpan -> Bool) -> R (Maybe RealSrcSpan)-getEnclosingSpan f =- listToMaybe . filter f <$> R (asks rcEnclosingSpans)+getEnclosingSpanWhere f =+ find f <$> R (asks rcEnclosingSpans) -- | Set 'RealSrcSpan' of enclosing span for the given computation. withEnclosingSpan :: RealSrcSpan -> R () -> R ()
src/Ormolu/Printer/Meat/Common.hs view
@@ -164,7 +164,7 @@ -- It's often the case that the comment itself doesn't have a span -- attached to it and instead its location can be obtained from -- nearest enclosing span.- getEnclosingSpan (const True) >>= mapM_ (setSpanMark . HaddockSpan hstyle)+ getEnclosingSpan >>= mapM_ (setSpanMark . HaddockSpan hstyle) RealSrcSpan spn _ -> setSpanMark (HaddockSpan hstyle spn) -- | Print anchor of named doc section.
src/Ormolu/Printer/Meat/Declaration/Data.hs view
@@ -59,10 +59,18 @@ txt " #-}" let constructorSpans = getLocA name : fmap lhsTypeArgSrcSpan tpats sigSpans = maybeToList . fmap getLocA $ dd_kindSig- declHeaderSpans = constructorSpans ++ sigSpans+ declHeaderSpans =+ maybeToList (getLocA <$> dd_ctxt) ++ constructorSpans ++ sigSpans switchLayout declHeaderSpans $ do breakpoint inci $ do+ case dd_ctxt of+ Nothing -> pure ()+ Just ctxt -> do+ located ctxt p_hsContext+ space+ txt "=>"+ breakpoint switchLayout constructorSpans $ p_infixDefHelper (isInfix fixity)
src/Ormolu/Printer/Meat/Declaration/OpTree.hs view
@@ -23,7 +23,8 @@ import Ormolu.Printer.Combinators import Ormolu.Printer.Meat.Common (p_rdrName) import Ormolu.Printer.Meat.Declaration.Value- ( cmdTopPlacement,+ ( IsApplicand (..),+ cmdTopPlacement, exprPlacement, p_hsCmdTop, p_hsExpr,@@ -91,7 +92,7 @@ -- operator fixity OpTree (LHsExpr GhcPs) (OpInfo (LHsExpr GhcPs)) -> R ()-p_exprOpTree s (OpNode x) = located x (p_hsExpr' s)+p_exprOpTree s (OpNode x) = located x (p_hsExpr' NotApplicand s) p_exprOpTree s t@(OpBranches exprs ops) = do let firstExpr = head exprs otherExprs = tail exprs@@ -114,7 +115,7 @@ couldBeTrailing (prevExpr, opi) = -- An operator with fixity InfixR 0, like seq, $, and $ variants, -- is required- isHardSplitterOp (opiFix opi)+ isHardSplitterOp (opiFixityApproximation opi) -- the LHS must be single-line && isOneLineSpan (opTreeLoc prevExpr) -- can only happen when a breakpoint would have been added anyway
src/Ormolu/Printer/Meat/Declaration/Value.hs view
@@ -10,6 +10,7 @@ p_hsExpr, p_hsUntypedSplice, p_stringLit,+ IsApplicand (..), p_hsExpr', p_hsCmdTop, exprPlacement,@@ -186,7 +187,7 @@ False <$ case style of Function name -> p_rdrName name _ -> return ()- Just ne_pats -> do+ Just ne_pats@(head_pat :| tail_pats) -> do let combinedSpans = case style of Function name -> combineSrcSpans (getLocA name) patSpans _ -> patSpans@@ -204,7 +205,7 @@ PatternBind -> stdCase Case -> stdCase Lambda -> do- let needsSpace = case unLoc (NE.head ne_pats) of+ let needsSpace = case unLoc head_pat of LazyPat _ _ -> True BangPat _ _ -> True SplicePat _ _ -> True@@ -212,7 +213,13 @@ txt "\\" when needsSpace space sitcc stdCase- LambdaCase -> stdCase+ LambdaCase -> do+ located' p_pat head_pat+ unless (null tail_pats) $ do+ breakpoint+ -- When we have multiple patterns (with `\cases`) across multiple+ -- lines, we have to indent all but the first pattern.+ inci $ sep breakpoint (located' p_pat) tail_pats return indentBody let -- Calculate position of end of patterns. This is useful when we decide -- about putting certain constructions in hanging positions.@@ -317,10 +324,10 @@ p_body = located body render p_hsCmd :: HsCmd GhcPs -> R ()-p_hsCmd = p_hsCmd' N+p_hsCmd = p_hsCmd' NotApplicand N -p_hsCmd' :: BracketStyle -> HsCmd GhcPs -> R ()-p_hsCmd' s = \case+p_hsCmd' :: IsApplicand -> BracketStyle -> HsCmd GhcPs -> R ()+p_hsCmd' isApp s = \case HsCmdArrApp _ body input arrType rightToLeft -> do let (l, r) = if rightToLeft then (body, input) else (input, body) located l p_hsExpr@@ -339,34 +346,33 @@ breakpoint inci (sequence_ (intersperse breakpoint (located' (p_hsCmdTop N) <$> cmds))) HsCmdArrForm _ form Infix _ [left, right] -> do- fixityOverrides <- askFixityOverrides- fixityMap <- askFixityMap+ modFixityMap <- askModuleFixityMap let opTree = OpBranches [cmdOpTree left, cmdOpTree right] [form] p_cmdOpTree s- (reassociateOpTree (getOpName . unLoc) fixityOverrides fixityMap opTree)+ (reassociateOpTree (getOpName . unLoc) modFixityMap opTree) HsCmdArrForm _ _ Infix _ _ -> notImplemented "HsCmdArrForm" HsCmdApp _ cmd expr -> do- located cmd (p_hsCmd' s)- space- located expr p_hsExpr+ located cmd (p_hsCmd' Applicand s)+ breakpoint+ inci $ located expr p_hsExpr HsCmdLam _ mgroup -> p_matchGroup' cmdPlacement p_hsCmd Lambda mgroup HsCmdPar _ _ c _ -> parens N (located c p_hsCmd) HsCmdCase _ e mgroup ->- p_case cmdPlacement p_hsCmd e mgroup+ p_case isApp cmdPlacement p_hsCmd e mgroup HsCmdLamCase _ variant mgroup ->- p_lamcase variant cmdPlacement p_hsCmd mgroup+ p_lamcase isApp variant cmdPlacement p_hsCmd mgroup HsCmdIf _ _ if' then' else' -> p_if cmdPlacement p_hsCmd if' then' else' HsCmdLet _ _ localBinds _ c -> p_let p_hsCmd localBinds c HsCmdDo _ es -> do txt "do"- p_stmts cmdPlacement (p_hsCmd' S) es+ p_stmts isApp cmdPlacement (p_hsCmd' NotApplicand S) es -- | Print a top-level command. p_hsCmdTop :: BracketStyle -> HsCmdTop GhcPs -> R ()-p_hsCmdTop s (HsCmdTop _ cmd) = located cmd (p_hsCmd' s)+p_hsCmdTop s (HsCmdTop _ cmd) = located cmd (p_hsCmd' NotApplicand s) -- | Render an expression preserving blank lines between such consecutive -- expressions found in the original source code.@@ -472,6 +478,7 @@ ( Anno (Stmt GhcPs (LocatedA body)) ~ SrcSpanAnnA, Anno [LocatedA (Stmt GhcPs (LocatedA body))] ~ SrcSpanAnnL ) =>+ IsApplicand -> -- | Placer (body -> Placement) -> -- | Render@@ -479,10 +486,10 @@ -- | Statements to render LocatedL [LocatedA (Stmt GhcPs (LocatedA body))] -> R ()-p_stmts placer render es = do+p_stmts isApp placer render es = do breakpoint ub <- layoutToBraces <$> getLayout- inci . located es $+ inciApplicand isApp . located es $ sepSemi (ub . withSpacing (p_stmt' placer render)) @@ -567,10 +574,24 @@ placeHanging placement (located hfbRHS p_hsExpr) p_hsExpr :: HsExpr GhcPs -> R ()-p_hsExpr = p_hsExpr' N+p_hsExpr = p_hsExpr' NotApplicand N -p_hsExpr' :: BracketStyle -> HsExpr GhcPs -> R ()-p_hsExpr' s = \case+-- | An applicand is the left-hand side in a function application, i.e. @f@ in+-- @f a@. We need to track this in order to add extra identation in cases like+--+-- > foo =+-- > do+-- > succ+-- > 1+data IsApplicand = Applicand | NotApplicand++inciApplicand :: IsApplicand -> R () -> R ()+inciApplicand = \case+ Applicand -> inci . inci+ NotApplicand -> inci++p_hsExpr' :: IsApplicand -> BracketStyle -> HsExpr GhcPs -> R ()+p_hsExpr' isApp s = \case HsVar _ name -> p_rdrName name HsUnboundVar _ occ -> atom occ HsRecSel _ fldOcc -> p_fieldOcc fldOcc@@ -589,7 +610,7 @@ HsLam _ mgroup -> p_matchGroup Lambda mgroup HsLamCase _ variant mgroup ->- p_lamcase variant exprPlacement p_hsExpr mgroup+ p_lamcase isApp variant exprPlacement p_hsExpr mgroup HsApp _ f x -> do let -- In order to format function applications with multiple parameters -- nicer, traverse the AST to gather the function and all the@@ -617,35 +638,20 @@ -- one. case placement of Normal -> do- let -- Usually we want to bump indentation for arguments for the- -- sake of readability. However:- -- When the function is itself a multi line do-block or a case- -- expression, we can't indent by indentStep or more.- -- When we are on the other hand *in* a do block, we have to- -- indent by at least 1.- -- Thus, we indent by half of indentStep when the function is- -- a multi line do block or case expression.- indentArg- | isOneLineSpan (getLocA func) = inci- | otherwise = case unLoc func of- HsDo {} -> inciHalf- HsCase {} -> inciHalf- HsLamCase {} -> inciHalf- _ -> inci ub <- getLayout <&> \case SingleLine -> useBraces MultiLine -> id ub $ do- located func (p_hsExpr' s)+ located func (p_hsExpr' Applicand s) breakpoint- indentArg $ sep breakpoint (located' p_hsExpr) initp- indentArg $ do+ inci $ sep breakpoint (located' p_hsExpr) initp+ inci $ do unless (null initp) breakpoint located lastp p_hsExpr Hanging -> do useBraces . switchLayout [initSpan] $ do- located func (p_hsExpr' s)+ located func (p_hsExpr' Applicand s) breakpoint sep breakpoint (located' p_hsExpr) initp placeHanging placement . dontUseBraces $@@ -662,12 +668,11 @@ _ -> return () located (hswc_body a) p_hsType OpApp _ x op y -> do- fixityOverrides <- askFixityOverrides- fixityMap <- askFixityMap+ modFixityMap <- askModuleFixityMap let opTree = OpBranches [exprOpTree x, exprOpTree y] [op] p_exprOpTree s- (reassociateOpTree (getOpName . unLoc) fixityOverrides fixityMap opTree)+ (reassociateOpTree (getOpName . unLoc) modFixityMap opTree) NegApp _ e _ -> do negativeLiterals <- isExtensionEnabled NegativeLiterals let isLiteral = case unLoc e of@@ -704,7 +709,7 @@ Unboxed -> parensHash enclSpan <- fmap (flip RealSrcSpan Strict.Nothing) . maybeToList- <$> getEnclosingSpan (const True)+ <$> getEnclosingSpan if isSection then switchLayout [] . parens' s $@@ -715,20 +720,20 @@ ExplicitSum _ tag arity e -> p_unboxedSum N tag arity (located e p_hsExpr) HsCase _ e mgroup ->- p_case exprPlacement p_hsExpr e mgroup+ p_case isApp exprPlacement p_hsExpr e mgroup HsIf _ if' then' else' -> p_if exprPlacement p_hsExpr if' then' else' HsMultiIf _ guards -> do txt "if" breakpoint- inci . inci $ sep newline (located' (p_grhs RightArrow)) guards+ inciApplicand isApp $ sep newline (located' (p_grhs RightArrow)) guards HsLet _ _ localBinds _ e -> p_let p_hsExpr localBinds e HsDo _ doFlavor es -> do let doBody moduleName header = do forM_ moduleName $ \m -> atom m *> txt "." txt header- p_stmts exprPlacement (p_hsExpr' S) es+ p_stmts isApp exprPlacement (p_hsExpr' NotApplicand S) es compBody = brackets s . located es $ \xs -> do let p_parBody = sep@@ -911,6 +916,7 @@ ( Anno (GRHS GhcPs (LocatedA body)) ~ SrcAnn NoEpAnns, Anno (Match GhcPs (LocatedA body)) ~ SrcSpanAnnA ) =>+ IsApplicand -> -- | Placer (body -> Placement) -> -- | Render@@ -920,19 +926,20 @@ -- | Match group MatchGroup GhcPs (LocatedA body) -> R ()-p_case placer render e mgroup = do+p_case isApp placer render e mgroup = do txt "case" space located e p_hsExpr space txt "of" breakpoint- inci (p_matchGroup' placer render Case mgroup)+ inciApplicand isApp (p_matchGroup' placer render Case mgroup) p_lamcase :: ( Anno (GRHS GhcPs (LocatedA body)) ~ SrcAnn NoEpAnns, Anno (Match GhcPs (LocatedA body)) ~ SrcSpanAnnA ) =>+ IsApplicand -> -- | Variant (@\\case@ or @\\cases@) LamCaseVariant -> -- | Placer@@ -942,12 +949,12 @@ -- | Expression MatchGroup GhcPs (LocatedA body) -> R ()-p_lamcase variant placer render mgroup = do+p_lamcase isApp variant placer render mgroup = do txt $ case variant of LamCase -> "\\case" LamCases -> "\\cases" breakpoint- inci (p_matchGroup' placer render LambdaCase mgroup)+ inciApplicand isApp (p_matchGroup' placer render LambdaCase mgroup) p_if :: -- | Placer
src/Ormolu/Printer/Meat/Declaration/Value.hs-boot view
@@ -19,7 +19,10 @@ p_hsExpr :: HsExpr GhcPs -> R () p_hsUntypedSplice :: SpliceDecoration -> HsUntypedSplice GhcPs -> R () p_stringLit :: String -> R ()-p_hsExpr' :: BracketStyle -> HsExpr GhcPs -> R ()++data IsApplicand++p_hsExpr' :: IsApplicand -> BracketStyle -> HsExpr GhcPs -> R () p_hsCmdTop :: BracketStyle -> HsCmdTop GhcPs -> R () exprPlacement :: HsExpr GhcPs -> Placement cmdTopPlacement :: HsCmdTop GhcPs -> Placement
src/Ormolu/Printer/Meat/ImportExport.hs view
@@ -19,10 +19,6 @@ import Ormolu.Utils (RelativePos (..), attachRelativePos) p_hsmodExports :: [LIE GhcPs] -> R ()-p_hsmodExports [] = do- txt "("- breakpoint'- txt ")" p_hsmodExports xs = parens N $ do layout <- getLayout
src/Ormolu/Printer/Meat/Module.hs view
@@ -24,9 +24,9 @@ -- signature). p_hsModule :: -- | Stack header- Maybe (RealLocated Comment) ->+ Maybe LComment -> -- | Pragmas and the associated comments- [([RealLocated Comment], Pragma)] ->+ [([LComment], Pragma)] -> -- | AST to print HsModule GhcPs -> R ()@@ -54,7 +54,7 @@ case hsmodExports of Nothing -> return () Just l -> do- located l $ \exports -> do+ encloseLocated l $ \exports -> do inci (p_hsmodExports exports) breakpoint txt "where"
src/Ormolu/Printer/Meat/Pragma.hs view
@@ -51,7 +51,7 @@ deriving (Eq, Ord) -- | Print a collection of 'Pragma's with their associated comments.-p_pragmas :: [([RealLocated Comment], Pragma)] -> R ()+p_pragmas :: [([LComment], Pragma)] -> R () p_pragmas ps = do let prepare = L.sortOn snd . L.nub . concatMap analyze analyze = \case@@ -63,7 +63,7 @@ forM_ (prepare ps) $ \(cs, (pragmaTy, x)) -> p_pragma cs pragmaTy x -p_pragma :: [RealLocated Comment] -> PragmaTy -> Text -> R ()+p_pragma :: [LComment] -> PragmaTy -> Text -> R () p_pragma comments ty x = do forM_ comments $ \(L l comment) -> do spitCommentNow l comment
src/Ormolu/Printer/Meat/Type.hs view
@@ -110,11 +110,10 @@ parensHash N $ sep (space >> txt "|" >> breakpoint) (sitcc . located' p_hsType) xs HsOpTy _ _ x op y -> do- fixityOverrides <- askFixityOverrides- fixityMap <- askFixityMap+ modFixityMap <- askModuleFixityMap let opTree = OpBranches [tyOpTree x, tyOpTree y] [op] p_tyOpTree- (reassociateOpTree (Just . unLoc) fixityOverrides fixityMap opTree)+ (reassociateOpTree (Just . unLoc) modFixityMap opTree) HsParTy _ t -> parens N (located t p_hsType) HsIParamTy _ n t -> sitcc $ do
src/Ormolu/Printer/Operators.hs view
@@ -11,10 +11,7 @@ ) where -import Control.Applicative ((<|>)) import Data.List.NonEmpty qualified as NE-import Data.Map.Strict qualified as Map-import Data.Maybe (fromMaybe) import GHC.Types.Name.Reader import GHC.Types.SrcLoc import Ormolu.Fixity@@ -42,13 +39,13 @@ data OpInfo op = OpInfo { -- | The actual operator opiOp :: op,- -- | Its name, if available. We use 'Maybe OpName' here instead of 'OpName'- -- because the name-fetching function received by 'reassociateOpTree'- -- returns a 'Maybe'- opiName :: Maybe OpName,+ -- | Its name, if available. We use 'Maybe RdrName' here instead of+ -- 'RdrName' because the name-fetching function received by+ -- 'reassociateOpTree' returns a 'Maybe'+ opiName :: Maybe RdrName, -- | Information about the fixity direction and precedence level of the -- operator- opiFix :: FixityInfo+ opiFixityApproximation :: FixityApproximation } deriving (Eq) @@ -57,21 +54,21 @@ -- of equality. compareOp :: OpInfo op -> OpInfo op -> Maybe Ordering compareOp- (OpInfo _ mName1 FixityInfo {fiMinPrecedence = min1, fiMaxPrecedence = max1})- (OpInfo _ mName2 FixityInfo {fiMinPrecedence = min2, fiMaxPrecedence = max2}) =+ (OpInfo _ mName1 FixityApproximation {faMinPrecedence = min1, faMaxPrecedence = max1})+ (OpInfo _ mName2 FixityApproximation {faMinPrecedence = min2, faMaxPrecedence = max2}) = if- -- Only declare two precedence levels as equal when- -- * either both precedence levels are precise- -- (fiMinPrecedence == fiMaxPrecedence) and match- -- * or when the precedence levels are imprecise but when the- -- operator names match- | min1 == min2- && max1 == max2- && (min1 == max1 || sameSymbol) ->- Just EQ- | max1 < min2 -> Just LT- | max2 < min1 -> Just GT- | otherwise -> Nothing+ -- Only declare two precedence levels as equal when+ -- * either both precedence levels are precise+ -- (fiMinPrecedence == fiMaxPrecedence) and match+ -- * or when the precedence levels are imprecise but when the+ -- operator names match+ | min1 == min2+ && max1 == max2+ && (min1 == max1 || sameSymbol) ->+ Just EQ+ | max1 < min2 -> Just LT+ | max2 < min1 -> Just GT+ | otherwise -> Nothing where sameSymbol = case (mName1, mName2) of (Just n1, Just n2) -> n1 == n2@@ -89,48 +86,40 @@ reassociateOpTree :: -- | How to get name of an operator (op -> Maybe RdrName) ->- -- | Fixity overrides- FixityMap -> -- | Fixity Map- LazyFixityMap ->+ ModuleFixityMap -> -- | Original 'OpTree' OpTree ty op -> -- | Re-associated 'OpTree', with added context and info around operators OpTree ty (OpInfo op)-reassociateOpTree getOpName fixityOverrides fixityMap =+reassociateOpTree getOpName modFixityMap = reassociateFlatOpTree . makeFlatOpTree- . addFixityInfo fixityOverrides fixityMap getOpName+ . addFixityInfo modFixityMap getOpName -- | Wrap every operator of the tree with 'OpInfo' to carry the information -- about its fixity (extracted from the specified fixity map). addFixityInfo ::- -- | Fixity overrides- FixityMap -> -- | Fixity map for operators- LazyFixityMap ->+ ModuleFixityMap -> -- | How to get the name of an operator (op -> Maybe RdrName) -> -- | 'OpTree' OpTree ty op -> -- | 'OpTree', with fixity info wrapped around each operator OpTree ty (OpInfo op)-addFixityInfo _ _ _ (OpNode n) = OpNode n-addFixityInfo fixityOverrides fixityMap getOpName (OpBranches exprs ops) =+addFixityInfo _ _ (OpNode n) = OpNode n+addFixityInfo modFixityMap getOpName (OpBranches exprs ops) = OpBranches- (addFixityInfo fixityOverrides fixityMap getOpName <$> exprs)+ (addFixityInfo modFixityMap getOpName <$> exprs) (toOpInfo <$> ops) where- toOpInfo o = OpInfo o mName fixityInfo+ toOpInfo o = OpInfo o mrdrName fixityApproximation where- mName = occOpName . rdrNameOcc <$> getOpName o- fixityInfo =- fromMaybe- defaultFixityInfo- ( do- name <- mName- Map.lookup name fixityOverrides <|> lookupFixity name fixityMap- )+ mrdrName = getOpName o+ fixityApproximation = case mrdrName of+ Nothing -> defaultFixityApproximation+ Just rdrName -> inferFixity rdrName modFixityMap -- | Given a 'OpTree' of any shape, produce a flat 'OpTree', where every -- node and operator is directly connected to the root.@@ -202,7 +191,7 @@ where indicesOfHardSplitter = fmap fst $- filter (isHardSplitterOp . opiFix . snd) $+ filter (isHardSplitterOp . opiFixityApproximation . snd) $ zip [0 ..] noptOps indexOfMinMaxPrecOps [] = (Nothing, Nothing) indexOfMinMaxPrecOps (oo : oos) = go oos 1 oo (Just [0]) oo (Just [0])@@ -367,5 +356,5 @@ -- class of operators because they often have, like ('$'), a specific -- “separator” use-case, and we sometimes format them differently than other -- operators.-isHardSplitterOp :: FixityInfo -> Bool-isHardSplitterOp = (== FixityInfo (Just InfixR) 0 0)+isHardSplitterOp :: FixityApproximation -> Bool+isHardSplitterOp = (== FixityApproximation (Just InfixR) 0 0)
src/Ormolu/Utils.hs view
@@ -17,6 +17,7 @@ getLoc', matchAddEpAnn, textToStringBuffer,+ ghcModuleNameToCabal, ) where @@ -27,16 +28,19 @@ import Data.Text (Text) import Data.Text qualified as T import Data.Text.Foreign qualified as TFFI+import Distribution.ModuleName (ModuleName)+import Distribution.ModuleName qualified as ModuleName import Foreign (pokeElemOff, withForeignPtr) import GHC.Data.Strict qualified as Strict import GHC.Data.StringBuffer (StringBuffer (..)) import GHC.Driver.Ppr import GHC.DynFlags (baseDynFlags) import GHC.ForeignPtr (mallocPlainForeignPtrBytes)-import GHC.Hs+import GHC.Hs hiding (ModuleName) import GHC.IO.Unsafe (unsafePerformIO) import GHC.Types.SrcLoc import GHC.Utils.Outputable (Outputable (..))+import Language.Haskell.Syntax.Module.Name qualified as GHC -- | Relative positions in a list. data RelativePos@@ -169,3 +173,7 @@ pure StringBuffer {buf, len, cur = 0} where len = TFFI.lengthWord8 txt++-- | Convert GHC's 'ModuleName' into the one used by Cabal.+ghcModuleNameToCabal :: GHC.ModuleName -> ModuleName+ghcModuleNameToCabal = ModuleName.fromString . GHC.moduleNameString
src/Ormolu/Utils/Cabal.hs view
@@ -1,6 +1,5 @@ {-# LANGUAGE LambdaCase #-} {-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE ViewPatterns #-} module Ormolu.Utils.Cabal ( CabalSearchResult (..),@@ -29,9 +28,9 @@ import Language.Haskell.Extension import Ormolu.Config import Ormolu.Exception+import Ormolu.Utils.IO (findClosestFileSatisfying) import System.Directory import System.FilePath-import System.IO.Error (isDoesNotExistError) import System.IO.Unsafe (unsafePerformIO) -- | The result of searching for a @.cabal@ file.@@ -68,8 +67,8 @@ FilePath -> -- | Extracted cabal info, if any m CabalSearchResult-getCabalInfoForSourceFile sourceFile = liftIO $ do- findCabalFile sourceFile >>= \case+getCabalInfoForSourceFile sourceFile =+ liftIO (findCabalFile sourceFile) >>= \case Just cabalFile -> do (mentioned, cabalInfo) <- parseCabalInfo cabalFile sourceFile return@@ -79,34 +78,16 @@ ) Nothing -> return CabalNotFound --- | Find the path to an appropriate .cabal file for a Haskell source file,--- if available.+-- | Find the path to an appropriate @.cabal@ file for a Haskell source+-- file, if available. findCabalFile :: (MonadIO m) =>- -- | Path to a Haskell source file in a project with a .cabal file+ -- | Path to a Haskell source file in a project with a @.cabal@ file FilePath ->- -- | Absolute path to the .cabal file if available+ -- | Absolute path to the @.cabal@ file, if available m (Maybe FilePath)-findCabalFile sourceFile = liftIO $ do- parentDir <- takeDirectory <$> makeAbsolute sourceFile- dirEntries <-- listDirectory parentDir `catch` \case- (isDoesNotExistError -> True) -> pure []- e -> throwIO e- let findDotCabal = \case- [] -> pure Nothing- e : es- | takeExtension e == ".cabal" ->- doesFileExist (parentDir </> e) >>= \case- True -> pure $ Just e- False -> findDotCabal es- _ : es -> findDotCabal es- findDotCabal dirEntries >>= \case- Just cabalFile -> pure . Just $ parentDir </> cabalFile- Nothing ->- if isDrive parentDir- then pure Nothing- else findCabalFile parentDir+findCabalFile = findClosestFileSatisfying $ \x ->+ takeExtension x == ".cabal" -- | Parsed cabal file information to be shared across multiple source files. data CachedCabalFile = CachedCabalFile@@ -118,12 +99,12 @@ } deriving (Show) --- | Cache ref that stores 'CachedCabalFile' per cabal file.-cabalCacheRef :: IORef (Map FilePath CachedCabalFile)-cabalCacheRef = unsafePerformIO $ newIORef M.empty-{-# NOINLINE cabalCacheRef #-}+-- | Cache ref that stores 'CachedCabalFile' per Cabal file.+cacheRef :: IORef (Map FilePath CachedCabalFile)+cacheRef = unsafePerformIO $ newIORef M.empty+{-# NOINLINE cacheRef #-} --- | Parse 'CabalInfo' from a .cabal file at the given 'FilePath'.+-- | Parse 'CabalInfo' from a @.cabal@ file at the given 'FilePath'. parseCabalInfo :: (MonadIO m) => -- | Location of the .cabal file@@ -136,7 +117,7 @@ parseCabalInfo cabalFileAsGiven sourceFileAsGiven = liftIO $ do cabalFile <- makeAbsolute cabalFileAsGiven sourceFileAbs <- makeAbsolute sourceFileAsGiven- cabalCache <- readIORef cabalCacheRef+ cabalCache <- readIORef cacheRef CachedCabalFile {..} <- whenNothing (M.lookup cabalFile cabalCache) $ do cabalFileBs <- B.readFile cabalFile genericPackageDescription <-@@ -145,7 +126,7 @@ let extensionsAndDeps = getExtensionAndDepsMap cabalFile genericPackageDescription cachedCabalFile = CachedCabalFile {..}- atomicModifyIORef cabalCacheRef $+ atomicModifyIORef cacheRef $ (,cachedCabalFile) . M.insert cabalFile cachedCabalFile let (dynOpts, dependencies, mentioned) = case M.lookup (dropExtensions sourceFileAbs) extensionsAndDeps of
src/Ormolu/Utils/Fixity.hs view
@@ -1,8 +1,9 @@-{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE LambdaCase #-} module Ormolu.Utils.Fixity- ( getFixityOverridesForSourceFile,+ ( getDotOrmoluForSourceFile, parseFixityDeclarationStr,+ parseModuleReexportDeclarationStr, ) where @@ -10,51 +11,61 @@ import Control.Monad.IO.Class import Data.Bifunctor (first) import Data.IORef+import Data.List.NonEmpty (NonEmpty) import Data.Map.Strict (Map) import Data.Map.Strict qualified as Map import Data.Text qualified as T+import Distribution.ModuleName (ModuleName) import Ormolu.Exception import Ormolu.Fixity import Ormolu.Fixity.Parser-import Ormolu.Utils.Cabal-import Ormolu.Utils.IO (readFileUtf8)+import Ormolu.Utils.IO (findClosestFileSatisfying, readFileUtf8) import System.Directory-import System.FilePath import System.IO.Unsafe (unsafePerformIO) import Text.Megaparsec (errorBundlePretty) --- | Cache ref that stores fixity overrides per cabal file.-cacheRef :: IORef (Map FilePath FixityMap)-cacheRef = unsafePerformIO (newIORef Map.empty)-{-# NOINLINE cacheRef #-}- -- | Attempt to locate and parse an @.ormolu@ file. If it does not exist,--- empty fixity map is returned. This function maintains a cache of fixity--- overrides where cabal file paths act as keys.-getFixityOverridesForSourceFile ::+-- default fixity map and module reexports are returned. This function+-- maintains a cache of fixity overrides and module re-exports where cabal+-- file paths act as keys.+getDotOrmoluForSourceFile :: (MonadIO m) => -- | 'CabalInfo' already obtained for this source file- CabalInfo ->- m FixityMap-getFixityOverridesForSourceFile CabalInfo {..} = liftIO $ do- cache <- readIORef cacheRef- case Map.lookup ciCabalFilePath cache of- Nothing -> do- let dotOrmolu = replaceFileName ciCabalFilePath ".ormolu"- exists <- doesFileExist dotOrmolu- if exists- then do- dotOrmoluRelative <- makeRelativeToCurrentDirectory dotOrmolu- contents <- readFileUtf8 dotOrmolu- case parseFixityMap dotOrmoluRelative contents of+ FilePath ->+ m (FixityOverrides, ModuleReexports)+getDotOrmoluForSourceFile sourceFile =+ liftIO (findDotOrmoluFile sourceFile) >>= \case+ Just dotOrmoluFile -> liftIO $ do+ cache <- readIORef cacheRef+ case Map.lookup dotOrmoluFile cache of+ Nothing -> do+ dotOrmoluRelative <- makeRelativeToCurrentDirectory dotOrmoluFile+ contents <- readFileUtf8 dotOrmoluFile+ case parseDotOrmolu dotOrmoluRelative contents of Left errorBundle -> throwIO (OrmoluFixityOverridesParseError errorBundle) Right x -> do- modifyIORef' cacheRef (Map.insert ciCabalFilePath x)+ modifyIORef' cacheRef (Map.insert dotOrmoluFile x) return x- else return Map.empty- Just x -> return x+ Just x -> return x+ Nothing -> return (defaultFixityOverrides, defaultModuleReexports) +-- | Find the path to an appropriate @.ormolu@ file for a Haskell source+-- file, if available.+findDotOrmoluFile ::+ (MonadIO m) =>+ -- | Path to a Haskell source file+ FilePath ->+ -- | Absolute path to the closest @.ormolu@ file, if available+ m (Maybe FilePath)+findDotOrmoluFile = findClosestFileSatisfying $ \x ->+ x == ".ormolu"++-- | Cache ref that maps names of @.ormolu@ files to their contents.+cacheRef :: IORef (Map FilePath (FixityOverrides, ModuleReexports))+cacheRef = unsafePerformIO (newIORef Map.empty)+{-# NOINLINE cacheRef #-}+ -- | A wrapper around 'parseFixityDeclaration' for parsing individual fixity -- definitions. parseFixityDeclarationStr ::@@ -64,3 +75,13 @@ Either String [(OpName, FixityInfo)] parseFixityDeclarationStr = first errorBundlePretty . parseFixityDeclaration . T.pack++-- | A wrapper around 'parseModuleReexportDeclaration' for parsing+-- a individual module reexport.+parseModuleReexportDeclarationStr ::+ -- | Input to parse+ String ->+ -- | Parse result+ Either String (ModuleName, NonEmpty ModuleName)+parseModuleReexportDeclarationStr =+ first errorBundlePretty . parseModuleReexportDeclaration . T.pack
src/Ormolu/Utils/IO.hs view
@@ -1,18 +1,25 @@+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE ViewPatterns #-}+ -- | Write 'Text' to files using UTF8 and ignoring native -- line ending conventions. module Ormolu.Utils.IO ( writeFileUtf8, readFileUtf8, getContentsUtf8,+ findClosestFileSatisfying, ) where -import Control.Exception (throwIO)+import Control.Exception (catch, throwIO) import Control.Monad.IO.Class import Data.ByteString (ByteString) import Data.ByteString qualified as B import Data.Text (Text) import Data.Text.Encoding qualified as TE+import System.Directory+import System.FilePath+import System.IO.Error (isDoesNotExistError) -- | Write a 'Text' to a file using UTF8 and ignoring native -- line ending conventions.@@ -32,3 +39,35 @@ -- strict and fails immediately if decoding encounters a problem. decodeUtf8 :: (MonadIO m) => ByteString -> m Text decodeUtf8 = liftIO . either throwIO pure . TE.decodeUtf8'++-- | Find the path to the closest file higher in the file hierarchy that+-- satisfies a given predicate.+findClosestFileSatisfying ::+ (MonadIO m) =>+ -- | The predicate that determines what we are looking for+ (FilePath -> Bool) ->+ -- | Path to the starting point for the search+ FilePath ->+ -- | Absolute path to the found file if available+ m (Maybe FilePath)+findClosestFileSatisfying isRightFile rootOfSearch = liftIO $ do+ parentDir <- takeDirectory <$> makeAbsolute rootOfSearch+ dirEntries <-+ listDirectory parentDir `catch` \case+ (isDoesNotExistError -> True) -> pure []+ e -> throwIO e+ let searchAtParentDirLevel = \case+ [] -> pure Nothing+ x : xs ->+ if isRightFile x+ then+ doesFileExist (parentDir </> x) >>= \case+ True -> pure (Just x)+ False -> searchAtParentDirLevel xs+ else searchAtParentDirLevel xs+ searchAtParentDirLevel dirEntries >>= \case+ Just foundFile -> pure . Just $ parentDir </> foundFile+ Nothing ->+ if isDrive parentDir+ then pure Nothing+ else findClosestFileSatisfying isRightFile parentDir
tests/Ormolu/CabalInfoSpec.hs view
@@ -44,7 +44,7 @@ mentioned `shouldBe` True unPackageName ciPackageName `shouldBe` "ormolu" ciDynOpts `shouldBe` [DynOption "-XGHC2021"]- Set.map unPackageName ciDependencies `shouldBe` Set.fromList ["Cabal-syntax", "QuickCheck", "base", "containers", "directory", "filepath", "ghc-lib-parser", "hspec", "hspec-megaparsec", "ormolu", "path", "path-io", "temporary", "text"]+ Set.map unPackageName ciDependencies `shouldBe` Set.fromList ["Cabal-syntax", "QuickCheck", "base", "containers", "directory", "filepath", "ghc-lib-parser", "hspec", "hspec-megaparsec", "megaparsec", "ormolu", "path", "path-io", "temporary", "text"] ciCabalFilePath `shouldSatisfy` isAbsolute makeRelativeToCurrentDirectory ciCabalFilePath `shouldReturn` "ormolu.cabal" it "handles correctly files that are not mentioned in ormolu.cabal" $ do
tests/Ormolu/Fixity/ParserSpec.hs view
@@ -2,6 +2,7 @@ module Ormolu.Fixity.ParserSpec (spec) where +import Data.List.NonEmpty (NonEmpty (..)) import Data.Map.Strict qualified as Map import Data.Text (Text) import Data.Text qualified as T@@ -9,35 +10,126 @@ import Ormolu.Fixity.Parser import Test.Hspec import Test.Hspec.Megaparsec+import Text.Megaparsec.Error (ErrorFancy (..)) spec :: Spec spec = do+ describe "parseDotOrmolu" $ do+ it "parses the empty input without choking" $+ parseDotOrmolu "" ""+ `shouldParse` (FixityOverrides Map.empty, ModuleReexports Map.empty)+ it "parses a collection of fixity declarations" $+ -- The example is taken from base.+ parseDotOrmolu+ ""+ ( T.unlines+ [ "infixr 9 .",+ "infixr 5 ++",+ "infixl 4 <$",+ "infixl 1 >>, >>=",+ "infixr 1 =<<",+ "infixr 0 $, $!",+ "infixl 4 <*>, <*, *>, <**>"+ ]+ )+ `shouldParse` ( exampleFixityOverrides,+ ModuleReexports Map.empty+ )+ it "combines conflicting fixity declarations correctly" $+ parseDotOrmolu+ ""+ ( T.unlines+ [ "infixr 9 ., ^",+ "infixr 7 ., $",+ "infixr 9 ^ ",+ "infixl 7 $"+ ]+ )+ `shouldParse` ( FixityOverrides+ ( Map.fromList+ [ ("$", FixityInfo InfixL 7),+ (".", FixityInfo InfixR 7),+ ("^", FixityInfo InfixR 9)+ ]+ ),+ ModuleReexports Map.empty+ )+ it "handles CRLF line endings correctly" $+ parseDotOrmolu ""+ `shouldSucceedOn` unlinesCrlf+ [ "infixr 9 .",+ "infixr 5 ++"+ ]+ it "fails with correct parse error (keyword wrong second line)" $+ parseDotOrmolu "" "infixr 5 .\nfoobar 5 $"+ `shouldFailWith` err+ 11+ ( mconcat+ [ utok 'f',+ etoks "infix",+ etoks "infixl",+ etoks "infixr",+ etoks "module",+ eeof+ ]+ )+ it "parses module re-exports and combines them correctly" $+ parseDotOrmolu+ ""+ ( T.unlines+ [ "module Control.Lens exports Control.Lens.Lens",+ "module Control.Lens exports Control.Lens.At",+ "module Text.Megaparsec exports Control.Monad.Combinators"+ ]+ )+ `shouldParse` (FixityOverrides Map.empty, exampleModuleReexports)+ it "parses fixity declarations + module re-export declarations with blanks" $+ parseDotOrmolu+ ""+ ( T.unlines+ [ "module Control.Lens exports Control.Lens.Lens",+ "",+ "infixr 5 ++",+ "infixl 4 <$",+ "",+ "",+ "module Control.Lens exports Control.Lens.At",+ "infixr 9 .",+ "module Text.Megaparsec exports Control.Monad.Combinators",+ "infixl 1 >>, >>=",+ "infixr 1 =<<",+ "",+ "infixr 0 $, $!",+ "infixl 4 <*>, <*, *>, <**>"+ ]+ )+ `shouldParse` (exampleFixityOverrides, exampleModuleReexports) describe "parseFixtiyDeclaration" $ do it "parses a simple infixr declaration" $ parseFixityDeclaration "infixr 5 $"- `shouldParse` [("$", FixityInfo (Just InfixR) 5 5)]+ `shouldParse` [("$", FixityInfo InfixR 5)] it "parses a simple infixl declaration" $ parseFixityDeclaration "infixl 5 $"- `shouldParse` [("$", FixityInfo (Just InfixL) 5 5)]+ `shouldParse` [("$", FixityInfo InfixL 5)] it "parses a simple infix declaration" $ parseFixityDeclaration "infix 5 $"- `shouldParse` [("$", FixityInfo (Just InfixN) 5 5)]+ `shouldParse` [("$", FixityInfo InfixN 5)] it "parses a declaration for a ticked identifier" $ parseFixityDeclaration "infixl 5 `foo`"- `shouldParse` [("foo", FixityInfo (Just InfixL) 5 5)]+ `shouldParse` [("foo", FixityInfo InfixL 5)] it "parses a declaration for a ticked identifier (constructor case)" $ parseFixityDeclaration "infixl 5 `Foo`"- `shouldParse` [("Foo", FixityInfo (Just InfixL) 5 5)]+ `shouldParse` [("Foo", FixityInfo InfixL 5)] it "parses a multi-operator declaration" $ parseFixityDeclaration "infixl 5 $, ., `Foo`, `bar`"- `shouldParse` [ ("$", FixityInfo (Just InfixL) 5 5),- (".", FixityInfo (Just InfixL) 5 5),- ("Foo", FixityInfo (Just InfixL) 5 5),- ("bar", FixityInfo (Just InfixL) 5 5)+ `shouldParse` [ ("$", FixityInfo InfixL 5),+ (".", FixityInfo InfixL 5),+ ("Foo", FixityInfo InfixL 5),+ ("bar", FixityInfo InfixL 5) ] it "parses a declaration with a unicode operator" $ parseFixityDeclaration "infixr 5 ×"- `shouldParse` [("×", FixityInfo (Just InfixR) 5 5)]+ `shouldParse` [("×", FixityInfo InfixR 5)] it "fails with correct parse error (keyword wrong)" $ parseFixityDeclaration "foobar 5 $" `shouldFailWith` err@@ -69,72 +161,74 @@ elabel "operator character" ] )- describe "parseFixityMap" $ do- it "parses the empty input without choking" $- parseFixityMap "" ""- `shouldParse` Map.empty- it "parses a collection of declarations" $- -- The example is taken from base.- parseFixityMap- ""- ( T.unlines- [ "infixr 9 .",- "infixr 5 ++",- "infixl 4 <$",- "infixl 1 >>, >>=",- "infixr 1 =<<",- "infixr 0 $, $!",- "infixl 4 <*>, <*, *>, <**>"- ]- )- `shouldParse` Map.fromList- [ ("$", FixityInfo (Just InfixR) 0 0),- ("$!", FixityInfo (Just InfixR) 0 0),- ("*>", FixityInfo (Just InfixL) 4 4),- ("++", FixityInfo (Just InfixR) 5 5),- (".", FixityInfo (Just InfixR) 9 9),- ("<$", FixityInfo (Just InfixL) 4 4),- ("<*", FixityInfo (Just InfixL) 4 4),- ("<**>", FixityInfo (Just InfixL) 4 4),- ("<*>", FixityInfo (Just InfixL) 4 4),- ("=<<", FixityInfo (Just InfixR) 1 1),- (">>", FixityInfo (Just InfixL) 1 1),- (">>=", FixityInfo (Just InfixL) 1 1)- ]- it "combines conflicting declarations correctly" $- parseFixityMap- ""- ( T.unlines- [ "infixr 9 ., ^",- "infixr 7 ., $",- "infixr 9 ^ ",- "infixl 7 $"- ]- )- `shouldParse` Map.fromList- [ ("$", FixityInfo Nothing 7 7),- (".", FixityInfo (Just InfixR) 7 9),- ("^", FixityInfo (Just InfixR) 9 9)- ]- it "handles CRLF line endings correctly" $- parseFixityMap ""- `shouldSucceedOn` ( unlinesCrlf- [ "infixr 9 .",- "infixr 5 ++"- ]- )- it "fails with correct parse error (keyword wrong second line)" $- parseFixityMap "" "infixr 5 .\nfoobar 5 $"+ it "fails with correct parse error (precedence greater than 9)" $+ parseFixityDeclaration "infixl 10 $"+ `shouldFailWith` errFancy+ 7+ (fancy (ErrorFail "precedence should not be greater than 9"))+ describe "parseModuleReexportDeclaration" $ do+ it "parses a re-export declaration" $+ parseModuleReexportDeclaration "module Control.Lens exports Control.Lens.Lens"+ `shouldParse` ( "Control.Lens",+ "Control.Lens.Lens" :| []+ )+ it "fails with correct parse error (keyword wrong)" $+ parseModuleReexportDeclaration "foo Control.Lens exports Control.Lens.Lens" `shouldFailWith` err- 11+ 0 ( mconcat- [ utok 'f',- etoks "infix",- etoks "infixl",- etoks "infixr",- eeof+ [ utoks "foo Co",+ etoks "module" ] )+ it "fails with correct parse error (module syntax)" $+ parseModuleReexportDeclaration "module control.Lens exports Control.Lens.Lens"+ `shouldFailWith` err+ 7+ ( mconcat+ [ utok 'c',+ elabel "module name"+ ]+ )+ it "fails with correct parse error (typo: export intead exports)" $+ parseModuleReexportDeclaration "module Control.Lens export Control.Lens.Lens"+ `shouldFailWith` err+ 20+ ( mconcat+ [ utoks "export ",+ etoks "exports"+ ]+ )++exampleFixityOverrides :: FixityOverrides+exampleFixityOverrides =+ FixityOverrides+ ( Map.fromList+ [ ("$", FixityInfo InfixR 0),+ ("$!", FixityInfo InfixR 0),+ ("*>", FixityInfo InfixL 4),+ ("++", FixityInfo InfixR 5),+ (".", FixityInfo InfixR 9),+ ("<$", FixityInfo InfixL 4),+ ("<*", FixityInfo InfixL 4),+ ("<**>", FixityInfo InfixL 4),+ ("<*>", FixityInfo InfixL 4),+ ("=<<", FixityInfo InfixR 1),+ (">>", FixityInfo InfixL 1),+ (">>=", FixityInfo InfixL 1)+ ]+ )++exampleModuleReexports :: ModuleReexports+exampleModuleReexports =+ ModuleReexports . Map.fromList $+ [ ( "Control.Lens",+ "Control.Lens.At" :| ["Control.Lens.Lens"]+ ),+ ( "Text.Megaparsec",+ "Control.Monad.Combinators" :| []+ )+ ] unlinesCrlf :: [Text] -> Text unlinesCrlf = T.concat . fmap (<> "\r\n")
tests/Ormolu/Fixity/PrinterSpec.hs view
@@ -1,10 +1,15 @@ {-# LANGUAGE RecordWildCards #-}+{-# OPTIONS_GHC -fno-warn-orphans #-} module Ormolu.Fixity.PrinterSpec (spec) where -import Data.Char qualified as Char+import Data.List (intercalate)+import Data.List.NonEmpty qualified as NE import Data.Map.Strict qualified as Map import Data.Text qualified as T+import Distribution.ModuleName (ModuleName)+import Distribution.ModuleName qualified as ModuleName+import Distribution.Types.PackageName (PackageName, mkPackageName) import Ormolu.Fixity import Ormolu.Fixity.Parser import Ormolu.Fixity.Printer@@ -12,41 +17,59 @@ import Test.Hspec.Megaparsec import Test.QuickCheck -newtype FixityMapWrapper = FixityMapWrapper FixityMap- deriving (Show)--instance Arbitrary FixityMapWrapper where+instance Arbitrary FixityOverrides where arbitrary =- FixityMapWrapper . Map.fromListWith (<>)+ FixityOverrides . Map.fromList <$> listOf ((,) <$> genOperator <*> genFixityInfo) where- scaleDown = scale (`div` 4) genOperator = OpName . T.pack <$> oneof [genNormalOperator, genIdentifier] genNormalOperator = listOf1 (scaleDown arbitrary `suchThat` isOperatorConstituent)- isOperatorConstituent x =- (Char.isSymbol x || Char.isPunctuation x) && x `notElem` ",`()" genIdentifier = do- x <- arbitrary `suchThat` Char.isLetter+ x <- arbitrary `suchThat` isIdentifierFirstChar xs <- listOf1 (scaleDown arbitrary `suchThat` isIdentifierConstituent) return (x : xs)- isIdentifierConstituent x = Char.isAlphaNum x || x == '_' || x == '\'' genFixityInfo = do fiDirection <- elements- [ Nothing,- Just InfixL,- Just InfixR,- Just InfixN+ [ InfixL,+ InfixR,+ InfixN ]- fiMinPrecedence <- chooseInt (0, 9)- fiMaxPrecedence <- chooseInt (0, 9) `suchThat` (>= fiMinPrecedence)+ fiPrecedence <- chooseInt (0, 9) return FixityInfo {..} +instance Arbitrary ModuleReexports where+ arbitrary = ModuleReexports . Map.fromListWith combine <$> listOf genReexport+ where+ combine x y = NE.sort (x <> y)+ genReexport = do+ exportingModule <- arbitrary+ exports <- NE.sort . NE.fromList . getNonEmpty <$> scaleDown arbitrary+ return (exportingModule, exports)++instance Arbitrary PackageName where+ arbitrary =+ mkPackageName+ <$> listOf1 (scaleDown arbitrary `suchThat` isPackageNameConstituent)++instance Arbitrary ModuleName where+ arbitrary =+ ModuleName.fromString . intercalate "." <$> scaleDown (listOf1 genSegment)+ where+ genSegment = do+ x <- arbitrary `suchThat` isModuleSegmentFirstChar+ xs <- listOf (arbitrary `suchThat` isModuleSegmentConstituent)+ return (x : xs)++scaleDown :: Gen a -> Gen a+scaleDown = scale (`div` 4)+ spec :: Spec spec = do- describe "parseFixityMap & printFixityMap" $+ describe "parseFixityOverrides & printFixityOverrides" $ it "arbitrary fixity maps are printed and parsed back correctly" $- property $ \(FixityMapWrapper fixityMap) ->- parseFixityMap "" (printFixityMap fixityMap) `shouldParse` fixityMap+ property $ \fixityOverrides moduleReexports ->+ parseDotOrmolu "" (printDotOrmolu fixityOverrides moduleReexports)+ `shouldParse` (fixityOverrides, moduleReexports)
+ tests/Ormolu/FixitySpec.hs view
@@ -0,0 +1,327 @@+{-# LANGUAGE OverloadedStrings #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}++module Ormolu.FixitySpec (spec) where++import Data.Function ((&))+import Data.List.NonEmpty (NonEmpty (..))+import Data.Map.Strict qualified as Map+import Data.Set qualified as Set+import Data.Text qualified as T+import Distribution.ModuleName (ModuleName)+import Distribution.Types.PackageName (PackageName)+import GHC.Types.Name (OccName)+import GHC.Types.Name.Occurrence (mkVarOcc)+import GHC.Types.Name.Reader+import Language.Haskell.Syntax.ImpExp (ImportListInterpretation (..))+import Language.Haskell.Syntax.Module.Name (mkModuleName)+import Ormolu.Fixity+import Ormolu.Fixity.Imports+import Ormolu.Fixity.Internal+import Ormolu.Utils (showOutputable)+import Test.Hspec++instance Show RdrName where+ show = showOutputable++spec :: Spec+spec = do+ it "gives the correct fixity info for (:) (built-in)" $+ checkFixities+ []+ []+ [(unqual ":", FixityApproximation (Just InfixR) 5 5)]+ it "does not know operators from base if base is not a dependency" $+ checkFixities+ []+ []+ [ (unqual "$", defaultFixityApproximation),+ (unqual "+", defaultFixityApproximation),+ (unqual "++", defaultFixityApproximation)+ ]+ it "does not know operators from base if Prelude is not imported" $+ checkFixities+ []+ []+ [ (unqual "$", defaultFixityApproximation),+ (unqual "+", defaultFixityApproximation),+ (unqual "++", defaultFixityApproximation)+ ]+ it "infers fixities of operators from base correctly" $+ checkFixities+ ["base"]+ [import_ "Prelude"]+ [ (unqual "$", FixityApproximation (Just InfixR) 0 0),+ (unqual "+", FixityApproximation (Just InfixL) 6 6),+ (unqual "++", FixityApproximation (Just InfixR) 5 5)+ ]+ it "does not know (>>>) when Control.Category is not imported" $+ checkFixities+ ["base"]+ [import_ "Prelude"]+ [ (unqual ">>>", defaultFixityApproximation)+ ]+ it "infer correct fixity for (>>>) when Control.Category is imported" $+ checkFixities+ ["base"]+ [ import_ "Prelude",+ import_ "Control.Category"+ ]+ [ (unqual ">>>", FixityApproximation (Just InfixR) 1 1)+ ]+ it "handles 'as' imports correctly" $+ checkFixities+ ["base"]+ [ import_ "Control.Category" & as_ "Foo"+ ]+ [ (unqual ">>>", FixityApproximation (Just InfixR) 1 1),+ (qual "Foo" ">>>", FixityApproximation (Just InfixR) 1 1),+ (qual "Bar" ">>>", defaultFixityApproximation)+ ]+ it "handles 'qualified' imports correctly" $+ checkFixities+ ["base"]+ [import_ "Control.Category" & qualified_]+ [ (unqual ">>>", defaultFixityApproximation),+ (qual "Control.Category" ">>>", FixityApproximation (Just InfixR) 1 1)+ ]+ it "handles 'qualified as' imports correctly" $+ checkFixities+ ["base"]+ [import_ "Control.Category" & qualified_ & as_ "Foo"]+ [ (unqual ">>>", defaultFixityApproximation),+ (qual "Control.Category" ">>>", defaultFixityApproximation),+ (qual "Foo" ">>>", FixityApproximation (Just InfixR) 1 1)+ ]+ it "handles explicit import lists correctly" $+ checkFixities+ ["base"]+ [import_ "Prelude" & exactly_ ["$"]]+ [ (unqual "$", FixityApproximation (Just InfixR) 0 0),+ (unqual "+", defaultFixityApproximation)+ ]+ it "handles hiding import lists correctly" $+ checkFixities+ ["base"]+ [import_ "Prelude" & hiding_ ["$"]]+ [ (unqual "$", defaultFixityApproximation),+ (unqual "+", FixityApproximation (Just InfixL) 6 6),+ (unqual "++", FixityApproximation (Just InfixR) 5 5)+ ]+ it "handles qualified imports with explicit import lists correctly" $+ checkFixities+ ["base"]+ [import_ "Prelude" & qualified_ & exactly_ ["$"]]+ [ (unqual "$", defaultFixityApproximation),+ (qual "Prelude" "$", FixityApproximation (Just InfixR) 0 0),+ (unqual "+", defaultFixityApproximation),+ (qual "Prelude" "+", defaultFixityApproximation)+ ]+ it "handles qualified import with hiding correctly" $+ checkFixities+ ["base"]+ [import_ "Prelude" & qualified_ & hiding_ ["$"]]+ [ (unqual "$", defaultFixityApproximation),+ (qual "Prelude" "$", defaultFixityApproximation),+ (unqual "+", defaultFixityApproximation),+ (qual "Prelude" "+", FixityApproximation (Just InfixL) 6 6)+ ]+ it "handles qualified import and explicit import lists correctly (1)" $+ checkFixities+ ["base"]+ [ import_ "Prelude" & qualified_,+ import_ "Prelude" & exactly_ ["$"]+ ]+ [ (unqual "$", FixityApproximation (Just InfixR) 0 0),+ (qual "Prelude" "$", FixityApproximation (Just InfixR) 0 0),+ (unqual "+", defaultFixityApproximation),+ (qual "Prelude" "+", FixityApproximation (Just InfixL) 6 6)+ ]+ it "handles qualified import and explicit import lists correctly (2)" $+ checkFixities+ ["base"]+ [ import_ "Prelude" & exactly_ ["$"],+ import_ "Prelude" & qualified_+ ]+ [ (unqual "$", FixityApproximation (Just InfixR) 0 0),+ (qual "Prelude" "$", FixityApproximation (Just InfixR) 0 0),+ (unqual "+", defaultFixityApproximation),+ (qual "Prelude" "+", FixityApproximation (Just InfixL) 6 6)+ ]+ it "handles qualified import and hiding import correctly (1)" $+ checkFixities+ ["base"]+ [ import_ "Prelude" & qualified_,+ import_ "Prelude" & hiding_ ["$"]+ ]+ [ (unqual "$", defaultFixityApproximation),+ (qual "Prelude" "$", FixityApproximation (Just InfixR) 0 0),+ (unqual "+", FixityApproximation (Just InfixL) 6 6),+ (qual "Prelude" "+", FixityApproximation (Just InfixL) 6 6)+ ]+ it "handles qualified import and hiding import correctly (2)" $+ checkFixities+ ["base"]+ [ import_ "Prelude" & hiding_ ["$"],+ import_ "Prelude" & qualified_+ ]+ [ (unqual "$", defaultFixityApproximation),+ (qual "Prelude" "$", FixityApproximation (Just InfixR) 0 0),+ (unqual "+", FixityApproximation (Just InfixL) 6 6),+ (qual "Prelude" "+", FixityApproximation (Just InfixL) 6 6)+ ]+ it "works for several imports from different packages" $+ checkFixities+ ["base", "esqueleto"]+ [ import_ "Prelude",+ import_ "Database.Esqueleto.Experimental" & qualified_ & as_ "E"+ ]+ [ (unqual "$", FixityApproximation (Just InfixR) 0 0),+ (qual "E" "++.", FixityApproximation (Just InfixR) 5 5),+ (qual "E" "on", FixityApproximation (Just InfixN) 9 9)+ ]+ it "merges approximations in case of a conflict" $+ checkFixities+ ["fclabels", "persistent"]+ [ import_ "Data.Label.Monadic",+ import_ "Database.Persist"+ ]+ [ (unqual "=.", FixityApproximation (Just InfixR) 2 3)+ ]+ it "correctly handles package-qualified imports (1)" $+ checkFixities+ ["esqueleto"]+ [package_ "esqueleto" $ import_ "Database.Esqueleto.Experimental"]+ [(unqual "++.", FixityApproximation (Just InfixR) 5 5)]+ it "correctly handles package-qualified imports (2)" $+ checkFixities+ ["esqueleto"]+ [package_ "bob" $ import_ "Database.Esqueleto.Experimental"]+ [(unqual "++.", defaultFixityApproximation)]+ it "default module re-exports: Control.Lens brings into scope Control.Lens.Lens" $+ checkFixities+ ["lens"]+ ( applyModuleReexports+ defaultModuleReexports+ [import_ "Control.Lens"]+ )+ [(unqual "<+~", FixityApproximation (Just InfixR) 4 4)]+ it "default module re-exports: Control.Lens qualified brings into scope Control.Lens.Lens" $+ checkFixities+ ["lens"]+ ( applyModuleReexports+ defaultModuleReexports+ [import_ "Control.Lens" & qualified_]+ )+ [ (unqual "<+~", defaultFixityApproximation),+ (qual "Control.Lens.Lens" "<+~", defaultFixityApproximation),+ (qual "Control.Lens" "<+~", FixityApproximation (Just InfixR) 4 4)+ ]+ it "default module re-exports: Control.Lens qualified as brings into scope Control.Lens.Lens" $+ checkFixities+ ["lens"]+ ( applyModuleReexports+ defaultModuleReexports+ [import_ "Control.Lens" & qualified_ & as_ "L"]+ )+ [ (unqual "<+~", defaultFixityApproximation),+ (qual "Control.Lens.Lens" "<+~", defaultFixityApproximation),+ (qual "Control.Lens" "<+~", defaultFixityApproximation),+ (qual "L" "<+~", FixityApproximation (Just InfixR) 4 4)+ ]+ it "re-export chains: exported module can itself re-export another module" $ do+ let reexports =+ ModuleReexports $+ Map.insert+ "Foo"+ ("Control.Lens" :| [])+ (unModuleReexports defaultModuleReexports)+ checkFixities+ ["lens"]+ ( applyModuleReexports+ reexports+ [import_ "Foo"]+ )+ [ (unqual "<+~", FixityApproximation (Just InfixR) 4 4)+ ]++-- | Build a fixity map using the Hoogle database and then check the fixity+-- of the specified subset of operators.+checkFixities ::+ -- | List of dependencies+ [PackageName] ->+ -- | Imports+ [FixityImport] ->+ -- | Associative list representing a subset of the resulting fixity map+ -- that should be checked.+ [(RdrName, FixityApproximation)] ->+ Expectation+checkFixities dependencies fixityImports expectedResult =+ actualResult `shouldBe` expectedResult+ where+ actualResult =+ fmap+ (\(k, _) -> (k, inferFixity k resultMap))+ expectedResult+ resultMap =+ moduleFixityMap+ (packageFixityMap (Set.fromList dependencies))+ fixityImports++qual :: String -> OpName -> RdrName+qual moduleName opName = mkRdrQual (mkModuleName moduleName) (opNameToOccName opName)++unqual :: OpName -> RdrName+unqual = mkRdrUnqual . opNameToOccName++opNameToOccName :: OpName -> OccName+opNameToOccName = mkVarOcc . T.unpack . unOpName++-- | Explicitly specify the package.+package_ :: PackageName -> FixityImport -> FixityImport+package_ packageName fixityImport =+ fixityImport+ { fimportPackage = Just packageName+ }++-- | Construct a simple 'FixityImport'.+import_ :: ModuleName -> FixityImport+import_ moduleName =+ FixityImport+ { fimportPackage = Nothing,+ fimportModule = moduleName,+ fimportQualified = UnqualifiedAndQualified moduleName,+ fimportList = Nothing+ }++-- | Adds an alias for an import.+as_ :: ModuleName -> FixityImport -> FixityImport+as_ moduleName fixityImport =+ fixityImport+ { fimportQualified = case fimportQualified fixityImport of+ UnqualifiedAndQualified _ -> UnqualifiedAndQualified moduleName+ OnlyQualified _ -> OnlyQualified moduleName+ }++-- | Qualified imports.+qualified_ :: FixityImport -> FixityImport+qualified_ fixityImport =+ fixityImport+ { fimportQualified = case fimportQualified fixityImport of+ UnqualifiedAndQualified m -> OnlyQualified m+ OnlyQualified m -> OnlyQualified m+ }++-- | Exact import lists.+exactly_ :: [OpName] -> FixityImport -> FixityImport+exactly_ opNames fixityImports =+ fixityImports+ { fimportList = Just (Exactly, opNames)+ }++-- | Hiding.+hiding_ :: [OpName] -> FixityImport -> FixityImport+hiding_ opNames fixityImports =+ fixityImports+ { fimportList = Just (EverythingBut, opNames)+ }
− tests/Ormolu/HackageInfoSpec.hs
@@ -1,507 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module Ormolu.HackageInfoSpec (spec) where--import Data.Map.Strict qualified as Map-import Data.Maybe (mapMaybe)-import Data.Set qualified as Set-import Distribution.Types.PackageName (PackageName)-import Ormolu.Fixity-import Test.Hspec---- | Build a fixity map using the Hackage/Hoogle database, and the boot--- package list, and then check the fixity of the specified subset of--- operators.-checkFixityMap ::- -- | List of dependencies- [PackageName] ->- -- | Threshold to choose the conflict resolution strategy- Float ->- -- | Associative list representing a subset of the resulting fixity map- -- that should be checked.- [(OpName, FixityInfo)] ->- Expectation-checkFixityMap- dependencies- threshold- expectedResult =- actualResult `shouldBe` expectedResult- where- actualResult =- mapMaybe- (\(k, _) -> (k,) <$> lookupFixity k resultMap)- expectedResult- resultMap =- buildFixityMap'- packageToOps- packageToPopularity- bootPackages- threshold- (Set.fromList dependencies)---- | Build a fixity map from a custom package database, and then check the--- fixity of the specified subset of operators.-checkFixityMap' ::- -- | Associative list for packageToOps:- -- package name -map-> (operator -map-> fixity)- [(PackageName, [(OpName, FixityInfo)])] ->- -- | Associative list for packageToPopularity:- -- package name -map-> download count- [(PackageName, Int)] ->- -- | List of packages that should have a higher priority than- -- unspecified packages (boot packages)- [PackageName] ->- -- | List of dependencies- [PackageName] ->- -- | Threshold to choose the conflict resolution strategy- Float ->- -- | Associative list representing a subset of the resulting fixity map- -- that should be checked.- [(OpName, FixityInfo)] ->- Expectation-checkFixityMap'- lPackageToOps- lPackageToPopularity- highPrioPackages- dependencies- threshold- expectedResult =- actualResult `shouldBe` expectedResult- where- actualResult =- mapMaybe- (\(k, _) -> (k,) <$> lookupFixity k resultMap)- expectedResult- resultMap =- buildFixityMap'- lPackageToOps'- lPackageToPopularity'- (Set.fromList highPrioPackages)- threshold- (Set.fromList dependencies)- lPackageToOps' =- Map.map Map.fromList $- Map.fromList lPackageToOps- lPackageToPopularity' = Map.fromList lPackageToPopularity--spec :: Spec-spec = do- it- "correctly merges fixities when a conflict appears in unspecified \- \packages, with max(pop) < threshold"- $ do- let operators =- [ ("A", [("+", FixityInfo (Just InfixL) 4 4)]),- ("B", [("+", FixityInfo (Just InfixR) 6 6)])- ]- popularity =- [ ("A", 3),- ("B", 5)- ]- dependencies = []- higherPriorityPackages = []- threshold = 0.9- result =- [ ("+", FixityInfo Nothing 4 6)- ]- checkFixityMap'- operators- popularity- higherPriorityPackages- dependencies- threshold- result-- it- "keeps only the most popular declaration when a conflict appears in \- \unspecified packages, with max(pop) >= threshold"- $ do- let operators =- [ ("A", [("+", FixityInfo (Just InfixL) 4 4)]),- ("B", [("+", FixityInfo (Just InfixR) 6 6)])- ]- popularity =- [ ("A", 5),- ("B", 103)- ]- dependencies = []- higherPriorityPackages = []- threshold = 0.9- result =- [ ("+", FixityInfo (Just InfixR) 6 6)- ]- checkFixityMap'- operators- popularity- higherPriorityPackages- dependencies- threshold- result-- it- "merges the ex-aequo most popular declaration when a conflict appears \- \in unspecified packages, with max(pop) >= threshold"- $ do- let operators =- [ ("A", [("+", FixityInfo (Just InfixL) 4 4)]),- ("B", [("+", FixityInfo (Just InfixR) 6 6)]),- ("C", [("+", FixityInfo (Just InfixR) 8 8)])- ]- popularity =- [ ("A", 5),- ("B", 103),- ("C", 103)- ]- dependencies = []- higherPriorityPackages = []- threshold = 0.4- result =- [ ("+", FixityInfo (Just InfixR) 6 8)- ]- checkFixityMap'- operators- popularity- higherPriorityPackages- dependencies- threshold- result-- it- "keeps only the most popular declaration when a conflict appears in \- \unspecified packages, threshold == 0"- $ do- let operators =- [ ("A", [("+", FixityInfo (Just InfixL) 4 4)]),- ("B", [("+", FixityInfo (Just InfixR) 6 6)])- ]- popularity =- [ ("A", 5),- ("B", 103)- ]- dependencies = []- higherPriorityPackages = []- threshold = 0.0- result =- [ ("+", FixityInfo (Just InfixR) 6 6)- ]- checkFixityMap'- operators- popularity- higherPriorityPackages- dependencies- threshold- result-- it- "merges all declarations when a conflict appears in unspecified \- \packages, threshold > 1"- $ do- let operators =- [ ("A", [("+", FixityInfo (Just InfixN) 4 4)]),- ("B", [("+", FixityInfo (Just InfixN) 6 6)]),- ("C", [("+", FixityInfo (Just InfixN) 8 8)])- ]- popularity =- [ ("A", 0),- ("B", 0),- ("C", 11103)- ]- dependencies = []- higherPriorityPackages = []- threshold = 10.0- result =- [ ("+", FixityInfo (Just InfixN) 4 8)- ]- checkFixityMap'- operators- popularity- higherPriorityPackages- dependencies- threshold- result-- it- "merges all declarations when a conflict appears in cabal \- \dependencies"- $ do- let operators =- [ ( "A",- [ ("+", FixityInfo (Just InfixR) 4 4),- ("-", FixityInfo (Just InfixR) 2 2)- ]- ),- ( "B",- [ ("+", FixityInfo (Just InfixN) 6 6),- ("-", FixityInfo (Just InfixL) 4 4)- ]- ),- ("C", [("+", FixityInfo (Just InfixN) 8 8)])- ]- popularity =- [ ("A", 0),- ("B", 0),- ("C", 11103)- ]- dependencies = ["B", "C"]- higherPriorityPackages = []- threshold = 0.4- result =- [ ("+", FixityInfo (Just InfixN) 6 8),- ("-", FixityInfo (Just InfixL) 4 4)- ]- checkFixityMap'- operators- popularity- higherPriorityPackages- dependencies- threshold- result-- it- "merges all declarations when a conflict appears in higher-priority \- \packages"- $ do- let operators =- [ ( "A",- [ ("+", FixityInfo (Just InfixR) 4 4),- ("-", FixityInfo (Just InfixR) 2 2)- ]- ),- ( "B",- [ ("+", FixityInfo (Just InfixN) 6 6),- ("-", FixityInfo (Just InfixL) 4 4)- ]- ),- ("C", [("+", FixityInfo (Just InfixN) 8 8)])- ]- popularity =- [ ("A", 0),- ("B", 0),- ("C", 11103)- ]- dependencies = []- higherPriorityPackages = ["B", "C"]- threshold = 0.4- result =- [ ("+", FixityInfo (Just InfixN) 6 8),- ("-", FixityInfo (Just InfixL) 4 4)- ]- checkFixityMap'- operators- popularity- higherPriorityPackages- dependencies- threshold- result-- it- "whitelists declarations from base even when it is not listed in \- \cabal dependencies"- $ do- let operators =- [ ( "base",- [ ("+", FixityInfo (Just InfixR) 4 4),- ("-", FixityInfo (Just InfixR) 2 2)- ]- ),- ( "B",- [ ("+", FixityInfo (Just InfixN) 6 6),- ("-", FixityInfo (Just InfixL) 4 4)- ]- ),- ( "C",- [ ("+", FixityInfo (Just InfixN) 8 8),- ("|>", FixityInfo (Just InfixN) 1 1)- ]- )- ]- popularity =- [ ("base", 0),- ("B", 2),- ("C", 11103)- ]- dependencies = ["B", "C"]- higherPriorityPackages = []- threshold = 0.4- result =- [ ("+", FixityInfo (Just InfixR) 4 4),- ("-", FixityInfo (Just InfixR) 2 2),- ("|>", FixityInfo (Just InfixN) 1 1)- ]- checkFixityMap'- operators- popularity- higherPriorityPackages- dependencies- threshold- result-- it- "whitelists declarations from base when base is also listed in cabal \- \dependencies"- $ do- let operators =- [ ( "base",- [ ("+", FixityInfo (Just InfixR) 4 4),- ("-", FixityInfo (Just InfixR) 2 2)- ]- ),- ( "B",- [ ("+", FixityInfo (Just InfixN) 6 6),- ("?=", FixityInfo (Just InfixL) 4 4)- ]- ),- ( "C",- [ ("<|>", FixityInfo (Just InfixN) 8 8),- ("?=", FixityInfo (Just InfixN) 1 1)- ]- )- ]- popularity =- [ ("base", 0),- ("B", 2),- ("C", 11103)- ]- dependencies = ["base", "B"]- higherPriorityPackages = []- threshold = 0.6- result =- [ ("+", FixityInfo (Just InfixR) 4 4),- ("-", FixityInfo (Just InfixR) 2 2),- ("?=", FixityInfo (Just InfixL) 4 4)- ]- checkFixityMap'- operators- popularity- higherPriorityPackages- dependencies- threshold- result-- it- "gives higher priority to declarations from cabal dependencies than \- \declarations from both higher-priority & unspecified packages"- $ do- let operators =- [ ( "base",- [ ("+", FixityInfo (Just InfixR) 4 4),- ("-", FixityInfo (Just InfixR) 2 2)- ]- ),- ( "B",- [ ("+", FixityInfo (Just InfixN) 6 6),- ("?=", FixityInfo (Just InfixL) 4 4)- ]- ),- ( "C",- [ ("<|>", FixityInfo (Just InfixN) 8 8),- ("?=", FixityInfo (Just InfixN) 1 1)- ]- ),- ("D", [("+", FixityInfo (Just InfixR) 2 2)])- ]- popularity =- [ ("base", 0),- ("B", 2),- ("C", 11103)- ]- dependencies = ["base", "B"]- higherPriorityPackages = ["D"]- threshold = 0.6- result =- [ ("?=", FixityInfo (Just InfixL) 4 4),- ("<|>", FixityInfo (Just InfixN) 8 8)- ]- checkFixityMap'- operators- popularity- higherPriorityPackages- dependencies- threshold- result-- it- "gives higher priority to declarations from higher-priority packages \- \than declarations from unspecified packages"- $ do- let operators =- [ ( "base",- [ ("+", FixityInfo (Just InfixR) 4 4),- ("-", FixityInfo (Just InfixR) 2 2)- ]- ),- ( "B",- [ ("+", FixityInfo (Just InfixN) 6 6),- ("?=", FixityInfo (Just InfixL) 4 4)- ]- ),- ( "C",- [ ("<|>", FixityInfo (Just InfixN) 8 8),- ("?=", FixityInfo (Just InfixN) 1 1)- ]- ),- ("D", [("+", FixityInfo (Just InfixR) 2 2)])- ]- popularity =- [ ("base", 0),- ("B", 2),- ("C", 11103)- ]- dependencies = []- higherPriorityPackages = ["B"]- threshold = 0.6- result =- [ ("+", FixityInfo (Just InfixR) 4 4),- ("?=", FixityInfo (Just InfixL) 4 4),- ("<|>", FixityInfo (Just InfixN) 8 8)- ]- checkFixityMap'- operators- popularity- higherPriorityPackages- dependencies- threshold- result-- it "gives the correct fixity info for ':' (from base)" $ do- let dependencies = []- threshold = 0.6- result =- [ (":", FixityInfo (Just InfixR) 5 5)- ]- checkFixityMap dependencies threshold result-- it- "gives the base's fixity info for '<|>', even when a dependency has a \- \conflicting declaration for it"- $ do- let dependencies = ["pandoc"]- threshold = 0.9- result =- [ ("<|>", FixityInfo (Just InfixL) 3 3)- ]- checkFixityMap dependencies threshold result-- it- "gives the containers's fixity info for ':>' (because 'containers' is \- \a higher-priority package), even though max(pop) < threshold for \- \this operator)"- $ do- let dependencies = []- threshold = 0.9- result =- [ (":>", FixityInfo (Just InfixL) 5 5)- ]- checkFixityMap dependencies threshold result-- it- "gives the servant's fixity info for ':>' once servant is added as a \- \dependency (although ':>' is also defined in 'containers', a \- \higher-priority package)"- $ do- let dependencies = ["servant"]- threshold = 0.9- result =- [ (":>", FixityInfo (Just InfixR) 4 4)- ]- checkFixityMap dependencies threshold result
tests/Ormolu/OpTreeSpec.hs view
@@ -3,13 +3,12 @@ module Ormolu.OpTreeSpec (spec) where import Data.Map.Strict qualified as Map-import Data.Maybe (fromJust) import Data.Text (Text) import Data.Text qualified as T import GHC.Types.Name (mkOccName, varName) import GHC.Types.Name.Reader (mkRdrUnqual) import Ormolu.Fixity-import Ormolu.Fixity.Internal (LazyFixityMap (..))+import Ormolu.Fixity.Internal import Ormolu.Printer.Operators import Test.Hspec @@ -25,20 +24,16 @@ -- | Expected output tree OpTree Text OpName -> Expectation-checkReassociate lFixities inputTree expectedOutputTree =+checkReassociate fixities inputTree expectedOutputTree = removeOpInfo actualOutputTree `shouldBe` expectedOutputTree where removeOpInfo (OpNode x) = OpNode x removeOpInfo (OpBranches exprs ops) = OpBranches (removeOpInfo <$> exprs) (opiOp <$> ops)- actualOutputTree = reassociateOpTree convertName Map.empty fixityMap inputTree- fixityMap = LazyFixityMap [Map.fromList lFixities]+ actualOutputTree = reassociateOpTree convertName modFixityMap inputTree+ modFixityMap = ModuleFixityMap (Map.map Given (Map.fromList fixities)) convertName = Just . mkRdrUnqual . mkOccName varName . T.unpack . unOpName --- | Associative list of fixities for operators from "base"-baseFixities :: [(OpName, FixityInfo)]-baseFixities = Map.toList . fromJust $ Map.lookup "base" packageToOps- spec :: Spec spec = do it "flattens a tree correctly" $ do@@ -52,7 +47,7 @@ ["+"] outputTree = OpBranches [n "a", n "b", n "c", n "d"] ["+", "+", "+"]- fixities = [("+", FixityInfo (Just InfixL) 5 5)]+ fixities = [("+", FixityInfo InfixL 5)] checkReassociate fixities inputTree outputTree it "uses 'minOps' strategy by default" $ do@@ -68,9 +63,9 @@ ] ["+", "-"] fixities =- [ ("+", FixityInfo (Just InfixL) 5 5),- ("*", FixityInfo (Just InfixL) 7 7),- ("-", FixityInfo (Just InfixL) 5 5)+ [ ("+", FixityInfo InfixL 5),+ ("*", FixityInfo InfixL 7),+ ("-", FixityInfo InfixL 5) ] checkReassociate fixities inputTree outputTree @@ -87,9 +82,9 @@ ] ["+", "-"] fixities =- [ ("+", FixityInfo (Just InfixL) 5 7),- ("*", FixityInfo (Just InfixL) 8 8),- ("-", FixityInfo (Just InfixL) 4 6)+ [ ("+", FixityInfo InfixL 5),+ ("*", FixityInfo InfixL 8),+ ("-", FixityInfo InfixL 5) ] checkReassociate fixities inputTree outputTree @@ -110,9 +105,9 @@ ] ["$"] fixities =- [ ("@", FixityInfo (Just InfixL) 0 5),- ("|", FixityInfo (Just InfixL) 4 8),- ("$", FixityInfo (Just InfixR) 0 0)+ [ ("@", FixityInfo InfixL 4),+ ("|", FixityInfo InfixL 4),+ ("$", FixityInfo InfixR 0) ] checkReassociate fixities inputTree outputTree @@ -132,4 +127,9 @@ ["+"] ] ["$", "$"]- checkReassociate baseFixities inputTree outputTree+ fixities =+ [ ("$", FixityInfo InfixR 0),+ ("+", FixityInfo InfixL 6),+ ("*", FixityInfo InfixL 7)+ ]+ checkReassociate fixities inputTree outputTree
tests/Ormolu/PrinterSpec.hs view
@@ -8,6 +8,7 @@ import Data.List (isSuffixOf) import Data.Map qualified as Map import Data.Maybe (isJust)+import Data.Set qualified as Set import Data.Text (Text) import Data.Text qualified as T import Data.Text.IO qualified as T@@ -25,13 +26,15 @@ es <- runIO locateExamples forM_ es checkExample --- | Fixities that are to be used with the test examples.-testsuiteFixities :: FixityMap-testsuiteFixities =- Map.fromList- [ (".=", FixityInfo (Just InfixR) 8 8),- ("#", FixityInfo (Just InfixR) 5 5)- ]+-- | Fixity overrides that are to be used with the test examples.+testsuiteOverrides :: FixityOverrides+testsuiteOverrides =+ FixityOverrides+ ( Map.fromList+ [ (".=", FixityInfo InfixR 8),+ ("#", FixityInfo InfixR 5)+ ]+ ) -- | Check a single given example. checkExample :: Path Rel File -> Spec@@ -41,7 +44,14 @@ config = defaultConfig { cfgSourceType = detectSourceType inputPath,- cfgFixityOverrides = testsuiteFixities+ cfgFixityOverrides = testsuiteOverrides,+ cfgDependencies =+ Set.fromList+ [ "base",+ "esqueleto",+ "lens",+ "servant"+ ] } expectedOutputPath <- deriveOutput srcPath -- 1. Given input snippet of source code parse it and pretty print it.