1
1
mirror of https://github.com/github/semantic.git synced 2024-11-28 01:47:01 +03:00

Merge branch 'look-up-its-a-core' into compile-string-literals

This commit is contained in:
Patrick Thomson 2019-10-23 14:52:29 -04:00
commit edd83d466f
55 changed files with 373 additions and 660 deletions

View File

@ -34,6 +34,10 @@
# Change the severity of hints we dont want to fail CI for # Change the severity of hints we dont want to fail CI for
- suggest: {name: Eta reduce} - suggest: {name: Eta reduce}
# While I think DerivingStrategies is good, it's too noisy to suggest by default
- ignore:
name: Use DerivingStrategies
# Ignore eta reduce in the assignment modules # Ignore eta reduce in the assignment modules
- ignore: - ignore:
name: Eta reduce name: Eta reduce
@ -45,8 +49,29 @@
- ignore: {name: Use ., within: [Analysis.Abstract.Graph.graphingModules, Semantic.Distribute]} - ignore: {name: Use ., within: [Analysis.Abstract.Graph.graphingModules, Semantic.Distribute]}
- ignore: {name: Reduce duplication, within: [Semantic.Util, Semantic.UtilDisabled]} - ignore:
- ignore: {name: Use newtype instead of data, within: [Semantic.Api.V1.CodeAnalysisPB]} within:
- Proto.Semantic
- Proto.Semantic_Fields
- Proto.Semantic_JSON
- ignore:
name: Reduce duplication
within:
- Semantic.Util
# hlint has issues with QuantifiedConstraints (see https://github.com/ndmitchell/hlint/issues/759)
# Once the above is fixed, we can drop this error.
- ignore: { name: Parse error }
# hlint is too paranoid about NonEmpty functions (https://github.com/ndmitchell/hlint/issues/787)
- ignore:
name: Avoid restricted function
within:
- Language.Python.Syntax
- Data.Syntax.Expression
# Our customized warnings # Our customized warnings

View File

@ -24,9 +24,14 @@ matrix:
before_install: before_install:
- mkdir -p $HOME/.local/bin - mkdir -p $HOME/.local/bin
- curl -L -o /tmp/hlint.tar.gz "https://github.com/ndmitchell/hlint/releases/download/v2.2.3/hlint-2.2.3-x86_64-linux.tar.gz"
- tar -xf /tmp/hlint.tar.gz -C /tmp
- cp /tmp/hlint-2.2.3/hlint $HOME/.local/bin
- cp -r /tmp/hlint-2.2.3/data $HOME/.local/bin
- "PATH=/opt/ghc/bin:$HOME/local/bin:$PATH" - "PATH=/opt/ghc/bin:$HOME/local/bin:$PATH"
- ghc --version - ghc --version
- cabal --version - cabal --version
- hlint --version
install: install:
- cabal v2-update -v - cabal v2-update -v
@ -34,6 +39,7 @@ install:
- cabal v2-build --only-dependencies - cabal v2-build --only-dependencies
script: script:
- hlint src semantic-python
- cabal v2-build - cabal v2-build
- cabal v2-run semantic:test - cabal v2-run semantic:test
- cabal v2-run semantic-core:test - cabal v2-run semantic-core:test

View File

@ -26,6 +26,7 @@ module Core.Core
, load , load
, record , record
, (...) , (...)
, (.?)
, (.=) , (.=)
, Ann(..) , Ann(..)
, ann , ann
@ -75,6 +76,8 @@ data Core f a
| Record [(Name, f a)] | Record [(Name, f a)]
-- | Projection from a record. -- | Projection from a record.
| f a :. Name | f a :. Name
-- | Projection of a record, with failure.
| f a :? Name
-- | Assignment of a value to the reference returned by the lhs. -- | Assignment of a value to the reference returned by the lhs.
| f a := f a | f a := f a
deriving (Foldable, Functor, Generic1, Traversable) deriving (Foldable, Functor, Generic1, Traversable)
@ -105,6 +108,7 @@ instance RightModule Core where
Load b >>=* f = Load (b >>= f) Load b >>=* f = Load (b >>= f)
Record fs >>=* f = Record (map (fmap (>>= f)) fs) Record fs >>=* f = Record (map (fmap (>>= f)) fs)
(a :. b) >>=* f = (a >>= f) :. b (a :. b) >>=* f = (a >>= f) :. b
(a :? b) >>=* f = (a >>= f) :. b
(a := b) >>=* f = (a >>= f) := (b >>= f) (a := b) >>=* f = (a >>= f) := (b >>= f)
@ -209,6 +213,11 @@ a ... b = send (a :. b)
infixl 9 ... infixl 9 ...
(.?) :: (Carrier sig m, Member Core sig) => m a -> Name -> m a
a .? b = send (a :? b)
infixl 9 .?
(.=) :: (Carrier sig m, Member Core sig) => m a -> m a -> m a (.=) :: (Carrier sig m, Member Core sig) => m a -> m a -> m a
a .= b = send (a := b) a .= b = send (a := b)

View File

@ -20,7 +20,7 @@ import Control.Monad ((>=>))
import Core.Core as Core import Core.Core as Core
import Core.Name import Core.Name
import Data.Functor import Data.Functor
import Data.Maybe (fromMaybe) import Data.Maybe (fromMaybe, isJust)
import GHC.Stack import GHC.Stack
import Prelude hiding (fail) import Prelude hiding (fail)
import Source.Span import Source.Span
@ -68,6 +68,11 @@ eval Analysis{..} eval = \case
a :. b -> do a :. b -> do
a' <- ref a a' <- ref a
a' ... b >>= maybe (freeVariable (show b)) (deref' b) a' ... b >>= maybe (freeVariable (show b)) (deref' b)
a :? b -> do
a' <- ref a
mFound <- a' ... b
bool (isJust mFound)
a := b -> do a := b -> do
b' <- eval b b' <- eval b
addr <- ref a addr <- ref a

View File

@ -11,12 +11,12 @@ module Core.Parser
-- Consult @doc/grammar.md@ for an EBNF grammar. -- Consult @doc/grammar.md@ for an EBNF grammar.
import Control.Applicative import Control.Applicative
import Control.Category ((>>>))
import Control.Effect.Carrier import Control.Effect.Carrier
import Core.Core ((:<-) (..), Core) import Core.Core ((:<-) (..), Core)
import qualified Core.Core as Core import qualified Core.Core as Core
import Core.Name import Core.Name
import qualified Data.Char as Char import qualified Data.Char as Char
import Data.Foldable (foldl')
import Data.String import Data.String
import qualified Text.Parser.Token as Token import qualified Text.Parser.Token as Token
import qualified Text.Parser.Token.Highlight as Highlight import qualified Text.Parser.Token.Highlight as Highlight
@ -61,7 +61,12 @@ application :: (TokenParsing m, Carrier sig t, Member Core sig, Monad m) => m (t
application = projection `chainl1` (pure (Core.$$)) application = projection `chainl1` (pure (Core.$$))
projection :: (TokenParsing m, Carrier sig t, Member Core sig, Monad m) => m (t Name) projection :: (TokenParsing m, Carrier sig t, Member Core sig, Monad m) => m (t Name)
projection = foldl' (Core....) <$> atom <*> many (namedValue <$ dot <*> name) projection = let a <$$> b = flip a <$> b in do
head <- atom
res <- many (choice [ (Core..?) <$$> (symbol ".?" *> identifier)
, (Core....) <$$> (dot *> identifier)
])
pure (foldr (>>>) id res head)
atom :: (TokenParsing m, Carrier sig t, Member Core sig, Monad m) => m (t Name) atom :: (TokenParsing m, Carrier sig t, Member Core sig, Monad m) => m (t Name)
atom = choice atom = choice

View File

@ -74,6 +74,7 @@ prettyCore style = unPrec . go . fmap name
Load p -> prec 3 (keyword "load" <+> withPrec 9 (go p)) Load p -> prec 3 (keyword "load" <+> withPrec 9 (go p))
item :. body -> prec 9 (withPrec 9 (go item) <> symbol "." <> name body) item :. body -> prec 9 (withPrec 9 (go item) <> symbol "." <> name body)
item :? body -> prec 9 (withPrec 9 (go item) <> symbol ".?" <> name body)
lhs := rhs -> prec 3 . group . nest 2 $ vsep lhs := rhs -> prec 3 . group . nest 2 $ vsep
[ withPrec 4 (go lhs) [ withPrec 4 (go lhs)

View File

@ -70,5 +70,6 @@ expr = Gen.recursive Gen.choice atoms
, Gen.subterm expr Core.load , Gen.subterm expr Core.load
, record expr , record expr
, Gen.subtermM expr (\ x -> (x Core....) . namedValue <$> name) , Gen.subtermM expr (\ x -> (x Core....) . namedValue <$> name)
, Gen.subtermM expr (\ x -> (x Core..?) . namedValue <$> name)
, Gen.subterm2 expr expr (Core..=) , Gen.subterm2 expr expr (Core..=)
] ]

View File

@ -1,14 +1,20 @@
{ {
type <- \name -> \bases -> \dict -> type <- \name -> \super -> \slots ->
#record { __name: name, __bases: bases, __dict: dict }; #record { __name: name, __super: super, __slots: slots };
instance <- \class -> \prim -> \slots -> instance <- \class -> \prim -> \slots ->
#record { __class: class, __prim: prim, __slots: slots }; #record { __class: class, __prim: prim, __slots: slots };
object <- type "object" #unit #record{}; object <- type "object" type #record{};
str <- type "str" object #record { }; str <- type "str" object #record { };
str.__class.__new__ = (\contents -> instance str contents #record{}); str.__new__ = (\contents -> instance str contents #record{});
#record { type: type, object: object, str: str } #record { type: type, object: object, str: str }
getitem <- rec getitem = \item -> \attr ->
if item.slots.?attr then item.slots.attr else #unit;
#record { type: type, object: object, getitem: getitem }
} }

View File

@ -7,8 +7,8 @@ module Directive ( Directive (..)
import Control.Applicative import Control.Applicative
import Control.Monad import Control.Monad
import Core.Core (Core) import Core.Core (Core)
import qualified Core.Parser as Core.Parser import qualified Core.Parser
import qualified Core.Pretty as Core.Pretty import qualified Core.Pretty
import Core.Name (Name) import Core.Name (Name)
import Data.ByteString.Char8 (ByteString) import Data.ByteString.Char8 (ByteString)
import qualified Data.ByteString.Char8 as ByteString import qualified Data.ByteString.Char8 as ByteString

View File

@ -1,4 +1,4 @@
{-# LANGUAGE DeriveAnyClass, DerivingStrategies, GeneralizedNewtypeDeriving, LambdaCase, StandaloneDeriving, FlexibleInstances, NamedFieldPuns, OverloadedStrings, QuantifiedConstraints, TypeOperators, UndecidableInstances, TypeApplications #-} {-# LANGUAGE DerivingStrategies, GeneralizedNewtypeDeriving, StandaloneDeriving, FlexibleInstances, NamedFieldPuns, OverloadedStrings, QuantifiedConstraints, TypeOperators, UndecidableInstances #-}
{-# OPTIONS_GHC -fno-warn-orphans #-} {-# OPTIONS_GHC -fno-warn-orphans #-}
module Instances () where module Instances () where

View File

@ -67,9 +67,7 @@ common dependencies
, semantic-source ^>= 0.0 , semantic-source ^>= 0.0
, semilattices ^>= 0.0.0.3 , semilattices ^>= 0.0.0.3
, streaming ^>= 0.2.2.0 , streaming ^>= 0.2.2.0
, streaming-bytestring ^>= 0.1.6
, text ^>= 1.2.3.1 , text ^>= 1.2.3.1
, these >= 0.7 && <1
, unix ^>= 2.7.2.2 , unix ^>= 2.7.2.2
, lingo ^>= 0.3.0.0 , lingo ^>= 0.3.0.0
@ -148,9 +146,9 @@ library
, Data.Blob.IO , Data.Blob.IO
, Data.Diff , Data.Diff
, Data.Duration , Data.Duration
, Data.Edit
, Data.Error , Data.Error
, Data.Flag , Data.Flag
, Data.Functor.Both
, Data.Functor.Classes.Generic , Data.Functor.Classes.Generic
, Data.Graph , Data.Graph
, Data.Graph.ControlFlowVertex , Data.Graph.ControlFlowVertex
@ -160,7 +158,6 @@ library
, Data.JSON.Fields , Data.JSON.Fields
, Data.Language , Data.Language
, Data.Map.Monoidal , Data.Map.Monoidal
, Data.Patch
, Data.Project , Data.Project
, Data.Quieterm , Data.Quieterm
, Data.Semigroup.App , Data.Semigroup.App
@ -229,7 +226,6 @@ library
, Semantic.Config , Semantic.Config
, Semantic.Distribute , Semantic.Distribute
, Semantic.Env , Semantic.Env
, Semantic.Git
, Semantic.Graph , Semantic.Graph
, Semantic.IO , Semantic.IO
, Semantic.Resolution , Semantic.Resolution
@ -288,8 +284,6 @@ library
, semantic-tags ^>= 0 , semantic-tags ^>= 0
, semigroupoids ^>= 5.3.2 , semigroupoids ^>= 5.3.2
, split ^>= 0.2.3.3 , split ^>= 0.2.3.3
, streaming-attoparsec ^>= 1.0.0.1
, streaming-process ^>= 0.1
, stm-chans ^>= 3.0.0.4 , stm-chans ^>= 3.0.0.4
, template-haskell ^>= 2.14 , template-haskell ^>= 2.14
, time ^>= 1.8.0.2 , time ^>= 1.8.0.2
@ -362,7 +356,6 @@ test-suite test
, hspec >= 2.6 && <3 , hspec >= 2.6 && <3
, hspec-core >= 2.6 && <3 , hspec-core >= 2.6 && <3
, hspec-expectations ^>= 0.8.2 , hspec-expectations ^>= 0.8.2
, shelly >= 1.5 && <2
, tasty ^>= 1.2.3 , tasty ^>= 1.2.3
, tasty-golden ^>= 2.3.2 , tasty-golden ^>= 2.3.2
, tasty-hedgehog ^>= 1.0.0.1 , tasty-hedgehog ^>= 1.0.0.1
@ -384,7 +377,7 @@ test-suite parse-examples
, foldl ^>= 1.4.5 , foldl ^>= 1.4.5
, resourcet ^>= 1.2 , resourcet ^>= 1.2
, streaming , streaming
, streaming-bytestring , streaming-bytestring ^>= 0.1.6
, tasty , tasty
, tasty-hunit , tasty-hunit

View File

@ -292,15 +292,9 @@ liftNumeric2 :: (Member (Numeric value) sig, Carrier sig m)
-> m value -> m value
liftNumeric2 t v1 v2 = send (LiftNumeric2 (Numeric2Function t) v1 v2 pure) liftNumeric2 t v1 v2 = send (LiftNumeric2 (Numeric2Function t) v1 v2 pure)
data NumericFunction = NumericFunction (forall a . Num a => a -> a) newtype NumericFunction = NumericFunction { runNumericFunction :: forall a . Num a => a -> a }
runNumericFunction :: Num a => NumericFunction -> a -> a newtype Numeric2Function = Numeric2Function { runNumeric2Function :: forall a b. Number a -> Number b -> SomeNumber }
runNumericFunction (NumericFunction f) a = f a
data Numeric2Function = Numeric2Function (forall a b. Number a -> Number b -> SomeNumber)
runNumeric2Function :: Numeric2Function -> Number a -> Number b -> SomeNumber
runNumeric2Function (Numeric2Function f) a b = f a b
data Numeric value (m :: * -> *) k data Numeric value (m :: * -> *) k
= Integer Integer (value -> m k) = Integer Integer (value -> m k)
@ -347,15 +341,9 @@ unsignedRShift :: (Member (Bitwise value) sig, Carrier sig m)
-> m value -> m value
unsignedRShift v1 v2 = send (UnsignedRShift v1 v2 pure) unsignedRShift v1 v2 = send (UnsignedRShift v1 v2 pure)
data BitwiseFunction = BitwiseFunction (forall a . Bits a => a -> a) newtype BitwiseFunction = BitwiseFunction { runBitwiseFunction :: forall a . Bits a => a -> a }
runBitwiseFunction :: Bits a => BitwiseFunction -> a -> a newtype Bitwise2Function = Bitwise2Function { runBitwise2Function :: forall a . (Integral a, Bits a) => a -> a -> a }
runBitwiseFunction (BitwiseFunction f) a = f a
data Bitwise2Function = Bitwise2Function (forall a . (Integral a, Bits a) => a -> a -> a)
runBitwise2Function :: (Integral a, Bits a) => Bitwise2Function -> a -> a -> a
runBitwise2Function (Bitwise2Function f) a b = f a b
data Bitwise value (m :: * -> *) k data Bitwise value (m :: * -> *) k
= CastToInteger value (value -> m k) = CastToInteger value (value -> m k)

View File

@ -63,7 +63,7 @@ runParser timeout blob@Blob{..} parser = case parser of
let term = cmarkParser blobSource let term = cmarkParser blobSource
in length term `seq` pure term in length term `seq` pure term
data ParseFailure = ParseFailure String newtype ParseFailure = ParseFailure String
deriving (Show, Typeable) deriving (Show, Typeable)
instance Exception ParseFailure instance Exception ParseFailure

View File

@ -12,9 +12,9 @@ import Control.Effect.Error
import Control.Exception (SomeException) import Control.Exception (SomeException)
import Data.Bitraversable import Data.Bitraversable
import Data.Blob import Data.Blob
import Data.Edit
import Data.Language import Data.Language
import qualified Data.Map as Map import qualified Data.Map as Map
import Data.These
import Parsing.Parser import Parsing.Parser
data Parse m k data Parse m k
@ -51,11 +51,11 @@ parseWith parsers with blob = case Map.lookup (blobLanguage blob) parsers of
-- | Parse a 'BlobPair' with one of the provided parsers, and run an action on the abstracted term pair. -- | Parse a 'BlobPair' with one of the provided parsers, and run an action on the abstracted term pair.
parsePairWith parsePairWith
:: (Carrier sig m, Member (Error SomeException) sig, Member Parse sig) :: (Carrier sig m, Member (Error SomeException) sig, Member Parse sig)
=> Map.Map Language (SomeParser c ann) -- ^ The set of parsers to select from. => Map.Map Language (SomeParser c ann) -- ^ The set of parsers to select from.
-> (forall term . c term => These (Blob, term ann) (Blob, term ann) -> m a) -- ^ A function to run on the parsed terms. Note that the terms are abstract, but constrained by @c@, allowing you to do anything @c@ allows, and requiring that all the input parsers produce terms supporting @c@. -> (forall term . c term => Edit (Blob, term ann) (Blob, term ann) -> m a) -- ^ A function to run on the parsed terms. Note that the terms are abstract, but constrained by @c@, allowing you to do anything @c@ allows, and requiring that all the input parsers produce terms supporting @c@.
-> BlobPair -- ^ The blob pair to parse. -> BlobPair -- ^ The blob pair to parse.
-> m a -> m a
parsePairWith parsers with blobPair = case Map.lookup (languageForBlobPair blobPair) parsers of parsePairWith parsers with blobPair = case Map.lookup (languageForBlobPair blobPair) parsers of
Just (SomeParser parser) -> bitraverse (p parser) (p parser) (getBlobPair blobPair) >>= with Just (SomeParser parser) -> bitraverse (p parser) (p parser) blobPair >>= with
_ -> noLanguageForBlob (pathForBlobPair blobPair) _ -> noLanguageForBlob (pathForBlobPair blobPair)
where p parser blob = (,) blob <$> parse parser blob where p parser blob = (,) blob <$> parse parser blob

View File

@ -13,10 +13,7 @@ module Data.Blob
, nullBlob , nullBlob
, sourceBlob , sourceBlob
, noLanguageForBlob , noLanguageForBlob
, BlobPair(..) , BlobPair
, pattern Diffing
, pattern Inserting
, pattern Deleting
, maybeBlobPair , maybeBlobPair
, decodeBlobPairs , decodeBlobPairs
, languageForBlobPair , languageForBlobPair
@ -30,6 +27,7 @@ import Prologue
import Control.Effect.Error import Control.Effect.Error
import Data.Aeson import Data.Aeson
import qualified Data.ByteString.Lazy as BL import qualified Data.ByteString.Lazy as BL
import Data.Edit
import Data.JSON.Fields import Data.JSON.Fields
import Data.Language import Data.Language
import Source.Source (Source) import Source.Source (Source)
@ -101,62 +99,33 @@ noLanguageForBlob blobPath = throwError (SomeException (NoLanguageForBlob blobPa
-- | Represents a blobs suitable for diffing which can be either a blob to -- | Represents a blobs suitable for diffing which can be either a blob to
-- delete, a blob to insert, or a pair of blobs to diff. -- delete, a blob to insert, or a pair of blobs to diff.
newtype BlobPair = BlobPair { getBlobPair :: These Blob Blob } type BlobPair = Edit Blob Blob
deriving (Eq, Show)
instance FromJSON BlobPair where instance FromJSON BlobPair where
parseJSON = withObject "BlobPair" $ \o -> do parseJSON = withObject "BlobPair" $ \o ->
before <- o .:? "before" fromMaybes <$> (o .:? "before") <*> (o .:? "after")
after <- o .:? "after" >>= maybeM (Prelude.fail "Expected object with 'before' and/or 'after' keys only")
case (before, after) of
(Just b, Just a) -> pure $ Diffing b a
(Just b, Nothing) -> pure $ Deleting b
(Nothing, Just a) -> pure $ Inserting a
_ -> Prelude.fail "Expected object with 'before' and/or 'after' keys only"
pattern Diffing :: Blob -> Blob -> BlobPair
pattern Diffing a b = BlobPair (These a b)
pattern Inserting :: Blob -> BlobPair
pattern Inserting a = BlobPair (That a)
pattern Deleting :: Blob -> BlobPair
pattern Deleting b = BlobPair (This b)
{-# COMPLETE Diffing, Inserting, Deleting #-}
maybeBlobPair :: MonadFail m => Maybe Blob -> Maybe Blob -> m BlobPair maybeBlobPair :: MonadFail m => Maybe Blob -> Maybe Blob -> m BlobPair
maybeBlobPair a b = case (a, b) of maybeBlobPair a b = maybeM (Prologue.fail "expected file pair with content on at least one side") (fromMaybes a b)
(Just a, Nothing) -> pure (Deleting a)
(Nothing, Just b) -> pure (Inserting b)
(Just a, Just b) -> pure (Diffing a b)
_ -> Prologue.fail "expected file pair with content on at least one side"
languageForBlobPair :: BlobPair -> Language languageForBlobPair :: BlobPair -> Language
languageForBlobPair (Deleting b) = blobLanguage b languageForBlobPair = mergeEdit combine . bimap blobLanguage blobLanguage where
languageForBlobPair (Inserting b) = blobLanguage b combine a b
languageForBlobPair (Diffing a b) | a == Unknown || b == Unknown = Unknown
| blobLanguage a == Unknown || blobLanguage b == Unknown | otherwise = b
= Unknown
| otherwise
= blobLanguage b
pathForBlobPair :: BlobPair -> FilePath pathForBlobPair :: BlobPair -> FilePath
pathForBlobPair x = blobPath $ case x of pathForBlobPair = blobPath . mergeEdit (const id)
(Inserting b) -> b
(Deleting b) -> b
(Diffing _ b) -> b
languageTagForBlobPair :: BlobPair -> [(String, String)] languageTagForBlobPair :: BlobPair -> [(String, String)]
languageTagForBlobPair pair = showLanguage (languageForBlobPair pair) languageTagForBlobPair pair = showLanguage (languageForBlobPair pair)
where showLanguage = pure . (,) "language" . show where showLanguage = pure . (,) "language" . show
pathKeyForBlobPair :: BlobPair -> FilePath pathKeyForBlobPair :: BlobPair -> FilePath
pathKeyForBlobPair blobs = case bimap blobPath blobPath (getBlobPair blobs) of pathKeyForBlobPair = mergeEdit combine . bimap blobPath blobPath where
This before -> before combine before after | before == after = after
That after -> after | otherwise = before <> " -> " <> after
These before after | before == after -> after
| otherwise -> before <> " -> " <> after
instance ToJSONFields Blob where instance ToJSONFields Blob where
toJSONFields p = [ "path" .= blobPath p, "language" .= blobLanguage p] toJSONFields p = [ "path" .= blobPath p, "language" .= blobLanguage p]

View File

@ -6,8 +6,6 @@ module Data.Blob.IO
( readBlobFromFile ( readBlobFromFile
, readBlobFromFile' , readBlobFromFile'
, readBlobsFromDir , readBlobsFromDir
, readBlobsFromGitRepo
, readBlobsFromGitRepoPath
, readFilePair , readFilePair
) where ) where
@ -17,12 +15,9 @@ import qualified Control.Concurrent.Async as Async
import Data.Blob import Data.Blob
import qualified Data.ByteString as B import qualified Data.ByteString as B
import Data.Language import Data.Language
import Data.Text.Encoding (decodeUtf8)
import qualified Semantic.Git as Git
import Semantic.IO import Semantic.IO
import qualified Source.Source as Source import qualified Source.Source as Source
import qualified System.Path as Path import qualified System.Path as Path
import qualified System.Path.PartClass as Part
-- | Read a utf8-encoded file to a 'Blob'. -- | Read a utf8-encoded file to a 'Blob'.
readBlobFromFile :: forall m. MonadIO m => File -> m (Maybe Blob) readBlobFromFile :: forall m. MonadIO m => File -> m (Maybe Blob)
@ -42,28 +37,6 @@ readBlobsFromDir :: MonadIO m => Path.AbsRelDir -> m [Blob]
readBlobsFromDir path = liftIO . fmap catMaybes $ readBlobsFromDir path = liftIO . fmap catMaybes $
findFilesInDir path supportedExts mempty >>= Async.mapConcurrently (readBlobFromFile . fileForTypedPath) findFilesInDir path supportedExts mempty >>= Async.mapConcurrently (readBlobFromFile . fileForTypedPath)
readBlobsFromGitRepoPath :: (Part.AbsRel ar, MonadIO m) => Path.Dir ar -> Git.OID -> [Path.RelFile] -> [Path.RelFile] -> m [Blob]
readBlobsFromGitRepoPath path oid excludePaths includePaths
= readBlobsFromGitRepo (Path.toString path) oid (fmap Path.toString excludePaths) (fmap Path.toString includePaths)
-- | Read all blobs from a git repo. Prefer readBlobsFromGitRepoPath, which is typed.
readBlobsFromGitRepo :: MonadIO m => FilePath -> Git.OID -> [FilePath] -> [FilePath] -> m [Blob]
readBlobsFromGitRepo path oid excludePaths includePaths = liftIO . fmap catMaybes $
Git.lsTree path oid >>= Async.mapConcurrently (blobFromTreeEntry path)
where
-- Only read tree entries that are normal mode, non-minified blobs in a language we can parse.
blobFromTreeEntry :: FilePath -> Git.TreeEntry -> IO (Maybe Blob)
blobFromTreeEntry gitDir (Git.TreeEntry Git.NormalMode Git.BlobObject oid path)
| lang <- languageForFilePath path
, lang `elem` codeNavLanguages
, not (pathIsMinified path)
, path `notElem` excludePaths
, null includePaths || path `elem` includePaths
= Just . sourceBlob' path lang oid <$> Git.catFile gitDir oid
blobFromTreeEntry _ _ = pure Nothing
sourceBlob' filepath language (Git.OID oid) source = makeBlob source filepath language (decodeUtf8 oid)
readFilePair :: MonadIO m => File -> File -> m BlobPair readFilePair :: MonadIO m => File -> File -> m BlobPair
readFilePair a b = do readFilePair a b = do
before <- readBlobFromFile a before <- readBlobFromFile a

View File

@ -1,9 +1,9 @@
{-# LANGUAGE DataKinds, RankNTypes, TypeFamilies, TypeOperators, ScopedTypeVariables, UndecidableInstances #-} {-# LANGUAGE DataKinds, LambdaCase, RankNTypes, TypeFamilies, TypeOperators, ScopedTypeVariables, UndecidableInstances #-}
module Data.Diff module Data.Diff
( Diff(..) ( Diff(..)
, DiffF(..) , DiffF(..)
, replacing , comparing
, replaceF , compareF
, inserting , inserting
, insertF , insertF
, deleting , deleting
@ -18,10 +18,10 @@ import Data.Aeson
import Data.Bifoldable import Data.Bifoldable
import Data.Bifunctor import Data.Bifunctor
import Data.Bitraversable import Data.Bitraversable
import Data.Edit
import Data.Functor.Classes import Data.Functor.Classes
import Data.Functor.Foldable import Data.Functor.Foldable
import Data.JSON.Fields import Data.JSON.Fields
import Data.Patch
import Data.Term import Data.Term
import Text.Show import Text.Show
@ -30,19 +30,19 @@ newtype Diff syntax ann1 ann2 = Diff { unDiff :: DiffF syntax ann1 ann2 (Diff sy
-- | A single entry within a recursive 'Diff'. -- | A single entry within a recursive 'Diff'.
data DiffF syntax ann1 ann2 recur data DiffF syntax ann1 ann2 recur
-- | A changed node, represented as 'Insert'ed, 'Delete'd, or 'Replace'd 'TermF's, consisting of syntax labelled with an annotation. -- | A changed node, represented as 'Insert'ed, 'Delete'd, or 'Compare'd 'TermF's, consisting of syntax labelled with an annotation.
= Patch (Patch (TermF syntax ann1 recur) = Patch (Edit (TermF syntax ann1 recur)
(TermF syntax ann2 recur)) (TermF syntax ann2 recur))
-- | An unchanged node, consisting of syntax labelled with both the original annotations. -- | An unchanged node, consisting of syntax labelled with both the original annotations.
| Merge (TermF syntax (ann1, ann2) recur) | Merge (TermF syntax (ann1, ann2) recur)
-- | Constructs a 'Diff' replacing one 'Term' with another recursively. -- | Constructs a 'Diff' comparing one 'Term' with another recursively.
replacing :: Functor syntax => Term syntax ann1 -> Term syntax ann2 -> Diff syntax ann1 ann2 comparing :: Functor syntax => Term syntax ann1 -> Term syntax ann2 -> Diff syntax ann1 ann2
replacing (Term (In a1 r1)) (Term (In a2 r2)) = replaceF (In a1 (deleting <$> r1)) (In a2 (inserting <$> r2)) comparing (Term (In a1 r1)) (Term (In a2 r2)) = compareF (In a1 (deleting <$> r1)) (In a2 (inserting <$> r2))
-- | Constructs a 'Diff' replacing one 'TermF' populated by further 'Diff's with another. -- | Constructs a 'Diff' comparing one 'TermF' populated by further 'Diff's with another.
replaceF :: TermF syntax ann1 (Diff syntax ann1 ann2) -> TermF syntax ann2 (Diff syntax ann1 ann2) -> Diff syntax ann1 ann2 compareF :: TermF syntax ann1 (Diff syntax ann1 ann2) -> TermF syntax ann2 (Diff syntax ann1 ann2) -> Diff syntax ann1 ann2
replaceF t1 t2 = Diff (Patch (Replace t1 t2)) compareF t1 t2 = Diff (Patch (Compare t1 t2))
-- | Constructs a 'Diff' inserting a 'Term' recursively. -- | Constructs a 'Diff' inserting a 'Term' recursively.
inserting :: Functor syntax => Term syntax ann2 -> Diff syntax ann1 ann2 inserting :: Functor syntax => Term syntax ann2 -> Diff syntax ann1 ann2
@ -75,8 +75,8 @@ merging :: Functor syntax => Term syntax ann -> Diff syntax ann ann
merging = cata (\ (In ann syntax) -> mergeF (In (ann, ann) syntax)) merging = cata (\ (In ann syntax) -> mergeF (In (ann, ann) syntax))
diffPatches :: (Foldable syntax, Functor syntax) => Diff syntax ann1 ann2 -> [Patch (TermF syntax ann1 (Diff syntax ann1 ann2)) (TermF syntax ann2 (Diff syntax ann1 ann2))] diffPatches :: (Foldable syntax, Functor syntax) => Diff syntax ann1 ann2 -> [Edit (TermF syntax ann1 (Diff syntax ann1 ann2)) (TermF syntax ann2 (Diff syntax ann1 ann2))]
diffPatches = para $ \ diff -> case diff of diffPatches = para $ \case
Patch patch -> bimap (fmap fst) (fmap fst) patch : bifoldMap (foldMap snd) (foldMap snd) patch Patch patch -> bimap (fmap fst) (fmap fst) patch : bifoldMap (foldMap snd) (foldMap snd) patch
Merge merge -> foldMap snd merge Merge merge -> foldMap snd merge

71
src/Data/Edit.hs Normal file
View File

@ -0,0 +1,71 @@
{-# LANGUAGE LambdaCase #-}
module Data.Edit
( Edit(..)
, edit
, mergeEdit
, fromMaybes
) where
import Control.Applicative ((<|>), liftA2)
import Data.Bifoldable
import Data.Bifunctor
import Data.Bitraversable
import Data.Functor.Classes
import GHC.Generics (Generic, Generic1)
-- | The deletion, insertion, or comparison of values.
data Edit a b
= Delete a
| Insert b
| Compare a b
deriving (Eq, Foldable, Functor, Generic, Generic1, Ord, Show, Traversable)
-- | Eliminate an 'Edit' by case analysis.
edit :: (l -> a) -> (r -> a) -> (l -> r -> a) -> Edit l r -> a
edit delete insert compare = \case
Delete a -> delete a
Insert b -> insert b
Compare a b -> compare a b
-- | Extract the values from an 'Edit', combining 'Compare's with the passed function.
mergeEdit :: (a -> a -> a) -> Edit a a -> a
mergeEdit = edit id id
fromMaybes :: Maybe a -> Maybe b -> Maybe (Edit a b)
fromMaybes a b = liftA2 Compare a b <|> Delete <$> a <|> Insert <$> b
instance Bifunctor Edit where
bimap = bimapDefault
instance Bifoldable Edit where
bifoldMap = bifoldMapDefault
instance Bitraversable Edit where
bitraverse f g = \case
Delete a -> Delete <$> f a
Insert b -> Insert <$> g b
Compare a b -> Compare <$> f a <*> g b
instance Eq2 Edit where
liftEq2 eql eqr = curry $ \case
(Delete a1 , Delete a2 ) -> eql a1 a2
(Insert b1, Insert b2) -> eqr b1 b2
(Compare a1 b1, Compare a2 b2) -> eql a1 a2 && eqr b1 b2
_ -> False
instance Ord2 Edit where
liftCompare2 cmpl cmpr = curry $ \case
(Delete a1 , Delete a2 ) -> cmpl a1 a2
(Delete _ , _ ) -> LT
(Insert b1, Insert b2) -> cmpr b1 b2
(Insert _ , _ ) -> LT
(Compare a1 b1, Compare a2 b2) -> cmpl a1 a2 <> cmpr b1 b2
_ -> GT
instance Show2 Edit where
liftShowsPrec2 spl _ spr _ d = \case
Delete a -> showsUnaryWith spl "Delete" d a
Insert b -> showsUnaryWith spr "Insert" d b
Compare a b -> showsBinaryWith spl spr "Compare" d a b

View File

@ -1,23 +0,0 @@
{-# LANGUAGE DerivingVia #-}
module Data.Functor.Both
( Both (..)
, runBothWith
) where
import Data.Functor.Classes
import Data.Functor.Classes.Generic
import Data.Monoid.Generic
import GHC.Generics
-- | A computation over both sides of a pair.
data Both a = Both a a
deriving (Eq, Show, Ord, Functor, Foldable, Traversable, Generic1, Generic)
deriving Semigroup via GenericSemigroup (Both a)
deriving Monoid via GenericMonoid (Both a)
deriving (Eq1, Show1, Ord1) via Generically Both
-- | Apply a function to `Both` sides of a computation.
-- The eliminator/catamorphism over 'Both'.
runBothWith :: (a -> a -> b) -> Both a -> b
runBothWith f (Both a b) = f a b

View File

@ -19,8 +19,9 @@ data ImportPath = ImportPath { unPath :: FilePath, pathIsRelative :: IsRelative
importPath :: Text -> ImportPath importPath :: Text -> ImportPath
importPath str = let path = stripQuotes str in ImportPath (T.unpack path) (pathType path) importPath str = let path = stripQuotes str in ImportPath (T.unpack path) (pathType path)
where where
pathType xs | not (T.null xs), T.head xs == '.' = Relative -- head call here is safe pathType xs | startsWithDot xs = Relative -- head call here is safe
| otherwise = NonRelative | otherwise = NonRelative
startsWithDot t = fmap fst (T.uncons t) == Just '.'
defaultAlias :: ImportPath -> Name defaultAlias :: ImportPath -> Name
defaultAlias = name . T.pack . takeFileName . unPath defaultAlias = name . T.pack . takeFileName . unPath

View File

@ -9,6 +9,7 @@ module Data.JSON.Fields
) where ) where
import Data.Aeson import Data.Aeson
import Data.Edit
import qualified Data.Map as Map import qualified Data.Map as Map
import Data.Sum (Apply (..), Sum) import Data.Sum (Apply (..), Sum)
import qualified Data.Text as Text import qualified Data.Text as Text
@ -57,6 +58,11 @@ instance ToJSONFields Span where
instance ToJSONFields Loc where instance ToJSONFields Loc where
toJSONFields Loc{..} = toJSONFields byteRange <> toJSONFields span toJSONFields Loc{..} = toJSONFields byteRange <> toJSONFields span
instance (ToJSONFields a, ToJSONFields b) => ToJSONFields (Edit a b) where
toJSONFields (Insert a) = [ "insert" .= object (toJSONFields a) ]
toJSONFields (Delete a) = [ "delete" .= object (toJSONFields a) ]
toJSONFields (Compare a b) = [ "replace" .= [object (toJSONFields a), object (toJSONFields b)] ]
newtype JSONFields a = JSONFields { unJSONFields :: a } newtype JSONFields a = JSONFields { unJSONFields :: a }

View File

@ -1,76 +0,0 @@
{-# OPTIONS_GHC -funbox-strict-fields #-}
module Data.Patch
( Patch(..)
, after
, before
, patch
) where
import Prologue
import Data.Aeson
import Data.Align
import Data.JSON.Fields
-- | An operation to replace, insert, or delete an item.
data Patch a b
= Delete a
| Insert b
| Replace a b
deriving (Eq, Foldable, Functor, Generic, Generic1, Ord, Show, Traversable)
-- | Return the item from the after side of the patch.
after :: Patch before after -> Maybe after
after = patch (const Nothing) Just (\ _ b -> Just b)
-- | Return the item from the before side of the patch.
before :: Patch before after -> Maybe before
before = patch Just (const Nothing) (\ a _ -> Just a)
-- | Return both sides of a patch.
patch :: (before -> result) -> (after -> result) -> (before -> after -> result) -> Patch before after -> result
patch ifDelete _ _ (Delete a) = ifDelete a
patch _ ifInsert _ (Insert b) = ifInsert b
patch _ _ ifReplace (Replace a b) = ifReplace a b
-- Instances
instance Bifunctor Patch where
bimap f _ (Delete a) = Delete (f a)
bimap _ g (Insert b) = Insert (g b)
bimap f g (Replace a b) = Replace (f a) (g b)
instance Bifoldable Patch where
bifoldMap f _ (Delete a) = f a
bifoldMap _ g (Insert b) = g b
bifoldMap f g (Replace a b) = f a `mappend` g b
instance Bitraversable Patch where
bitraverse f _ (Delete a) = Delete <$> f a
bitraverse _ g (Insert b) = Insert <$> g b
bitraverse f g (Replace a b) = Replace <$> f a <*> g b
instance Bicrosswalk Patch where
bicrosswalk f _ (Delete a) = Delete <$> f a
bicrosswalk _ g (Insert b) = Insert <$> g b
bicrosswalk f g (Replace a b) = alignWith (these Delete Insert Replace) (f a) (g b)
instance Eq2 Patch where
liftEq2 eqBefore eqAfter p1 p2 = case (p1, p2) of
(Delete a1, Delete a2) -> eqBefore a1 a2
(Insert b1, Insert b2) -> eqAfter b1 b2
(Replace a1 b1, Replace a2 b2) -> eqBefore a1 a2 && eqAfter b1 b2
_ -> False
instance Show2 Patch where
liftShowsPrec2 spBefore _ spAfter _ d p = case p of
Delete a -> showsUnaryWith spBefore "Delete" d a
Insert b -> showsUnaryWith spAfter "Insert" d b
Replace a b -> showsBinaryWith spBefore spAfter "Replace" d a b
instance (ToJSONFields a, ToJSONFields b) => ToJSONFields (Patch a b) where
toJSONFields (Insert a) = [ "insert" .= object (toJSONFields a) ]
toJSONFields (Delete a) = [ "delete" .= object (toJSONFields a) ]
toJSONFields (Replace a b) = [ "replace" .= [object (toJSONFields a), object (toJSONFields b)] ]

View File

@ -35,16 +35,26 @@ projectExtensions = extensionsForLanguage . projectLanguage
projectFiles :: Project -> [File] projectFiles :: Project -> [File]
projectFiles = fmap blobFile . projectBlobs projectFiles = fmap blobFile . projectBlobs
readProjectFromPaths :: MonadIO m => Maybe FilePath -> FilePath -> Language -> [FilePath] -> m Project readProjectFromPaths :: MonadIO m
=> Maybe Path.AbsRelDir -- ^ An optional root directory for the project
-> Path.AbsRelFileDir -- ^ A file or directory to parse. Passing a file path loads all files in that file's parent directory.
-> Language
-> [Path.AbsRelDir] -- ^ Directories to exclude.
-> m Project
readProjectFromPaths maybeRoot path lang excludeDirs = do readProjectFromPaths maybeRoot path lang excludeDirs = do
isDir <- isDirectory path let rootDir :: Path.AbsRelDir
let rootDir = if isDir rootDir = case maybeRoot >>= Path.fromAbsRel of
then fromMaybe path maybeRoot -- If we were provided a root directory, use that.
else fromMaybe (takeDirectory path) maybeRoot Just root -> root
Nothing -> case Path.fileFromFileDir path of
-- If we weren't and the path is a file, drop its file name.
Just fp -> Path.takeDirectory fp
-- Otherwise, load from the path.
Nothing -> Path.dirFromFileDir path
paths <- liftIO $ findFilesInDir (Path.absRel rootDir) exts (fmap Path.absRel excludeDirs) paths <- liftIO $ findFilesInDir rootDir exts excludeDirs
blobs <- liftIO $ traverse (readBlobFromFile' . toFile) paths blobs <- liftIO $ traverse (readBlobFromFile' . toFile) paths
pure $ Project rootDir blobs lang excludeDirs pure $ Project (Path.toString rootDir) blobs lang (fmap Path.toString excludeDirs)
where where
toFile path = File (Path.toString path) lang toFile path = File (Path.toString path) lang
exts = extensionsForLanguage lang exts = extensionsForLanguage lang

View File

@ -216,9 +216,7 @@ instance Evaluatable Class where
currentScope' <- currentScope currentScope' <- currentScope
superScopes <- for classSuperclasses $ \superclass -> do superScopes <- for classSuperclasses $ \superclass -> do
name <- case declaredName superclass of name <- maybeM gensym (declaredName superclass)
Just name -> pure name
Nothing -> gensym
scope <- associatedScope (Declaration name) scope <- associatedScope (Declaration name)
slot <- lookupSlot (Declaration name) slot <- lookupSlot (Declaration name)
superclassFrame <- scopedEnvironment =<< deref slot superclassFrame <- scopedEnvironment =<< deref slot

View File

@ -3,7 +3,7 @@
module Data.Syntax.Expression where module Data.Syntax.Expression where
import Prelude hiding (null) import Prelude hiding (null)
import Prologue hiding (This, index, null) import Prologue hiding (index, null)
import Control.Abstract hiding (Bitwise (..), Call, Member) import Control.Abstract hiding (Bitwise (..), Call, Member)
import Data.Abstract.Evaluatable as Abstract hiding (Member) import Data.Abstract.Evaluatable as Abstract hiding (Member)

View File

@ -5,7 +5,7 @@ module Diffing.Algorithm
, Diffable (..) , Diffable (..)
, Equivalence (..) , Equivalence (..)
, diff , diff
, diffThese , diffEdit
, diffMaybe , diffMaybe
, linearly , linearly
, byReplacing , byReplacing
@ -17,6 +17,7 @@ module Diffing.Algorithm
import Control.Effect.Carrier hiding ((:+:)) import Control.Effect.Carrier hiding ((:+:))
import Control.Effect.NonDet import Control.Effect.NonDet
import qualified Data.Diff as Diff import qualified Data.Diff as Diff
import qualified Data.Edit as Edit
import Data.Sum import Data.Sum
import Data.Term import Data.Term
import GHC.Generics import GHC.Generics
@ -53,9 +54,9 @@ instance Carrier sig m => Carrier sig (Algorithm term1 term2 diff m) where
diff :: (Carrier sig m, Member (Diff term1 term2 diff) sig) => term1 -> term2 -> m diff diff :: (Carrier sig m, Member (Diff term1 term2 diff) sig) => term1 -> term2 -> m diff
diff a1 a2 = send (Diff a1 a2 pure) diff a1 a2 = send (Diff a1 a2 pure)
-- | Diff a These of terms without specifying the algorithm to be used. -- | Diff an 'Edit.Edit' of terms without specifying the algorithm to be used.
diffThese :: (Carrier sig m, Member (Diff term1 term2 diff) sig) => These term1 term2 -> Algorithm term1 term2 diff m diff diffEdit :: (Carrier sig m, Member (Diff term1 term2 diff) sig) => Edit.Edit term1 term2 -> Algorithm term1 term2 diff m diff
diffThese = these byDeleting byInserting diff diffEdit = Edit.edit byDeleting byInserting diff
-- | Diff a pair of optional terms without specifying the algorithm to be used. -- | Diff a pair of optional terms without specifying the algorithm to be used.
diffMaybe :: (Carrier sig m, Member (Diff term1 term2 diff) sig) => Maybe term1 -> Maybe term2 -> Algorithm term1 term2 diff m (Maybe diff) diffMaybe :: (Carrier sig m, Member (Diff term1 term2 diff) sig) => Maybe term1 -> Maybe term2 -> Algorithm term1 term2 diff m (Maybe diff)
@ -143,8 +144,8 @@ class Diffable f where
-> Algorithm term1 term2 diff m (f diff) -> Algorithm term1 term2 diff m (f diff)
algorithmFor = genericAlgorithmFor algorithmFor = genericAlgorithmFor
tryAlignWith :: Alternative g => (These a1 a2 -> g b) -> f a1 -> f a2 -> g (f b) tryAlignWith :: Alternative g => (Edit.Edit a1 a2 -> g b) -> f a1 -> f a2 -> g (f b)
default tryAlignWith :: (Alternative g, Generic1 f, GDiffable (Rep1 f)) => (These a1 a2 -> g b) -> f a1 -> f a2 -> g (f b) default tryAlignWith :: (Alternative g, Generic1 f, GDiffable (Rep1 f)) => (Edit.Edit a1 a2 -> g b) -> f a1 -> f a2 -> g (f b)
tryAlignWith f a b = to1 <$> gtryAlignWith f (from1 a) (from1 b) tryAlignWith f a b = to1 <$> gtryAlignWith f (from1 a) (from1 b)
-- | Construct an algorithm to diff against positions inside an @f@. -- | Construct an algorithm to diff against positions inside an @f@.
@ -207,30 +208,30 @@ instance Apply Diffable fs => Diffable (Sum fs) where
instance Diffable Maybe where instance Diffable Maybe where
algorithmFor = diffMaybe algorithmFor = diffMaybe
tryAlignWith f (Just a1) (Just a2) = Just <$> f (These a1 a2) tryAlignWith f (Just a1) (Just a2) = Just <$> f (Edit.Compare a1 a2)
tryAlignWith f (Just a1) Nothing = Just <$> f (This a1) tryAlignWith f (Just a1) Nothing = Just <$> f (Edit.Delete a1)
tryAlignWith f Nothing (Just a2) = Just <$> f (That a2) tryAlignWith f Nothing (Just a2) = Just <$> f (Edit.Insert a2)
tryAlignWith _ Nothing Nothing = pure Nothing tryAlignWith _ Nothing Nothing = pure Nothing
-- | Diff two lists using RWS. -- | Diff two lists using RWS.
instance Diffable [] where instance Diffable [] where
algorithmFor = byRWS algorithmFor = byRWS
tryAlignWith f (a1:as1) (a2:as2) = (:) <$> f (These a1 a2) <*> tryAlignWith f as1 as2 tryAlignWith f (a1:as1) (a2:as2) = (:) <$> f (Edit.Compare a1 a2) <*> tryAlignWith f as1 as2
tryAlignWith f [] as2 = traverse (f . That) as2 tryAlignWith f [] as2 = traverse (f . Edit.Insert) as2
tryAlignWith f as1 [] = traverse (f . This) as1 tryAlignWith f as1 [] = traverse (f . Edit.Delete) as1
-- | Diff two non-empty lists using RWS. -- | Diff two non-empty lists using RWS.
instance Diffable NonEmpty where instance Diffable NonEmpty where
algorithmFor (a1:|as1) (a2:|as2) = nonEmpty <$> byRWS (a1:as1) (a2:as2) >>= maybeM empty algorithmFor (a1:|as1) (a2:|as2) = nonEmpty <$> byRWS (a1:as1) (a2:as2) >>= maybeM empty
tryAlignWith f (a1:|as1) (a2:|as2) = (:|) <$> f (These a1 a2) <*> tryAlignWith f as1 as2 tryAlignWith f (a1:|as1) (a2:|as2) = (:|) <$> f (Edit.Compare a1 a2) <*> tryAlignWith f as1 as2
-- | A generic type class for diffing two terms defined by the Generic1 interface. -- | A generic type class for diffing two terms defined by the Generic1 interface.
class GDiffable f where class GDiffable f where
galgorithmFor :: (Alternative m, Carrier sig m, Member (Diff term1 term2 diff) sig, Member NonDet sig) => f term1 -> f term2 -> Algorithm term1 term2 diff m (f diff) galgorithmFor :: (Alternative m, Carrier sig m, Member (Diff term1 term2 diff) sig, Member NonDet sig) => f term1 -> f term2 -> Algorithm term1 term2 diff m (f diff)
gtryAlignWith :: Alternative g => (These a1 a2 -> g b) -> f a1 -> f a2 -> g (f b) gtryAlignWith :: Alternative g => (Edit.Edit a1 a2 -> g b) -> f a1 -> f a2 -> g (f b)
gcomparableTo :: f term1 -> f term2 -> Bool gcomparableTo :: f term1 -> f term2 -> Bool
gcomparableTo _ _ = True gcomparableTo _ _ = True
@ -271,7 +272,7 @@ instance (GDiffable f, GDiffable g) => GDiffable (f :+: g) where
instance GDiffable Par1 where instance GDiffable Par1 where
galgorithmFor (Par1 a1) (Par1 a2) = Par1 <$> diff a1 a2 galgorithmFor (Par1 a1) (Par1 a2) = Par1 <$> diff a1 a2
gtryAlignWith f (Par1 a) (Par1 b) = Par1 <$> f (These a b) gtryAlignWith f (Par1 a) (Par1 b) = Par1 <$> f (Edit.Compare a b)
-- | Diff two constant parameters (K1 is the Generic1 newtype representing type parameter constants). -- | Diff two constant parameters (K1 is the Generic1 newtype representing type parameter constants).
-- i.e. data Foo = Foo Int (the 'Int' is a constant parameter). -- i.e. data Foo = Foo Int (the 'Int' is a constant parameter).

View File

@ -15,7 +15,8 @@ module Diffing.Algorithm.RWS
) where ) where
import Control.Monad.State.Strict import Control.Monad.State.Strict
import Data.Diff (DiffF(..), deleting, inserting, merge, replacing) import Data.Diff (DiffF(..), comparing, deleting, inserting, merge)
import Data.Edit
import qualified Data.KdMap.Static as KdMap import qualified Data.KdMap.Static as KdMap
import Data.List (sortOn) import Data.List (sortOn)
import Data.Term as Term import Data.Term as Term
@ -37,7 +38,7 @@ rws :: (Foldable syntax, Functor syntax, Diffable syntax)
-> [Edit (Term syntax (FeatureVector, ann1)) (Term syntax (FeatureVector, ann2))] -> [Edit (Term syntax (FeatureVector, ann1)) (Term syntax (FeatureVector, ann2))]
rws _ _ as [] = Delete <$> as rws _ _ as [] = Delete <$> as
rws _ _ [] bs = Insert <$> bs rws _ _ [] bs = Insert <$> bs
rws canCompare _ [a] [b] = if canCompareTerms canCompare a b then [Copy a b] else [Insert b, Delete a] rws canCompare _ [a] [b] = if canCompareTerms canCompare a b then [Compare a b] else [Insert b, Delete a]
rws canCompare equivalent as bs rws canCompare equivalent as bs
= ses equivalent as bs = ses equivalent as bs
& mapContiguous [] [] & mapContiguous [] []
@ -46,15 +47,15 @@ rws canCompare equivalent as bs
-- Map contiguous sequences of unmapped terms separated by SES-mapped equivalencies. -- Map contiguous sequences of unmapped terms separated by SES-mapped equivalencies.
mapContiguous as bs [] = mapSimilar (reverse as) (reverse bs) mapContiguous as bs [] = mapSimilar (reverse as) (reverse bs)
mapContiguous as bs (first : rest) = case first of mapContiguous as bs (first : rest) = case first of
Delete a -> mapContiguous (a : as) bs rest Delete a -> mapContiguous (a : as) bs rest
Insert b -> mapContiguous as (b : bs) rest Insert b -> mapContiguous as (b : bs) rest
Copy _ _ -> mapSimilar (reverse as) (reverse bs) <> (first : mapContiguous [] [] rest) Compare _ _ -> mapSimilar (reverse as) (reverse bs) <> (first : mapContiguous [] [] rest)
-- Map comparable, mutually similar terms, inserting & deleting surrounding terms. -- Map comparable, mutually similar terms, inserting & deleting surrounding terms.
mapSimilar as' bs' = go as bs mapSimilar as' bs' = go as bs
where go as [] = Delete . snd <$> as where go as [] = Delete . snd <$> as
go [] bs = Insert . snd <$> bs go [] bs = Insert . snd <$> bs
go [a] [b] | canCompareTerms canCompare (snd a) (snd b) = [Copy (snd a) (snd b)] go [a] [b] | canCompareTerms canCompare (snd a) (snd b) = [Compare (snd a) (snd b)]
| otherwise = [Insert (snd b), Delete (snd a)] | otherwise = [Insert (snd b), Delete (snd a)]
go as@((i, _) : _) ((j, b) : restB) = go as@((i, _) : _) ((j, b) : restB) =
fromMaybe (Insert b : go as restB) $ do fromMaybe (Insert b : go as restB) $ do
@ -66,7 +67,7 @@ rws canCompare equivalent as bs
guard (j == j') guard (j == j')
-- Delete any elements of as before the selected element. -- Delete any elements of as before the selected element.
let (deleted, _ : restA) = span ((< i') . fst) as let (deleted, _ : restA) = span ((< i') . fst) as
pure $! (Delete . snd <$> deleted) <> (Copy a b : go restA restB) pure $! (Delete . snd <$> deleted) <> (Compare a b : go restA restB)
(as, bs) = (zip [0..] as', zip [0..] bs') (as, bs) = (zip [0..] as', zip [0..] bs')
(kdMapA, kdMapB) = (toKdMap as, toKdMap bs) (kdMapA, kdMapB) = (toKdMap as, toKdMap bs)
@ -158,7 +159,7 @@ editDistanceUpTo m a b = diffCost m (approximateDiff a b)
_ | m <= 0 -> 0 _ | m <= 0 -> 0
Merge body -> sum (fmap ($ pred m) body) Merge body -> sum (fmap ($ pred m) body)
body -> succ (sum (fmap ($ pred m) body)) body -> succ (sum (fmap ($ pred m) body))
approximateDiff a b = maybe (replacing a b) (merge (termAnnotation a, termAnnotation b)) (tryAlignWith (Just . these deleting inserting approximateDiff) (termOut a) (termOut b)) approximateDiff a b = maybe (comparing a b) (merge (termAnnotation a, termAnnotation b)) (tryAlignWith (Just . edit deleting inserting approximateDiff) (termOut a) (termOut b))
data Label syntax where data Label syntax where

View File

@ -1,35 +1,13 @@
{-# LANGUAGE BangPatterns, GADTs, LambdaCase, MultiParamTypeClasses, ScopedTypeVariables #-} {-# LANGUAGE BangPatterns, GADTs, MultiParamTypeClasses, ScopedTypeVariables #-}
module Diffing.Algorithm.SES module Diffing.Algorithm.SES
( Edit(..) ( ses
, toThese
, ses
) where ) where
import Data.Array ((!)) import Data.Array ((!))
import qualified Data.Array as Array import qualified Data.Array as Array
import Data.Bifunctor import Data.Edit
import Data.Foldable (find, toList) import Data.Foldable (find, toList)
import Data.Ix import Data.Ix
import Data.These
-- | An edit script, i.e. a sequence of changes/copies of elements.
data Edit a b
= Delete a
| Insert b
| Copy a b
deriving (Eq, Functor, Ord, Show)
instance Bifunctor Edit where
bimap f g = \case
Delete a -> Delete (f a)
Insert b -> Insert (g b)
Copy a b -> Copy (f a) (g b)
toThese :: Edit a b -> These a b
toThese = \case
Delete a -> This a
Insert b -> That b
Copy a b -> These a b
data Endpoint a b = Endpoint { x :: {-# UNPACK #-} !Int, _y :: {-# UNPACK #-} !Int, _script :: [Edit a b] } data Endpoint a b = Endpoint { x :: {-# UNPACK #-} !Int, _y :: {-# UNPACK #-} !Int, _script :: [Edit a b] }
deriving (Eq, Show) deriving (Eq, Show)
@ -78,11 +56,11 @@ ses eq as' bs'
slideFrom (Endpoint x y script) slideFrom (Endpoint x y script)
| Just a <- as !? x | Just a <- as !? x
, Just b <- bs !? y , Just b <- bs !? y
, a `eq` b = slideFrom (Endpoint (succ x) (succ y) (Copy a b : script)) , a `eq` b = slideFrom (Endpoint (succ x) (succ y) (Compare a b : script))
| otherwise = Endpoint x y script | otherwise = Endpoint x y script
(!?) :: Ix i => Array.Array i a -> i -> Maybe a (!?) :: Ix i => Array.Array i a -> i -> Maybe a
(!?) v i | inRange (Array.bounds v) i, !a <- v ! i = Just a v !? i | inRange (Array.bounds v) i, !a <- v ! i = Just a
| otherwise = Nothing | otherwise = Nothing
{-# INLINE (!?) #-} {-# INLINE (!?) #-}

View File

@ -9,10 +9,10 @@ import Control.Effect.Carrier
import Control.Effect.Cull import Control.Effect.Cull
import Control.Effect.NonDet import Control.Effect.NonDet
import qualified Data.Diff as Diff import qualified Data.Diff as Diff
import Data.Edit (Edit, edit)
import Data.Term import Data.Term
import Diffing.Algorithm import Diffing.Algorithm
import Diffing.Algorithm.RWS import Diffing.Algorithm.RWS
import Diffing.Algorithm.SES (toThese)
import Prologue import Prologue
-- | Diff two à la carte terms recursively. -- | Diff two à la carte terms recursively.
@ -20,7 +20,7 @@ diffTerms :: (Diffable syntax, Eq1 syntax, Hashable1 syntax, Traversable syntax)
=> Term syntax ann1 => Term syntax ann1
-> Term syntax ann2 -> Term syntax ann2
-> Diff.Diff syntax ann1 ann2 -> Diff.Diff syntax ann1 ann2
diffTerms t1 t2 = stripDiff (fromMaybe (Diff.replacing t1' t2') (run (runNonDetOnce (runDiff (algorithmForTerms t1' t2'))))) diffTerms t1 t2 = stripDiff (fromMaybe (Diff.comparing t1' t2') (run (runNonDetOnce (runDiff (algorithmForTerms t1' t2')))))
where (t1', t2') = ( defaultFeatureVectorDecorator t1 where (t1', t2') = ( defaultFeatureVectorDecorator t1
, defaultFeatureVectorDecorator t2) , defaultFeatureVectorDecorator t2)
@ -37,12 +37,12 @@ class Bifoldable (DiffFor term) => DiffTerms term where
-- Note that the dependency means that the diff type is in 1:1 correspondence with the term type. This allows subclasses of 'DiffTerms' to receive e.g. @'DiffFor' term a b@ without incurring ambiguity, since every diff type is unique to its term type. -- Note that the dependency means that the diff type is in 1:1 correspondence with the term type. This allows subclasses of 'DiffTerms' to receive e.g. @'DiffFor' term a b@ without incurring ambiguity, since every diff type is unique to its term type.
type DiffFor term = (diff :: * -> * -> *) | diff -> term type DiffFor term = (diff :: * -> * -> *) | diff -> term
-- | Diff a 'These' of terms. -- | Diff an 'Edit' of terms.
diffTermPair :: These (term ann1) (term ann2) -> DiffFor term ann1 ann2 diffTermPair :: Edit (term ann1) (term ann2) -> DiffFor term ann1 ann2
instance (Diffable syntax, Eq1 syntax, Hashable1 syntax, Traversable syntax) => DiffTerms (Term syntax) where instance (Diffable syntax, Eq1 syntax, Hashable1 syntax, Traversable syntax) => DiffTerms (Term syntax) where
type DiffFor (Term syntax) = Diff.Diff syntax type DiffFor (Term syntax) = Diff.Diff syntax
diffTermPair = these Diff.deleting Diff.inserting diffTerms diffTermPair = edit Diff.deleting Diff.inserting diffTerms
-- | Run an 'Algorithm' to completion in an 'Alternative' context using the supplied comparability & equivalence relations. -- | Run an 'Algorithm' to completion in an 'Alternative' context using the supplied comparability & equivalence relations.
@ -71,10 +71,10 @@ instance ( Alternative m
(Diff (Term syntax (FeatureVector, ann1)) (Term syntax (FeatureVector, ann2)) (Diff.Diff syntax (FeatureVector, ann1) (FeatureVector, ann2)) :+: sig) (Diff (Term syntax (FeatureVector, ann1)) (Term syntax (FeatureVector, ann2)) (Diff.Diff syntax (FeatureVector, ann1) (FeatureVector, ann2)) :+: sig)
(DiffC (Term syntax (FeatureVector, ann1)) (Term syntax (FeatureVector, ann2)) (Diff.Diff syntax (FeatureVector, ann1) (FeatureVector, ann2)) m) where (DiffC (Term syntax (FeatureVector, ann1)) (Term syntax (FeatureVector, ann2)) (Diff.Diff syntax (FeatureVector, ann1) (FeatureVector, ann2)) m) where
eff (L op) = case op of eff (L op) = case op of
Diff t1 t2 k -> runDiff (algorithmForTerms t1 t2) <|> pure (Diff.replacing t1 t2) >>= k Diff t1 t2 k -> runDiff (algorithmForTerms t1 t2) <|> pure (Diff.comparing t1 t2) >>= k
Linear (Term (In ann1 f1)) (Term (In ann2 f2)) k -> Diff.merge (ann1, ann2) <$> tryAlignWith (runDiff . diffThese) f1 f2 >>= k Linear (Term (In ann1 f1)) (Term (In ann2 f2)) k -> Diff.merge (ann1, ann2) <$> tryAlignWith (runDiff . diffEdit) f1 f2 >>= k
RWS as bs k -> traverse (runDiff . diffThese . toThese) (rws comparableTerms equivalentTerms as bs) >>= k RWS as bs k -> traverse (runDiff . diffEdit) (rws comparableTerms equivalentTerms as bs) >>= k
Delete a k -> k (Diff.deleting a) Delete a k -> k (Diff.deleting a)
Insert b k -> k (Diff.inserting b) Insert b k -> k (Diff.inserting b)
Replace a b k -> k (Diff.replacing a b) Replace a b k -> k (Diff.comparing a b)
eff (R other) = DiffC . eff . handleCoercible $ other eff (R other) = DiffC . eff . handleCoercible $ other

View File

@ -16,7 +16,6 @@ import Data.Bits as X
import Data.ByteString as X (ByteString) import Data.ByteString as X (ByteString)
import Data.Coerce as X import Data.Coerce as X
import Data.Int as X (Int8, Int16, Int32, Int64) import Data.Int as X (Int8, Int16, Int32, Int64)
import Data.Functor.Both as X (Both (Both), runBothWith)
import Data.Either as X (fromLeft, fromRight) import Data.Either as X (fromLeft, fromRight)
import Data.IntMap as X (IntMap) import Data.IntMap as X (IntMap)
import Data.IntSet as X (IntSet) import Data.IntSet as X (IntSet)
@ -30,7 +29,6 @@ import Data.Semilattice.Lower as X (Lower(..))
import Data.Set as X (Set) import Data.Set as X (Set)
import Data.Sum as X (Sum, Element, Elements, (:<), (:<:), Apply (..), inject) import Data.Sum as X (Sum, Element, Elements, (:<), (:<:), Apply (..), inject)
import Data.Text as X (Text) import Data.Text as X (Text)
import Data.These as X
import Data.Word as X (Word8, Word16, Word32, Word64) import Data.Word as X (Word8, Word16, Word32, Word64)
import Control.Exception as X hiding (Handler (..), assert, evaluate, throw, throwIO, throwTo) import Control.Exception as X hiding (Handler (..), assert, evaluate, throw, throwIO, throwTo)

View File

@ -14,8 +14,8 @@ import Control.Effect.Reader
import Control.Effect.State import Control.Effect.State
import Control.Lens import Control.Lens
import Data.Diff import Data.Diff
import Data.Edit
import Data.Graph import Data.Graph
import Data.Patch
import Data.ProtoLens (defMessage) import Data.ProtoLens (defMessage)
import Data.String (IsString (..)) import Data.String (IsString (..))
import Data.Term import Data.Term
@ -99,7 +99,7 @@ instance (ConstructorName syntax, Foldable syntax) =>
Patch (Insert t2@(In a2 syntax)) -> diffAlgebra t2 . DiffTreeVertex'Inserted $ defMessage Patch (Insert t2@(In a2 syntax)) -> diffAlgebra t2 . DiffTreeVertex'Inserted $ defMessage
& P.term .~ T.pack (constructorName syntax) & P.term .~ T.pack (constructorName syntax)
& P.maybe'span .~ ann a2 & P.maybe'span .~ ann a2
Patch (Replace t1@(In a1 syntax1) t2@(In a2 syntax2)) -> do Patch (Compare t1@(In a1 syntax1) t2@(In a2 syntax2)) -> do
i <- fresh i <- fresh
parent <- ask parent <- ask
let (beforeName, beforeSpan) = (T.pack (constructorName syntax1), ann a1) let (beforeName, beforeSpan) = (T.pack (constructorName syntax1), ann a1)

View File

@ -15,9 +15,8 @@ module Rendering.JSON
import Data.Aeson (ToJSON, toJSON, object, (.=)) import Data.Aeson (ToJSON, toJSON, object, (.=))
import Data.Aeson as A import Data.Aeson as A
import Data.JSON.Fields
import Data.Blob import Data.Blob
import Data.Patch import Data.JSON.Fields
import Data.Text (pack) import Data.Text (pack)
import GHC.TypeLits import GHC.TypeLits
import Prologue import Prologue
@ -56,8 +55,8 @@ newtype JSONStat = JSONStat { jsonStatBlobs :: BlobPair }
deriving (Eq, Show) deriving (Eq, Show)
instance ToJSON JSONStat where instance ToJSON JSONStat where
toJSON JSONStat{..} = object ("path" .= pathKeyForBlobPair jsonStatBlobs : toJSONFields (these Delete Insert Replace (getBlobPair jsonStatBlobs))) toJSON JSONStat{..} = object ("path" .= pathKeyForBlobPair jsonStatBlobs : toJSONFields jsonStatBlobs)
toEncoding JSONStat{..} = pairs (fold ("path" .= pathKeyForBlobPair jsonStatBlobs : toJSONFields (these Delete Insert Replace (getBlobPair jsonStatBlobs)))) toEncoding JSONStat{..} = pairs (fold ("path" .= pathKeyForBlobPair jsonStatBlobs : toJSONFields jsonStatBlobs))
-- | Render a term to a value representing its JSON. -- | Render a term to a value representing its JSON.
renderJSONTerm :: ToJSON a => Blob -> a -> JSON "trees" SomeJSON renderJSONTerm :: ToJSON a => Blob -> a -> JSON "trees" SomeJSON

View File

@ -12,13 +12,12 @@ module Rendering.TOC
import Prologue hiding (index) import Prologue hiding (index)
import Analysis.TOCSummary import Analysis.TOCSummary
import Data.Align (bicrosswalk)
import Data.Aeson (ToJSON(..), Value, (.=), object) import Data.Aeson (ToJSON(..), Value, (.=), object)
import Data.Diff import Data.Diff
import Data.Edit
import Data.Language as Language import Data.Language as Language
import Data.List (sortOn) import Data.List (sortOn)
import qualified Data.Map.Monoidal as Map import qualified Data.Map.Monoidal as Map
import Data.Patch
import Data.Term import Data.Term
import qualified Data.Text as T import qualified Data.Text as T
import Source.Loc import Source.Loc
@ -77,12 +76,17 @@ tableOfContentsBy :: (Foldable f, Functor f)
=> (forall b. TermF f ann b -> Maybe a) -- ^ A function mapping relevant nodes onto values in Maybe. => (forall b. TermF f ann b -> Maybe a) -- ^ A function mapping relevant nodes onto values in Maybe.
-> Diff f ann ann -- ^ The diff to compute the table of contents for. -> Diff f ann ann -- ^ The diff to compute the table of contents for.
-> [(Change, a)] -- ^ A list of entries for relevant changed nodes in the diff. -> [(Change, a)] -- ^ A list of entries for relevant changed nodes in the diff.
tableOfContentsBy selector = fromMaybe [] . cata (\ r -> case r of tableOfContentsBy selector = fromMaybe [] . cata (\case
Patch patch -> (pure . patchEntry <$> bicrosswalk selector selector patch) <> bifoldMap fold fold patch <> Just [] Patch edit -> (pure . patchEntry <$> select (bimap selector selector edit)) <> bifoldMap fold fold edit <> Just []
Merge (In (_, ann2) r) -> case (selector (In ann2 r), fold r) of Merge (In (_, ann2) r) -> case (selector (In ann2 r), fold r) of
(Just a, Just entries) -> Just ((Changed, a) : entries) (Just a, Just entries) -> Just ((Changed, a) : entries)
(_ , entries) -> entries) (_ , entries) -> entries)
where patchEntry = patch (Deleted,) (Inserted,) (const (Replaced,)) where patchEntry = edit (Deleted,) (Inserted,) (const (Replaced,))
select = \case
Delete a -> Delete <$> a
Insert b -> Insert <$> b
Compare a b -> liftA2 Compare a b <|> Delete <$> a <|> Insert <$> b
data DedupeKey = DedupeKey !Kind {-# UNPACK #-} !T.Text data DedupeKey = DedupeKey !Kind {-# UNPACK #-} !T.Text

View File

@ -46,10 +46,10 @@ type DomainC term address value m
-- | Evaluate a list of modules with the prelude for the passed language available, and applying the passed function to every module. -- | Evaluate a list of modules with the prelude for the passed language available, and applying the passed function to every module.
evaluate :: ( Carrier outerSig outer evaluate :: ( Carrier outerSig outer
, derefSig ~ (Deref value :+: allocatorSig) , derefSig ~ (Deref value :+: allocatorSig)
, derefC ~ (DerefC address value allocatorC) , derefC ~ DerefC address value allocatorC
, Carrier derefSig derefC , Carrier derefSig derefC
, allocatorSig ~ (Allocator address :+: Reader ModuleInfo :+: outerSig) , allocatorSig ~ (Allocator address :+: Reader ModuleInfo :+: outerSig)
, allocatorC ~ (AllocatorC address (ReaderC ModuleInfo outer)) , allocatorC ~ AllocatorC address (ReaderC ModuleInfo outer)
, Carrier allocatorSig allocatorC , Carrier allocatorSig allocatorC
, Effect outerSig , Effect outerSig
, Member Fresh outerSig , Member Fresh outerSig

View File

@ -1,4 +1,4 @@
{-# LANGUAGE FunctionalDependencies, LambdaCase #-} {-# LANGUAGE FunctionalDependencies #-}
module Semantic.Api.Bridge module Semantic.Api.Bridge
( APIBridge (..) ( APIBridge (..)
, APIConvert (..) , APIConvert (..)
@ -7,6 +7,7 @@ module Semantic.Api.Bridge
import Control.Lens import Control.Lens
import qualified Data.Blob as Data import qualified Data.Blob as Data
import qualified Data.Edit as Data
import qualified Data.Language as Data import qualified Data.Language as Data
import Data.ProtoLens (defMessage) import Data.ProtoLens (defMessage)
import qualified Data.Text as T import qualified Data.Text as T
@ -78,11 +79,11 @@ instance APIConvert API.BlobPair Data.BlobPair where
converting = prism' blobPairToApiBlobPair apiBlobPairToBlobPair where converting = prism' blobPairToApiBlobPair apiBlobPairToBlobPair where
apiBlobPairToBlobPair blobPair = case (blobPair^.maybe'before, blobPair^.maybe'after) of apiBlobPairToBlobPair blobPair = case (blobPair^.maybe'before, blobPair^.maybe'after) of
(Just before, Just after) -> Just $ Data.Diffing (before^.bridging) (after^.bridging) (Just before, Just after) -> Just $ Data.Compare (before^.bridging) (after^.bridging)
(Just before, Nothing) -> Just $ Data.Deleting (before^.bridging) (Just before, Nothing) -> Just $ Data.Delete (before^.bridging)
(Nothing, Just after) -> Just $ Data.Inserting (after^.bridging) (Nothing, Just after) -> Just $ Data.Insert (after^.bridging)
_ -> Nothing _ -> Nothing
blobPairToApiBlobPair (Data.Diffing before after) = defMessage & P.maybe'before .~ (bridging #? before) & P.maybe'after .~ (bridging #? after) blobPairToApiBlobPair (Data.Compare before after) = defMessage & P.maybe'before .~ (bridging #? before) & P.maybe'after .~ (bridging #? after)
blobPairToApiBlobPair (Data.Inserting after) = defMessage & P.maybe'before .~ Nothing & P.maybe'after .~ (bridging #? after) blobPairToApiBlobPair (Data.Insert after) = defMessage & P.maybe'before .~ Nothing & P.maybe'after .~ (bridging #? after)
blobPairToApiBlobPair (Data.Deleting before) = defMessage & P.maybe'before .~ (bridging #? before) & P.maybe'after .~ Nothing blobPairToApiBlobPair (Data.Delete before) = defMessage & P.maybe'before .~ (bridging #? before) & P.maybe'after .~ Nothing

View File

@ -18,6 +18,7 @@ import Control.Lens
import Control.Monad.IO.Class import Control.Monad.IO.Class
import Data.Blob import Data.Blob
import Data.ByteString.Builder import Data.ByteString.Builder
import Data.Edit
import Data.Graph import Data.Graph
import Data.JSON.Fields import Data.JSON.Fields
import Data.Language import Data.Language
@ -156,9 +157,9 @@ diffWith
diffWith parsers render = parsePairWith parsers (render <=< diffTerms) diffWith parsers render = parsePairWith parsers (render <=< diffTerms)
diffTerms :: (DiffTerms term, Member Telemetry sig, Carrier sig m, MonadIO m) diffTerms :: (DiffTerms term, Member Telemetry sig, Carrier sig m, MonadIO m)
=> These (Blob, term ann) (Blob, term ann) -> m (DiffFor term ann ann) => Edit (Blob, term ann) (Blob, term ann) -> m (DiffFor term ann ann)
diffTerms terms = time "diff" languageTag $ do diffTerms terms = time "diff" languageTag $ do
let diff = diffTermPair (bimap snd snd terms) let diff = diffTermPair (bimap snd snd terms)
diff <$ writeStat (Stat.count "diff.nodes" (bilength diff) languageTag) diff <$ writeStat (Stat.count "diff.nodes" (bilength diff) languageTag)
where languageTag = languageTagForBlobPair blobs where languageTag = languageTagForBlobPair blobs
blobs = BlobPair (bimap fst fst terms) blobs = bimap fst fst terms

View File

@ -18,6 +18,7 @@ import Control.Monad.IO.Class
import Data.Aeson import Data.Aeson
import Data.Blob import Data.Blob
import Data.ByteString.Builder import Data.ByteString.Builder
import Data.Edit
import Data.Either (partitionEithers) import Data.Either (partitionEithers)
import Data.Function (on) import Data.Function (on)
import Data.Functor.Classes import Data.Functor.Classes
@ -30,7 +31,6 @@ import Data.ProtoLens (defMessage)
import Data.Semilattice.Lower import Data.Semilattice.Lower
import Data.Term (Term) import Data.Term (Term)
import qualified Data.Text as T import qualified Data.Text as T
import Data.These (These, fromThese)
import Diffing.Algorithm (Diffable) import Diffing.Algorithm (Diffable)
import qualified Diffing.Algorithm.SES as SES import qualified Diffing.Algorithm.SES as SES
import qualified Language.Java as Java import qualified Language.Java as Java
@ -107,7 +107,7 @@ summarizeDiffParsers :: PerLanguageModes -> Map Language (SomeParser SummarizeDi
summarizeDiffParsers = allParsers summarizeDiffParsers = allParsers
class SummarizeDiff term where class SummarizeDiff term where
summarizeTerms :: (Member Telemetry sig, Carrier sig m, MonadIO m) => These (Blob, term Loc) (Blob, term Loc) -> m [Either ErrorSummary TOCSummary] summarizeTerms :: (Member Telemetry sig, Carrier sig m, MonadIO m) => Edit (Blob, term Loc) (Blob, term Loc) -> m [Either ErrorSummary TOCSummary]
instance (Diffable syntax, Eq1 syntax, HasDeclaration syntax, Hashable1 syntax, Traversable syntax) => SummarizeDiff (Term syntax) where instance (Diffable syntax, Eq1 syntax, HasDeclaration syntax, Hashable1 syntax, Traversable syntax) => SummarizeDiff (Term syntax) where
summarizeTerms = fmap diffTOC . diffTerms . bimap decorateTerm decorateTerm where summarizeTerms = fmap diffTOC . diffTerms . bimap decorateTerm decorateTerm where
@ -123,16 +123,16 @@ deriving via (ViaTags Python.Term) instance SummarizeDiff Python.Term
newtype ViaTags t a = ViaTags (t a) newtype ViaTags t a = ViaTags (t a)
instance Tagging.ToTags t => SummarizeDiff (ViaTags t) where instance Tagging.ToTags t => SummarizeDiff (ViaTags t) where
summarizeTerms terms = pure . map (uncurry summarizeChange) . dedupe . mapMaybe toChange . uncurry (SES.ses compare) . fromThese [] [] . bimap (uncurry go) (uncurry go) $ terms where summarizeTerms terms = pure . map (uncurry summarizeChange) . dedupe . mapMaybe toChange . edit (map Delete) (map Insert) (SES.ses compare) . bimap (uncurry go) (uncurry go) $ terms where
go blob (ViaTags t) = Tagging.tags (blobSource blob) t go blob (ViaTags t) = Tagging.tags (blobSource blob) t
lang = languageForBlobPair (BlobPair (bimap fst fst terms)) lang = languageForBlobPair (bimap fst fst terms)
(s1, s2) = fromThese mempty mempty (bimap (blobSource . fst) (blobSource . fst) terms) (s1, s2) = edit (,mempty) (mempty,) (,) (bimap (blobSource . fst) (blobSource . fst) terms)
compare = liftA2 (&&) <$> ((==) `on` Tag.kind) <*> ((==) `on` Tag.name) compare = liftA2 (&&) <$> ((==) `on` Tag.kind) <*> ((==) `on` Tag.name)
toChange = \case toChange = \case
SES.Delete tag -> (Deleted,) <$> toDecl tag Delete tag -> (Deleted,) <$> toDecl tag
SES.Insert tag -> (Inserted,) <$> toDecl tag Insert tag -> (Inserted,) <$> toDecl tag
SES.Copy t1 t2 Compare t1 t2
| Source.slice s1 (byteRange (Tag.loc t1)) /= Source.slice s2 (byteRange (Tag.loc t2)) | Source.slice s1 (byteRange (Tag.loc t1)) /= Source.slice s2 (byteRange (Tag.loc t2))
-> (Changed,) <$> toDecl t2 -> (Changed,) <$> toDecl t2
| otherwise -> Nothing | otherwise -> Nothing

View File

@ -6,7 +6,6 @@ import Control.Effect.Reader
import Control.Exception as Exc (displayException) import Control.Exception as Exc (displayException)
import Data.Blob import Data.Blob
import Data.Blob.IO import Data.Blob.IO
import qualified Data.ByteString.Char8 as B
import Data.Handle import Data.Handle
import qualified Data.Language as Language import qualified Data.Language as Language
import Data.List (intercalate) import Data.List (intercalate)
@ -18,7 +17,6 @@ import Semantic.Api hiding (File)
import Semantic.Config import Semantic.Config
import qualified Semantic.Graph as Graph import qualified Semantic.Graph as Graph
import qualified Semantic.Task as Task import qualified Semantic.Task as Task
import qualified Semantic.Git as Git
import Semantic.Task.Files import Semantic.Task.Files
import Semantic.Telemetry import Semantic.Telemetry
import qualified Semantic.Telemetry.Log as Log import qualified Semantic.Telemetry.Log as Log
@ -99,7 +97,7 @@ diffCommand = command "diff" (info diffArgumentsParser (progDesc "Compute change
<|> flag' (diffSummaryBuilder JSON) (long "toc" <> help "Output JSON table of contents diff summary") <|> flag' (diffSummaryBuilder JSON) (long "toc" <> help "Output JSON table of contents diff summary")
<|> flag' (parseDiffBuilder DiffDotGraph) (long "dot" <> help "Output the diff as a DOT graph") <|> flag' (parseDiffBuilder DiffDotGraph) (long "dot" <> help "Output the diff as a DOT graph")
<|> flag' (parseDiffBuilder DiffShow) (long "show" <> help "Output using the Show instance (debug only, format subject to change without notice)") <|> flag' (parseDiffBuilder DiffShow) (long "show" <> help "Output using the Show instance (debug only, format subject to change without notice)")
filesOrStdin <- Right <$> some (Both <$> argument filePathReader (metavar "FILE_A") <*> argument filePathReader (metavar "FILE_B")) <|> pure (Left stdin) filesOrStdin <- Right <$> some ((,) <$> argument filePathReader (metavar "FILE_A") <*> argument filePathReader (metavar "FILE_B")) <|> pure (Left stdin)
pure $ Task.readBlobPairs filesOrStdin >>= runReader languageModes . renderer pure $ Task.readBlobPairs filesOrStdin >>= runReader languageModes . renderer
parseCommand :: Mod CommandFields (Parse.ParseC Task.TaskC Builder) parseCommand :: Mod CommandFields (Parse.ParseC Task.TaskC Builder)
@ -134,14 +132,7 @@ parseCommand = command "parse" (info parseArgumentsParser (progDesc "Generate pa
<|> flag' (parseTermBuilder TermQuiet) <|> flag' (parseTermBuilder TermQuiet)
( long "quiet" ( long "quiet"
<> help "Don't produce output, but show timing stats") <> help "Don't produce output, but show timing stats")
filesOrStdin <- FilesFromGitRepo filesOrStdin <- FilesFromPaths <$> some (argument filePathReader (metavar "FILES..."))
<$> option str (long "gitDir" <> help "A .git directory to read from")
<*> option shaReader (long "sha" <> help "The commit SHA1 to read from")
<*> ( ExcludePaths <$> many (option str (long "exclude" <> short 'x' <> help "Paths to exclude"))
<|> ExcludeFromHandle <$> flag' stdin (long "exclude-stdin" <> help "Exclude paths given to stdin")
<|> IncludePaths <$> many (option str (long "only" <> help "Only include the specified paths"))
<|> IncludePathsFromHandle <$> flag' stdin (long "only-stdin" <> help "Include only the paths given to stdin"))
<|> FilesFromPaths <$> some (argument filePathReader (metavar "FILES..."))
<|> pure (FilesFromHandle stdin) <|> pure (FilesFromHandle stdin)
pure $ Task.readBlobs filesOrStdin >>= runReader languageModes . renderer pure $ Task.readBlobs filesOrStdin >>= runReader languageModes . renderer
@ -169,9 +160,9 @@ graphCommand = command "graph" (info graphArgumentsParser (progDesc "Compute a g
_ -> pure $! Project "/" mempty Language.Unknown mempty _ -> pure $! Project "/" mempty Language.Unknown mempty
readProjectRecursively = makeReadProjectRecursivelyTask readProjectRecursively = makeReadProjectRecursivelyTask
<$> option auto (long "language" <> help "The language for the analysis.") <$> option auto (long "language" <> help "The language for the analysis.")
<*> optional (strOption (long "root" <> help "Root directory of project. Optional, defaults to entry file/directory." <> metavar "DIR")) <*> optional (pathOption (long "root" <> help "Root directory of project. Optional, defaults to entry file/directory." <> metavar "DIR"))
<*> many (strOption (long "exclude-dir" <> help "Exclude a directory (e.g. vendor)" <> metavar "DIR")) <*> many (pathOption (long "exclude-dir" <> help "Exclude a directory (e.g. vendor)" <> metavar "DIR"))
<*> argument str (metavar "DIR") <*> argument path (metavar "PATH")
makeReadProjectRecursivelyTask language rootDir excludeDirs dir = Task.readProject rootDir dir language excludeDirs makeReadProjectRecursivelyTask language rootDir excludeDirs dir = Task.readProject rootDir dir language excludeDirs
makeGraphTask graphType includePackages serializer projectTask = projectTask >>= Graph.runGraph graphType includePackages >>= serializer makeGraphTask graphType includePackages serializer projectTask = projectTask >>= Graph.runGraph graphType includePackages >>= serializer
@ -183,12 +174,6 @@ languageModes = Language.PerLanguageModes
<> value Language.ALaCarte <> value Language.ALaCarte
<> showDefault) <> showDefault)
shaReader :: ReadM Git.OID
shaReader = eitherReader parseSha
where parseSha arg = if length arg == 40 || arg == "HEAD"
then Right (Git.OID (B.pack arg))
else Left (arg <> " is not a valid sha1")
filePathReader :: ReadM File filePathReader :: ReadM File
filePathReader = fileForPath <$> str filePathReader = fileForPath <$> str

View File

@ -1,109 +0,0 @@
module Semantic.Git
( -- Primary (partial) API for cmd line git
clone
, lsTree
, catFile
-- Intermediate datatypes
, TreeEntry(..)
, ObjectType(..)
, ObjectMode(..)
, OID(..)
-- Testing Purposes
, parseEntries
, parseEntry
) where
import Prologue
import Data.Attoparsec.ByteString (Parser)
import Data.Attoparsec.ByteString as AP
import Data.ByteString (ByteString)
import Data.ByteString.Internal (w2c)
import qualified Data.ByteString.UTF8 as UTF8
import qualified Data.ByteString.Streaming as ByteStream
import qualified Data.Attoparsec.ByteString.Streaming as AP.Streaming
import Data.Char
import Data.Either (fromRight)
import Data.Text as Text
import Text.Parser.Combinators (sepEndBy)
import qualified Streaming.Process
import qualified System.Process as Process
import qualified Source.Source as Source
-- | git clone --bare
clone :: Text -> FilePath -> IO ()
clone url path = Process.callProcess "git"
["clone", "--bare", Text.unpack url, path]
-- | Runs @git cat-file -p@. Throws 'ProcessExitedUnsuccessfully' if the
-- underlying git command returns a nonzero exit code. Loads the contents
-- of the file into memory all at once and strictly.
catFile :: FilePath -> OID -> IO Source.Source
catFile gitDir (OID oid) =
let process = Process.proc "git" ["-C", gitDir, "cat-file", "-p", UTF8.toString oid]
consumeStdout stream = Streaming.Process.withProcessOutput stream ByteStream.toStrict_
in Source.fromUTF8 <$> Streaming.Process.withStreamProcess process consumeStdout
-- | git ls-tree -rz
lsTree :: FilePath -> OID -> IO [TreeEntry]
lsTree gitDir (OID sha) =
let process = Process.proc "git" ["-C", gitDir, "ls-tree", "-rz", UTF8.toString sha]
allEntries = (entryParser `sepEndBy` AP.word8 0) <* AP.endOfInput
ignoreFailures = fmap (fromRight [] . fst)
in Streaming.Process.withStreamProcess process $
\stream -> Streaming.Process.withProcessOutput stream (ignoreFailures . AP.Streaming.parse allEntries)
-- | Parses an list of entries separated by \NUL, and on failure return []
parseEntries :: ByteString -> [TreeEntry]
parseEntries = fromRight [] . AP.parseOnly everything
where
everything = AP.sepBy entryParser (AP.word8 0)
-- | Parse the entire input with entryParser, and on failure return a default
-- For testing purposes only
parseEntry :: ByteString -> Either String TreeEntry
parseEntry = AP.parseOnly (entryParser <* AP.endOfInput)
-- | Parses a TreeEntry
entryParser :: Parser TreeEntry
entryParser = TreeEntry
<$> modeParser <* AP.word8 space
<*> typeParser <* AP.word8 space
<*> oidParser <* AP.word8 tab
<*> (UTF8.toString <$> AP.takeWhile (/= nul))
where
char = fromIntegral @Int @Word8 . ord
space = char ' '
tab = char '\t'
nul = char '\NUL'
typeParser = AP.choice [BlobObject <$ "blob", TreeObject <$ "tree", OtherObjectType <$ AP.takeWhile (isAlphaNum . w2c)]
modeParser = AP.choice [NormalMode <$ "100644", ExecutableMode <$ "100755", SymlinkMode <$ "120000", TreeMode <$ "040000", OtherMode <$ AP.takeWhile (isAlphaNum . w2c)]
oidParser = OID <$> AP.takeWhile (isHexDigit . w2c)
newtype OID = OID ByteString
deriving (Eq, Show, Ord)
data ObjectMode
= NormalMode
| ExecutableMode
| SymlinkMode
| TreeMode
| OtherMode
deriving (Eq, Show)
data ObjectType
= BlobObject
| TreeObject
| OtherObjectType
deriving (Eq, Show)
data TreeEntry
= TreeEntry
{ treeEntryMode :: ObjectMode
, treeEntryType :: ObjectType
, treeEntryOid :: OID
, treeEntryPath :: FilePath
} deriving (Eq, Show)

View File

@ -1,5 +1,5 @@
{-# LANGUAGE ConstraintKinds, ExistentialQuantification, GADTs, GeneralizedNewtypeDeriving, KindSignatures, {-# LANGUAGE ConstraintKinds, ExistentialQuantification, GADTs, GeneralizedNewtypeDeriving, KindSignatures,
ScopedTypeVariables, StandaloneDeriving, TypeOperators, UndecidableInstances #-} ScopedTypeVariables, TypeOperators, UndecidableInstances #-}
module Semantic.Task module Semantic.Task
( TaskC ( TaskC
, Level(..) , Level(..)

View File

@ -14,7 +14,6 @@ module Semantic.Task.Files
, Handle (..) , Handle (..)
, FilesC(..) , FilesC(..)
, FilesArg(..) , FilesArg(..)
, PathFilter(..)
) where ) where
import Control.Effect.Carrier import Control.Effect.Carrier
@ -28,7 +27,6 @@ import Data.Language
import Data.Project import Data.Project
import Prelude hiding (readFile) import Prelude hiding (readFile)
import Prologue hiding (catch) import Prologue hiding (catch)
import qualified Semantic.Git as Git
import Semantic.IO import Semantic.IO
import qualified System.IO as IO hiding (withBinaryFile) import qualified System.IO as IO hiding (withBinaryFile)
import qualified System.Path.IO as IO (withBinaryFile) import qualified System.Path.IO as IO (withBinaryFile)
@ -38,22 +36,15 @@ data Source blob where
FromPath :: File -> Source Blob FromPath :: File -> Source Blob
FromHandle :: Handle 'IO.ReadMode -> Source [Blob] FromHandle :: Handle 'IO.ReadMode -> Source [Blob]
FromDir :: Path.AbsRelDir -> Source [Blob] FromDir :: Path.AbsRelDir -> Source [Blob]
FromGitRepo :: FilePath -> Git.OID -> PathFilter -> Source [Blob] FromPathPair :: File -> File -> Source BlobPair
FromPathPair :: Both File -> Source BlobPair
FromPairHandle :: Handle 'IO.ReadMode -> Source [BlobPair] FromPairHandle :: Handle 'IO.ReadMode -> Source [BlobPair]
data Destination = ToPath Path.AbsRelFile | ToHandle (Handle 'IO.WriteMode) data Destination = ToPath Path.AbsRelFile | ToHandle (Handle 'IO.WriteMode)
data PathFilter
= ExcludePaths [FilePath]
| ExcludeFromHandle (Handle 'IO.ReadMode)
| IncludePaths [FilePath]
| IncludePathsFromHandle (Handle 'IO.ReadMode)
-- | An effect to read/write 'Blob's from 'Handle's or 'FilePath's. -- | An effect to read/write 'Blob's from 'Handle's or 'FilePath's.
data Files (m :: * -> *) k data Files (m :: * -> *) k
= forall a . Read (Source a) (a -> m k) = forall a . Read (Source a) (a -> m k)
| ReadProject (Maybe FilePath) FilePath Language [FilePath] (Project -> m k) | ReadProject (Maybe Path.AbsRelDir) Path.AbsRelFileDir Language [Path.AbsRelDir] (Project -> m k)
| FindFiles Path.AbsRelDir [String] [Path.AbsRelDir] ([Path.AbsRelFile] -> m k) | FindFiles Path.AbsRelDir [String] [Path.AbsRelDir] ([Path.AbsRelFile] -> m k)
| Write Destination B.Builder (m k) | Write Destination B.Builder (m k)
@ -84,11 +75,7 @@ instance (Member (Error SomeException) sig, Member Catch sig, MonadIO m, Carrier
Read (FromPath path) k -> rethrowing (readBlobFromFile' path) >>= k Read (FromPath path) k -> rethrowing (readBlobFromFile' path) >>= k
Read (FromHandle handle) k -> rethrowing (readBlobsFromHandle handle) >>= k Read (FromHandle handle) k -> rethrowing (readBlobsFromHandle handle) >>= k
Read (FromDir dir) k -> rethrowing (readBlobsFromDir dir) >>= k Read (FromDir dir) k -> rethrowing (readBlobsFromDir dir) >>= k
Read (FromGitRepo path sha (ExcludePaths excludePaths)) k -> rethrowing (readBlobsFromGitRepo path sha excludePaths mempty) >>= k Read (FromPathPair p1 p2) k -> rethrowing (readFilePair p1 p2) >>= k
Read (FromGitRepo path sha (ExcludeFromHandle handle)) k -> rethrowing (readPathsFromHandle handle >>= (\x -> readBlobsFromGitRepo path sha x mempty)) >>= k
Read (FromGitRepo path sha (IncludePaths includePaths)) k -> rethrowing (readBlobsFromGitRepo path sha mempty includePaths) >>= k
Read (FromGitRepo path sha (IncludePathsFromHandle h)) k -> rethrowing (readPathsFromHandle h >>= readBlobsFromGitRepo path sha mempty) >>= k
Read (FromPathPair paths) k -> rethrowing (runBothWith readFilePair paths) >>= k
Read (FromPairHandle handle) k -> rethrowing (readBlobPairsFromHandle handle) >>= k Read (FromPairHandle handle) k -> rethrowing (readBlobPairsFromHandle handle) >>= k
ReadProject rootDir dir language excludeDirs k -> rethrowing (readProjectFromPaths rootDir dir language excludeDirs) >>= k ReadProject rootDir dir language excludeDirs k -> rethrowing (readProjectFromPaths rootDir dir language excludeDirs) >>= k
FindFiles dir exts excludeDirs k -> rethrowing (findFilesInDir dir exts excludeDirs) >>= k FindFiles dir exts excludeDirs k -> rethrowing (findFilesInDir dir exts excludeDirs) >>= k
@ -102,7 +89,6 @@ readBlob file = send (Read (FromPath file) pure)
data FilesArg data FilesArg
= FilesFromHandle (Handle 'IO.ReadMode) = FilesFromHandle (Handle 'IO.ReadMode)
| FilesFromPaths [File] | FilesFromPaths [File]
| FilesFromGitRepo FilePath Git.OID PathFilter
-- | A task which reads a list of 'Blob's from a 'Handle' or a list of 'FilePath's optionally paired with 'Language's. -- | A task which reads a list of 'Blob's from a 'Handle' or a list of 'FilePath's optionally paired with 'Language's.
readBlobs :: (Member Files sig, Carrier sig m, MonadIO m) => FilesArg -> m [Blob] readBlobs :: (Member Files sig, Carrier sig m, MonadIO m) => FilesArg -> m [Blob]
@ -113,14 +99,13 @@ readBlobs (FilesFromPaths [path]) = do
then send (Read (FromDir (Path.path (filePath path))) pure) then send (Read (FromDir (Path.path (filePath path))) pure)
else pure <$> send (Read (FromPath path) pure) else pure <$> send (Read (FromPath path) pure)
readBlobs (FilesFromPaths paths) = traverse (send . flip Read pure . FromPath) paths readBlobs (FilesFromPaths paths) = traverse (send . flip Read pure . FromPath) paths
readBlobs (FilesFromGitRepo path sha filter) = send (Read (FromGitRepo path sha filter) pure)
-- | A task which reads a list of pairs of 'Blob's from a 'Handle' or a list of pairs of 'FilePath's optionally paired with 'Language's. -- | A task which reads a list of pairs of 'Blob's from a 'Handle' or a list of pairs of 'FilePath's optionally paired with 'Language's.
readBlobPairs :: (Member Files sig, Carrier sig m) => Either (Handle 'IO.ReadMode) [Both File] -> m [BlobPair] readBlobPairs :: (Member Files sig, Carrier sig m) => Either (Handle 'IO.ReadMode) [(File, File)] -> m [BlobPair]
readBlobPairs (Left handle) = send (Read (FromPairHandle handle) pure) readBlobPairs (Left handle) = send (Read (FromPairHandle handle) pure)
readBlobPairs (Right paths) = traverse (send . flip Read pure . FromPathPair) paths readBlobPairs (Right paths) = traverse (send . flip Read pure . uncurry FromPathPair) paths
readProject :: (Member Files sig, Carrier sig m) => Maybe FilePath -> FilePath -> Language -> [FilePath] -> m Project readProject :: (Member Files sig, Carrier sig m) => Maybe Path.AbsRelDir -> Path.AbsRelFileDir -> Language -> [Path.AbsRelDir] -> m Project
readProject rootDir dir lang excludeDirs = send (ReadProject rootDir dir lang excludeDirs pure) readProject rootDir dir lang excludeDirs = send (ReadProject rootDir dir lang excludeDirs pure)
findFiles :: (Member Files sig, Carrier sig m) => Path.AbsRelDir -> [String] -> [Path.AbsRelDir] -> m [Path.AbsRelFile] findFiles :: (Member Files sig, Carrier sig m) => Path.AbsRelDir -> [String] -> [Path.AbsRelDir] -> m [Path.AbsRelFile]

View File

@ -8,7 +8,7 @@ module Serializing.SExpression
import Analysis.ConstructorName import Analysis.ConstructorName
import Data.ByteString.Builder import Data.ByteString.Builder
import Data.Diff import Data.Diff
import Data.Patch import Data.Edit
import Data.Term import Data.Term
import Prelude import Prelude
import Prologue import Prologue
@ -43,6 +43,6 @@ instance (ConstructorName syntax, Foldable syntax, Show ann1, Show ann2) => ToSE
toSExpression options diff n = case diff of toSExpression options diff n = case diff of
Patch (Delete term) -> nl n <> pad (n - 1) <> "{-" <> namedBranch options term n <> "-}" Patch (Delete term) -> nl n <> pad (n - 1) <> "{-" <> namedBranch options term n <> "-}"
Patch (Insert term) -> nl n <> pad (n - 1) <> "{+" <> namedBranch options term n <> "+}" Patch (Insert term) -> nl n <> pad (n - 1) <> "{+" <> namedBranch options term n <> "+}"
Patch (Replace term1 term2) -> nl n <> pad (n - 1) <> "{ " <> namedBranch options term1 n Patch (Compare term1 term2) -> nl n <> pad (n - 1) <> "{ " <> namedBranch options term1 n
<> nl (n + 1) <> pad (n - 1) <> "->" <> namedBranch options term2 n <> " }" <> nl (n + 1) <> pad (n - 1) <> "->" <> namedBranch options term2 n <> " }"
Merge term -> nl n <> pad n <> namedBranch options term n Merge term -> nl n <> pad n <> namedBranch options term n

View File

@ -1,4 +1,4 @@
{-# LANGUAGE AllowAmbiguousTypes, DataKinds, FlexibleContexts, FlexibleInstances, KindSignatures, MultiParamTypeClasses, OverloadedStrings, ScopedTypeVariables, TypeApplications, TypeFamilies, TypeOperators, UndecidableInstances #-} {-# LANGUAGE AllowAmbiguousTypes, DataKinds, FlexibleContexts, FlexibleInstances, MultiParamTypeClasses, OverloadedStrings, ScopedTypeVariables, TypeApplications, TypeFamilies, TypeOperators, UndecidableInstances #-}
module Serializing.SExpression.Precise module Serializing.SExpression.Precise
( serializeSExpression ( serializeSExpression
, ToSExpression(..) , ToSExpression(..)

View File

@ -12,7 +12,7 @@ identify a new syntax as Taggable, you need to:
constructor name of this syntax. constructor name of this syntax.
-} -}
{-# LANGUAGE AllowAmbiguousTypes, ConstraintKinds, KindSignatures, ScopedTypeVariables, TypeFamilies, UndecidableInstances #-} {-# LANGUAGE AllowAmbiguousTypes, ConstraintKinds, ScopedTypeVariables, TypeFamilies, UndecidableInstances #-}
module Tags.Taggable module Tags.Taggable
( Tagger ( Tagger
, Token(..) , Token(..)

View File

@ -57,12 +57,11 @@ contextualizing :: ( Member (State [ContextToken]) sig
contextualizing source toKind = Streaming.mapMaybeM $ \case contextualizing source toKind = Streaming.mapMaybeM $ \case
Enter x r -> Nothing <$ enterScope (x, r) Enter x r -> Nothing <$ enterScope (x, r)
Exit x r -> Nothing <$ exitScope (x, r) Exit x r -> Nothing <$ exitScope (x, r)
Iden iden loc docsLiteralRange -> get @[ContextToken] >>= pure . \case Iden iden loc docsLiteralRange -> fmap go (get @[ContextToken]) where
((x, r):("Context", cr):_) | Just kind <- toKind x go = \case
-> Just $ Tag iden kind loc (firstLine (slice r)) (Just (slice cr)) ((x, r):("Context", cr):_) | Just kind <- toKind x -> Just $ Tag iden kind loc (firstLine (slice r)) (Just (slice cr))
((x, r):_) | Just kind <- toKind x ((x, r):_) | Just kind <- toKind x -> Just $ Tag iden kind loc (firstLine (slice r)) (slice <$> docsLiteralRange)
-> Just $ Tag iden kind loc (firstLine (slice r)) (slice <$> docsLiteralRange) _ -> Nothing
_ -> Nothing
where where
slice = stripEnd . Source.toText . Source.slice source slice = stripEnd . Source.toText . Source.slice source
firstLine = T.take 180 . fst . breakOn "\n" firstLine = T.take 180 . fst . breakOn "\n"

View File

@ -20,10 +20,9 @@ import qualified Analysis.TOCSummary as ToC
import Data.Abstract.ScopeGraph (AccessControl(..)) import Data.Abstract.ScopeGraph (AccessControl(..))
import Data.Bifunctor.Join import Data.Bifunctor.Join
import Data.Diff import Data.Diff
import Data.Functor.Both import Data.Edit
import qualified Data.Language as Language import qualified Data.Language as Language
import Data.List.NonEmpty import Data.List.NonEmpty
import Data.Patch
import qualified Data.Syntax as Syntax import qualified Data.Syntax as Syntax
import qualified Data.Syntax.Comment as Comment import qualified Data.Syntax.Comment as Comment
import qualified Data.Syntax.Declaration as Declaration import qualified Data.Syntax.Declaration as Declaration
@ -122,9 +121,6 @@ instance Listable1 NonEmpty where
instance Listable2 p => Listable1 (Join p) where instance Listable2 p => Listable1 (Join p) where
liftTiers tiers = liftCons1 (liftTiers2 tiers tiers) Join liftTiers tiers = liftCons1 (liftTiers2 tiers tiers) Join
instance Listable1 Both where
liftTiers tiers = liftCons2 tiers tiers Both
instance Listable1 f => Listable2 (TermF f) where instance Listable1 f => Listable2 (TermF f) where
liftTiers2 annotationTiers recurTiers = liftCons2 annotationTiers (liftTiers recurTiers) In liftTiers2 annotationTiers recurTiers = liftCons2 annotationTiers (liftTiers recurTiers) In
@ -160,10 +156,10 @@ instance (Listable1 syntax, Listable ann1, Listable ann2) => Listable (Diff synt
tiers = tiers2 tiers = tiers2
instance Listable2 Patch where instance Listable2 Edit where
liftTiers2 t1 t2 = liftCons1 t2 Insert \/ liftCons1 t1 Delete \/ liftCons2 t1 t2 Replace liftTiers2 t1 t2 = liftCons1 t2 Insert \/ liftCons1 t1 Delete \/ liftCons2 t1 t2 Compare
instance (Listable a, Listable b) => Listable (Patch a b) where instance (Listable a, Listable b) => Listable (Edit a b) where
tiers = tiers2 tiers = tiers2

View File

@ -7,11 +7,9 @@ import Data.Functor.Listable (ListableSyntax)
import Data.Sum import Data.Sum
import qualified Data.Syntax as Syntax import qualified Data.Syntax as Syntax
import Data.Term import Data.Term
import Data.These
import Diffing.Algorithm (comparableTerms) import Diffing.Algorithm (comparableTerms)
import Diffing.Interpreter (stripDiff) import Diffing.Interpreter (stripDiff)
import Diffing.Algorithm.RWS import Diffing.Algorithm.RWS
import Diffing.Algorithm.SES
import Diffing.Interpreter.Spec (afterTerm, beforeTerm) import Diffing.Interpreter.Spec (afterTerm, beforeTerm)
import Test.Hspec.LeanCheck import Test.Hspec.LeanCheck
import SpecHelpers import SpecHelpers
@ -31,16 +29,16 @@ spec = do
\ (as, bs) -> let tas = decorate <$> (as :: [Term ListableSyntax ()]) \ (as, bs) -> let tas = decorate <$> (as :: [Term ListableSyntax ()])
tbs = decorate <$> (bs :: [Term ListableSyntax ()]) tbs = decorate <$> (bs :: [Term ListableSyntax ()])
wrap = termIn emptyAnnotation . inject wrap = termIn emptyAnnotation . inject
diff = merge (emptyAnnotation, emptyAnnotation) (inject (stripDiff . diffThese . toThese <$> rws comparableTerms (equalTerms comparableTerms) tas tbs)) in diff = merge (emptyAnnotation, emptyAnnotation) (inject (stripDiff . diffEdit <$> rws comparableTerms (equalTerms comparableTerms) tas tbs)) in
(beforeTerm diff, afterTerm diff) `shouldBe` (Just (wrap (stripTerm <$> tas)), Just (wrap (stripTerm <$> tbs))) (beforeTerm diff, afterTerm diff) `shouldBe` (Just (wrap (stripTerm <$> tas)), Just (wrap (stripTerm <$> tbs)))
it "produces unbiased insertions within branches" $ it "produces unbiased insertions within branches" $
let (a, b) = (decorate (termIn emptyAnnotation (inject [ termIn emptyAnnotation (inject (Syntax.Identifier "a")) ])), decorate (termIn emptyAnnotation (inject [ termIn emptyAnnotation (inject (Syntax.Identifier "b")) ]))) in let (a, b) = (decorate (termIn emptyAnnotation (inject [ termIn emptyAnnotation (inject (Syntax.Identifier "a")) ])), decorate (termIn emptyAnnotation (inject [ termIn emptyAnnotation (inject (Syntax.Identifier "b")) ]))) in
fmap (bimap stripTerm stripTerm) (rws comparableTerms (equalTerms comparableTerms) [ b ] [ a, b ]) `shouldBe` fmap (bimap stripTerm stripTerm) [ Insert a, Copy b b ] fmap (bimap stripTerm stripTerm) (rws comparableTerms (equalTerms comparableTerms) [ b ] [ a, b ]) `shouldBe` fmap (bimap stripTerm stripTerm) [ Insert a, Compare b b ]
where decorate = defaultFeatureVectorDecorator where decorate = defaultFeatureVectorDecorator
diffThese = these deleting inserting replacing diffEdit = edit deleting inserting comparing
stripTerm :: Functor f => Term f (FeatureVector, ()) -> Term f () stripTerm :: Functor f => Term f (FeatureVector, ()) -> Term f ()
stripTerm = fmap snd stripTerm = fmap snd

View File

@ -1,6 +1,6 @@
module Diffing.Algorithm.SES.Spec (spec) where module Diffing.Algorithm.SES.Spec (spec) where
import Data.These import Data.Edit
import Diffing.Algorithm.SES import Diffing.Algorithm.SES
import Test.Hspec import Test.Hspec
import Test.Hspec.LeanCheck import Test.Hspec.LeanCheck
@ -8,17 +8,17 @@ import Test.Hspec.LeanCheck
spec :: Spec spec :: Spec
spec = do spec = do
describe "ses" $ do describe "ses" $ do
prop "returns equal lists in These" $ prop "returns equal lists in Compare" $
\ as -> (ses (==) as as :: [Edit Char Char]) `shouldBe` zipWith Copy as as \ as -> (ses (==) as as :: [Edit Char Char]) `shouldBe` zipWith Compare as as
prop "returns deletions in This" $ prop "returns deletions in Delete" $
\ as -> (ses (==) as [] :: [Edit Char Char]) `shouldBe` fmap Delete as \ as -> (ses (==) as [] :: [Edit Char Char]) `shouldBe` fmap Delete as
prop "returns insertions in That" $ prop "returns insertions in Insert" $
\ bs -> (ses (==) [] bs :: [Edit Char Char]) `shouldBe` fmap Insert bs \ bs -> (ses (==) [] bs :: [Edit Char Char]) `shouldBe` fmap Insert bs
prop "returns all elements individually for disjoint inputs" $ prop "returns all elements individually for disjoint inputs" $
\ as bs -> length (ses (==) ((,) 0 <$> as :: [(Int, Char)]) ((,) 1 <$> bs :: [(Int, Char)])) `shouldBe` length as + length bs \ as bs -> length (ses (==) ((,) 0 <$> as :: [(Int, Char)]) ((,) 1 <$> bs :: [(Int, Char)])) `shouldBe` length as + length bs
prop "is lossless w.r.t. both input elements & ordering" $ prop "is lossless w.r.t. both input elements & ordering" $
\ as bs -> foldr (\ each (as, bs) -> these (flip (,) bs. (:as)) ((,) as . (:bs)) (\ a b -> (a:as, b:bs)) (toThese each)) ([], []) (ses (==) as bs :: [Edit Char Char]) `shouldBe` (as, bs) \ as bs -> foldr (\ each (as, bs) -> edit (flip (,) bs. (:as)) ((,) as . (:bs)) (\ a b -> (a:as, b:bs)) each) ([], []) (ses (==) as bs :: [Edit Char Char]) `shouldBe` (as, bs)

View File

@ -8,17 +8,15 @@ import Data.Functor.Foldable (cata)
import Data.Functor.Listable import Data.Functor.Listable
import Data.Maybe import Data.Maybe
import Data.Mergeable import Data.Mergeable
import Data.Patch (after, before)
import Data.Sum import Data.Sum
import Data.Term import Data.Term
import Data.These
import Diffing.Interpreter import Diffing.Interpreter
import qualified Data.Syntax as Syntax import qualified Data.Syntax as Syntax
import Test.Hspec (Spec, describe, it) import Test.Hspec (Spec, describe, it)
import Test.Hspec.Expectations import Test.Hspec.Expectations
import Test.Hspec.LeanCheck import Test.Hspec.LeanCheck
import Test.LeanCheck.Core import Test.LeanCheck.Core
import SpecHelpers () import SpecHelpers (Edit(..), edit)
spec :: Spec spec :: Spec
spec = do spec = do
@ -26,7 +24,7 @@ spec = do
it "returns a replacement when comparing two unicode equivalent terms" $ it "returns a replacement when comparing two unicode equivalent terms" $
let termA = termIn emptyAnnotation (inject (Syntax.Identifier "t\776")) let termA = termIn emptyAnnotation (inject (Syntax.Identifier "t\776"))
termB = termIn emptyAnnotation (inject (Syntax.Identifier "\7831")) in termB = termIn emptyAnnotation (inject (Syntax.Identifier "\7831")) in
diffTerms termA termB `shouldBe` replacing termA (termB :: Term ListableSyntax ()) diffTerms termA termB `shouldBe` comparing termA (termB :: Term ListableSyntax ())
prop "produces correct diffs" $ prop "produces correct diffs" $
\ a b -> let diff = diffTerms a b :: Diff ListableSyntax () () in \ a b -> let diff = diffTerms a b :: Diff ListableSyntax () () in
@ -61,11 +59,11 @@ spec = do
describe "diffTermPair" $ do describe "diffTermPair" $ do
prop "produces an Insert when the first term is missing" $ do prop "produces an Insert when the first term is missing" $ do
\ after -> let diff = diffTermPair (That after) :: Diff ListableSyntax () () in \ after -> let diff = diffTermPair (Insert after) :: Diff ListableSyntax () () in
diff `shouldBe` inserting after diff `shouldBe` inserting after
prop "produces a Delete when the second term is missing" $ do prop "produces a Delete when the second term is missing" $ do
\ before -> let diff = diffTermPair (This before) :: Diff ListableSyntax () () in \ before -> let diff = diffTermPair (Delete before) :: Diff ListableSyntax () () in
diff `shouldBe` deleting before diff `shouldBe` deleting before
@ -81,5 +79,13 @@ afterTerm = cata $ \ diff -> case diff of
Patch patch -> (after patch >>= \ (In b r) -> termIn b <$> sequenceAlt r) <|> (before patch >>= asum) Patch patch -> (after patch >>= \ (In b r) -> termIn b <$> sequenceAlt r) <|> (before patch >>= asum)
Merge (In (_, b) r) -> termIn b <$> sequenceAlt r Merge (In (_, b) r) -> termIn b <$> sequenceAlt r
-- | Return the item from the after side of the patch.
after :: Edit l r -> Maybe r
after = edit (const Nothing) Just (\ _ b -> Just b)
-- | Return the item from the before side of the patch.
before :: Edit l r -> Maybe l
before = edit Just (const Nothing) (\ a _ -> Just a)
emptyAnnotation :: () emptyAnnotation :: ()
emptyAnnotation = () emptyAnnotation = ()

View File

@ -39,7 +39,7 @@ testForExample = \case
("diffs " <> Path.toString diffOutput) ("diffs " <> Path.toString diffOutput)
(\ref new -> ["git", "diff", ref, new]) (\ref new -> ["git", "diff", ref, new])
(Path.toString diffOutput) (Path.toString diffOutput)
(BL.fromStrict <$> diffFilePaths ?session (Both fileA fileB)) (BL.fromStrict <$> diffFilePaths ?session fileA fileB)
ParseExample{file, parseOutput} -> ParseExample{file, parseOutput} ->
goldenVsStringDiff goldenVsStringDiff
("parses " <> Path.toString parseOutput) ("parses " <> Path.toString parseOutput)

View File

@ -8,7 +8,6 @@ import Data.Aeson hiding (defaultOptions)
import Data.Bifunctor import Data.Bifunctor
import Data.Diff import Data.Diff
import Data.Either (isRight) import Data.Either (isRight)
import Data.Patch
import Data.Sum import Data.Sum
import Data.Term import Data.Term
import Data.Text (Text) import Data.Text (Text)
@ -37,9 +36,9 @@ spec = do
\ term -> tableOfContentsBy (Just . termFAnnotation) (diffTerms term (term :: Term ListableSyntax ())) `shouldBe` [] \ term -> tableOfContentsBy (Just . termFAnnotation) (diffTerms term (term :: Term ListableSyntax ())) `shouldBe` []
prop "produces inserted/deleted/replaced entries for relevant nodes within patches" $ prop "produces inserted/deleted/replaced entries for relevant nodes within patches" $
\ p -> tableOfContentsBy (Just . termFAnnotation) (patch deleting inserting replacing p) \ p -> tableOfContentsBy (Just . termFAnnotation) (edit deleting inserting comparing p)
`shouldBe` `shouldBe`
patch (fmap (Deleted,)) (fmap (Inserted,)) (\ as bs -> (Replaced, head bs) : fmap (Deleted,) (tail as) <> fmap (Inserted,) (tail bs)) (bimap (foldMap pure) (foldMap pure) (p :: Patch (Term ListableSyntax Int) (Term ListableSyntax Int))) edit (fmap (Deleted,)) (fmap (Inserted,)) (\ as bs -> (Replaced, head bs) : fmap (Deleted,) (tail as) <> fmap (Inserted,) (tail bs)) (bimap (foldMap pure) (foldMap pure) (p :: Edit (Term ListableSyntax Int) (Term ListableSyntax Int)))
prop "produces changed entries for relevant nodes containing irrelevant patches" $ prop "produces changed entries for relevant nodes containing irrelevant patches" $
\ diff -> do \ diff -> do
@ -53,7 +52,7 @@ spec = do
diffTOC blankDiff `shouldBe` [ ] diffTOC blankDiff `shouldBe` [ ]
it "summarizes changed methods" $ do it "summarizes changed methods" $ do
sourceBlobs <- blobsForPaths (Both (Path.relFile "ruby/toc/methods.A.rb") (Path.relFile "ruby/toc/methods.B.rb")) sourceBlobs <- blobsForPaths (Path.relFile "ruby/toc/methods.A.rb") (Path.relFile "ruby/toc/methods.B.rb")
diff <- runTaskOrDie $ summarize sourceBlobs diff <- runTaskOrDie $ summarize sourceBlobs
diff `shouldBe` diff `shouldBe`
[ Right $ TOCSummary (Method (Just "self")) "self.foo" (Span (Pos 1 1) (Pos 2 4)) Inserted [ Right $ TOCSummary (Method (Just "self")) "self.foo" (Span (Pos 1 1) (Pos 2 4)) Inserted
@ -62,37 +61,37 @@ spec = do
] ]
it "dedupes changes in same parent method" $ do it "dedupes changes in same parent method" $ do
sourceBlobs <- blobsForPaths (Both (Path.relFile "javascript/toc/duplicate-parent.A.js") (Path.relFile "javascript/toc/duplicate-parent.B.js")) sourceBlobs <- blobsForPaths (Path.relFile "javascript/toc/duplicate-parent.A.js") (Path.relFile "javascript/toc/duplicate-parent.B.js")
diff <- runTaskOrDie $ summarize sourceBlobs diff <- runTaskOrDie $ summarize sourceBlobs
diff `shouldBe` diff `shouldBe`
[ Right $ TOCSummary Function "myFunction" (Span (Pos 1 1) (Pos 6 2)) Changed ] [ Right $ TOCSummary Function "myFunction" (Span (Pos 1 1) (Pos 6 2)) Changed ]
it "dedupes similar methods" $ do it "dedupes similar methods" $ do
sourceBlobs <- blobsForPaths (Both (Path.relFile "javascript/toc/erroneous-duplicate-method.A.js") (Path.relFile "javascript/toc/erroneous-duplicate-method.B.js")) sourceBlobs <- blobsForPaths (Path.relFile "javascript/toc/erroneous-duplicate-method.A.js") (Path.relFile "javascript/toc/erroneous-duplicate-method.B.js")
diff <- runTaskOrDie $ summarize sourceBlobs diff <- runTaskOrDie $ summarize sourceBlobs
diff `shouldBe` diff `shouldBe`
[ Right $ TOCSummary Function "performHealthCheck" (Span (Pos 8 1) (Pos 29 2)) Replaced ] [ Right $ TOCSummary Function "performHealthCheck" (Span (Pos 8 1) (Pos 29 2)) Replaced ]
it "summarizes Go methods with receivers with special formatting" $ do it "summarizes Go methods with receivers with special formatting" $ do
sourceBlobs <- blobsForPaths (Both (Path.relFile "go/toc/method-with-receiver.A.go") (Path.relFile "go/toc/method-with-receiver.B.go")) sourceBlobs <- blobsForPaths (Path.relFile "go/toc/method-with-receiver.A.go") (Path.relFile "go/toc/method-with-receiver.B.go")
diff <- runTaskOrDie $ summarize sourceBlobs diff <- runTaskOrDie $ summarize sourceBlobs
diff `shouldBe` diff `shouldBe`
[ Right $ TOCSummary (Method (Just "*apiClient")) "(*apiClient) CheckAuth" (Span (Pos 3 1) (Pos 3 101)) Inserted ] [ Right $ TOCSummary (Method (Just "*apiClient")) "(*apiClient) CheckAuth" (Span (Pos 3 1) (Pos 3 101)) Inserted ]
it "summarizes Ruby methods that start with two identifiers" $ do it "summarizes Ruby methods that start with two identifiers" $ do
sourceBlobs <- blobsForPaths (Both (Path.relFile "ruby/toc/method-starts-with-two-identifiers.A.rb") (Path.relFile "ruby/toc/method-starts-with-two-identifiers.B.rb")) sourceBlobs <- blobsForPaths (Path.relFile "ruby/toc/method-starts-with-two-identifiers.A.rb") (Path.relFile "ruby/toc/method-starts-with-two-identifiers.B.rb")
diff <- runTaskOrDie $ summarize sourceBlobs diff <- runTaskOrDie $ summarize sourceBlobs
diff `shouldBe` diff `shouldBe`
[ Right $ TOCSummary (Method Nothing) "foo" (Span (Pos 1 1) (Pos 4 4)) Changed ] [ Right $ TOCSummary (Method Nothing) "foo" (Span (Pos 1 1) (Pos 4 4)) Changed ]
it "handles unicode characters in file" $ do it "handles unicode characters in file" $ do
sourceBlobs <- blobsForPaths (Both (Path.relFile "ruby/toc/unicode.A.rb") (Path.relFile "ruby/toc/unicode.B.rb")) sourceBlobs <- blobsForPaths (Path.relFile "ruby/toc/unicode.A.rb") (Path.relFile "ruby/toc/unicode.B.rb")
diff <- runTaskOrDie $ summarize sourceBlobs diff <- runTaskOrDie $ summarize sourceBlobs
diff `shouldBe` diff `shouldBe`
[ Right $ TOCSummary (Method Nothing) "foo" (Span (Pos 6 1) (Pos 7 4)) Inserted ] [ Right $ TOCSummary (Method Nothing) "foo" (Span (Pos 6 1) (Pos 7 4)) Inserted ]
it "properly slices source blob that starts with a newline and has multi-byte chars" $ do it "properly slices source blob that starts with a newline and has multi-byte chars" $ do
sourceBlobs <- blobsForPaths (Both (Path.relFile "javascript/toc/starts-with-newline.js") (Path.relFile "javascript/toc/starts-with-newline.js")) sourceBlobs <- blobsForPaths (Path.relFile "javascript/toc/starts-with-newline.js") (Path.relFile "javascript/toc/starts-with-newline.js")
diff <- runTaskOrDie $ summarize sourceBlobs diff <- runTaskOrDie $ summarize sourceBlobs
diff `shouldBe` [] diff `shouldBe` []
@ -135,22 +134,22 @@ spec = do
describe "diff with ToCDiffRenderer'" $ do describe "diff with ToCDiffRenderer'" $ do
it "produces JSON output" $ do it "produces JSON output" $ do
blobs <- blobsForPaths (Both (Path.relFile "ruby/toc/methods.A.rb") (Path.relFile "ruby/toc/methods.B.rb")) blobs <- blobsForPaths (Path.relFile "ruby/toc/methods.A.rb") (Path.relFile "ruby/toc/methods.B.rb")
output <- runTaskOrDie (runReader defaultLanguageModes (diffSummaryBuilder Format.JSON [blobs])) output <- runTaskOrDie (runReader defaultLanguageModes (diffSummaryBuilder Format.JSON [blobs]))
runBuilder output `shouldBe` ("{\"files\":[{\"path\":\"test/fixtures/ruby/toc/methods.A.rb -> test/fixtures/ruby/toc/methods.B.rb\",\"language\":\"Ruby\",\"changes\":[{\"category\":\"Method\",\"term\":\"self.foo\",\"span\":{\"start\":{\"line\":1,\"column\":1},\"end\":{\"line\":2,\"column\":4}},\"changeType\":\"ADDED\"},{\"category\":\"Method\",\"term\":\"bar\",\"span\":{\"start\":{\"line\":4,\"column\":1},\"end\":{\"line\":6,\"column\":4}},\"changeType\":\"MODIFIED\"},{\"category\":\"Method\",\"term\":\"baz\",\"span\":{\"start\":{\"line\":4,\"column\":1},\"end\":{\"line\":5,\"column\":4}},\"changeType\":\"REMOVED\"}]}]}\n" :: ByteString) runBuilder output `shouldBe` ("{\"files\":[{\"path\":\"test/fixtures/ruby/toc/methods.A.rb -> test/fixtures/ruby/toc/methods.B.rb\",\"language\":\"Ruby\",\"changes\":[{\"category\":\"Method\",\"term\":\"self.foo\",\"span\":{\"start\":{\"line\":1,\"column\":1},\"end\":{\"line\":2,\"column\":4}},\"changeType\":\"ADDED\"},{\"category\":\"Method\",\"term\":\"bar\",\"span\":{\"start\":{\"line\":4,\"column\":1},\"end\":{\"line\":6,\"column\":4}},\"changeType\":\"MODIFIED\"},{\"category\":\"Method\",\"term\":\"baz\",\"span\":{\"start\":{\"line\":4,\"column\":1},\"end\":{\"line\":5,\"column\":4}},\"changeType\":\"REMOVED\"}]}]}\n" :: ByteString)
it "produces JSON output if there are parse errors" $ do it "produces JSON output if there are parse errors" $ do
blobs <- blobsForPaths (Both (Path.relFile "ruby/toc/methods.A.rb") (Path.relFile "ruby/toc/methods.X.rb")) blobs <- blobsForPaths (Path.relFile "ruby/toc/methods.A.rb") (Path.relFile "ruby/toc/methods.X.rb")
output <- runTaskOrDie (runReader defaultLanguageModes (diffSummaryBuilder Format.JSON [blobs])) output <- runTaskOrDie (runReader defaultLanguageModes (diffSummaryBuilder Format.JSON [blobs]))
runBuilder output `shouldBe` ("{\"files\":[{\"path\":\"test/fixtures/ruby/toc/methods.A.rb -> test/fixtures/ruby/toc/methods.X.rb\",\"language\":\"Ruby\",\"changes\":[{\"category\":\"Method\",\"term\":\"bar\",\"span\":{\"start\":{\"line\":1,\"column\":1},\"end\":{\"line\":2,\"column\":4}},\"changeType\":\"REMOVED\"},{\"category\":\"Method\",\"term\":\"baz\",\"span\":{\"start\":{\"line\":4,\"column\":1},\"end\":{\"line\":5,\"column\":4}},\"changeType\":\"REMOVED\"}],\"errors\":[{\"error\":\"expected end of input nodes, but got ParseError\",\"span\":{\"start\":{\"line\":1,\"column\":1},\"end\":{\"line\":2,\"column\":3}}}]}]}\n" :: ByteString) runBuilder output `shouldBe` ("{\"files\":[{\"path\":\"test/fixtures/ruby/toc/methods.A.rb -> test/fixtures/ruby/toc/methods.X.rb\",\"language\":\"Ruby\",\"changes\":[{\"category\":\"Method\",\"term\":\"bar\",\"span\":{\"start\":{\"line\":1,\"column\":1},\"end\":{\"line\":2,\"column\":4}},\"changeType\":\"REMOVED\"},{\"category\":\"Method\",\"term\":\"baz\",\"span\":{\"start\":{\"line\":4,\"column\":1},\"end\":{\"line\":5,\"column\":4}},\"changeType\":\"REMOVED\"}],\"errors\":[{\"error\":\"expected end of input nodes, but got ParseError\",\"span\":{\"start\":{\"line\":1,\"column\":1},\"end\":{\"line\":2,\"column\":3}}}]}]}\n" :: ByteString)
it "ignores anonymous functions" $ do it "ignores anonymous functions" $ do
blobs <- blobsForPaths (Both (Path.relFile "ruby/toc/lambda.A.rb") (Path.relFile "ruby/toc/lambda.B.rb")) blobs <- blobsForPaths (Path.relFile "ruby/toc/lambda.A.rb") (Path.relFile "ruby/toc/lambda.B.rb")
output <- runTaskOrDie (runReader defaultLanguageModes (diffSummaryBuilder Format.JSON [blobs])) output <- runTaskOrDie (runReader defaultLanguageModes (diffSummaryBuilder Format.JSON [blobs]))
runBuilder output `shouldBe` ("{\"files\":[{\"path\":\"test/fixtures/ruby/toc/lambda.A.rb -> test/fixtures/ruby/toc/lambda.B.rb\",\"language\":\"Ruby\"}]}\n" :: ByteString) runBuilder output `shouldBe` ("{\"files\":[{\"path\":\"test/fixtures/ruby/toc/lambda.A.rb -> test/fixtures/ruby/toc/lambda.B.rb\",\"language\":\"Ruby\"}]}\n" :: ByteString)
it "summarizes Markdown headings" $ do it "summarizes Markdown headings" $ do
blobs <- blobsForPaths (Both (Path.relFile "markdown/toc/headings.A.md") (Path.relFile "markdown/toc/headings.B.md")) blobs <- blobsForPaths (Path.relFile "markdown/toc/headings.A.md") (Path.relFile "markdown/toc/headings.B.md")
output <- runTaskOrDie (runReader defaultLanguageModes (diffSummaryBuilder Format.JSON [blobs])) output <- runTaskOrDie (runReader defaultLanguageModes (diffSummaryBuilder Format.JSON [blobs]))
runBuilder output `shouldBe` ("{\"files\":[{\"path\":\"test/fixtures/markdown/toc/headings.A.md -> test/fixtures/markdown/toc/headings.B.md\",\"language\":\"Markdown\",\"changes\":[{\"category\":\"Heading 1\",\"term\":\"Introduction\",\"span\":{\"start\":{\"line\":1,\"column\":1},\"end\":{\"line\":3,\"column\":16}},\"changeType\":\"REMOVED\"},{\"category\":\"Heading 2\",\"term\":\"Two\",\"span\":{\"start\":{\"line\":5,\"column\":1},\"end\":{\"line\":7,\"column\":4}},\"changeType\":\"MODIFIED\"},{\"category\":\"Heading 3\",\"term\":\"This heading is new\",\"span\":{\"start\":{\"line\":9,\"column\":1},\"end\":{\"line\":11,\"column\":10}},\"changeType\":\"ADDED\"},{\"category\":\"Heading 1\",\"term\":\"Final\",\"span\":{\"start\":{\"line\":13,\"column\":1},\"end\":{\"line\":14,\"column\":4}},\"changeType\":\"ADDED\"}]}]}\n" :: ByteString) runBuilder output `shouldBe` ("{\"files\":[{\"path\":\"test/fixtures/markdown/toc/headings.A.md -> test/fixtures/markdown/toc/headings.B.md\",\"language\":\"Markdown\",\"changes\":[{\"category\":\"Heading 1\",\"term\":\"Introduction\",\"span\":{\"start\":{\"line\":1,\"column\":1},\"end\":{\"line\":3,\"column\":16}},\"changeType\":\"REMOVED\"},{\"category\":\"Heading 2\",\"term\":\"Two\",\"span\":{\"start\":{\"line\":5,\"column\":1},\"end\":{\"line\":7,\"column\":4}},\"changeType\":\"MODIFIED\"},{\"category\":\"Heading 3\",\"term\":\"This heading is new\",\"span\":{\"start\":{\"line\":9,\"column\":1},\"end\":{\"line\":11,\"column\":10}},\"changeType\":\"ADDED\"},{\"category\":\"Heading 1\",\"term\":\"Final\",\"span\":{\"start\":{\"line\":13,\"column\":1},\"end\":{\"line\":14,\"column\":4}},\"changeType\":\"ADDED\"}]}]}\n" :: ByteString)
@ -161,14 +160,14 @@ type Term' = Term ListableSyntax (Maybe Declaration)
numTocSummaries :: Diff' -> Int numTocSummaries :: Diff' -> Int
numTocSummaries diff = length $ filter isRight (diffTOC diff) numTocSummaries diff = length $ filter isRight (diffTOC diff)
-- Return a diff where body is inserted in the expressions of a function. The function is present in Both sides of the diff. -- Return a diff where body is inserted in the expressions of a function. The function is present in both sides of the diff.
programWithChange :: Term' -> Diff' programWithChange :: Term' -> Diff'
programWithChange body = merge (Nothing, Nothing) (inject [ function' ]) programWithChange body = merge (Nothing, Nothing) (inject [ function' ])
where where
function' = merge (Just (Declaration Function "foo" lowerBound Ruby), Just (Declaration Function "foo" lowerBound Ruby)) (inject (Declaration.Function [] name' [] (merge (Nothing, Nothing) (inject [ inserting body ])))) function' = merge (Just (Declaration Function "foo" lowerBound Ruby), Just (Declaration Function "foo" lowerBound Ruby)) (inject (Declaration.Function [] name' [] (merge (Nothing, Nothing) (inject [ inserting body ]))))
name' = merge (Nothing, Nothing) (inject (Syntax.Identifier (name "foo"))) name' = merge (Nothing, Nothing) (inject (Syntax.Identifier (name "foo")))
-- Return a diff where term is inserted in the program, below a function found on Both sides of the diff. -- Return a diff where term is inserted in the program, below a function found on both sides of the diff.
programWithChangeOutsideFunction :: Term' -> Diff' programWithChangeOutsideFunction :: Term' -> Diff'
programWithChangeOutsideFunction term = merge (Nothing, Nothing) (inject [ function', term' ]) programWithChangeOutsideFunction term = merge (Nothing, Nothing) (inject [ function', term' ])
where where
@ -183,7 +182,7 @@ programWithDelete :: Text -> Term' -> Diff'
programWithDelete name body = programOf $ deleting (functionOf name body) programWithDelete name body = programOf $ deleting (functionOf name body)
programWithReplace :: Text -> Term' -> Diff' programWithReplace :: Text -> Term' -> Diff'
programWithReplace name body = programOf $ replacing (functionOf name body) (functionOf (name <> "2") body) programWithReplace name body = programOf $ comparing (functionOf name body) (functionOf (name <> "2") body)
programOf :: Diff' -> Diff' programOf :: Diff' -> Diff'
programOf diff = merge (Nothing, Nothing) (inject [ diff ]) programOf diff = merge (Nothing, Nothing) (inject [ diff ])
@ -208,8 +207,9 @@ isMethodOrFunction a
| any isJust (foldMap (:[]) a) = True | any isJust (foldMap (:[]) a) = True
| otherwise = False | otherwise = False
blobsForPaths :: Both Path.RelFile -> IO BlobPair blobsForPaths :: Path.RelFile -> Path.RelFile -> IO BlobPair
blobsForPaths = readFilePathPair . fmap (Path.relDir "test/fixtures" </>) blobsForPaths p1 p2 = readFilePathPair (prefix p1) (prefix p2) where
prefix = (Path.relDir "test/fixtures" </>)
blankDiff :: Diff' blankDiff :: Diff'
blankDiff = merge (Nothing, Nothing) (inject [ inserting (termIn Nothing (inject (Syntax.Identifier (name "\"a\"")))) ]) blankDiff = merge (Nothing, Nothing) (inject [ inserting (termIn Nothing (inject (Syntax.Identifier (name "\"a\"")))) ])

View File

@ -34,7 +34,7 @@ renderDiff ref new = unsafePerformIO $ do
else ["git", "diff", ref, new] else ["git", "diff", ref, new]
{-# NOINLINE renderDiff #-} {-# NOINLINE renderDiff #-}
testForDiffFixture :: (String, [BlobPair] -> ParseC TaskC Builder, [Both File], Path.RelFile) -> TestTree testForDiffFixture :: (String, [BlobPair] -> ParseC TaskC Builder, [(File, File)], Path.RelFile) -> TestTree
testForDiffFixture (diffRenderer, runDiff, files, expected) = testForDiffFixture (diffRenderer, runDiff, files, expected) =
goldenVsStringDiff goldenVsStringDiff
("diff fixture renders to " <> diffRenderer <> " " <> show files) ("diff fixture renders to " <> diffRenderer <> " " <> show files)
@ -65,12 +65,12 @@ parseFixtures =
prefix = Path.relDir "test/fixtures/cli" prefix = Path.relDir "test/fixtures/cli"
run = runReader (PerLanguageModes ALaCarte) run = runReader (PerLanguageModes ALaCarte)
diffFixtures :: [(String, [BlobPair] -> ParseC TaskC Builder, [Both File], Path.RelFile)] diffFixtures :: [(String, [BlobPair] -> ParseC TaskC Builder, [(File, File)], Path.RelFile)]
diffFixtures = diffFixtures =
[ ("json diff", parseDiffBuilder DiffJSONTree, pathMode, prefix </> Path.file "diff-tree.json") [ ("json diff", parseDiffBuilder DiffJSONTree, pathMode, prefix </> Path.file "diff-tree.json")
, ("s-expression diff", parseDiffBuilder DiffSExpression, pathMode, Path.relFile "test/fixtures/ruby/corpus/method-declaration.diffA-B.txt") , ("s-expression diff", parseDiffBuilder DiffSExpression, pathMode, Path.relFile "test/fixtures/ruby/corpus/method-declaration.diffA-B.txt")
, ("toc summaries diff", runReader defaultLanguageModes . diffSummaryBuilder Serializing.Format.JSON, pathMode, prefix </> Path.file "diff-tree.toc.json") , ("toc summaries diff", runReader defaultLanguageModes . diffSummaryBuilder Serializing.Format.JSON, pathMode, prefix </> Path.file "diff-tree.toc.json")
, ("protobuf diff", runReader defaultLanguageModes . diffSummaryBuilder Serializing.Format.Proto, pathMode, prefix </> Path.file "diff-tree.toc.protobuf.bin") , ("protobuf diff", runReader defaultLanguageModes . diffSummaryBuilder Serializing.Format.Proto, pathMode, prefix </> Path.file "diff-tree.toc.protobuf.bin")
] ]
where pathMode = [Both (File "test/fixtures/ruby/corpus/method-declaration.A.rb" Ruby) (File "test/fixtures/ruby/corpus/method-declaration.B.rb" Ruby)] where pathMode = [(File "test/fixtures/ruby/corpus/method-declaration.A.rb" Ruby, File "test/fixtures/ruby/corpus/method-declaration.B.rb" Ruby)]
prefix = Path.relDir "test/fixtures/cli" prefix = Path.relDir "test/fixtures/cli"

View File

@ -4,86 +4,12 @@ module Semantic.IO.Spec (spec) where
import Prelude hiding (readFile) import Prelude hiding (readFile)
import Control.Monad.IO.Class
import Data.List
import Data.String
import qualified Data.Text as Text
import System.Directory
import System.IO.Temp
import Data.Blob import Data.Blob
import Data.Handle import Data.Handle
import qualified Semantic.Git as Git
import Shelly (cd, run_, shelly, silently, touchfile, writefile)
import qualified Source.Source as Source
import SpecHelpers import SpecHelpers
import System.Path ((</>))
import qualified System.Path as Path
makeGitRepo :: FilePath -> IO ()
makeGitRepo dir = shelly . silently $ do
cd (fromString dir)
let git = run_ "git"
git ["init"]
touchfile "bar.py"
writefile "日本語.rb" "# coding: utf-8\n日本語 = 'hello'"
git ["add", "日本語.rb", "bar.py"]
git ["config", "user.name", "'Test'"]
git ["config", "user.email", "'test@test.test'"]
git ["commit", "-am", "'test commit'"]
spec :: Spec spec :: Spec
spec = do spec = do
describe "catFile" $ do
hasGit <- runIO $ isJust <$> findExecutable "git"
when hasGit . it "should not corrupt the output of files with UTF-8 identifiers" $ do
result <- liftIO . withSystemTempDirectory "semantic-temp-git-repo" $ \dir -> do
makeGitRepo dir
trees <- Git.lsTree (dir <> "/.git") (Git.OID "HEAD")
Just it <- pure $ find (\p -> "日本語" `isInfixOf` Git.treeEntryPath p) trees
Git.catFile (dir <> "/.git") (Git.treeEntryOid it)
Source.toText result `shouldSatisfy` ("日本語" `Text.isInfixOf`)
describe "lsTree" $ do
hasGit <- runIO $ isJust <$> findExecutable "git"
when hasGit . it "should read all tree entries from a repo" $ do
items <- liftIO . withSystemTempDirectory "semantic-temp-git-repo" $ \dir -> do
makeGitRepo dir
Git.lsTree dir (Git.OID "HEAD")
length items `shouldBe` 2
describe "readBlobsFromGitRepo" $ do
hasGit <- runIO $ isJust <$> findExecutable "git"
when hasGit . it "should read from a git directory" $ do
-- This temporary directory will be cleaned after use.
blobs <- liftIO . withSystemTempDirectory "semantic-temp-git-repo" $ \dir -> do
makeGitRepo dir
readBlobsFromGitRepoPath (Path.absDir dir </> Path.relDir ".git") (Git.OID "HEAD") [] []
let files = sortOn fileLanguage (blobFile <$> blobs)
files `shouldBe` [ File "bar.py" Python
, File "日本語.rb" Ruby
]
when hasGit . it "should read from a git directory with --only" $ do
-- This temporary directory will be cleaned after use.
blobs <- liftIO . withSystemTempDirectory "semantic-temp-git-repo" $ \dir -> do
let pdir = Path.absDir dir
makeGitRepo dir
readBlobsFromGitRepoPath (pdir </> Path.relDir ".git") (Git.OID "HEAD") [] [Path.relFile "日本語.rb"]
let files = sortOn fileLanguage (blobFile <$> blobs)
files `shouldBe` [ File "日本語.rb" Ruby ]
when hasGit . it "should read from a git directory with --exclude" $ do
-- This temporary directory will be cleaned after use.
blobs <- liftIO . withSystemTempDirectory "semantic-temp-git-repo" $ \dir -> do
makeGitRepo dir
readBlobsFromGitRepoPath (Path.absDir dir </> Path.relDir ".git") (Git.OID "HEAD") [Path.relFile "日本語.rb"] []
let files = sortOn fileLanguage (blobFile <$> blobs)
files `shouldBe` [ File "bar.py" Python ]
describe "readFile" $ do describe "readFile" $ do
it "returns a blob for extant files" $ do it "returns a blob for extant files" $ do
Just blob <- readBlobFromFile (File "semantic.cabal" Unknown) Just blob <- readBlobFromFile (File "semantic.cabal" Unknown)
@ -97,34 +23,34 @@ spec = do
let b = sourceBlob "method.rb" Ruby "def bar(x); end" let b = sourceBlob "method.rb" Ruby "def bar(x); end"
it "returns blobs for valid JSON encoded diff input" $ do it "returns blobs for valid JSON encoded diff input" $ do
blobs <- blobsFromFilePath "test/fixtures/cli/diff.json" blobs <- blobsFromFilePath "test/fixtures/cli/diff.json"
blobs `shouldBe` [Diffing a b] blobs `shouldBe` [Compare a b]
it "returns blobs when there's no before" $ do it "returns blobs when there's no before" $ do
blobs <- blobsFromFilePath "test/fixtures/cli/diff-no-before.json" blobs <- blobsFromFilePath "test/fixtures/cli/diff-no-before.json"
blobs `shouldBe` [Inserting b] blobs `shouldBe` [Insert b]
it "returns blobs when there's null before" $ do it "returns blobs when there's null before" $ do
blobs <- blobsFromFilePath "test/fixtures/cli/diff-null-before.json" blobs <- blobsFromFilePath "test/fixtures/cli/diff-null-before.json"
blobs `shouldBe` [Inserting b] blobs `shouldBe` [Insert b]
it "returns blobs when there's no after" $ do it "returns blobs when there's no after" $ do
blobs <- blobsFromFilePath "test/fixtures/cli/diff-no-after.json" blobs <- blobsFromFilePath "test/fixtures/cli/diff-no-after.json"
blobs `shouldBe` [Deleting a] blobs `shouldBe` [Delete a]
it "returns blobs when there's null after" $ do it "returns blobs when there's null after" $ do
blobs <- blobsFromFilePath "test/fixtures/cli/diff-null-after.json" blobs <- blobsFromFilePath "test/fixtures/cli/diff-null-after.json"
blobs `shouldBe` [Deleting a] blobs `shouldBe` [Delete a]
it "returns blobs for unsupported language" $ do it "returns blobs for unsupported language" $ do
h <- openFileForReading "test/fixtures/cli/diff-unsupported-language.json" h <- openFileForReading "test/fixtures/cli/diff-unsupported-language.json"
blobs <- readBlobPairsFromHandle h blobs <- readBlobPairsFromHandle h
let b' = sourceBlob "test.kt" Unknown "fun main(args: Array<String>) {\nprintln(\"hi\")\n}\n" let b' = sourceBlob "test.kt" Unknown "fun main(args: Array<String>) {\nprintln(\"hi\")\n}\n"
blobs `shouldBe` [Inserting b'] blobs `shouldBe` [Insert b']
it "detects language based on filepath for empty language" $ do it "detects language based on filepath for empty language" $ do
blobs <- blobsFromFilePath "test/fixtures/cli/diff-empty-language.json" blobs <- blobsFromFilePath "test/fixtures/cli/diff-empty-language.json"
blobs `shouldBe` [Diffing a b] blobs `shouldBe` [Compare a b]
it "throws on blank input" $ do it "throws on blank input" $ do
h <- openFileForReading "test/fixtures/cli/blank.json" h <- openFileForReading "test/fixtures/cli/blank.json"

View File

@ -6,7 +6,6 @@ import SpecHelpers
import Data.Blob (NoLanguageForBlob (..)) import Data.Blob (NoLanguageForBlob (..))
import Semantic.Api hiding (Blob) import Semantic.Api hiding (Blob)
import Semantic.Git
-- we need some lenses here, oof -- we need some lenses here, oof
setBlobLanguage :: Language -> Blob -> Blob setBlobLanguage :: Language -> Blob -> Blob
@ -15,6 +14,8 @@ setBlobLanguage lang b = b { blobFile = (blobFile b) { fileLanguage = lang }}
spec :: Spec spec :: Spec
spec = do spec = do
describe "parseBlob" $ do describe "parseBlob" $ do
let methodsBlob = makeBlob "def foo\nend\n" "methods.rb" Ruby mempty
it "returns error if given an unknown language (json)" $ do it "returns error if given an unknown language (json)" $ do
output <- fmap runBuilder . runTaskOrDie . runReader (PerLanguageModes ALaCarte) $ parseTermBuilder TermJSONTree [ setBlobLanguage Unknown methodsBlob ] output <- fmap runBuilder . runTaskOrDie . runReader (PerLanguageModes ALaCarte) $ parseTermBuilder TermJSONTree [ setBlobLanguage Unknown methodsBlob ]
output `shouldBe` "{\"trees\":[{\"path\":\"methods.rb\",\"error\":\"NoLanguageForBlob \\\"methods.rb\\\"\",\"language\":\"Unknown\"}]}\n" output `shouldBe` "{\"trees\":[{\"path\":\"methods.rb\",\"error\":\"NoLanguageForBlob \\\"methods.rb\\\"\",\"language\":\"Unknown\"}]}\n"
@ -28,27 +29,3 @@ spec = do
it "renders with the specified renderer" $ do it "renders with the specified renderer" $ do
output <- fmap runBuilder . runTaskOrDie . runReader (PerLanguageModes ALaCarte) $ parseTermBuilder TermSExpression [methodsBlob] output <- fmap runBuilder . runTaskOrDie . runReader (PerLanguageModes ALaCarte) $ parseTermBuilder TermSExpression [methodsBlob]
output `shouldBe` "(Statements\n (Method\n (Empty)\n (Identifier)\n (Statements)))\n" output `shouldBe` "(Statements\n (Method\n (Empty)\n (Identifier)\n (Statements)))\n"
describe "git ls-tree parsing" $ do
it "parses a git output string" $ do
let input = "100644 tree abcdef\t/this/is/the/path"
let expected = Right $ TreeEntry NormalMode TreeObject (OID "abcdef") "/this/is/the/path"
parseEntry input `shouldBe` expected
it "allows whitespace in the path" $ do
let input = "100644 tree 12345\t/this\n/is\t/the /path\r"
let expected = Right $ TreeEntry NormalMode TreeObject (OID "12345") "/this\n/is\t/the /path\r"
parseEntry input `shouldBe` expected
it "parses many outputs separated by \\NUL" $ do
let input = "100644 tree abcdef\t/this/is/the/path\NUL120000 blob 17776\t/dev/urandom\NUL\n"
let expected = [ TreeEntry NormalMode TreeObject (OID "abcdef") "/this/is/the/path", TreeEntry SymlinkMode BlobObject (OID "17776") "/dev/urandom"]
parseEntries input `shouldBe` expected
it "parses submodules and other types" $ do
let input = "160000 commit 50865e8895c54037bf06c4c1691aa925d030a59d\tgemoji"
let expected = Right $ TreeEntry OtherMode OtherObjectType (OID "50865e8895c54037bf06c4c1691aa925d030a59d") "gemoji"
parseEntry input `shouldBe` expected
where
methodsBlob = makeBlob "def foo\nend\n" "methods.rb" Ruby mempty

View File

@ -60,7 +60,7 @@ import Source.Span as X hiding (HasSpan(..), start, end, point)
import Debug.Trace as X (traceShowM, traceM) import Debug.Trace as X (traceShowM, traceM)
import Data.ByteString as X (ByteString) import Data.ByteString as X (ByteString)
import Data.Functor.Both as X (Both (Both), runBothWith) import Data.Edit as X
import Data.Maybe as X import Data.Maybe as X
import Data.Monoid as X (Monoid(..), First(..), Last(..)) import Data.Monoid as X (Monoid(..), First(..), Last(..))
import Data.Semigroup as X (Semigroup(..)) import Data.Semigroup as X (Semigroup(..))
@ -87,11 +87,11 @@ instance IsString Name where
fromString = X.name . fromString fromString = X.name . fromString
-- | Returns an s-expression formatted diff for the specified FilePath pair. -- | Returns an s-expression formatted diff for the specified FilePath pair.
diffFilePaths :: TaskSession -> Both Path.RelFile -> IO ByteString diffFilePaths :: TaskSession -> Path.RelFile -> Path.RelFile -> IO ByteString
diffFilePaths session paths diffFilePaths session p1 p2 = do
= readFilePathPair paths blobs <- readFilePathPair p1 p2
>>= runTask session . runParse (configTreeSitterParseTimeout (config session)) . parseDiffBuilder @[] DiffSExpression . pure builder <- runTask session (runParse (configTreeSitterParseTimeout (config session)) (parseDiffBuilder DiffSExpression [ blobs ]))
>>= either (die . displayException) (pure . runBuilder) either (die . displayException) (pure . runBuilder) builder
-- | Returns an s-expression parse tree for the specified path. -- | Returns an s-expression parse tree for the specified path.
parseFilePath :: TaskSession -> Path.RelFile -> IO (Either SomeException ByteString) parseFilePath :: TaskSession -> Path.RelFile -> IO (Either SomeException ByteString)
@ -104,9 +104,8 @@ runParseWithConfig :: (Carrier sig m, Member (Reader Config) sig) => ParseC m a
runParseWithConfig task = asks configTreeSitterParseTimeout >>= \ timeout -> runParse timeout task runParseWithConfig task = asks configTreeSitterParseTimeout >>= \ timeout -> runParse timeout task
-- | Read two files to a BlobPair. -- | Read two files to a BlobPair.
readFilePathPair :: Both Path.RelFile -> IO BlobPair readFilePathPair :: Path.RelFile -> Path.RelFile -> IO BlobPair
readFilePathPair paths = let paths' = fmap fileForTypedPath paths in readFilePathPair p1 p2 = readFilePair (fileForTypedPath p1) (fileForTypedPath p2)
runBothWith readFilePair paths'
parseTestFile :: Parser term -> Path.RelFile -> IO (Blob, term) parseTestFile :: Parser term -> Path.RelFile -> IO (Blob, term)
parseTestFile parser path = runTaskOrDie $ do parseTestFile parser path = runTaskOrDie $ do