{-# LANGUAGE DataKinds, TypeFamilies, ScopedTypeVariables #-} module DiffSummary (DiffSummary(..), diffSummary, DiffInfo(..), annotatedSummaries) where import Prologue hiding (snd, intercalate) import Diff import Patch import Term import Info (category) import Syntax as S import Category as C import Data.Functor.Foldable as Foldable import Data.Functor.Both import Data.Text as Text (intercalate) import Test.QuickCheck hiding (Fixed) import Patch.Arbitrary() import Data.Record import Text.PrettyPrint.Leijen.Text ((<+>), squotes, space, string, Doc, punctuate, pretty) import qualified Text.PrettyPrint.Leijen.Text as P data DiffInfo = LeafInfo { categoryName :: Text, termName :: Text } | BranchInfo { branches :: [ DiffInfo ], categoryName :: Text, branchType :: Branch } deriving (Eq, Show) toTermName :: (HasCategory leaf, HasField fields Category) => Term leaf (Record fields) -> Text toTermName term = case unwrap term of S.Fixed children -> fromMaybe "branch" $ (toCategoryName . category) . extract <$> head children S.Indexed children -> fromMaybe "branch" $ (toCategoryName . category) . extract <$> head children Leaf leaf -> toCategoryName leaf S.Assignment identifier value -> toTermName identifier <> toTermName value S.Function identifier _ _ -> (maybe "anonymous" toTermName identifier) S.FunctionCall i _ -> toTermName i S.MemberAccess base property -> case (unwrap base, unwrap property) of (S.FunctionCall{}, S.FunctionCall{}) -> toTermName base <> "()." <> toTermName property <> "()" (S.FunctionCall{}, _) -> toTermName base <> "()." <> toTermName property (_, S.FunctionCall{}) -> toTermName base <> "." <> toTermName property <> "()" (_, _) -> toTermName base <> "." <> toTermName property S.MethodCall targetId methodId _ -> toTermName targetId <> sep <> toTermName methodId <> "()" where sep = case unwrap targetId of S.FunctionCall{} -> "()." _ -> "." S.SubscriptAccess base element -> case (unwrap base, unwrap element) of (S.FunctionCall{}, S.FunctionCall{}) -> toTermName base <> "()." <> toTermName element <> "()" (S.FunctionCall{}, _) -> toTermName base <> "()." <> toTermName element (_, S.FunctionCall{}) -> toTermName base <> "[" <> toTermName element <> "()" <> "]" (_, _) -> toTermName base <> "[" <> toTermName element <> "]" S.VarAssignment varId _ -> toTermName varId S.VarDecl decl -> toTermName decl -- TODO: We should remove Args from Syntax since I don't think we should ever -- evaluate Args as a single toTermName Text - joshvera S.Args args -> mconcat $ toTermName <$> args -- TODO: We should remove Case from Syntax since I don't think we should ever -- evaluate Case as a single toTermName Text - joshvera S.Case expr _ -> toTermName expr S.Switch expr _ -> toTermName expr S.Ternary expr _ -> toTermName expr S.MathAssignment id _ -> toTermName id S.Operator syntaxes -> mconcat $ toTermName <$> syntaxes S.Object kvs -> "{" <> intercalate ", " (toTermName <$> kvs) <> "}" S.Pair a b -> toTermName a <> ": " <> toTermName b Comment a -> toCategoryName a class HasCategory a where toCategoryName :: a -> Text instance HasCategory Text where toCategoryName = identity newtype Categorizable a = Categorizable a instance (HasField fields Category) => HasCategory (Categorizable (Record fields)) where toCategoryName (Categorizable a)= toCategoryName $ category a instance HasCategory Category where toCategoryName = \case ArrayLiteral -> "array" BinaryOperator -> "binary operator" Boolean -> "boolean" DictionaryLiteral -> "dictionary" C.Error -> "error" ExpressionStatements -> "expression statements" C.Assignment -> "assignment" C.Function -> "function" C.FunctionCall -> "function call" C.MemberAccess -> "member access" C.MethodCall -> "method call" C.Args -> "arguments" C.VarAssignment -> "var assignment" C.VarDecl -> "variable" C.Switch -> "switch statement" C.Case -> "case statement" C.SubscriptAccess -> "subscript access" C.MathAssignment -> "math assignment" C.Ternary -> "ternary" C.Operator -> "operator" Identifier -> "identifier" IntegerLiteral -> "integer" Other s -> s C.Pair -> "pair" Params -> "params" Program -> "top level" Regex -> "regex" StringLiteral -> "string" SymbolLiteral -> "symbol" TemplateString -> "template string" C.Object -> "object" instance (HasCategory leaf, HasField fields Category) => HasCategory (Term leaf (Record fields)) where toCategoryName = toCategoryName . category . extract data Branch = BIndexed | BFixed | BCommented | BError deriving (Show, Eq, Generic) instance Arbitrary Branch where arbitrary = oneof [ pure BIndexed, pure BFixed ] shrink = genericShrink data DiffSummary a = DiffSummary { patch :: Patch a, parentAnnotations :: [Category] } deriving (Eq, Functor, Show, Generic) instance (Eq a, Arbitrary a) => Arbitrary (DiffSummary a) where arbitrary = DiffSummary <$> arbitrary <*> arbitrary shrink = genericShrink instance P.Pretty DiffInfo where pretty LeafInfo{..} = squotes (string $ toSL termName) <+> (string $ toSL categoryName) pretty BranchInfo{..} = mconcat $ punctuate (string "," <> space) (pretty <$> branches) annotatedSummaries :: DiffSummary DiffInfo -> [Text] annotatedSummaries DiffSummary{..} = show . (P.<> maybeParentContext parentAnnotations) <$> summaries patch summaries :: Patch DiffInfo -> [P.Doc] summaries (Insert info) = (("Added" <+> "the") <+>) <$> toLeafInfos info summaries (Delete info) = (("Deleted" <+> "the") <+>) <$> toLeafInfos info summaries (Replace i1 i2) = zipWith (\a b -> "Replaced" <+> "the" <+> a <+> "with the" <+> b) (toLeafInfos i1) (toLeafInfos i2) toLeafInfos :: DiffInfo -> [Doc] toLeafInfos LeafInfo{..} = [ squotes (toDoc termName) <+> (toDoc categoryName) ] toLeafInfos BranchInfo{..} = pretty <$> branches maybeParentContext :: [Category] -> Doc maybeParentContext annotations = if null annotations then "" else space <> "in the" <+> (toDoc . intercalate "/" $ toCategoryName <$> annotations) <+> "context" toDoc :: Text -> Doc toDoc = string . toS diffSummary :: (HasCategory leaf, HasField fields Category) => Diff leaf (Record fields) -> [DiffSummary DiffInfo] diffSummary = cata $ \case -- Skip comments and leaves since they don't have any changes (Free (_ :< Leaf _)) -> [] Free (_ :< (S.Comment _)) -> [] (Free (infos :< S.Indexed children)) -> prependSummary (category $ snd infos) <$> join children (Free (infos :< S.Fixed children)) -> prependSummary (category $ snd infos) <$> join children (Free (infos :< S.FunctionCall identifier children)) -> prependSummary (category $ snd infos) <$> join (Prologue.toList (identifier : children)) (Free (infos :< S.Function id ps body)) -> prependSummary (category $ snd infos) <$> (fromMaybe [] id) <> (fromMaybe [] ps) <> body (Free (infos :< S.Assignment id value)) -> prependSummary (category $ snd infos) <$> id <> value (Free (infos :< S.MemberAccess base property)) -> prependSummary (category $ snd infos) <$> base <> property (Free (infos :< S.SubscriptAccess base property)) -> prependSummary (category $ snd infos) <$> base <> property (Free (infos :< S.MethodCall targetId methodId ps)) -> prependSummary (category $ snd infos) <$> targetId <> methodId <> ps (Free (infos :< S.VarAssignment varId value)) -> prependSummary (category $ snd infos) <$> varId <> value (Free (infos :< S.VarDecl decl)) -> prependSummary (category $ snd infos) <$> decl (Free (infos :< S.Args args)) -> prependSummary (category $ snd infos) <$> join args (Free (infos :< S.Switch expr cases)) -> prependSummary (category $ snd infos) <$> expr <> join cases (Free (infos :< S.Case expr body)) -> prependSummary (category $ snd infos) <$> expr <> body Free (infos :< (S.Ternary expr cases)) -> prependSummary (category $ snd infos) <$> expr <> join cases Free (infos :< (S.MathAssignment id value)) -> prependSummary (category $ snd infos) <$> id <> value Free (infos :< (S.Operator syntaxes)) -> prependSummary (category $ snd infos) <$> join syntaxes Free (infos :< (S.Object kvs)) -> prependSummary (category $ snd infos) <$> join kvs Free (infos :< (S.Pair a b)) -> prependSummary (category $ snd infos) <$> a <> b Free (infos :< (S.Commented cs leaf)) -> prependSummary (category $ snd infos) <$> join cs <> fromMaybe [] leaf Free (infos :< (S.Error children)) -> prependSummary (category $ snd infos) <$> join children (Pure (Insert term)) -> [ DiffSummary (Insert $ termToDiffInfo term) [] ] (Pure (Delete term)) -> [ DiffSummary (Delete $ termToDiffInfo term) [] ] (Pure (Replace t1 t2)) -> [ DiffSummary (Replace (termToDiffInfo t1) (termToDiffInfo t2)) [] ] termToDiffInfo :: (HasCategory leaf, HasField fields Category) => Term leaf (Record fields) -> DiffInfo termToDiffInfo term = case unwrap term of Leaf _ -> LeafInfo (toCategoryName term) (toTermName term) S.Indexed children -> BranchInfo (termToDiffInfo <$> children) (toCategoryName term) BIndexed S.Fixed children -> BranchInfo (termToDiffInfo <$> children) (toCategoryName term) BFixed S.FunctionCall identifier _ -> LeafInfo (toCategoryName term) (toTermName identifier) S.Ternary ternaryCondition _ -> LeafInfo (toCategoryName term) (toTermName ternaryCondition) S.Function identifier _ _ -> LeafInfo (toCategoryName term) (maybe "anonymous" toTermName identifier) S.Assignment identifier _ -> LeafInfo (toCategoryName term) (toTermName identifier) S.MathAssignment identifier _ -> LeafInfo (toCategoryName term) (toTermName identifier) -- Currently we cannot express the operator for an operator production from TreeSitter. Eventually we should be able to -- use the term name of the operator identifier when we have that production value. Until then, I'm using a placeholder value -- to indicate where that value should be when constructing DiffInfos. S.Operator _ -> LeafInfo (toCategoryName term) "x" Commented cs leaf -> BranchInfo (termToDiffInfo <$> cs <> maybeToList leaf) (toCategoryName term) BCommented _ -> LeafInfo (toCategoryName term) (toTermName term) prependSummary :: Category -> DiffSummary DiffInfo -> DiffSummary DiffInfo prependSummary annotation summary = summary { parentAnnotations = annotation : parentAnnotations summary }