1
1
mirror of https://github.com/github/semantic.git synced 2024-12-25 07:55:12 +03:00

Put both diff and parse rendering back together

This commit is contained in:
Timothy Clem 2017-04-20 15:13:28 -07:00
parent 389bfaf220
commit 19e87dd523
15 changed files with 186 additions and 250 deletions

View File

@ -146,8 +146,7 @@ test-suite test
hs-source-dirs: test
main-is: Spec.hs
other-modules: AlignmentSpec
, Command.Spec
, Command.Diff.Spec
, CommandSpec
, Data.Mergeable.Spec
, Data.RandomWalkSimilarity.Spec
, Data.Syntax.Assignment.Spec

View File

@ -1,4 +1,4 @@
{-# LANGUAGE GADTs, DuplicateRecordFields, RankNTypes #-}
{-# LANGUAGE GADTs, DuplicateRecordFields, RankNTypes, ViewPatterns #-}
{-# OPTIONS_GHC -funbox-strict-fields #-}
module Arguments where
@ -20,7 +20,6 @@ data DiffArguments where
, gitDir :: FilePath
, alternateObjectDirs :: [FilePath]
} -> DiffArguments
-- deriving Show
patchDiff :: DiffMode -> FilePath -> [FilePath] -> DiffArguments
patchDiff = DiffArguments PatchRenderer
@ -52,15 +51,36 @@ data ParseArguments where
, gitDir :: FilePath
, alternateObjectDirs :: [FilePath]
} -> ParseArguments
-- deriving Show
sExpressionParseTree :: ParseMode -> Bool -> FilePath -> [FilePath] -> ParseArguments
sExpressionParseTree = ParseArguments (SExpressionParseTreeRenderer TreeOnly)
jsonParseTree :: ParseMode -> Bool -> FilePath -> [FilePath] -> ParseArguments
jsonParseTree = ParseArguments JSONParseTreeRenderer
jsonIndexParseTree :: ParseMode -> Bool -> FilePath -> [FilePath] -> ParseArguments
jsonIndexParseTree = ParseArguments JSONIndexParseTreeRenderer
data ProgramMode = Parse ParseArguments | Diff DiffArguments
-- deriving Show
deriving Show
data Arguments = Arguments
{ programMode :: ProgramMode
, outputFilePath :: Maybe FilePath
} -- deriving Show
} deriving Show
instance Show DiffArguments where
showsPrec d (DiffArguments renderer mode gitDir alternateObjectDirs) = showParen (d >= 10) $ showString "DiffArguments "
. showString "diffRenderer = " . shows renderer . showString ", "
. showString "diffMode = " . shows mode . showString ", "
. showString "gitDir = " . shows gitDir . showString ", "
. showString "alternateObjectDirs = " . shows alternateObjectDirs
instance Show ParseArguments where
showsPrec d (ParseArguments renderer mode debug gitDir alternateObjectDirs) = showParen (d >= 10) $ showString "ParseArguments "
. showString "parseTreeRenderer = " . shows renderer . showString ", "
. showString "parseMode = " . shows mode . showString ", "
. showString "debug = " . shows debug . showString ", "
. showString "gitDir = " . shows gitDir . showString ", "
. showString "alternateObjectDirs = " . shows alternateObjectDirs

View File

@ -11,14 +11,6 @@ module Command
, maybeDiff
, renderDiffs
, concurrently
-- , patchDiff
-- , splitDiff
-- , jsonDiff
-- , summaryDiff
-- , sExpressionDiff
-- , tocDiff
-- , DiffEncoder
-- , ParseTreeEncoder
-- Evaluation
, runCommand
) where
@ -29,11 +21,9 @@ import Control.Exception (catch)
import Control.Monad.Free.Freer
import Control.Monad.IO.Class
import Control.Parallel.Strategies
import Data.Aeson hiding (json)
import qualified Data.ByteString as B
import Data.Functor.Both
import Data.Functor.Classes
import Data.Functor.Listable
import Data.List ((\\), nub)
import Data.RandomWalkSimilarity
import Data.Record
@ -53,8 +43,6 @@ import Language
import Patch
import Parser.Language
import Prologue hiding (concurrently, Concurrently, readFile)
import qualified Renderer as R
import qualified Renderer.SExpression as R
import Renderer
import Source
import Syntax
@ -204,59 +192,6 @@ runRenderDiffs :: (Monoid output, StringConv output ByteString) => DiffRenderer
runRenderDiffs = runDiffRenderer
-- type ParseTreeEncoder = Bool -> [Term (Syntax Text) (Record DefaultFields)] -> Command ByteString
-- type DiffEncoder = [(Both SourceBlob, Diff (Syntax Text) (Record DefaultFields))] -> Command ByteString
-- patchDiff :: DiffEncoder
-- patchDiff = fmap encodeText . renderDiffs R.PatchRenderer
--
-- splitDiff :: DiffEncoder
-- splitDiff = fmap encodeText . renderDiffs R.SplitRenderer
--
-- jsonDiff :: DiffEncoder
-- jsonDiff = fmap encodeJSON . renderDiffs R.JSONDiffRenderer
-- summaryDiff :: DiffEncoder
-- summaryDiff = fmap encodeSummaries . renderDiffs R.SummaryRenderer
-- sExpressionDiff :: DiffEncoder
-- sExpressionDiff = renderDiffs (R.SExpressionDiffRenderer R.TreeOnly)
-- tocDiff :: DiffEncoder
-- tocDiff = fmap encodeSummaries . renderDiffs R.ToCRenderer
-- encodeJSON :: Map Text Value -> ByteString
-- encodeJSON = toS . (<> "\n") . encode
--
-- encodeText :: File -> ByteString
-- encodeText = encodeUtf8 . R.unFile
-- encodeSummaries :: Summaries -> ByteString
-- encodeSummaries = toS . (<> "\n") . encode
--
-- instance Show ParseTreeEncoder where
-- showsPrec d _ = showParen (d >= 10) $ showString "ParseTreeEncoder "
-- --
-- instance Listable ParseTreeEncoder where
-- tiers = cons0 jsonParseTree
-- \/ cons0 jsonIndexParseTree
-- \/ cons0 sExpressionParseTree
-- instance Show DiffEncoder where
-- showsPrec d encodeDiff = showParen (d >= 10) $ showString "DiffEncoder "
-- . showsPrec 10 (encodeDiff []) . showChar ' '
-- instance Listable DiffEncoder where
-- tiers = cons0
-- tiers = cons0 patchDiff
-- \/ cons0 splitDiff
-- \/ cons0 jsonDiff
-- \/ cons0 summaryDiff
-- \/ cons0 sExpressionDiff
-- \/ cons0 tocDiff
instance MonadIO Command where
liftIO io = LiftIO io `Then` return

View File

@ -28,7 +28,7 @@ import Data.Aeson
type DefaultFields = '[ Range, Category, SourceSpan ]
-- | A type alias for HasField constraints commonly used throughout semantic-diff.
type HasDefaultFields fields = (HasField fields Category, HasField fields Range, HasField fields SourceSpan)
type HasDefaultFields fields = (HasField fields Range, HasField fields Category, HasField fields SourceSpan)
newtype SourceText = SourceText { unText :: Text }
deriving (Show, ToJSON)

View File

@ -27,7 +27,6 @@ import Renderer.TOC as R
import Source (SourceBlob)
import Syntax
import Term
import Data.Functor.Listable
data DiffRenderer fields output where
@ -49,13 +48,14 @@ runDiffRenderer renderer = foldMap . uncurry $ case renderer of
data ParseTreeRenderer fields output where
SExpressionParseTreeRenderer :: (HasField fields Category, HasField fields SourceSpan) => SExpressionFormat -> ParseTreeRenderer fields ByteString
-- JSONParseTreeRenderer :: ParseTreeRenderer ParseTreeFile
JSONParseTreeRenderer :: HasDefaultFields fields => ParseTreeRenderer fields Value
JSONIndexParseTreeRenderer :: HasDefaultFields fields => ParseTreeRenderer fields Value
runParseTreeRenderer :: (Monoid output, StringConv output ByteString) => ParseTreeRenderer fields output -> [(SourceBlob, Term (Syntax Text) (Record fields))] -> output
runParseTreeRenderer renderer = foldMap . uncurry $ case renderer of
SExpressionParseTreeRenderer format -> R.sExpressionParseTree format
-- where
-- printTerm format term = R.printTerm term 0 format
JSONParseTreeRenderer -> R.jsonParseTree False
JSONIndexParseTreeRenderer -> R.jsonIndexParseTree False
newtype File = File { unFile :: Text }
deriving Show
@ -73,12 +73,14 @@ instance Show (DiffRenderer fields output) where
instance Show (ParseTreeRenderer fields output) where
showsPrec d (SExpressionParseTreeRenderer format) = showsUnaryWith showsPrec "SExpressionParseTreeRenderer" d format
-- showsPrec _ JSONParseTreeRenderer = showString "JSONParseTreeRenderer"
showsPrec _ JSONParseTreeRenderer = showString "JSONParseTreeRenderer"
showsPrec _ JSONIndexParseTreeRenderer = showString "JSONIndexParseTreeRenderer"
instance Monoid File where
mempty = File mempty
mappend (File a) (File b) = File (a <> "\n" <> b)
-- instance Listable ParseTreeRenderer where
-- instance Listable (ParseTreeRenderer DefaultFields output) where
-- tiers = cons0 (SExpressionParseTreeRenderer TreeOnly)
-- tiers = cons0 (SExpressionParseTreeRenderer TreeOnly)
-- \/ cons0 JSONParseTreeRenderer

View File

@ -5,13 +5,14 @@
module Renderer.JSON
( json
, jsonParseTree
, jsonIndexParseTree
, ParseTreeFile(..)
) where
import Alignment
import Data.Aeson (ToJSON, toJSON, encode, object, (.=))
import Data.Aeson as A hiding (json)
import Data.Aeson.Types (Pair)
import Data.Aeson.Types (Pair, emptyArray)
import Data.Bifunctor.Join
import Data.Functor.Both
import Data.Functor.Foldable hiding (Nil)
@ -20,8 +21,6 @@ import Data.These
import Data.Vector as Vector hiding (toList)
import Diff
import Info
import Parser
import Parser.Language
import Prologue
import qualified Data.Map as Map
import qualified Data.Text as T
@ -175,10 +174,17 @@ data ParseTreeFile = ParseTreeFile { parseTreeFilePath :: FilePath, node :: Rose
data Rose a = Rose a [Rose a]
deriving (Eq, Show)
instance ToJSON ParseTreeFile where
toJSON ParseTreeFile{..} = object [ "filePath" .= parseTreeFilePath, "programNode" .= cata algebra node ]
where algebra (RoseF a as) = object $ parseNodeToJSONFields a <> [ "children" .= as ]
instance Monoid Value where
mempty = emptyArray
mappend a b = A.Array $ Vector.fromList [a, b]
instance StringConv Value ByteString where
strConv _ = toS . (<> "\n") . encode
data IndexFile = IndexFile { indexFilePath :: FilePath, nodes :: [ParseNode] } deriving (Show)
@ -187,7 +193,7 @@ instance ToJSON IndexFile where
where singleton a = [a]
data ParseNode = ParseNode
{ category :: Text
{ category :: Category
, sourceRange :: Range
, sourceText :: Maybe SourceText
, sourceSpan :: SourceSpan
@ -198,56 +204,46 @@ data ParseNode = ParseNode
-- | Produce a list of JSON 'Pair's for the fields in a given ParseNode.
parseNodeToJSONFields :: ParseNode -> [Pair]
parseNodeToJSONFields ParseNode{..} =
[ "category" .= category, "sourceRange" .= sourceRange, "sourceSpan" .= sourceSpan ]
[ "category" .= (toS category :: Text), "sourceRange" .= sourceRange, "sourceSpan" .= sourceSpan ]
<> [ "sourceText" .= sourceText | isJust sourceText ]
<> [ "identifier" .= identifier | isJust identifier ]
jsonParseTree :: HasDefaultFields fields => Bool -> SourceBlob -> Term (Syntax Text) (Record fields) -> Value
jsonParseTree = jsonParseTree' ParseTreeFile Rose
jsonParseTree :: Bool -> SourceBlob -> ByteString
jsonParseTree = undefined
jsonIndexParseTree :: HasDefaultFields fields => Bool -> SourceBlob -> Term (Syntax Text) (Record fields) -> Value
jsonIndexParseTree = jsonParseTree' IndexFile combine
where combine node siblings = node : Prologue.concat siblings
-- -- | Constructs ParseTreeFile nodes for the provided arguments and encodes them to JSON.
-- jsonParseTree :: Bool -> [SourceBlob] -> IO ByteString
-- jsonParseTree debug = fmap (toS . encode) . parseRoot debug ParseTreeFile Rose
--
-- -- | Constructs IndexFile nodes for the provided arguments and encodes them to JSON.
-- jsonIndexParseTree :: Bool -> [SourceBlob] -> IO ByteString
-- jsonIndexParseTree debug = fmap (toS . encode) . parseRoot debug IndexFile (\ node siblings -> node : Prologue.concat siblings)
jsonParseTree' :: (ToJSON root, HasDefaultFields fields) => (FilePath -> a -> root) -> (ParseNode -> [a] -> a) -> Bool -> SourceBlob -> Term (Syntax Text) (Record fields) -> Value
jsonParseTree' constructor combine debug SourceBlob{..} term = toJSON $ constructor path (para algebra term')
where
term' = decorateTerm (parseDecorator debug source) term
algebra (annotation :< syntax) = combine (makeNode annotation (Prologue.fst <$> syntax)) (toList (Prologue.snd <$> syntax))
parseRoot :: Bool -> (FilePath -> f ParseNode -> root) -> (ParseNode -> [f ParseNode] -> f ParseNode) -> [SourceBlob] -> IO [root]
parseRoot debug construct combine blobs = for blobs (\ sourceBlob@SourceBlob{..} -> do
parsedTerm <- parseWithDecorator (decorator source) path sourceBlob
pure $! construct path (para algebra parsedTerm))
where algebra (annotation :< syntax) = combine (makeNode annotation (Prologue.fst <$> syntax)) (toList (Prologue.snd <$> syntax))
decorator = parseDecorator debug
makeNode :: Record (Maybe SourceText ': DefaultFields) -> Syntax Text (Term (Syntax Text) (Record (Maybe SourceText ': DefaultFields))) -> ParseNode
makeNode (head :. range :. category :. sourceSpan :. Nil) syntax =
ParseNode (toS category) range head sourceSpan (identifierFor syntax)
makeNode :: HasDefaultFields fields => Record (Maybe SourceText ': fields) -> Syntax Text (Term (Syntax Text) (Record (Maybe SourceText ': fields))) -> ParseNode
makeNode (sourceText :. record) syntax = ParseNode (getField record) (getField record) sourceText (getField record) (identifierFor syntax)
-- | Determines the term decorator to use when parsing.
parseDecorator :: (Functor f, HasField fields Range) => Bool -> (Source -> TermDecorator f fields (Maybe SourceText))
parseDecorator True = termSourceTextDecorator
parseDecorator False = const . const Nothing
-- | Determines the term decorator to use when parsing.
parseDecorator :: (Functor f, HasField fields Range) => Bool -> (Source -> TermDecorator f fields (Maybe SourceText))
parseDecorator True = termSourceTextDecorator
parseDecorator False = const . const Nothing
-- | Returns a Just identifier text if the given Syntax term contains an identifier (leaf) syntax. Otherwise returns Nothing.
identifierFor :: (HasField fields (Maybe SourceText), HasField fields Category, StringConv leaf Text) => Syntax leaf (Term (Syntax leaf) (Record fields)) -> Maybe Text
identifierFor = fmap toS . extractLeafValue . unwrap <=< maybeIdentifier
-- | Returns a Just identifier text if the given Syntax term contains an identifier (leaf) syntax. Otherwise returns Nothing.
identifierFor :: (HasField fields (Maybe SourceText), HasField fields Category, StringConv leaf Text) => Syntax leaf (Term (Syntax leaf) (Record fields)) -> Maybe Text
identifierFor = fmap toS . extractLeafValue . unwrap <=< maybeIdentifier
-- | Return a parser incorporating the provided TermDecorator.
parseWithDecorator :: TermDecorator (Syntax Text) DefaultFields field -> FilePath -> Parser (Syntax Text) (Record (field ': DefaultFields))
parseWithDecorator decorator path blob = decorateTerm decorator <$> parserForFilePath path blob
-- | Decorate a 'Term' using a function to compute the annotation values at every node.
decorateTerm :: (Functor f, HasDefaultFields fields) => TermDecorator f fields field -> Term f (Record fields) -> Term f (Record (field ': fields))
decorateTerm decorator = cata $ \ term -> cofree ((decorator term :. headF term) :< tailF term)
-- | Decorate a 'Term' using a function to compute the annotation values at every node.
decorateTerm :: (Functor f) => TermDecorator f fields field -> Term f (Record fields) -> Term f (Record (field ': fields))
decorateTerm decorator = cata $ \ term -> cofree ((decorator term :. headF term) :< tailF term)
-- | Term decorator extracting the source text for a term.
termSourceTextDecorator :: (Functor f, HasField fields Range) => Source -> TermDecorator f fields (Maybe SourceText)
termSourceTextDecorator source (ann :< _) = Just (SourceText (toText (Source.slice (byteRange ann) source)))
-- | A function computing a value to decorate terms with. This can be used to cache synthesized attributes on terms.
type TermDecorator f fields field = TermF f (Record fields) (Term f (Record (field ': fields))) -> field
-- | Term decorator extracting the source text for a term.
termSourceTextDecorator :: (Functor f, HasField fields Range) => Source -> TermDecorator f fields (Maybe SourceText)
termSourceTextDecorator source (ann :< _) = Just (SourceText (toText (Source.slice (byteRange ann) source)))
newtype Identifier = Identifier Text
deriving (Eq, Show, ToJSON)

View File

@ -2,8 +2,6 @@
module Renderer.SExpression
( sExpression
, sExpressionParseTree
, printTerm
, printTerms
, SExpressionFormat(..)
) where
@ -23,11 +21,11 @@ import Term
data SExpressionFormat = TreeOnly | TreeAndRanges
deriving (Show)
-- | ByteString SExpression formatted diff.
-- | Returns a ByteString SExpression formatted diff.
sExpression :: (HasField fields Category, HasField fields SourceSpan) => SExpressionFormat -> Both SourceBlob -> Diff (Syntax Text) (Record fields) -> ByteString
sExpression format _ diff = printDiff diff 0 format
-- | ByteString SExpression formatted term.
-- | Returns a ByteString SExpression formatted term.
sExpressionParseTree :: (HasField fields Category, HasField fields SourceSpan) => SExpressionFormat -> SourceBlob -> Term (Syntax Text) (Record fields) -> ByteString
sExpressionParseTree format _ term = printTerm term 0 format
@ -46,8 +44,8 @@ printDiff diff level format = case runFree diff of
| n < 1 = "\n"
| otherwise = "\n" <> replicate (2 * n) space
printTerms :: (HasField fields Category, HasField fields SourceSpan) => SExpressionFormat -> [Term (Syntax t) (Record fields)] -> ByteString
printTerms format terms = foldr (\t acc -> printTerm t 0 format <> acc) "" terms
-- printTerms :: (HasField fields Category, HasField fields SourceSpan) => SExpressionFormat -> [Term (Syntax t) (Record fields)] -> ByteString
-- printTerms format terms = foldr (\t acc -> printTerm t 0 format <> acc) "" terms
printTerm :: (HasField fields Category, HasField fields SourceSpan) => Term (Syntax t) (Record fields) -> Int -> SExpressionFormat -> ByteString
printTerm term level format = go term level 0

View File

@ -1,6 +1,4 @@
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE ScopedTypeVariables #-}
-- {-# OPTIONS_GHC -funbox-strict-fields #-}
{-# LANGUAGE MultiParamTypeClasses, ScopedTypeVariables #-}
{-# OPTIONS_GHC -Wno-deprecations #-}
-- Disabling deprecation warnings due to pattern match against RescueModifier.
module Renderer.Summary (Summaries(..), summary, diffSummaries, DiffSummary(..), DiffInfo(..), diffToDiffSummaries, isBranchInfo, isErrorSummary, JSONSummary(..)) where

View File

@ -1,6 +1,7 @@
{-# LANGUAGE GADTs #-}
module Semantic where
import Control.Parallel.Strategies
import Data.Functor.Both
import Data.RandomWalkSimilarity
import Data.Record
@ -12,7 +13,6 @@ import Prologue
import Renderer
import Source
import Syntax
import Control.Parallel.Strategies
import Term
@ -48,11 +48,11 @@ diffBlobs' blobs = do
parseBlobs :: (Monoid output, StringConv output ByteString) => ParseTreeRenderer DefaultFields output -> [SourceBlob] -> IO ByteString
parseBlobs renderer blobs = do
terms <- traverse go blobs
pure . toS $ runParseTreeRenderer renderer terms
pure . toS $ runParseTreeRenderer renderer (terms `using` parTraversable (parTuple2 r0 rdeepseq))
where
go blob = do
terms <- parseBlob' blob
pure (blob, terms)
term <- parseBlob' blob
pure (blob, term)
-- | Parse a SourceBlob.
parseBlob' :: SourceBlob -> IO (Term (Syntax Text) (Record DefaultFields))

View File

@ -16,7 +16,6 @@ import qualified Data.ByteString as B
import qualified Paths_semantic_diff as Library (version)
import Source
import Renderer
-- import Renderer.SExpression
import System.Directory
import System.Environment
import System.FilePath.Posix (takeFileName, (-<.>))
@ -98,29 +97,29 @@ arguments gitDir alternates = info (version <*> helper <*> argumentsParser) desc
<|> flag' summaryDiff (long "summary" <> help "Output a diff summary")
<|> flag' sExpressionDiff (long "sexpression" <> help "Output an s-expression diff tree")
<|> flag' tocDiff (long "toc" <> help "Output a table of contents diff summary") )
<*> ( DiffPaths
<$> argument str (metavar "FILE_A")
<*> argument str (metavar "FILE_B")
<|> DiffCommits
<$> option (eitherReader parseSha) (long "sha1" <> metavar "SHA" <> help "Starting commit SHA")
<*> option (eitherReader parseSha) (long "sha2" <> metavar "SHA" <> help "Ending commit SHA")
<*> many (argument str (metavar "FILES...")) )
<*> pure gitDir
<*> pure alternates )
<*> ( DiffPaths
<$> argument str (metavar "FILE_A")
<*> argument str (metavar "FILE_B")
<|> DiffCommits
<$> option (eitherReader parseSha) (long "sha1" <> metavar "SHA" <> help "Starting commit SHA")
<*> option (eitherReader parseSha) (long "sha2" <> metavar "SHA" <> help "Ending commit SHA")
<*> many (argument str (metavar "FILES...")) )
<*> pure gitDir
<*> pure alternates )
parseCommand = command "parse" (info parseArgumentsParser (progDesc "Print parse trees for a commit or paths"))
parseArgumentsParser = Parse
<$> ( ( flag sExpressionParseTree sExpressionParseTree (long "sexpression" <> help "Output s-expression parse trees (default)") )
-- <|> flag' jsonParseTree (long "json" <> help "Output JSON parse trees")
-- <|> flag' jsonIndexParseTree (long "index" <> help "Output JSON parse trees in index format") )
<*> ( ParsePaths
<$> some (argument str (metavar "FILES..."))
<|> ParseCommit
<$> option (eitherReader parseSha) (long "sha" <> metavar "SHA" <> help "Commit SHA")
<*> some (argument str (metavar "FILES...")) )
<*> switch (long "debug")
<*> pure gitDir
<*> pure alternates )
<$> ( ( flag sExpressionParseTree sExpressionParseTree (long "sexpression" <> help "Output s-expression parse trees (default)")
<|> flag' jsonParseTree (long "json" <> help "Output JSON parse trees")
<|> flag' jsonIndexParseTree (long "index" <> help "Output JSON parse trees in index format") )
<*> ( ParsePaths
<$> some (argument str (metavar "FILES..."))
<|> ParseCommit
<$> option (eitherReader parseSha) (long "sha" <> metavar "SHA" <> help "Commit SHA")
<*> some (argument str (metavar "FILES...")) )
<*> switch (long "debug")
<*> pure gitDir
<*> pure alternates )
parseSha :: String -> Either String String
parseSha s = case matchRegex regex s of

View File

@ -1,69 +0,0 @@
module Command.Spec where
import Command
import Command.Files
import Data.Functor.Both
import Data.String
import Language
import Prologue hiding (readFile)
import Source
import Renderer.JSON
import Renderer.SExpression
import Syntax
import Test.Hspec
spec :: Spec
spec = parallel $ do
describe "readFile" $ do
it "returns a blob for extant files" $ do
blob <- runCommand (readFile "semantic-diff.cabal")
fmap path blob `shouldBe` Just "semantic-diff.cabal"
it "returns Nothing for absent files" $ do
blob <- runCommand (readFile "this file should not exist")
blob `shouldBe` Nothing
describe "readFilesAtSHAs" $ do
it "returns blobs for the specified paths" $ do
blobs <- runCommand (readFilesAtSHAs repoPath [] ["methods.rb"] (shas methodsFixture))
blobs `shouldBe` expectedBlobs methodsFixture
it "returns blobs for all paths if none are specified" $ do
blobs <- runCommand (readFilesAtSHAs repoPath [] [] (shas methodsFixture))
blobs `shouldBe` expectedBlobs methodsFixture
it "returns entries for missing paths" $ do
blobs <- runCommand (readFilesAtSHAs repoPath [] ["this file should not exist"] (shas methodsFixture))
blobs `shouldBe` [("this file should not exist", pure Nothing)]
describe "parse" $ do
it "parses line by line if not given a language" $ do
term <- runCommand (parse Nothing methodsBlob)
fmap (const ()) term `shouldBe` cofree (() :< Indexed [ cofree (() :< Leaf "def foo\n"), cofree (() :< Leaf "end\n"), cofree (() :< Leaf "") ])
it "parses in the specified language" $ do
term <- runCommand (parse (Just Ruby) methodsBlob)
fmap (const ()) term `shouldBe` cofree (() :< Indexed [ cofree (() :< Method [] (cofree (() :< Leaf "foo")) Nothing [] []) ])
-- TODO
-- let blobs = sourceBlobsFromPaths ["test/fixtures/ruby/and-or.A.rb"]
-- it "should produce s-expression trees" $ do
-- blobs <- sourceBlobsFromPaths ["test/fixtures/ruby/and-or.A.rb"]
-- let output = foldMap (sExpressionParseTree TreeOnly) blobs
-- output `shouldNotBe` ""
-- it "should produce JSON trees" $ do
-- output <- jsonParseTree False =<< blobs
-- output `shouldNotBe` ""
--
-- it "should produce JSON index" $ do
-- output <- jsonIndexParseTree False =<< blobs
-- output `shouldNotBe` ""
where repoPath = "test/fixtures/git/examples/all-languages.git"
methodsFixture = Fixture
(both "dfac8fd681b0749af137aebf3203e77a06fbafc2" "2e4144eb8c44f007463ec34cb66353f0041161fe")
[ ("methods.rb", both Nothing (Just methodsBlob)) ]
methodsBlob = SourceBlob (Source "def foo\nend\n") "ff7bbbe9495f61d9e1e58c597502d152bab1761e" "methods.rb" (Just defaultPlainBlob)
data Fixture = Fixture { shas :: Both String, expectedBlobs :: [(FilePath, Both (Maybe SourceBlob))] }

View File

@ -1,24 +1,56 @@
module Command.Diff.Spec where
module CommandSpec where
import Command
import Data.Aeson
import Data.Aeson.Types
import Data.Aeson.Types hiding (parse)
import Data.Functor.Both
import Data.Map as Map
import Data.Map
import Data.Maybe
import Data.Text.Lazy as T
import Data.String
import Info (DefaultFields)
import Language
import Prologue hiding (readFile, toList)
import qualified Data.Vector as V
import qualified Git.Types as Git
import Info
import Prelude
import Prologue (($), fmap, (.), pure, for, panic)
import Renderer hiding (errors)
import Source
import Syntax
import Test.Hspec hiding (shouldBe, shouldNotBe, shouldThrow, errorCall)
import Test.Hspec.Expectations.Pretty
spec :: Spec
spec = parallel $ do
describe "readFile" $ do
it "returns a blob for extant files" $ do
blob <- runCommand (readFile "semantic-diff.cabal")
fmap path blob `shouldBe` Just "semantic-diff.cabal"
it "returns Nothing for absent files" $ do
blob <- runCommand (readFile "this file should not exist")
blob `shouldBe` Nothing
describe "readFilesAtSHAs" $ do
it "returns blobs for the specified paths" $ do
blobs <- runCommand (readFilesAtSHAs repoPath [] ["methods.rb"] (shas methodsFixture))
blobs `shouldBe` expectedBlobs methodsFixture
it "returns blobs for all paths if none are specified" $ do
blobs <- runCommand (readFilesAtSHAs repoPath [] [] (shas methodsFixture))
blobs `shouldBe` expectedBlobs methodsFixture
it "returns entries for missing paths" $ do
blobs <- runCommand (readFilesAtSHAs repoPath [] ["this file should not exist"] (shas methodsFixture))
blobs `shouldBe` [("this file should not exist", pure Nothing)]
describe "parse" $ do
it "parses line by line if not given a language" $ do
term <- runCommand (parse Nothing methodsBlob)
void term `shouldBe` cofree (() :< Indexed [ cofree (() :< Leaf "def foo\n"), cofree (() :< Leaf "end\n"), cofree (() :< Leaf "") ])
it "parses in the specified language" $ do
term <- runCommand (parse (Just Ruby) methodsBlob)
void term `shouldBe` cofree (() :< Indexed [ cofree (() :< Method [] (cofree (() :< Leaf "foo")) Nothing [] []) ])
describe "fetchDiffs" $ do
it "generates diff summaries for two shas" $ do
(errors, summaries) <- fetchDiffsOutput summaryText "test/fixtures/git/examples/all-languages.git" "dfac8fd681b0749af137aebf3203e77a06fbafc2" "2e4144eb8c44f007463ec34cb66353f0041161fe" ["methods.rb"] Renderer.SummaryRenderer
@ -43,6 +75,14 @@ spec = parallel $ do
fetchDiffsOutput summaryText "test/fixtures/git/examples/not-a-repo.git" "dfac8fd681b0749af137aebf3203e77a06fbafc2" "2e4144eb8c44f007463ec34cb66353f0041161fe" ["methods.rb"] Renderer.SummaryRenderer
`shouldThrow` errorCall "Could not open repository \"test/fixtures/git/examples/not-a-repo.git\""
where repoPath = "test/fixtures/git/examples/all-languages.git"
methodsFixture = Fixture
(both "dfac8fd681b0749af137aebf3203e77a06fbafc2" "2e4144eb8c44f007463ec34cb66353f0041161fe")
[ ("methods.rb", both Nothing (Just methodsBlob)) ]
methodsBlob = SourceBlob (Source "def foo\nend\n") "ff7bbbe9495f61d9e1e58c597502d152bab1761e" "methods.rb" (Just defaultPlainBlob)
data Fixture = Fixture { shas :: Both String, expectedBlobs :: [(FilePath, Both (Maybe SourceBlob))] }
fetchDiffsOutput :: (Object -> Text) -> FilePath -> String -> String -> [FilePath] -> DiffRenderer DefaultFields Summaries -> IO (Maybe (Map Text Value), Maybe (Map Text [Text]))
fetchDiffsOutput f gitDir sha1 sha2 filePaths renderer = do
results <- fmap encode . runCommand $ do

View File

@ -5,23 +5,42 @@ import Arguments
import SemanticCmdLine
import Test.Hspec hiding (shouldBe, shouldNotBe, shouldThrow, errorCall)
import Test.Hspec.Expectations.Pretty
import Test.Hspec.LeanCheck
spec :: Spec
spec = parallel $ do
-- describe "runDiff" $ do
-- prop "produces diffs for all formats" $
-- \ encoder -> do
-- let mode = DiffPaths "test/fixtures/ruby/and-or.A.rb" "test/fixtures/ruby/and-or.B.rb"
-- output <- runDiff $ DiffArguments encoder mode "" []
-- output `shouldNotBe` ""
describe "runDiff" $ do
it "patchDiff" $ assertDiffOutput patchDiff patchOutput
it "splitDiff" $ assertDiffOutput splitDiff splitOutput
it "jsonDiff" $ assertDiffOutput jsonDiff jsonOutput
it "summaryDiff" $ assertDiffOutput summaryDiff summaryOutput
it "sExpressionDiff" $ assertDiffOutput sExpressionDiff sExpressionOutput
it "tocDiff" $ assertDiffOutput tocDiff tocOutput
describe "runParse" $ do
it "sExpression" $ do
it "sExpressionParseTree" $ assertParseOutput sExpressionParseTree sExpressionParseTreeOutput
it "jsonParseTree" $ assertParseOutput jsonParseTree jsonParseTreeOutput
it "jsonIndexParseTree" $ assertParseOutput jsonIndexParseTree jsonIndexParseTreeOutput
where
assertDiffOutput format expected = do
let mode = DiffPaths "test/fixtures/ruby/method-declaration.A.rb" "test/fixtures/ruby/method-declaration.B.rb"
output <- runDiff $ format mode "" []
when (output /= expected) $ print output -- Helpful for debugging
output `shouldBe` expected
assertParseOutput format expected = do
let mode = ParsePaths ["test/fixtures/ruby/and-or.A.rb"]
output <- runParse $ sExpressionParseTree mode False "" []
output `shouldNotBe` ""
-- prop "produces parse trees for all formats" $
-- \ renderer -> do
-- let mode = ParsePaths ["test/fixtures/ruby/and-or.A.rb"]
-- output <- runParse $ ParseArguments renderer mode False "" []
-- output `shouldNotBe` ""
output <- runParse $ format mode False "" []
when (output /= expected) $ print output -- Helpful for debugging
output `shouldBe` expected
patchOutput = "diff --git a/test/fixtures/ruby/method-declaration.A.rb b/test/fixtures/ruby/method-declaration.B.rb\nindex 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644\n--- a/test/fixtures/ruby/method-declaration.A.rb\n+++ b/test/fixtures/ruby/method-declaration.B.rb\n@@ -1,3 +1,4 @@\n-def foo\n+def bar(a)\n+ baz\n end\n\n"
splitOutput = "<!DOCTYPE HTML>\n<html><head><link rel=\"stylesheet\" href=\"style.css\"></head><body><table class=\"diff\"><colgroup><col width=\"40\"><col><col width=\"40\"><col></colgroup><tr><td class=\"blob-num blob-num-replacement\">1</td><td class=\"blob-code blob-code-replacement\"><ul class=\"category-program\"><li><ul class=\"category-method\">def <li><div class=\"patch replace\"><span class=\"category-identifier\">foo</span></div></li>\n</ul></li></ul></td>\n<td class=\"blob-num blob-num-replacement\">1</td><td class=\"blob-code blob-code-replacement\"><ul class=\"category-program\"><li><ul class=\"category-method\">def <li><div class=\"patch replace\"><span class=\"category-identifier\">bar</span></div></li><li><div class=\"patch insert\"><ul class=\"category-parameters\">(<li><span class=\"category-identifier\">a</span></li>)\n</ul></div></li></ul></li></ul></td>\n\n</tr><tr><td class=\"blob-num blob-num-empty empty-cell\"></td><td class=\"blob-code blob-code-empty empty-cell\"></td>\n<td class=\"blob-num blob-num-replacement\">2</td><td class=\"blob-code blob-code-replacement\"><ul class=\"category-program\"><li><ul class=\"category-method\"><li><div class=\"patch insert\"><ul class=\"category-parameters\"> </ul></div></li><li><div class=\"patch insert\"><span class=\"category-identifier\">baz</span></div></li>\n</ul></li></ul></td>\n\n</tr><tr><td class=\"blob-num\">2</td><td class=\"blob-code\"><ul class=\"category-program\"><li><ul class=\"category-method\">end</ul></li>\n</ul></td>\n<td class=\"blob-num\">3</td><td class=\"blob-code\"><ul class=\"category-program\"><li><ul class=\"category-method\">end</ul></li>\n</ul></td>\n\n</tr><tr><td class=\"blob-num\">3</td><td class=\"blob-code\"><ul class=\"category-program\"></ul></td>\n<td class=\"blob-num\">4</td><td class=\"blob-code\"><ul class=\"category-program\"></ul></td>\n\n</tr></table></body></html>\n"
summaryOutput = "{\"changes\":{\"test/fixtures/ruby/method-declaration.A.rb -> test/fixtures/ruby/method-declaration.B.rb\":[{\"span\":{\"replace\":[{\"start\":[1,5],\"end\":[1,8]},{\"start\":[1,5],\"end\":[1,8]}]},\"summary\":\"Replaced the 'foo' identifier with the 'bar' identifier in the 'bar(\226\128\166)' method\"},{\"span\":{\"insert\":{\"start\":[1,9],\"end\":[1,10]}},\"summary\":\"Added the 'a' identifier in the 'bar(\226\128\166)' method\"},{\"span\":{\"insert\":{\"start\":[2,3],\"end\":[2,6]}},\"summary\":\"Added the 'baz' identifier in the 'bar(\226\128\166)' method\"}]},\"errors\":{}}\n"
jsonOutput = "{\"oids\":[\"0000000000000000000000000000000000000000\",\"0000000000000000000000000000000000000000\"],\"paths\":[\"test/fixtures/ruby/method-declaration.A.rb\",\"test/fixtures/ruby/method-declaration.B.rb\"],\"rows\":[[{\"terms\":[{\"category\":\"Program\",\"children\":[{\"category\":\"Method\",\"children\":[{\"category\":\"Identifier\",\"patch\":\"replace\",\"range\":[4,7]}],\"range\":[0,8]}],\"range\":[0,8]}],\"hasChanges\":true,\"range\":[0,8],\"number\":1},{\"terms\":[{\"category\":\"Program\",\"children\":[{\"category\":\"Method\",\"children\":[{\"category\":\"Identifier\",\"patch\":\"replace\",\"range\":[4,7]},{\"category\":\"Params\",\"children\":[{\"category\":\"Identifier\",\"range\":[8,9]}],\"patch\":\"insert\",\"range\":[7,11]}],\"range\":[0,11]}],\"range\":[0,11]}],\"hasChanges\":true,\"range\":[0,11],\"number\":1}],[{\"terms\":[{\"category\":\"Program\",\"children\":[{\"category\":\"Method\",\"children\":[{\"category\":\"Params\",\"children\":[],\"patch\":\"insert\",\"range\":[11,13]},{\"category\":\"Identifier\",\"patch\":\"insert\",\"range\":[13,16]}],\"range\":[11,17]}],\"range\":[11,17]}],\"hasChanges\":true,\"range\":[11,17],\"number\":2}],[{\"terms\":[{\"category\":\"Program\",\"children\":[{\"category\":\"Method\",\"children\":[],\"range\":[8,11]}],\"range\":[8,12]}],\"hasChanges\":false,\"range\":[8,12],\"number\":2},{\"terms\":[{\"category\":\"Program\",\"children\":[{\"category\":\"Method\",\"children\":[],\"range\":[17,20]}],\"range\":[17,21]}],\"hasChanges\":false,\"range\":[17,21],\"number\":3}],[{\"terms\":[{\"category\":\"Program\",\"children\":[],\"range\":[12,12]}],\"hasChanges\":false,\"range\":[12,12],\"number\":3},{\"terms\":[{\"category\":\"Program\",\"children\":[],\"range\":[21,21]}],\"hasChanges\":false,\"range\":[21,21],\"number\":4}]]}\n"
sExpressionOutput = "(Program\n (Method\n { (Identifier)\n ->(Identifier) }\n {+(Params\n (Identifier))+}\n {+(Identifier)+}))"
tocOutput = "{\"changes\":{\"test/fixtures/ruby/method-declaration.A.rb -> test/fixtures/ruby/method-declaration.B.rb\":[{\"span\":{\"start\":[1,1],\"end\":[3,4]},\"category\":\"Method\",\"term\":\"bar\",\"changeType\":\"modified\"}]},\"errors\":{}}\n"
sExpressionParseTreeOutput = "(Program\n (Binary\n (Identifier)\n (Other \"and\")\n (Identifier)))"
jsonParseTreeOutput = "[{\"filePath\":\"test/fixtures/ruby/and-or.A.rb\",\"programNode\":{\"category\":\"Program\",\"children\":[{\"category\":\"Binary\",\"children\":[{\"category\":\"Identifier\",\"children\":[],\"sourceRange\":[0,3],\"sourceSpan\":{\"start\":[1,1],\"end\":[1,4]}},{\"category\":\"Other \\\"and\\\"\",\"children\":[],\"sourceRange\":[4,7],\"sourceSpan\":{\"start\":[1,5],\"end\":[1,8]}},{\"category\":\"Identifier\",\"children\":[],\"sourceRange\":[8,11],\"sourceSpan\":{\"start\":[1,9],\"end\":[1,12]}}],\"sourceRange\":[0,11],\"sourceSpan\":{\"start\":[1,1],\"end\":[1,12]}}],\"sourceRange\":[0,12],\"sourceSpan\":{\"start\":[1,1],\"end\":[2,1]}}},[]]\n"
jsonIndexParseTreeOutput = "[{\"programNodes\":[{\"category\":\"Program\",\"sourceRange\":[0,12],\"sourceSpan\":{\"start\":[1,1],\"end\":[2,1]}},{\"category\":\"Binary\",\"sourceRange\":[0,11],\"sourceSpan\":{\"start\":[1,1],\"end\":[1,12]}},{\"category\":\"Identifier\",\"sourceRange\":[0,3],\"sourceSpan\":{\"start\":[1,1],\"end\":[1,4]}},{\"category\":\"Other \\\"and\\\"\",\"sourceRange\":[4,7],\"sourceSpan\":{\"start\":[1,5],\"end\":[1,8]}},{\"category\":\"Identifier\",\"sourceRange\":[8,11],\"sourceSpan\":{\"start\":[1,9],\"end\":[1,12]}}],\"filePath\":\"test/fixtures/ruby/and-or.A.rb\"},[]]\n"

View File

@ -2,8 +2,7 @@ module Main where
import Prologue
import qualified AlignmentSpec
import qualified Command.Spec
import qualified Command.Diff.Spec
import qualified CommandSpec
import qualified Data.Mergeable.Spec
import qualified Data.RandomWalkSimilarity.Spec
import qualified Data.Syntax.Assignment.Spec
@ -25,8 +24,7 @@ main :: IO ()
main = hspec $ do
parallel $ do
describe "Alignment" AlignmentSpec.spec
describe "Command" Command.Spec.spec
describe "Command.Diff" Command.Diff.Spec.spec
describe "Command" CommandSpec.spec
describe "Data.Mergeable" Data.Mergeable.Spec.spec
describe "Data.RandomWalkSimilarity" Data.RandomWalkSimilarity.Spec.spec
describe "Data.Syntax.Assignment" Data.Syntax.Assignment.Spec.spec

View File

@ -55,8 +55,9 @@ diffFilePaths paths = do
parseFilePath :: FilePath -> IO ByteString
parseFilePath path = do
source <- readFileToUnicode path
term <- parseBlob' $ sourceBlob source path
pure $ printTerm term 0 TreeOnly
let blob = sourceBlob source path
term <- parseBlob' blob
pure $ sExpressionParseTree TreeOnly blob term
-- | Read a file to a SourceBlob
readFileToSourceBlob :: FilePath -> IO (Maybe SourceBlob)