1
1
mirror of https://github.com/github/semantic.git synced 2024-12-23 14:54:16 +03:00
semantic/src/DiffSummary.hs

195 lines
11 KiB
Haskell

{-# 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, Cost)
import Syntax
import Category
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
import Data.Hashable
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
Syntax.Fixed children -> fromMaybe "branch" $ (toCategoryName . category) . extract <$> head children
Syntax.Indexed children -> fromMaybe "branch" $ (toCategoryName . category) . extract <$> head children
Leaf leaf -> toCategoryName leaf
Syntax.Assignment identifier value -> toTermName identifier <> toTermName value
Syntax.Function identifier _ _ -> (maybe "anonymous" toTermName identifier)
Syntax.FunctionCall i _ -> toTermName i
Syntax.MemberAccess base property -> case (unwrap base, unwrap property) of
(Syntax.FunctionCall{}, Syntax.FunctionCall{}) -> toTermName base <> "()." <> toTermName property <> "()"
(Syntax.FunctionCall{}, _) -> toTermName base <> "()." <> toTermName property
(_, Syntax.FunctionCall{}) -> toTermName base <> "." <> toTermName property <> "()"
(_, _) -> toTermName base <> "." <> toTermName property
Syntax.MethodCall targetId methodId _ -> toTermName targetId <> sep <> toTermName methodId <> "()"
where sep = case unwrap targetId of
Syntax.FunctionCall{} -> "()."
_ -> "."
Syntax.SubscriptAccess base element -> case (unwrap base, unwrap element) of
(Syntax.FunctionCall{}, Syntax.FunctionCall{}) -> toTermName base <> "()." <> toTermName element <> "()"
(Syntax.FunctionCall{}, _) -> toTermName base <> "()." <> toTermName element
(_, Syntax.FunctionCall{}) -> toTermName base <> "[" <> toTermName element <> "()" <> "]"
(_, _) -> toTermName base <> "[" <> toTermName element <> "]"
Syntax.VarAssignment varId _ -> toTermName varId
Syntax.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
Syntax.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
Syntax.Case expr _ -> toTermName expr
Syntax.Switch expr _ -> toTermName expr
Syntax.Ternary expr _ -> toTermName expr
Syntax.MathAssignment id _ -> toTermName id
Syntax.Operator syntaxes -> mconcat $ toTermName <$> syntaxes
Syntax.Object kvs -> "{" <> intercalate ", " (toTermName <$> kvs) <> "}"
Syntax.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"
Error -> "error"
ExpressionStatements -> "expression statements"
Category.Assignment -> "assignment"
Category.Function -> "function"
Category.FunctionCall -> "function call"
Category.MemberAccess -> "member access"
Category.MethodCall -> "method call"
Category.Args -> "arguments"
Category.VarAssignment -> "var assignment"
Category.VarDecl -> "variable"
Category.Switch -> "switch statement"
Category.Case -> "case statement"
Category.SubscriptAccess -> "subscript access"
Category.MathAssignment -> "math assignment"
Category.Ternary -> "ternary"
Category.Operator -> "operator"
Identifier -> "identifier"
IntegerLiteral -> "integer"
Other s -> s
Category.Pair -> "pair"
Params -> "params"
Program -> "top level"
Regex -> "regex"
StringLiteral -> "string"
SymbolLiteral -> "symbol"
TemplateString -> "template string"
Category.Object -> "object"
instance (HasCategory leaf, HasField fields Category) => HasCategory (Term leaf (Record fields)) where
toCategoryName = toCategoryName . category . extract
data Branch = BIndexed | BFixed | BCommented 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 (_ :< (Syntax.Comment _)) -> []
(Free (infos :< Syntax.Indexed children)) -> prependSummary (category $ snd infos) <$> join children
(Free (infos :< Syntax.Fixed children)) -> prependSummary (category $ snd infos) <$> join children
(Free (infos :< Syntax.FunctionCall identifier children)) -> prependSummary (category $ snd infos) <$> join (Prologue.toList (identifier : children))
(Free (infos :< Syntax.Function id ps body)) -> prependSummary (category $ snd infos) <$> (fromMaybe [] id) <> (fromMaybe [] ps) <> body
(Free (infos :< Syntax.Assignment id value)) -> prependSummary (category $ snd infos) <$> id <> value
(Free (infos :< Syntax.MemberAccess base property)) -> prependSummary (category $ snd infos) <$> base <> property
(Free (infos :< Syntax.SubscriptAccess base property)) -> prependSummary (category $ snd infos) <$> base <> property
(Free (infos :< Syntax.MethodCall targetId methodId ps)) -> prependSummary (category $ snd infos) <$> targetId <> methodId <> ps
(Free (infos :< Syntax.VarAssignment varId value)) -> prependSummary (category $ snd infos) <$> varId <> value
(Free (infos :< Syntax.VarDecl decl)) -> prependSummary (category $ snd infos) <$> decl
(Free (infos :< Syntax.Args args)) -> prependSummary (category $ snd infos) <$> join args
(Free (infos :< Syntax.Switch expr cases)) -> prependSummary (category $ snd infos) <$> expr <> join cases
(Free (infos :< Syntax.Case expr body)) -> prependSummary (category $ snd infos) <$> expr <> body
Free (infos :< (Syntax.Ternary expr cases)) -> prependSummary (category $ snd infos) <$> expr <> join cases
Free (infos :< (Syntax.MathAssignment id value)) -> prependSummary (category $ snd infos) <$> id <> value
Free (infos :< (Syntax.Operator syntaxes)) -> prependSummary (category $ snd infos) <$> join syntaxes
Free (infos :< (Syntax.Object kvs)) -> prependSummary (category $ snd infos) <$> join kvs
Free (infos :< (Syntax.Pair a b)) -> prependSummary (category $ snd infos) <$> a <> b
Free (infos :< (Syntax.Commented cs leaf)) -> prependSummary (category $ snd infos) <$> join cs <> fromMaybe [] leaf
(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 runCofree term of
(_ :< Leaf _) -> LeafInfo (toCategoryName term) (toTermName term)
(info :< Syntax.Indexed children) -> BranchInfo (termToDiffInfo <$> children) (toCategoryName (Categorizable info)) BIndexed
(info :< Syntax.Fixed children) -> BranchInfo (termToDiffInfo <$> children) (toCategoryName (Categorizable info)) BFixed
(info :< Syntax.FunctionCall identifier _) -> LeafInfo (toCategoryName (Categorizable info)) (toTermName identifier)
(info :< Syntax.Ternary ternaryCondition _) -> LeafInfo (toCategoryName (Categorizable info)) (toTermName ternaryCondition)
(info :< Syntax.Function identifier _ _) -> LeafInfo (toCategoryName $ Categorizable info) (maybe "anonymous" toTermName identifier)
(info :< Syntax.Assignment identifier _) -> LeafInfo (toCategoryName $ Categorizable info) (toTermName identifier)
(info :< Syntax.MathAssignment identifier _) -> LeafInfo (toCategoryName $ Categorizable info) (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.
(info :< Syntax.Operator _) -> LeafInfo (toCategoryName $ Categorizable info) "x"
(info :< Commented cs leaf) -> BranchInfo (termToDiffInfo <$> cs <> maybeToList leaf) (toCategoryName $ Categorizable info) BCommented
(info :< _) -> LeafInfo (toCategoryName $ Categorizable info) (toTermName term)
prependSummary :: Category -> DiffSummary DiffInfo -> DiffSummary DiffInfo
prependSummary annotation summary = summary { parentAnnotations = annotation : parentAnnotations summary }