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:
commit
edd83d466f
29
.hlint.yaml
29
.hlint.yaml
@ -34,6 +34,10 @@
|
|||||||
# Change the severity of hints we don’t want to fail CI for
|
# Change the severity of hints we don’t 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
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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..=)
|
||||||
]
|
]
|
||||||
|
@ -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 }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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]
|
||||||
|
@ -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
|
||||||
|
@ -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
71
src/Data/Edit.hs
Normal 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
|
@ -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
|
|
@ -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
|
||||||
|
@ -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 }
|
||||||
|
|
||||||
|
@ -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)] ]
|
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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).
|
||||||
|
@ -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
|
||||||
|
@ -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 (!?) #-}
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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)
|
|
@ -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(..)
|
||||||
|
@ -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]
|
||||||
|
@ -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
|
||||||
|
@ -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(..)
|
||||||
|
@ -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(..)
|
||||||
|
@ -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"
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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 = ()
|
||||||
|
@ -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)
|
||||||
|
@ -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\"")))) ])
|
||||||
|
@ -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"
|
||||||
|
@ -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"
|
||||||
|
@ -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
|
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user