exactprint/Thrift/ExactPrint/Codemod.hs (151 lines of code) (raw):

-- Copyright (c) Facebook, Inc. and its affiliates. {-# LANGUAGE CPP #-} module Thrift.ExactPrint.Codemod ( roundTripWith , codemodConsts , lookupEnum ) where import qualified Data.Map.Strict as Map import Data.Some import qualified Data.Text.Lazy as Text import Data.Text (Text) import Thrift.Compiler.Options import Thrift.Compiler.Parser import Thrift.Compiler.Pretty import Thrift.Compiler.Typechecker import qualified Thrift.Compiler.Typechecker.Monad as Typechecker import Thrift.Compiler.Types import Thrift.Compiler.Plugins.Linter import Thrift.ExactPrint.Convert import Thrift.ExactPrint.PrettyPrint import Thrift.ExactPrint.Types #if MIN_VERSION_dependent_sum(0,6,0) #define This Some #endif -- | Round Trip a file, applying some tranformation to the typechecked AST roundTripWith :: (Program Linter Offset -> Program Linter Offset) -> String -> String roundTripWith f input = case runParser parseThrift "" input of Left e -> error e Right tf -> case typecheck (defaultOptions NoOpts) (mkModuleMap tf) of Left es -> error $ concatMap renderTypeError es Right (prog,_) -> Text.unpack $ exactPrint $ f $ computeOffsets prog mkModuleMap :: ([Header Loc], [Parsed Decl]) -> ModuleMap mkModuleMap (headers, decls) = Map.singleton "" ThriftFile { thriftName = "" , thriftPath = "" , thriftHeaders = headers , thriftDecls = decls , thriftSplice = Nothing , thriftComments = [] } -- | Type aware codemod for constant values everywhere in the AST codemodConsts :: forall l a. (forall t. Env l -> Type l t -> ConstVal a -> ConstVal a) -> Program l a -> Program l a codemodConsts f p@Program{..} = p { progDecls = map codemodDecl progDecls } where codemodDecl (D_Struct s@Struct{..}) = D_Struct s { structMembers = map codemodField structMembers } codemodDecl (D_Const c@Const{..}) = D_Const c { constVal = codemodConst constResolvedType constVal } -- These have no constants codemodDecl d@D_Enum{} = d codemodDecl d@D_Typedef{} = d codemodDecl d@D_Union{} = d -- Services not supported yet codemodDecl d@D_Service{} = d codemodField :: Field u 'Resolved l a -> Field u 'Resolved l a codemodField fl@Field{..} = fl { fieldVal = codemodConst fieldResolvedType <$> fieldVal } codemodConst :: Type l t -> UntypedConst a -> UntypedConst a codemodConst ty c@UntypedConst{..} = c { ucConst = codemodConstVal ty ucConst } -- Recursively apply the refactoring function to everything codemodConstVal :: Type l t -> ConstVal a -> ConstVal a -- List Types codemodConstVal t@(TList u) c = codemodListVal t u c codemodConstVal t@(TSet u) c = codemodListVal t u c codemodConstVal t@(THashSet u) c = codemodListVal t u c -- Map Types codemodConstVal t@(TMap k v) c = codemodMapVal t k v c codemodConstVal t@(THashMap k v) c = codemodMapVal t k v c -- Aliases codemodConstVal t@(TTypedef _ u _loc) c = f progEnv t $ codemodConstVal u c codemodConstVal t@(TNewtype _ u _loc) c = f progEnv t $ codemodConstVal u c -- Structs codemodConstVal t@(TStruct name _loc) c | Just (This schema) <- lookupSchema name progEnv = f progEnv t $ codemodSchema schema c codemodConstVal t@(TException name _loc) c | Just (This schema) <- lookupSchema name progEnv = f progEnv t $ codemodSchema schema c codemodConstVal t@(TUnion name _loc) c | Just (This schema) <- lookupUnion name progEnv = f progEnv t $ codemodSchema schema c -- Everything Else codemodConstVal t c = f progEnv t c -- Apply the refactor inside the list and then outside the list codemodListVal :: Type l (f t) -> Type l t -> ConstVal a -> ConstVal a codemodListVal outer inner c@ListConst{..} = f progEnv outer c { lvElems = [ e { leElem = codemodConst inner leElem } | e@ListElem{..} <- lvElems ] } codemodListVal t _ c = f progEnv t c codemodMapVal :: Type l (f k v) -> Type l k -> Type l v -> ConstVal a -> ConstVal a codemodMapVal outer k v m@MapConst{..} = f progEnv outer m { mvElems = [ e { leElem = mp { mpKey = codemodConst k mpKey , mpVal = codemodConst v mpVal } } | e@ListElem{leElem=mp@MapPair{..}} <- mvElems ] } codemodMapVal t _ _ c = f progEnv t c codemodSchema :: SCHEMA l t s -> ConstVal a -> ConstVal a codemodSchema schema m@MapConst{..} = m { mvElems = [ e { leElem = mp { mpVal = codemodSchemaField schema s mpVal } } | e@ListElem {leElem= mp@MapPair{mpKey=UntypedConst{ucConst=StringConst s _},..}} <- mvElems ] } codemodSchema _ c = c codemodSchemaField :: SCHEMA l t s -> Text -> UntypedConst a -> UntypedConst a codemodSchemaField SEmpty _ c = c codemodSchemaField (SField _ name ty schema) fl c | name == fl = codemodConst ty c | otherwise = codemodSchemaField schema fl c codemodSchemaField (SReqField _ name ty schema) fl c | name == fl = codemodConst ty c | otherwise = codemodSchemaField schema fl c codemodSchemaField (SOptField _ name ty schema) fl c | name == fl = codemodConst ty c | otherwise = codemodSchemaField schema fl c lookupSchema :: Name -> Env l -> Maybe (Some (Schema l)) lookupSchema = wrapLookup Typechecker.lookupSchema lookupUnion :: Name -> Env l -> Maybe (Some (USchema l)) lookupUnion = wrapLookup Typechecker.lookupUnion lookupEnum :: Name -> Env l -> Maybe EnumValues lookupEnum = wrapLookup Typechecker.lookupEnum wrapLookup :: (ThriftName -> Loc -> Typechecker.TC l a) -> Name -> Env l -> Maybe a wrapLookup f Name{..} env = either (const Nothing) Just $ Typechecker.runTypechecker env $ f sourceName noLoc