diff --git a/semantic-diff.cabal b/semantic-diff.cabal index e7b346e5b..a0bb8982c 100644 --- a/semantic-diff.cabal +++ b/semantic-diff.cabal @@ -25,7 +25,6 @@ library , Diff , Diff.Arbitrary , Diffing - , DiffOutput , Info , Interpreter , Language diff --git a/src/Alignment.hs b/src/Alignment.hs index c59cc7e5a..fdb61405c 100644 --- a/src/Alignment.hs +++ b/src/Alignment.hs @@ -23,7 +23,6 @@ import Diff import Info import Patch import Prologue hiding (fst, snd) -import qualified Prologue import Range import Source hiding (break, fromList, uncons, (++)) import SplitDiff @@ -43,11 +42,11 @@ hasChanges :: SplitDiff leaf annotation -> Bool hasChanges = or . (True <$) -- | Align a Diff into a list of Join These SplitDiffs representing the (possibly blank) lines on either side. -alignDiff :: (Show leaf, Show (Record fields), HasField fields Range) => Both (Source Char) -> Diff leaf (Record fields) -> [Join These (SplitDiff leaf (Record fields))] -alignDiff sources diff = iter (alignSyntax (runBothWith ((Join .) . These)) (free . Free) getRange sources) (alignPatch sources <$> diff) +alignDiff :: HasField fields Range => Both (Source Char) -> Diff leaf (Record fields) -> [Join These (SplitDiff leaf (Record fields))] +alignDiff sources diff = iter (alignSyntax (runBothWith ((Join .) . These)) wrap getRange sources) (alignPatch sources <$> diff) -- | Align the contents of a patch into a list of lines on the corresponding side(s) of the diff. -alignPatch :: forall fields leaf. (Show leaf, Show (Record fields), HasField fields Range) => Both (Source Char) -> Patch (Term leaf (Record fields)) -> [Join These (SplitDiff leaf (Record fields))] +alignPatch :: forall fields leaf. HasField fields Range => Both (Source Char) -> Patch (Term leaf (Record fields)) -> [Join These (SplitDiff leaf (Record fields))] alignPatch sources patch = case patch of Delete term -> fmap (pure . SplitDelete) <$> alignSyntax' this (fst sources) term Insert term -> fmap (pure . SplitInsert) <$> alignSyntax' that (snd sources) term @@ -61,7 +60,7 @@ alignPatch sources patch = case patch of that = Join . That . runIdentity -- | The Applicative instance f is either Identity or Both. Identity is for Terms in Patches, Both is for Diffs in unchanged portions of the diff. -alignSyntax :: (Applicative f, Show term, HasField fields Range) => (forall a. f a -> Join These a) -> (CofreeF (Syntax leaf) (Record fields) term -> term) -> (term -> Range) -> f (Source Char) -> CofreeF (Syntax leaf) (f (Record fields)) [Join These term] -> [Join These term] +alignSyntax :: (Applicative f, HasField fields Range) => (forall a. f a -> Join These a) -> (CofreeF (Syntax leaf) (Record fields) term -> term) -> (term -> Range) -> f (Source Char) -> CofreeF (Syntax leaf) (f (Record fields)) [Join These term] -> [Join These term] alignSyntax toJoinThese toNode getRange sources (infos :< syntax) = case syntax of Leaf s -> catMaybes $ wrapInBranch (const (Leaf s)) . fmap (flip (,) []) <$> sequenceL lineRanges Comment a -> catMaybes $ wrapInBranch (const (Comment a)) . fmap (flip (,) []) <$> sequenceL lineRanges @@ -99,10 +98,9 @@ alignSyntax toJoinThese toNode getRange sources (infos :< syntax) = case syntax where bothRanges = modifyJoin (fromThese [] []) lineRanges lineRanges = toJoinThese $ actualLineRanges <$> (characterRange <$> infos) <*> sources wrapInBranch constructor = applyThese $ toJoinThese ((\ info (range, children) -> toNode (setCharacterRange info range :< constructor children)) <$> infos) - pairWithKey (key, values) = fmap ((,) key) <$> values -- | Given a function to get the range, a list of already-aligned children, and the lists of ranges spanned by a branch, return the aligned lines. -alignBranch :: Show term => (term -> Range) -> [Join These term] -> Both [Range] -> [Join These (Range, [term])] +alignBranch :: (term -> Range) -> [Join These term] -> Both [Range] -> [Join These (Range, [term])] -- There are no more ranges, so we’re done. alignBranch _ _ (Join ([], [])) = [] -- There are no more children, so we can just zip the remaining ranges together. diff --git a/src/Data/RandomWalkSimilarity.hs b/src/Data/RandomWalkSimilarity.hs index 08bb4c2ee..6f95a90bf 100644 --- a/src/Data/RandomWalkSimilarity.hs +++ b/src/Data/RandomWalkSimilarity.hs @@ -1,24 +1,33 @@ +{-# LANGUAGE RankNTypes #-} module Data.RandomWalkSimilarity where import Control.Arrow ((&&&)) import Control.Monad.Random import Control.Monad.State import qualified Data.DList as DList +import Data.Functor.Both hiding (fst, snd) import Data.Functor.Foldable as Foldable import Data.Hashable import qualified Data.KdTree.Static as KdTree import qualified Data.List as List import qualified Data.Vector as Vector -import Diff import Patch import Prologue -import Syntax -import Term +import Term () import Test.QuickCheck hiding (Fixed) import Test.QuickCheck.Random --- | Given a function comparing two terms recursively, and a function to compute a Hashable label from an annotation, compute the diff of a pair of lists of terms using a random walk similarity metric, which completes in log-linear time. This implementation is based on the paper [_RWS-Diff—Flexible and Efficient Change Detection in Hierarchical Data_](https://github.com/github/semantic-diff/files/325837/RWS-Diff.Flexible.and.Efficient.Change.Detection.in.Hierarchical.Data.pdf). -rws :: (Hashable label, Hashable leaf, Eq leaf, Eq annotation) => (Term leaf annotation -> Term leaf annotation -> Maybe (Diff leaf annotation)) -> (annotation -> label) -> [Term leaf annotation] -> [Term leaf annotation] -> [Diff leaf annotation] +-- | Given a function comparing two terms recursively, and a function to compute a Hashable label from an unpacked term, compute the diff of a pair of lists of terms using a random walk similarity metric, which completes in log-linear time. This implementation is based on the paper [_RWS-Diff—Flexible and Efficient Change Detection in Hierarchical Data_](https://github.com/github/semantic-diff/files/325837/RWS-Diff.Flexible.and.Efficient.Change.Detection.in.Hierarchical.Data.pdf). +rws :: (Hashable label, Eq annotation, Prologue.Foldable f, Functor f, Eq (f (Cofree f annotation))) => + -- | A function which comapres a pair of terms recursively, returning 'Just' their diffed value if appropriate, or 'Nothing' if they should not be compared. + (Cofree f annotation -> Cofree f annotation -> Maybe (Free (CofreeF f (Both annotation)) (Patch (Cofree f annotation)))) -> + -- | A function to compute a label for an unpacked term. + (forall b. CofreeF f annotation b -> label) -> + -- | The old list of terms. + [Cofree f annotation] -> + -- | The new list of terms. + [Cofree f annotation] -> + [Free (CofreeF f (Both annotation)) (Patch (Cofree f annotation))] rws compare getLabel as bs | null as, null bs = [] | null as = insert <$> bs @@ -44,29 +53,24 @@ rws compare getLabel as bs deleteRemaining diffs (_, unmapped) = foldl' (flip (List.insertBy (comparing fst))) diffs ((termIndex &&& delete . term) <$> unmapped) -- | A term which has not yet been mapped by `rws`, along with its feature vector summary & index. -data UnmappedTerm leaf annotation = UnmappedTerm { termIndex :: {-# UNPACK #-} !Int, feature :: !(Vector.Vector Double), term :: !(Term leaf annotation) } +data UnmappedTerm a = UnmappedTerm { termIndex :: {-# UNPACK #-} !Int, feature :: !(Vector.Vector Double), term :: !a } deriving Eq + -- | A `Gram` is a fixed-size view of some portion of a tree, consisting of a `stem` of _p_ labels for parent nodes, and a `base` of _q_ labels of sibling nodes. Collectively, the bag of `Gram`s for each node of a tree (e.g. as computed by `pqGrams`) form a summary of the tree. data Gram label = Gram { stem :: [Maybe label], base :: [Maybe label] } deriving (Eq, Show) -- | Compute the bag of grams with stems of length _p_ and bases of length _q_, with labels computed from annotations, which summarize the entire subtree of a term. -pqGrams :: Int -> Int -> (annotation -> label) -> Cofree (Syntax leaf) annotation -> DList.DList (Gram (label, Maybe leaf)) -pqGrams p q getLabel = cata merge . setRootBase . setRootStem . hylo go project - where go (annotation :< functor) = cofree (Gram [] [ Just (getLabel annotation, leafValue functor) ] :< (assignParent (Just (getLabel annotation, leafValue functor)) p <$> functor)) - leafValue (Leaf s) = Just s - leafValue _ = Nothing - merge (head :< tail) = DList.cons head (Prologue.fold tail) +pqGrams :: (Prologue.Foldable f, Functor f) => Int -> Int -> (forall b. CofreeF f annotation b -> label) -> Cofree f annotation -> DList.DList (Gram label) +pqGrams p q getLabel = uncurry DList.cons . cata merge . setRootBase . setRootStem . cata go + where go c = cofree (Gram [] [ Just (getLabel c) ] :< (assignParent (Just (getLabel c)) p <$> tailF c)) + merge (head :< tail) = let tail' = toList tail in (head, DList.fromList (windowed q setBases [] (fst <$> tail')) <> foldMap snd tail') assignParent parentLabel n tree - | n > 0 = let gram :< functor = runCofree tree in cofree $ prependParent parentLabel gram :< assignSiblings (assignParent parentLabel (pred n) <$> functor) + | n > 0 = let gram :< functor = runCofree tree in cofree $ prependParent parentLabel gram :< (assignParent parentLabel (pred n) <$> functor) | otherwise = tree prependParent parentLabel gram = gram { stem = parentLabel : stem gram } - assignSiblings functor = case functor of - Leaf a -> Leaf a - Indexed a -> Indexed $ windowed q setBases [] a - Fixed a -> Fixed $ windowed q setBases [] a - setBases child siblings rest = let (gram :< further) = (runCofree child) in cofree (setBase gram (siblings >>= base . extract) :< further) : rest + setBases gram siblings rest = setBase gram (siblings >>= base) : rest setBase gram newBase = gram { base = take q (newBase <> repeat Nothing) } setRootBase term = let (a :< f) = runCofree term in cofree (setBase a (base a) :< f) setRootStem = foldr (\ p rest -> assignParent Nothing p . rest) identity [0..p] diff --git a/src/DiffOutput.hs b/src/DiffOutput.hs deleted file mode 100644 index 026786e2d..000000000 --- a/src/DiffOutput.hs +++ /dev/null @@ -1,48 +0,0 @@ -module DiffOutput where - -import Prologue -import qualified Data.Text.IO as TextIO -import Data.Functor.Both -import Diffing -import Parser -import qualified Renderer.JSON as J -import qualified Renderer.Patch as P -import qualified Renderer.Summary as S -import Renderer -import Renderer.Split -import Source -import System.Directory -import System.FilePath -import qualified System.IO as IO - --- | Returns a rendered diff given a parser, diff arguments and two source blobs. -textDiff :: Parser -> DiffArguments -> Both SourceBlob -> IO Text -textDiff parser arguments sources = case format arguments of - Split -> diffFiles parser split sources - Patch -> diffFiles parser P.patch sources - JSON -> diffFiles parser J.json sources - Summary -> diffFiles parser S.summary sources - --- | Returns a truncated diff given diff arguments and two source blobs. -truncatedDiff :: DiffArguments -> Both SourceBlob -> IO Text -truncatedDiff arguments sources = case format arguments of - Split -> pure "" - Patch -> pure $ P.truncatePatch arguments sources - JSON -> pure "{}" - Summary -> pure "" - --- | Prints a rendered diff to stdio or a filepath given a parser, diff arguments and two source blobs. -printDiff :: Parser -> DiffArguments -> Both SourceBlob -> IO () -printDiff parser arguments sources = case format arguments of - Split -> put (output arguments) =<< diffFiles parser split sources - where - put Nothing rendered = TextIO.putStr rendered - put (Just path) rendered = do - isDir <- doesDirectoryExist path - let outputPath = if isDir - then path (takeFileName outputPath -<.> ".html") - else path - IO.withFile outputPath IO.WriteMode (`TextIO.hPutStr` rendered) - Patch -> TextIO.putStr =<< diffFiles parser P.patch sources - JSON -> TextIO.putStr =<< diffFiles parser J.json sources - Summary -> TextIO.putStr =<< diffFiles parser S.summary sources diff --git a/src/Diffing.hs b/src/Diffing.hs index cc813c00f..5e0b0725e 100644 --- a/src/Diffing.hs +++ b/src/Diffing.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE DataKinds #-} module Diffing where import Prologue hiding (fst, snd) @@ -5,7 +6,7 @@ import qualified Data.ByteString.Char8 as B1 import Data.Functor.Both import Data.Functor.Foldable import Data.Record -import qualified Data.Text as T +import qualified Data.Text.IO as TextIO import qualified Data.Text.ICU.Detect as Detect import qualified Data.Text.ICU.Convert as Convert import Data.These @@ -18,15 +19,22 @@ import Parser import Patch import Range import Renderer +import Renderer.JSON +import Renderer.Patch +import Renderer.Split +import Renderer.Summary import Source hiding ((++)) import Syntax +import System.Directory import System.FilePath +import qualified System.IO as IO import Term import TreeSitter import Text.Parser.TreeSitter.Language +import qualified Data.Text as T -- | Return a parser based on the file extension (including the "."). -parserForType :: T.Text -> Parser +parserForType :: Text -> Parser '[Range, Category, Cost] parserForType mediaType = case languageForType mediaType of Just C -> treeSitterParser C ts_language_c Just JavaScript -> treeSitterParser JavaScript ts_language_javascript @@ -34,35 +42,33 @@ parserForType mediaType = case languageForType mediaType of _ -> lineByLineParser -- | A fallback parser that treats a file simply as rows of strings. -lineByLineParser :: Parser +lineByLineParser :: Parser '[Range, Category, Cost] lineByLineParser input = pure . cofree . root $ case foldl' annotateLeaves ([], 0) lines of (leaves, _) -> cofree <$> leaves where lines = actualLines input - root children = let size = 1 + fromIntegral (length children) in - ((Range 0 $ length input) .: Other "program" .: size .: Cost (unSize size) .: RNil) :< Indexed children - leaf charIndex line = ((Range charIndex $ charIndex + T.length line) .: Other "program" .: 1 .: 1 .: RNil) :< Leaf line + root children = let cost = 1 + fromIntegral (length children) in + ((Range 0 $ length input) .: Other "program" .: cost .: RNil) :< Indexed children + leaf charIndex line = ((Range charIndex $ charIndex + T.length line) .: Other "program" .: 1 .: RNil) :< Leaf line annotateLeaves (accum, charIndex) line = - (accum ++ [ leaf charIndex (toText line) ] - , charIndex + length line) + (accum <> [ leaf charIndex (toText line) ] , charIndex + length line) toText = T.pack . Source.toString -- | Return the parser that should be used for a given path. -parserForFilepath :: FilePath -> Parser -parserForFilepath = parserForType . T.pack . takeExtension +parserForFilepath :: FilePath -> Parser '[Range, Category, Cost] +parserForFilepath = parserForType . toS . takeExtension -- | Replace every string leaf with leaves of the words in the string. -breakDownLeavesByWord :: Source Char -> Term T.Text Info -> Term T.Text Info +breakDownLeavesByWord :: (HasField fields Category, HasField fields Cost, HasField fields Range) => Source Char -> Term Text (Record fields) -> Term Text (Record fields) breakDownLeavesByWord source = cata replaceIn where - replaceIn :: TermF T.Text Info (Term T.Text Info) -> Term T.Text Info - replaceIn (info :< syntax) = let size' = 1 + sum (size . extract <$> syntax') in cofree $ setCost (setSize info size') (Cost (unSize size')) :< syntax' + replaceIn (info :< syntax) = let cost' = 1 + sum (cost . extract <$> syntax') in cofree $ setCost info cost' :< syntax' where syntax' = case (ranges, syntax) of (_:_:_, Leaf _) | category info /= Regex -> Indexed (makeLeaf info <$> ranges) _ -> syntax ranges = rangesAndWordsInSource (characterRange info) rangesAndWordsInSource range = rangesAndWordsFrom (start range) (toString $ slice range source) - makeLeaf info (range, substring) = cofree $ setCharacterRange info range :< Leaf (T.pack substring) + makeLeaf info (range, substring) = cofree $ setCharacterRange info range :< Leaf (toS substring) -- | Transcode a file to a unicode source. transcode :: B1.ByteString -> IO (Source Char) @@ -81,7 +87,7 @@ readAndTranscodeFile path = do -- | result. -- | Returns the rendered result strictly, so it's always fully evaluated -- | with respect to other IO actions. -diffFiles :: Parser -> Renderer -> Both SourceBlob -> IO T.Text +diffFiles :: (HasField fields Category, HasField fields Cost, HasField fields Range, Eq (Record fields)) => Parser fields -> Renderer (Record fields) -> Both SourceBlob -> IO Text diffFiles parser renderer sourceBlobs = do let sources = source <$> sourceBlobs terms <- sequence $ parser <$> sources @@ -92,11 +98,10 @@ diffFiles parser renderer sourceBlobs = do (True, False) -> pure $ Insert (snd terms) (False, True) -> pure $ Delete (fst terms) (_, _) -> - runBothWith (diffTerms construct shouldCompareTerms diffCostWithCachedTermSizes) $ replaceLeaves <*> terms + runBothWith (diffTerms construct shouldCompareTerms diffCostWithCachedTermCosts) (replaceLeaves <*> terms) pure $! renderer textDiff sourceBlobs - where construct :: CofreeF (Syntax Text) (Both Info) (Diff Text Info) -> Diff Text Info - construct (info :< syntax) = free (Free ((setCost <$> info <*> sumCost syntax) :< syntax)) + where construct (info :< syntax) = free (Free ((setCost <$> info <*> sumCost syntax) :< syntax)) sumCost = fmap getSum . foldMap (fmap Sum . getCost) getCost diff = case runFree diff of Free (info :< _) -> cost <$> info @@ -104,7 +109,37 @@ diffFiles parser renderer sourceBlobs = do shouldCompareTerms = (==) `on` category . extract -- | The sum of the node count of the diff’s patches. -diffCostWithCachedTermSizes :: Diff a Info -> Integer -diffCostWithCachedTermSizes diff = unCost $ case runFree diff of +diffCostWithCachedTermCosts :: HasField fields Cost => Diff leaf (Record fields) -> Integer +diffCostWithCachedTermCosts diff = unCost $ case runFree diff of Free (info :< _) -> sum (cost <$> info) Pure patch -> sum (cost . extract <$> patch) + + +-- | Returns a rendered diff given a parser, diff arguments and two source blobs. +textDiff :: (Eq (Record fields), HasField fields Category, HasField fields Cost, HasField fields Range) => Parser fields -> DiffArguments -> Both SourceBlob -> IO Text +textDiff parser arguments sources = case format arguments of + Split -> diffFiles parser split sources + Patch -> diffFiles parser patch sources + JSON -> diffFiles parser json sources + Summary -> diffFiles parser summary sources + +-- | Returns a truncated diff given diff arguments and two source blobs. +truncatedDiff :: DiffArguments -> Both SourceBlob -> IO Text +truncatedDiff arguments sources = case format arguments of + Split -> pure "" + Patch -> pure $ truncatePatch arguments sources + JSON -> pure "{}" + Summary -> pure "" + +-- | Prints a rendered diff to stdio or a filepath given a parser, diff arguments and two source blobs. +printDiff :: (Eq (Record fields), HasField fields Category, HasField fields Cost, HasField fields Range) => Parser fields -> DiffArguments -> Both SourceBlob -> IO () +printDiff parser arguments sources = do + rendered <- textDiff parser arguments sources + case (output arguments) of + Nothing -> TextIO.putStr rendered + Just path -> do + isDir <- doesDirectoryExist path + let outputPath = if isDir + then path (takeFileName outputPath -<.> ".html") + else path + IO.withFile outputPath IO.WriteMode (`TextIO.hPutStr` rendered) diff --git a/src/Info.hs b/src/Info.hs index 22f59cfd2..5c7b8223d 100644 --- a/src/Info.hs +++ b/src/Info.hs @@ -7,15 +7,9 @@ import Category import Range import Test.QuickCheck -newtype Size = Size { unSize :: Integer } - deriving (Eq, Num, Ord, Show) newtype Cost = Cost { unCost :: Integer } deriving (Eq, Num, Ord, Show) -type InfoFields = '[ Range, Category, Size, Cost ] - -type Info = Record InfoFields - characterRange :: HasField fields Range => Record fields -> Range characterRange = getField @@ -28,12 +22,6 @@ category = getField setCategory :: HasField fields Category => Record fields -> Category -> Record fields setCategory = setField -size :: HasField fields Size => Record fields -> Size -size = getField - -setSize :: HasField fields Size => Record fields -> Size -> Record fields -setSize = setField - cost :: HasField fields Cost => Record fields -> Cost cost = getField @@ -43,11 +31,6 @@ setCost = setField -- Instances -instance Arbitrary Size where - arbitrary = Size <$> arbitrary - - shrink = fmap Size . shrink . unSize - instance Arbitrary Cost where arbitrary = Cost <$> arbitrary diff --git a/src/Interpreter.hs b/src/Interpreter.hs index 15d8b0148..3ffec2810 100644 --- a/src/Interpreter.hs +++ b/src/Interpreter.hs @@ -1,8 +1,7 @@ module Interpreter (Comparable, DiffConstructor, diffTerms) where import Algorithm -import qualified Category as C -import Data.Align +import Category (Category) import Data.Align.Generic import Data.Functor.Foldable import Data.Functor.Both hiding (fst, snd) @@ -26,11 +25,11 @@ type Comparable leaf annotation = Term leaf annotation -> Term leaf annotation - type DiffConstructor leaf annotation = CofreeF (Syntax leaf) (Both annotation) (Diff leaf annotation) -> Diff leaf annotation -- | Diff two terms, given a function that determines whether two terms can be compared and a cost function. -diffTerms :: (Show leaf, Show (Record fields), Eq leaf, Hashable leaf, Ord (Record fields), HasField fields C.Category) => DiffConstructor leaf (Record fields) -> Comparable leaf (Record fields) -> SES.Cost (Diff leaf (Record fields)) -> Term leaf (Record fields) -> Term leaf (Record fields) -> Diff leaf (Record fields) +diffTerms :: (Eq leaf, Hashable leaf, Eq (Record fields), HasField fields Category) => DiffConstructor leaf (Record fields) -> Comparable leaf (Record fields) -> SES.Cost (Diff leaf (Record fields)) -> Term leaf (Record fields) -> Term leaf (Record fields) -> Diff leaf (Record fields) diffTerms construct comparable cost a b = fromMaybe (pure $ Replace a b) $ constructAndRun construct comparable cost a b -- | Constructs an algorithm and runs it -constructAndRun :: (Show leaf, Show (Record fields), Eq leaf, Hashable leaf, Ord (Record fields), HasField fields C.Category) => DiffConstructor leaf (Record fields) -> Comparable leaf (Record fields) -> SES.Cost (Diff leaf (Record fields)) -> Term leaf (Record fields) -> Term leaf (Record fields) -> Maybe (Diff leaf (Record fields)) +constructAndRun :: (Eq leaf, Hashable leaf, Eq (Record fields), HasField fields Category) => DiffConstructor leaf (Record fields) -> Comparable leaf (Record fields) -> SES.Cost (Diff leaf (Record fields)) -> Term leaf (Record fields) -> Term leaf (Record fields) -> Maybe (Diff leaf (Record fields)) constructAndRun construct comparable cost t1 t2 | not $ comparable t1 t2 = Nothing | (category <$> t1) == (category <$> t2) = hylo construct runCofree <$> zipTerms t1 t2 @@ -43,17 +42,18 @@ constructAndRun construct comparable cost t1 t2 annotate = pure . construct . (both annotation1 annotation2 :<) -- | Runs the diff algorithm -run :: (Show leaf, Show (Record fields), Eq leaf, Hashable leaf, Ord (Record fields), HasField fields C.Category) => DiffConstructor leaf (Record fields) -> Comparable leaf (Record fields) -> SES.Cost (Diff leaf (Record fields)) -> Algorithm leaf (Record fields) (Diff leaf (Record fields)) -> Maybe (Diff leaf (Record fields)) +run :: (Eq leaf, Hashable leaf, Eq (Record fields), HasField fields Category) => DiffConstructor leaf (Record fields) -> Comparable leaf (Record fields) -> SES.Cost (Diff leaf (Record fields)) -> Algorithm leaf (Record fields) (Diff leaf (Record fields)) -> Maybe (Diff leaf (Record fields)) run construct comparable cost algorithm = case runFree algorithm of Pure diff -> Just diff Free (Recursive t1 t2 f) -> run construct comparable cost . f $ recur a b where (annotation1 :< a, annotation2 :< b) = (runCofree t1, runCofree t2) annotate = construct . (both annotation1 annotation2 :<) - diffTerms' = diffTerms construct comparable cost recur a b = maybe (pure (Replace t1 t2)) (annotate . fmap diffThese) (galign a b) diffThese = these (pure . Delete) (pure . Insert) (diffTerms construct comparable cost) Free (ByIndex a b f) -> run construct comparable cost . f $ ses (constructAndRun construct comparable cost) cost a b Free (ByRandomWalkSimilarity a b f) -> run construct comparable cost . f $ rws (constructAndRun construct comparable cost) getLabel a b - where getLabel = category + where getLabel (h :< t) = (category h, case t of + Leaf s -> Just s + _ -> Nothing) diff --git a/src/Parser.hs b/src/Parser.hs index 38f68a63a..9a1f8b64c 100644 --- a/src/Parser.hs +++ b/src/Parser.hs @@ -1,9 +1,12 @@ +{-# LANGUAGE RankNTypes #-} module Parser where import Prologue hiding (Constructor) +import Data.Record import Data.Text (pack) import Category import Info +import Range import qualified Syntax as S import Term import qualified Data.Set as Set @@ -12,10 +15,7 @@ import Source -- | A function that takes a source file and returns an annotated AST. -- | The return is in the IO monad because some of the parsers are written in C -- | and aren't pure. -type Parser = Source Char -> IO (Term Text Info) - --- | A function which constructs a term from a source string, annotation, and children. -type Constructor = Source Char -> Info -> [Term Text Info] -> Term Text Info +type Parser fields = Source Char -> IO (Term Text (Record fields)) -- | Categories that are treated as fixed nodes. fixedCategories :: Set.Set Category @@ -27,11 +27,10 @@ isFixed = flip Set.member fixedCategories -- | Given a function that maps production names to sets of categories, produce -- | a Constructor. -termConstructor :: Constructor +termConstructor :: (Show (Record fields), HasField fields Category, HasField fields Range) => Source Char -> (Record fields) -> [Term Text (Record fields)] -> Term Text (Record fields) termConstructor source info = cofree . construct where withDefaultInfo syntax = (info :< syntax) - construct :: [Term Text Info] -> CofreeF (S.Syntax Text) Info (Term Text Info) construct [] = withDefaultInfo . S.Leaf . pack . toString $ slice (characterRange info) source construct children | Assignment == category info = case children of (identifier:value:[]) -> withDefaultInfo $ S.Assignment identifier value @@ -66,7 +65,7 @@ termConstructor source info = cofree . construct , [x, y] <- children = withDefaultInfo $ S.VarAssignment x y construct children | VarDecl == category info = withDefaultInfo . S.Indexed $ toVarDecl <$> children where - toVarDecl :: Term Text Info -> Term Text Info + toVarDecl :: (HasField fields Category) => Term Text (Record fields) -> Term Text (Record fields) toVarDecl child = cofree $ (setCategory (extract child) VarDecl :< S.VarDecl child) construct children | Switch == category info, (expr:_) <- children = @@ -77,7 +76,7 @@ termConstructor source info = cofree . construct construct children | Object == category info = withDefaultInfo . S.Object $ foldMap toTuple children where - toTuple :: Term Text Info -> [Term Text Info] + toTuple :: Term Text (Record fields) -> [Term Text (Record fields)] toTuple child | S.Indexed [key,value] <- unwrap child = [cofree (extract child :< S.Pair key value)] toTuple child | S.Fixed [key,value] <- unwrap child = [cofree (extract child :< S.Pair key value)] toTuple child | S.Leaf c <- unwrap child = [cofree (extract child :< S.Comment c)] diff --git a/src/Renderer.hs b/src/Renderer.hs index 2d8e8efed..7696016b2 100644 --- a/src/Renderer.hs +++ b/src/Renderer.hs @@ -3,11 +3,10 @@ module Renderer where import Prologue import Data.Functor.Both import Diff -import Info import Source -- | A function that will render a diff, given the two source files. -type Renderer = Diff Text Info -> Both SourceBlob -> Text +type Renderer annotation = Diff Text annotation -> Both SourceBlob -> Text data DiffArguments = DiffArguments { format :: Format, output :: Maybe FilePath, outputPath :: FilePath } deriving (Show) diff --git a/src/Renderer/JSON.hs b/src/Renderer/JSON.hs index 7fbccc3d4..a2ad3cb81 100644 --- a/src/Renderer/JSON.hs +++ b/src/Renderer/JSON.hs @@ -10,6 +10,7 @@ import Category import Data.Aeson hiding (json) import Data.Bifunctor.Join import Data.ByteString.Builder +import Data.Record import qualified Data.Text as T import Data.These import Data.Vector hiding (toList) @@ -22,13 +23,13 @@ import Syntax as S import Term -- | Render a diff to a string representing its JSON. -json :: Renderer +json :: (HasField fields Category, HasField fields Range) => Renderer (Record fields) json diff sources = toS . toLazyByteString . fromEncoding . pairs $ "rows" .= annotateRows (alignDiff (source <$> sources) diff) <> "oids" .= (oid <$> sources) <> "paths" .= (path <$> sources) where annotateRows = fmap (fmap NumberedLine) . numberedRows newtype NumberedLine a = NumberedLine (Int, a) -instance ToJSON (NumberedLine (SplitDiff leaf Info)) where +instance (HasField fields Category, HasField fields Range) => ToJSON (NumberedLine (SplitDiff leaf (Record fields))) where toJSON (NumberedLine (n, a)) = object (lineFields n a (getRange a)) toEncoding (NumberedLine (n, a)) = pairs $ mconcat (lineFields n a (getRange a)) instance ToJSON Category where @@ -43,25 +44,25 @@ instance ToJSON a => ToJSON (Join These a) where instance ToJSON a => ToJSON (Join (,) a) where toJSON (Join (a, b)) = Array . fromList $ toJSON <$> [ a, b ] toEncoding = foldable -instance ToJSON (SplitDiff leaf Info) where +instance (HasField fields Category, HasField fields Range) => ToJSON (SplitDiff leaf (Record fields)) where toJSON splitDiff = case runFree splitDiff of (Free (info :< syntax)) -> object (termFields info syntax) (Pure patch) -> object (patchFields patch) toEncoding splitDiff = case runFree splitDiff of (Free (info :< syntax)) -> pairs $ mconcat (termFields info syntax) (Pure patch) -> pairs $ mconcat (patchFields patch) -instance ToJSON (Term leaf Info) where +instance (HasField fields Category, HasField fields Range) => ToJSON (Term leaf (Record fields)) where toJSON term | (info :< syntax) <- runCofree term = object (termFields info syntax) toEncoding term | (info :< syntax) <- runCofree term = pairs $ mconcat (termFields info syntax) -lineFields :: KeyValue kv => Int -> SplitDiff leaf Info -> Range -> [kv] +lineFields :: (HasField fields Category, HasField fields Range) => KeyValue kv => Int -> SplitDiff leaf (Record fields) -> Range -> [kv] lineFields n term range = [ "number" .= n , "terms" .= [ term ] , "range" .= range , "hasChanges" .= hasChanges term ] -termFields :: (ToJSON recur, KeyValue kv) => Info -> Syntax leaf recur -> [kv] +termFields :: (ToJSON recur, KeyValue kv, HasField fields Category, HasField fields Range) => Record fields -> Syntax leaf recur -> [kv] termFields info syntax = "range" .= characterRange info : "category" .= category info : case syntax of Leaf _ -> [] Indexed c -> childrenFields c @@ -86,7 +87,7 @@ termFields info syntax = "range" .= characterRange info : "category" .= category S.Commented comments child -> childrenFields (comments <> maybeToList child) where childrenFields c = [ "children" .= c ] -patchFields :: KeyValue kv => SplitPatch (Term leaf Info) -> [kv] +patchFields :: (KeyValue kv, HasField fields Category, HasField fields Range) => SplitPatch (Term leaf (Record fields)) -> [kv] patchFields patch = case patch of SplitInsert term -> fields "insert" term SplitDelete term -> fields "delete" term diff --git a/src/Renderer/Patch.hs b/src/Renderer/Patch.hs index d97a0800d..0246f4013 100644 --- a/src/Renderer/Patch.hs +++ b/src/Renderer/Patch.hs @@ -9,27 +9,28 @@ import Alignment import Data.Bifunctor.Join import Data.Functor.Both as Both import Data.List (span, unzip) +import Data.Record import Data.String import Data.Text (pack) import Data.These import Diff -import Info import Patch import Prologue hiding (fst, snd) +import Range import Renderer -import Source hiding ((++), break) +import Source hiding (break) import SplitDiff -- | Render a timed out file as a truncated diff. truncatePatch :: DiffArguments -> Both SourceBlob -> Text -truncatePatch _ blobs = pack $ header blobs ++ "#timed_out\nTruncating diff: timeout reached.\n" +truncatePatch _ blobs = pack $ header blobs <> "#timed_out\nTruncating diff: timeout reached.\n" -- | Render a diff in the traditional patch format. -patch :: Renderer +patch :: HasField fields Range => Renderer (Record fields) patch diff blobs = pack $ case getLast (foldMap (Last . Just) string) of - Just c | c /= '\n' -> string ++ "\n\\ No newline at end of file\n" + Just c | c /= '\n' -> string <> "\n\\ No newline at end of file\n" _ -> string - where string = header blobs ++ mconcat (showHunk blobs <$> hunks diff blobs) + where string = header blobs <> mconcat (showHunk blobs <$> hunks diff blobs) -- | A hunk in a patch, including the offset, changes, and context. data Hunk a = Hunk { offset :: Both (Sum Int), changes :: [Change a], trailingContext :: [Join These a] } @@ -52,65 +53,65 @@ rowIncrement :: Join These a -> Both (Sum Int) rowIncrement = Join . fromThese (Sum 0) (Sum 0) . runJoin . (Sum 1 <$) -- | Given the before and after sources, render a hunk to a string. -showHunk :: Both SourceBlob -> Hunk (SplitDiff a Info) -> String -showHunk blobs hunk = maybeOffsetHeader ++ - concat (showChange sources <$> changes hunk) ++ +showHunk :: HasField fields Range => Both SourceBlob -> Hunk (SplitDiff a (Record fields)) -> String +showHunk blobs hunk = maybeOffsetHeader <> + concat (showChange sources <$> changes hunk) <> showLines (snd sources) ' ' (maybeSnd . runJoin <$> trailingContext hunk) where sources = source <$> blobs maybeOffsetHeader = if lengthA > 0 && lengthB > 0 then offsetHeader else mempty - offsetHeader = "@@ -" ++ offsetA ++ "," ++ show lengthA ++ " +" ++ offsetB ++ "," ++ show lengthB ++ " @@" ++ "\n" + offsetHeader = "@@ -" <> offsetA <> "," <> show lengthA <> " +" <> offsetB <> "," <> show lengthB <> " @@" <> "\n" (lengthA, lengthB) = runJoin . fmap getSum $ hunkLength hunk (offsetA, offsetB) = runJoin . fmap (show . getSum) $ offset hunk -- | Given the before and after sources, render a change to a string. -showChange :: Both (Source Char) -> Change (SplitDiff a Info) -> String -showChange sources change = showLines (snd sources) ' ' (maybeSnd . runJoin <$> context change) ++ deleted ++ inserted +showChange :: HasField fields Range => Both (Source Char) -> Change (SplitDiff a (Record fields)) -> String +showChange sources change = showLines (snd sources) ' ' (maybeSnd . runJoin <$> context change) <> deleted <> inserted where (deleted, inserted) = runJoin $ pure showLines <*> sources <*> both '-' '+' <*> Join (unzip (fromThese Nothing Nothing . runJoin . fmap Just <$> contents change)) -- | Given a source, render a set of lines to a string with a prefix. -showLines :: Source Char -> Char -> [Maybe (SplitDiff leaf Info)] -> String +showLines :: HasField fields Range => Source Char -> Char -> [Maybe (SplitDiff leaf (Record fields))] -> String showLines source prefix lines = fromMaybe "" . mconcat $ fmap prepend . showLine source <$> lines where prepend "" = "" prepend source = prefix : source -- | Given a source, render a line to a string. -showLine :: Source Char -> Maybe (SplitDiff leaf Info) -> Maybe String +showLine :: HasField fields Range => Source Char -> Maybe (SplitDiff leaf (Record fields)) -> Maybe String showLine source line | Just line <- line = Just . toString . (`slice` source) $ getRange line | otherwise = Nothing -- | Returns the header given two source blobs and a hunk. header :: Both SourceBlob -> String -header blobs = intercalate "\n" [filepathHeader, fileModeHeader, beforeFilepath, afterFilepath] ++ "\n" - where filepathHeader = "diff --git a/" ++ pathA ++ " b/" ++ pathB +header blobs = intercalate "\n" [filepathHeader, fileModeHeader, beforeFilepath, afterFilepath] <> "\n" + where filepathHeader = "diff --git a/" <> pathA <> " b/" <> pathB fileModeHeader = case (modeA, modeB) of - (Nothing, Just mode) -> intercalate "\n" [ "new file mode " ++ modeToDigits mode, blobOidHeader ] - (Just mode, Nothing) -> intercalate "\n" [ "deleted file mode " ++ modeToDigits mode, blobOidHeader ] - (Just mode, Just other) | mode == other -> "index " ++ oidA ++ ".." ++ oidB ++ " " ++ modeToDigits mode + (Nothing, Just mode) -> intercalate "\n" [ "new file mode " <> modeToDigits mode, blobOidHeader ] + (Just mode, Nothing) -> intercalate "\n" [ "deleted file mode " <> modeToDigits mode, blobOidHeader ] + (Just mode, Just other) | mode == other -> "index " <> oidA <> ".." <> oidB <> " " <> modeToDigits mode (Just mode1, Just mode2) -> intercalate "\n" [ - "old mode " ++ modeToDigits mode1, - "new mode " ++ modeToDigits mode2, + "old mode " <> modeToDigits mode1, + "new mode " <> modeToDigits mode2, blobOidHeader ] (Nothing, Nothing) -> "" - blobOidHeader = "index " ++ oidA ++ ".." ++ oidB + blobOidHeader = "index " <> oidA <> ".." <> oidB modeHeader :: String -> Maybe SourceKind -> String -> String modeHeader ty maybeMode path = case maybeMode of - Just _ -> ty ++ "/" ++ path + Just _ -> ty <> "/" <> path Nothing -> "/dev/null" - beforeFilepath = "--- " ++ modeHeader "a" modeA pathA - afterFilepath = "+++ " ++ modeHeader "b" modeB pathB + beforeFilepath = "--- " <> modeHeader "a" modeA pathA + afterFilepath = "+++ " <> modeHeader "b" modeB pathB (pathA, pathB) = runJoin $ path <$> blobs (oidA, oidB) = runJoin $ oid <$> blobs (modeA, modeB) = runJoin $ blobKind <$> blobs -- | A hunk representing no changes. -emptyHunk :: Hunk (SplitDiff a Info) +emptyHunk :: Hunk (SplitDiff a annotation) emptyHunk = Hunk { offset = mempty, changes = [], trailingContext = [] } -- | Render a diff as a series of hunks. -hunks :: Show a => Diff a Info -> Both SourceBlob -> [Hunk (SplitDiff a Info)] +hunks :: HasField fields Range => Diff a (Record fields) -> Both SourceBlob -> [Hunk (SplitDiff a (Record fields))] hunks _ blobs | sources <- source <$> blobs , sourcesEqual <- runBothWith (==) sources , sourcesNull <- runBothWith (&&) (null <$> sources) @@ -120,14 +121,14 @@ hunks diff blobs = hunksInRows (pure 1) $ alignDiff (source <$> blobs) diff -- | Given beginning line numbers, turn rows in a split diff into hunks in a -- | patch. -hunksInRows :: Both (Sum Int) -> [Join These (SplitDiff a Info)] -> [Hunk (SplitDiff a Info)] +hunksInRows :: Both (Sum Int) -> [Join These (SplitDiff a annotation)] -> [Hunk (SplitDiff a annotation)] hunksInRows start rows = case nextHunk start rows of Nothing -> [] Just (hunk, rest) -> hunk : hunksInRows (offset hunk <> hunkLength hunk) rest -- | Given beginning line numbers, return the next hunk and the remaining rows -- | of the split diff. -nextHunk :: Both (Sum Int) -> [Join These (SplitDiff a Info)] -> Maybe (Hunk (SplitDiff a Info), [Join These (SplitDiff a Info)]) +nextHunk :: Both (Sum Int) -> [Join These (SplitDiff a annotation)] -> Maybe (Hunk (SplitDiff a annotation), [Join These (SplitDiff a annotation)]) nextHunk start rows = case nextChange start rows of Nothing -> Nothing Just (offset, change, rest) -> let (changes, rest') = contiguousChanges rest in Just (Hunk offset (change : changes) $ take 3 rest', drop 3 rest') @@ -139,7 +140,7 @@ nextHunk start rows = case nextChange start rows of -- | Given beginning line numbers, return the number of lines to the next -- | the next change, and the remaining rows of the split diff. -nextChange :: Both (Sum Int) -> [Join These (SplitDiff a Info)] -> Maybe (Both (Sum Int), Change (SplitDiff a Info), [Join These (SplitDiff a Info)]) +nextChange :: Both (Sum Int) -> [Join These (SplitDiff a annotation)] -> Maybe (Both (Sum Int), Change (SplitDiff a annotation), [Join These (SplitDiff a annotation)]) nextChange start rows = case changeIncludingContext leadingContext afterLeadingContext of Nothing -> Nothing Just (change, afterChanges) -> Just (start <> mconcat (rowIncrement <$> skippedContext), change, afterChanges) @@ -149,12 +150,12 @@ nextChange start rows = case changeIncludingContext leadingContext afterLeadingC -- | Return a Change with the given context and the rows from the begginning of -- | the given rows that have changes, or Nothing if the first row has no -- | changes. -changeIncludingContext :: [Join These (SplitDiff a Info)] -> [Join These (SplitDiff a Info)] -> Maybe (Change (SplitDiff a Info), [Join These (SplitDiff a Info)]) +changeIncludingContext :: [Join These (SplitDiff a annotation)] -> [Join These (SplitDiff a annotation)] -> Maybe (Change (SplitDiff a annotation), [Join These (SplitDiff a annotation)]) changeIncludingContext leadingContext rows = case changes of [] -> Nothing _ -> Just (Change leadingContext changes, afterChanges) where (changes, afterChanges) = span rowHasChanges rows -- | Whether a row has changes on either side. -rowHasChanges :: Join These (SplitDiff a Info) -> Bool +rowHasChanges :: Join These (SplitDiff a annotation) -> Bool rowHasChanges row = or (hasChanges <$> row) diff --git a/src/Renderer/Split.hs b/src/Renderer/Split.hs index 8436357be..fe67726fa 100644 --- a/src/Renderer/Split.hs +++ b/src/Renderer/Split.hs @@ -1,5 +1,4 @@ -{-# LANGUAGE OverloadedStrings #-} -module Renderer.Split where +module Renderer.Split (split) where import Alignment import Category as C @@ -7,6 +6,7 @@ import Data.Bifunctor.Join import Data.Foldable import Data.Functor.Both import Data.Functor.Foldable (cata) +import Data.Record import qualified Data.Text.Lazy as TL import Data.These import Info @@ -14,7 +14,7 @@ import Prologue hiding (div, head, fst, snd, link) import qualified Prologue import Range import Renderer -import Source hiding ((++)) +import Source import SplitDiff import Syntax import Term @@ -24,10 +24,6 @@ import Text.Blaze.Html5 hiding (map) import qualified Text.Blaze.Html5.Attributes as A import qualified Text.Blaze.Internal as Blaze --- | Return the first item in the Foldable, or Nothing if it's empty. -maybeFirst :: Foldable f => f a -> Maybe a -maybeFirst = foldr (const . Just) Nothing - -- | Add the first category from a Foldable of categories as a class name as a -- | class name on the markup, prefixed by `category-`. classifyMarkup :: Category -> Markup -> Markup @@ -70,13 +66,13 @@ styleName category = "category-" <> case category of -- | Pick the class name for a split patch. splitPatchToClassName :: SplitPatch a -> AttributeValue -splitPatchToClassName patch = stringValue $ "patch " ++ case patch of +splitPatchToClassName patch = stringValue $ "patch " <> case patch of SplitInsert _ -> "insert" SplitDelete _ -> "delete" SplitReplace _ -> "replace" -- | Render a diff as an HTML split diff. -split :: Renderer +split :: (HasField fields Category, HasField fields Cost, HasField fields Range) => Renderer (Record fields) split diff blobs = TL.toStrict . renderHtml . docTypeHtml . ((head $ link ! A.rel "stylesheet" ! A.href "style.css") <>) @@ -99,50 +95,19 @@ split diff blobs = TL.toStrict . renderHtml columnWidth = max (20 + digits maxNumber * 8) 40 -- | Render a line with numbers as an HTML row. - numberedLinesToMarkup :: Join These (Int, SplitDiff a Info) -> Markup numberedLinesToMarkup numberedLines = tr $ runBothWith (<>) (renderLine <$> Join (fromThese Nothing Nothing (runJoin (Just <$> numberedLines))) <*> sources) <> string "\n" - renderLine :: Maybe (Int, SplitDiff leaf Info) -> Source Char -> Markup - renderLine (Just (number, line)) source = toMarkup $ Renderable (hasChanges line, number, Renderable (source, line)) + renderLine (Just (number, line)) source = toMarkup $ Cell (hasChanges line) number (Renderable source line) renderLine _ _ = td mempty ! A.class_ (stringValue "blob-num blob-num-empty empty-cell") <> td mempty ! A.class_ (stringValue "blob-code blob-code-empty empty-cell") <> string "\n" --- | Something that can be rendered as markup. -newtype Renderable a = Renderable a +-- | A cell in a table, characterized by whether it contains changes & its line number. +data Cell a = Cell !Bool !Int !a -instance ToMarkup f => ToMarkup (Renderable (Source Char, Info, Syntax a (f, Range))) where - toMarkup (Renderable (source, info, syntax)) = (! A.data_ (textValue (show . unSize $ size info))) . classifyMarkup (category info) $ case syntax of - Leaf _ -> span . text . toText $ slice (characterRange info) source - Indexed children -> ul . mconcat $ wrapIn li <$> contentElements source (characterRange info) children - Fixed children -> ul . mconcat $ wrapIn li <$> contentElements source (characterRange info) children - Comment _ -> span . text . toText $ slice (characterRange info) source - Commented cs child -> ul . mconcat $ wrapIn li <$> contentElements source (characterRange info) (cs <> maybeToList child) - Syntax.FunctionCall identifier children -> dl . mconcat $ (wrapIn dt <$> (contentElements source (characterRange info) [identifier])) <> (wrapIn dd <$> contentElements source (characterRange info) children) - Syntax.Function identifier params expressions -> ul . mconcat $ wrapIn li <$> - contentElements source (characterRange info) (catMaybes [identifier, params, Just expressions]) - Syntax.MethodCall targetId methodId methodParams -> - dl . mconcat $ (wrapIn dt <$> (contentElements source (characterRange info) [targetId])) <> (wrapIn dd <$> contentElements source (characterRange info) [methodId, methodParams]) - Syntax.Args children -> ul . mconcat $ wrapIn li <$> - contentElements source (characterRange info) children - Syntax.MemberAccess memberId property -> ul . mconcat $ wrapIn li <$> - contentElements source (characterRange info) [memberId, property] - Syntax.Assignment memberId value -> ul . mconcat $ wrapIn li <$> - contentElements source (characterRange info) [memberId, value] - Syntax.VarDecl decl -> ul . mconcat $ wrapIn li <$> contentElements source (characterRange info) [decl] - Syntax.VarAssignment varId value -> - dl . mconcat $ (wrapIn dt <$> (contentElements source (characterRange info) [varId])) <> (wrapIn dd <$> contentElements source (characterRange info) [value]) - Syntax.Switch expr cases -> ul . mconcat $ wrapIn li <$> contentElements source (characterRange info) (expr : cases) - Syntax.Case expr body -> ul . mconcat $ wrapIn li <$> contentElements source (characterRange info) [expr, body] - Syntax.Ternary expr cases -> ul . mconcat $ wrapIn li <$> contentElements source (characterRange info) (expr : cases) - Syntax.MathAssignment id value -> - dl . mconcat $ (wrapIn dt <$> (contentElements source (characterRange info) [id])) <> (wrapIn dd <$> contentElements source (characterRange info) [value]) - Syntax.SubscriptAccess target property -> - dl . mconcat $ (wrapIn dt <$> (contentElements source (characterRange info) [target])) <> (wrapIn dd <$> contentElements source (characterRange info) [property]) - Syntax.Operator syntaxes -> ul . mconcat $ wrapIn li <$> contentElements source (characterRange info) syntaxes - Syntax.Object children -> ul . mconcat $ wrapIn li <$> contentElements source (characterRange info) children - Syntax.Pair a b -> ul . mconcat $ wrapIn li <$> contentElements source (characterRange info) [a, b] +-- | Something that can be rendered as markup with reference to some source. +data Renderable a = Renderable !(Source Char) !a contentElements :: (Foldable t, ToMarkup f) => Source Char -> Range -> t (f, Range) -> [Markup] contentElements source range children = let (elements, next) = foldr' (markupForContextAndChild source) ([], end range) children in @@ -158,17 +123,27 @@ wrapIn _ l@Blaze.Content{} = l wrapIn _ l@Blaze.Comment{} = l wrapIn f p = f p -instance ToMarkup (Renderable (Source Char, Term a Info)) where - toMarkup (Renderable (source, term)) = Prologue.fst $ cata (\ (info :< syntax) -> (toMarkup $ Renderable (source, info, syntax), characterRange info)) term -instance ToMarkup (Renderable (Source Char, SplitDiff a Info)) where - toMarkup (Renderable (source, diff)) = Prologue.fst . iter (\ (info :< syntax) -> (toMarkup $ Renderable (source, info, syntax), characterRange info)) $ toMarkupAndRange <$> diff - where toMarkupAndRange :: SplitPatch (Term a Info) -> (Markup, Range) - toMarkupAndRange patch = let term@(info :< _) = runCofree $ getSplitTerm patch in - ((div ! A.class_ (splitPatchToClassName patch) ! A.data_ (stringValue (show (unSize (size info))))) . toMarkup $ Renderable (source, cofree term), characterRange info) +-- Instances -instance ToMarkup a => ToMarkup (Renderable (Bool, Int, a)) where - toMarkup (Renderable (hasChanges, num, line)) = +instance (ToMarkup f, HasField fields Category, HasField fields Cost, HasField fields Range) => ToMarkup (Renderable (CofreeF (Syntax leaf) (Record fields) (f, Range))) where + toMarkup (Renderable source (info :< syntax)) = classifyMarkup (category info) $ case syntax of + Leaf _ -> span . string . toString $ slice (characterRange info) source + _ -> ul . mconcat $ wrapIn li <$> contentElements source (characterRange info) (toList syntax) + +instance (HasField fields Category, HasField fields Cost, HasField fields Range) => ToMarkup (Renderable (Term leaf (Record fields))) where + toMarkup (Renderable source term) = Prologue.fst $ cata (\ t -> (toMarkup $ Renderable source t, characterRange (headF t))) term + +instance (HasField fields Category, HasField fields Cost, HasField fields Range) => ToMarkup (Renderable (SplitDiff leaf (Record fields))) where + toMarkup (Renderable source diff) = Prologue.fst . iter (\ t -> (toMarkup $ Renderable source t, characterRange (headF t))) $ toMarkupAndRange <$> diff + where toMarkupAndRange patch = let term@(info :< _) = runCofree $ getSplitTerm patch in + ((div ! patchAttribute patch `withCostAttribute` cost info) . toMarkup $ Renderable source (cofree term), characterRange info) + patchAttribute patch = A.class_ (splitPatchToClassName patch) + withCostAttribute a (Cost c) | c > 0 = a ! A.data_ (stringValue (show c)) + | otherwise = identity + +instance ToMarkup a => ToMarkup (Cell a) where + toMarkup (Cell hasChanges num line) = td (string $ show num) ! A.class_ (stringValue $ if hasChanges then "blob-num blob-num-replacement" else "blob-num") <> td (toMarkup line) ! A.class_ (stringValue $ if hasChanges then "blob-code blob-code-replacement" else "blob-code") <> string "\n" diff --git a/src/Renderer/Summary.hs b/src/Renderer/Summary.hs index 4536e9d2c..708e331e0 100644 --- a/src/Renderer/Summary.hs +++ b/src/Renderer/Summary.hs @@ -1,11 +1,14 @@ module Renderer.Summary where +import Category import Prologue import Renderer -import DiffSummary import Data.Aeson +import Data.Record +import Range +import DiffSummary import Data.Text (pack) import Text.PrettyPrint.Leijen.Text (pretty) -summary :: Renderer +summary :: (HasField fields Category, HasField fields Range) => Renderer (Record fields) summary diff _ = toS . encode $ pack . show . pretty <$> diffSummary diff diff --git a/src/TreeSitter.hs b/src/TreeSitter.hs index cd2279230..a567817fc 100644 --- a/src/TreeSitter.hs +++ b/src/TreeSitter.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE DataKinds #-} module TreeSitter where import Prologue hiding (Constructor) @@ -14,7 +15,7 @@ import Text.Parser.TreeSitter hiding (Language(..)) import qualified Text.Parser.TreeSitter as TS -- | Returns a TreeSitter parser for the given language and TreeSitter grammar. -treeSitterParser :: Language -> Ptr TS.Language -> Parser +treeSitterParser :: Language -> Ptr TS.Language -> Parser '[Range, Category, Cost] treeSitterParser language grammar contents = do document <- ts_document_make ts_document_set_language document grammar @@ -75,7 +76,7 @@ defaultCategoryForNodeName name = case name of _ -> Other name -- | Return a parser for a tree sitter language & document. -documentToTerm :: Language -> Ptr Document -> Parser +documentToTerm :: Language -> Ptr Document -> Parser '[Range, Category, Cost] documentToTerm language document contents = alloca $ \ root -> do ts_document_root_node_p document root toTerm root @@ -87,8 +88,8 @@ documentToTerm language document contents = alloca $ \ root -> do -- Note: The strict application here is semantically important. Without it, we may not evaluate the range until after we’ve exited the scope that `node` was allocated within, meaning `alloca` will free it & other stack data may overwrite it. range <- pure $! Range { start = fromIntegral $ ts_node_p_start_char node, end = fromIntegral $ ts_node_p_end_char node } - let size' = 1 + sum (size . extract <$> children) - let info = range .: (categoriesForLanguage language (toS name)) .: size' .: Cost (unSize size') .: RNil + let cost' = 1 + sum (cost . extract <$> children) + let info = range .: (categoriesForLanguage language (toS name)) .: cost' .: RNil pure $! termConstructor contents info children getChild node n out = do _ <- ts_node_p_named_child node n out diff --git a/test/AlignmentSpec.hs b/test/AlignmentSpec.hs index 13730d8a2..57b267355 100644 --- a/test/AlignmentSpec.hs +++ b/test/AlignmentSpec.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE DataKinds #-} module AlignmentSpec where import Alignment @@ -14,7 +15,6 @@ import Data.Record import Data.String import Data.Text.Arbitrary () import Data.These -import Info import Patch import Prologue hiding (fst, snd) import qualified Prologue @@ -22,7 +22,6 @@ import Range import qualified Source import SplitDiff import Syntax -import Category import Term import Test.Hspec import Test.Hspec.QuickCheck @@ -32,7 +31,7 @@ spec :: Spec spec = parallel $ do describe "alignBranch" $ do it "produces symmetrical context" $ - alignBranch getRange ([] :: [Join These (SplitDiff String Info)]) (both [Range 0 2, Range 2 4] [Range 0 2, Range 2 4]) `shouldBe` + alignBranch getRange ([] :: [Join These (SplitDiff String (Record '[Range]))]) (both [Range 0 2, Range 2 4] [Range 0 2, Range 2 4]) `shouldBe` [ Join (These (Range 0 2, []) (Range 0 2, [])) , Join (These (Range 2 4, []) @@ -40,7 +39,7 @@ spec = parallel $ do ] it "produces asymmetrical context" $ - alignBranch getRange ([] :: [Join These (SplitDiff String Info)]) (both [Range 0 2, Range 2 4] [Range 0 1]) `shouldBe` + alignBranch getRange ([] :: [Join These (SplitDiff String (Record '[Range]))]) (both [Range 0 2, Range 2 4] [Range 0 1]) `shouldBe` [ Join (These (Range 0 2, []) (Range 0 1, [])) , Join (This (Range 2 4, [])) @@ -254,13 +253,13 @@ instance Arbitrary BranchElement where counts :: [Join These (Int, a)] -> Both Int counts numbered = fromMaybe 0 . getLast . mconcat . fmap Last <$> Join (unalign (runJoin . fmap Prologue.fst <$> numbered)) -align :: Both (Source.Source Char) -> ConstructibleFree (Patch (Term String Info)) (Both Info) -> PrettyDiff (SplitDiff String Info) +align :: Both (Source.Source Char) -> ConstructibleFree (Patch (Term String (Record '[Range]))) (Both (Record '[Range])) -> PrettyDiff (SplitDiff String (Record '[Range])) align sources = PrettyDiff sources . fmap (fmap (getRange &&& identity)) . alignDiff sources . deconstruct -info :: Int -> Int -> Info -info start end = Range start end .: StringLiteral .: 0 .: 0 .: RNil +info :: Int -> Int -> Record '[Range] +info start end = Range start end .: RNil -prettyDiff :: Both (Source.Source Char) -> [Join These (ConstructibleFree (SplitPatch (Term String Info)) Info)] -> PrettyDiff (SplitDiff String Info) +prettyDiff :: Both (Source.Source Char) -> [Join These (ConstructibleFree (SplitPatch (Term String (Record '[Range]))) (Record '[Range]))] -> PrettyDiff (SplitDiff String (Record '[Range])) prettyDiff sources = PrettyDiff sources . fmap (fmap ((getRange &&& identity) . deconstruct)) data PrettyDiff a = PrettyDiff { unPrettySources :: Both (Source.Source Char), unPrettyLines :: [Join These (Range, a)] } @@ -279,14 +278,14 @@ newtype ConstructibleFree patch annotation = ConstructibleFree { deconstruct :: class PatchConstructible p where - insert :: Term String Info -> p - delete :: Term String Info -> p + insert :: Term String (Record '[Range]) -> p + delete :: Term String (Record '[Range]) -> p -instance PatchConstructible (Patch (Term String Info)) where +instance PatchConstructible (Patch (Term String (Record '[Range]))) where insert = Insert delete = Delete -instance PatchConstructible (SplitPatch (Term String Info)) where +instance PatchConstructible (SplitPatch (Term String (Record '[Range]))) where insert = SplitInsert delete = SplitDelete diff --git a/test/CorpusSpec.hs b/test/CorpusSpec.hs index 57459ac38..1b10f015d 100644 --- a/test/CorpusSpec.hs +++ b/test/CorpusSpec.hs @@ -1,21 +1,24 @@ -{-# LANGUAGE GeneralizedNewtypeDeriving #-} +{-# LANGUAGE DataKinds, FlexibleContexts, GeneralizedNewtypeDeriving #-} module CorpusSpec where import System.IO -import Data.String import Diffing import Renderer import qualified Renderer.JSON as J import qualified Renderer.Patch as P import qualified Renderer.Split as Split +import Category import Control.DeepSeq import Data.Functor.Both import Data.List as List import Data.Map as Map +import Data.Record import Data.Set as Set import qualified Data.Text as T +import Info import Prologue hiding (fst, snd) +import Range import qualified Source as S import System.FilePath import System.FilePath.Glob @@ -39,10 +42,8 @@ spec = parallel $ do let tests = correctTests =<< paths traverse_ (\ (formatName, renderer, paths, output) -> it (normalizeName (fst paths) ++ " (" ++ formatName ++ ")") $ testDiff renderer paths output matcher) tests - correctTests :: (Both FilePath, Maybe FilePath, Maybe FilePath, Maybe FilePath) -> [(String, Renderer, Both FilePath, Maybe FilePath)] correctTests paths@(_, Nothing, Nothing, Nothing) = testsForPaths paths correctTests paths = List.filter (\(_, _, _, output) -> isJust output) $ testsForPaths paths - testsForPaths :: (Both FilePath, Maybe FilePath, Maybe FilePath, Maybe FilePath) -> [(String, Renderer, Both FilePath, Maybe FilePath)] testsForPaths (paths, json, patch, split) = [ ("json", J.json, paths, json), ("patch", P.patch, paths, patch), ("split", Split.split, paths, split) ] -- | Return all the examples from the given directory. Examples are expected to @@ -69,7 +70,7 @@ normalizeName path = addExtension (dropExtension $ dropExtension path) (takeExte -- | Given file paths for A, B, and, optionally, a diff, return whether diffing -- | the files will produce the diff. If no diff is provided, then the result -- | is true, but the diff will still be calculated. -testDiff :: Renderer -> Both FilePath -> Maybe FilePath -> (Verbatim -> Verbatim -> Expectation) -> Expectation +testDiff :: Renderer (Record '[Range, Category, Cost]) -> Both FilePath -> Maybe FilePath -> (Verbatim -> Verbatim -> Expectation) -> Expectation testDiff renderer paths diff matcher = do sources <- sequence $ readAndTranscodeFile <$> paths actual <- Verbatim <$> diffFiles parser renderer (sourceBlobs sources) diff --git a/test/Data/RandomWalkSimilarity/Spec.hs b/test/Data/RandomWalkSimilarity/Spec.hs index 288e16d3b..97074107b 100644 --- a/test/Data/RandomWalkSimilarity/Spec.hs +++ b/test/Data/RandomWalkSimilarity/Spec.hs @@ -17,10 +17,10 @@ spec :: Spec spec = parallel $ do describe "pqGrams" $ do prop "produces grams with stems of the specified length" . forAll (arbitrary `suchThat` (\ (_, p, q) -> p > 0 && q > 0)) $ - \ (term, p, q) -> pqGrams p q identity (toTerm term :: Term Text Text) `shouldSatisfy` all ((== p) . length . stem) + \ (term, p, q) -> pqGrams p q headF (toTerm term :: Term Text Text) `shouldSatisfy` all ((== p) . length . stem) prop "produces grams with bases of the specified length" . forAll (arbitrary `suchThat` (\ (_, p, q) -> p > 0 && q > 0)) $ - \ (term, p, q) -> pqGrams p q identity (toTerm term :: Term Text Text) `shouldSatisfy` all ((== q) . length . base) + \ (term, p, q) -> pqGrams p q headF (toTerm term :: Term Text Text) `shouldSatisfy` all ((== q) . length . base) describe "featureVector" $ do prop "produces a vector of the specified dimension" . forAll (arbitrary `suchThat` ((> 0) . Prologue.snd)) $ @@ -31,5 +31,5 @@ spec = parallel $ do prop "produces correct diffs" . forAll (scale (`div` 4) arbitrary) $ \ (as, bs) -> let tas = toTerm <$> as tbs = toTerm <$> bs - diff = free (Free (pure Program :< Indexed (rws compare identity tas tbs :: [Diff Text Category]))) in + diff = free (Free (pure Program :< Indexed (rws compare headF tas tbs :: [Diff Text Category]))) in (beforeTerm diff, afterTerm diff) `shouldBe` (Just (cofree (Program :< Indexed tas)), Just (cofree (Program :< Indexed tbs))) diff --git a/test/DiffSummarySpec.hs b/test/DiffSummarySpec.hs index f381d66ba..cdf8cc1e5 100644 --- a/test/DiffSummarySpec.hs +++ b/test/DiffSummarySpec.hs @@ -6,23 +6,21 @@ import Data.Record import Test.Hspec import Test.Hspec.QuickCheck import Diff -import Info -import Interpreter -import Term.Arbitrary import Syntax import Patch -import Range import Category import DiffSummary import Text.PrettyPrint.Leijen.Text +import Term.Arbitrary +import Interpreter -arrayInfo :: Info -arrayInfo = rangeAt 0 .: ArrayLiteral .: 2 .: 0 .: RNil +arrayInfo :: Record '[Category] +arrayInfo = ArrayLiteral .: RNil -literalInfo :: Info -literalInfo = rangeAt 1 .: StringLiteral .: 1 .: 0 .: RNil +literalInfo :: Record '[Category] +literalInfo = StringLiteral .: RNil -testDiff :: Diff Text Info +testDiff :: Diff Text (Record '[Category]) testDiff = free $ Free (pure arrayInfo :< Indexed [ free $ Pure (Insert (cofree $ literalInfo :< Leaf "a")) ]) testSummary :: DiffSummary DiffInfo diff --git a/test/diffs/asymmetrical-context.split.js b/test/diffs/asymmetrical-context.split.js index 716e71293..57447bda9 100644 --- a/test/diffs/asymmetrical-context.split.js +++ b/test/diffs/asymmetrical-context.split.js @@ -1,31 +1,31 @@ -
1
      • console
      • .
      • log
      • (
          • '
          • hello
          • '
      • )
    • ;
  • + - - - - - - - - - + +
    1
        • console
        • .
        • log
        • (
            • '
            • hello
            • '
        • )
      • ;
    1
        • console
        • .
        • log
        • (
            • '
            • hello
            • '
        • )
      • ;
    • +
    1
        • console
        • .
        • log
        • (
            • '
            • hello
            • '
        • )
      • ;
    2
      +
    2
    3
      +
    3
    4
      +
    4
    5
      +
    5
    2
        • console
        • .
        • log
        • (
            • '
            • world
            • '
        • )
      • ;
    • +
    2
        • console
        • .
        • log
        • (
            • '
            • world
            • '
        • )
      • ;
    6
        • console
        • .
        • log
        • (
            • '
            • world
            • '
        • )
      • ;
    • +
    6
        • console
        • .
        • log
        • (
            • '
            • world
            • '
        • )
      • ;
    3
      7
        3
          7
            \ No newline at end of file diff --git a/test/diffs/dictionary.split.js b/test/diffs/dictionary.split.js index e441d3780..f4d729479 100644 --- a/test/diffs/dictionary.split.js +++ b/test/diffs/dictionary.split.js @@ -1,25 +1,25 @@ -
            1
                • { + - - - - - - - - - + +
                  1
                      • {
                  1
                      • { +
                  1
                      • {
                  2
                          • "
                          • b
                          • "
                        • :
                        • 4
                      • , +
                  2
                          • "
                          • b
                          • "
                        • :
                        • 4
                      • ,
                  2
                          • "
                          • b
                          • "
                        • :
                        • 5
                      • , +
                  2
                          • "
                          • b
                          • "
                        • :
                        • 5
                      • ,
                  3
                          • "
                          • a
                          • "
                        • :
                        • 5
                      • +
                  3
                          • "
                          • a
                          • "
                        • :
                        • 5
                  3
                          • "
                          • a
                          • "
                        • :
                        • 5
                      • +
                  3
                          • "
                          • a
                          • "
                        • :
                        • 5
                  4
                      • }
                    • +
                  4
                      • }
                  4
                      • }
                    • +
                  4
                      • }
                  5
                    5
                      5
                        5
                          \ No newline at end of file diff --git a/test/diffs/insert.split.js b/test/diffs/insert.split.js index be6673da3..32ebc47e6 100644 --- a/test/diffs/insert.split.js +++ b/test/diffs/insert.split.js @@ -1,14 +1,14 @@ -
                          1
                              • console
                              • .
                              • log
                              • (
                                  • '
                                  • hello
                                  • '
                              • )
                            • ;
                          • + - - - - + +
                            1
                                • console
                                • .
                                • log
                                • (
                                    • '
                                    • hello
                                    • '
                                • )
                              • ;
                            1
                                • console
                                • .
                                • log
                                • (
                                    • '
                                    • hello
                                    • '
                                • )
                              • ;
                            • +
                            1
                                • console
                                • .
                                • log
                                • (
                                    • '
                                    • hello
                                    • '
                                • )
                              • ;
                            2
                                • console
                                • .
                                • log
                                • (
                                    • '
                                    • world
                                    • '
                                • )
                              • ;
                            • +
                            2
                                • console
                                • .
                                • log
                                • (
                                    • '
                                    • world
                                    • '
                                • )
                              • ;
                            2
                              3
                                2
                                  3
                                    \ No newline at end of file diff --git a/test/diffs/multiline-insert.split.js b/test/diffs/multiline-insert.split.js index cd9466b1c..46622452f 100644 --- a/test/diffs/multiline-insert.split.js +++ b/test/diffs/multiline-insert.split.js @@ -1,27 +1,27 @@ -
                                    1
                                        • console
                                        • .
                                        • log
                                        • (
                                            • '
                                            • hello
                                            • '
                                        • )
                                      • ;
                                    • + - - - - - - - - + +
                                      1
                                          • console
                                          • .
                                          • log
                                          • (
                                              • '
                                              • hello
                                              • '
                                          • )
                                        • ;
                                      1
                                          • console
                                          • .
                                          • log
                                          • (
                                              • '
                                              • hello
                                              • '
                                          • )
                                        • ;
                                      • +
                                      1
                                          • console
                                          • .
                                          • log
                                          • (
                                              • '
                                              • hello
                                              • '
                                          • )
                                        • ;
                                      2
                                        • if (
                                        • true
                                        • )
                                          • { +
                                      2
                                        • if (
                                        • true
                                        • )
                                          • {
                                      3
                                              • console
                                              • .
                                              • log
                                              • (
                                                  • '
                                                  • cruel
                                                  • '
                                              • )
                                            • ;
                                          • +
                                      3
                                              • console
                                              • .
                                              • log
                                              • (
                                                  • '
                                                  • cruel
                                                  • '
                                              • )
                                            • ;
                                      4
                                          • }
                                      • +
                                      4
                                          • }
                                      2
                                          • console
                                          • .
                                          • log
                                          • (
                                              • '
                                              • world
                                              • '
                                          • )
                                        • ;
                                      • +
                                      2
                                          • console
                                          • .
                                          • log
                                          • (
                                              • '
                                              • world
                                              • '
                                          • )
                                        • ;
                                      5
                                          • console
                                          • .
                                          • log
                                          • (
                                              • '
                                              • world
                                              • '
                                          • )
                                        • ;
                                      • +
                                      5
                                          • console
                                          • .
                                          • log
                                          • (
                                              • '
                                              • world
                                              • '
                                          • )
                                        • ;
                                      3
                                        6
                                          3
                                            6
                                              \ No newline at end of file diff --git a/test/diffs/nested-insert.split.js b/test/diffs/nested-insert.split.js index 77749ff37..818b295f1 100644 --- a/test/diffs/nested-insert.split.js +++ b/test/diffs/nested-insert.split.js @@ -1,24 +1,24 @@ -
                                              1
                                                • if (
                                                • true
                                                • )
                                                  • { + - - - - - - - - + +
                                                    1
                                                      • if (
                                                      • true
                                                      • )
                                                        • {
                                                    1
                                                      • if (
                                                      • true
                                                      • )
                                                        • { +
                                                    1
                                                      • if (
                                                      • true
                                                      • )
                                                        • {
                                                    2
                                                            • console
                                                            • .
                                                            • log
                                                            • (
                                                                • '
                                                                • hello
                                                                • '
                                                            • )
                                                          • ;
                                                        • +
                                                    2
                                                            • console
                                                            • .
                                                            • log
                                                            • (
                                                                • '
                                                                • hello
                                                                • '
                                                            • )
                                                          • ;
                                                    2
                                                            • console
                                                            • .
                                                            • log
                                                            • (
                                                                • '
                                                                • hello
                                                                • '
                                                            • )
                                                          • ;
                                                        • +
                                                    2
                                                            • console
                                                            • .
                                                            • log
                                                            • (
                                                                • '
                                                                • hello
                                                                • '
                                                            • )
                                                          • ;
                                                    3
                                                            • console
                                                            • .
                                                            • log
                                                            • (
                                                                • '
                                                                • world
                                                                • '
                                                            • )
                                                          • ;
                                                        • +
                                                    3
                                                            • console
                                                            • .
                                                            • log
                                                            • (
                                                                • '
                                                                • world
                                                                • '
                                                            • )
                                                          • ;
                                                    3
                                                        • }
                                                    • +
                                                    3
                                                        • }
                                                    4
                                                        • }
                                                    • +
                                                    4
                                                        • }
                                                    4
                                                      5
                                                        4
                                                          5
                                                            \ No newline at end of file diff --git a/test/diffs/newline-at-eof.split.js b/test/diffs/newline-at-eof.split.js index 4ff547bbe..a648bae01 100644 --- a/test/diffs/newline-at-eof.split.js +++ b/test/diffs/newline-at-eof.split.js @@ -1,18 +1,18 @@ -
                                                            1
                                                                • console
                                                                • .
                                                                • log
                                                                • (
                                                                    • "
                                                                    • hello
                                                                    • ,
                                                                    • world
                                                                    • "
                                                                • )
                                                              • ;
                                                            • + - - - + - - +
                                                              1
                                                                  • console
                                                                  • .
                                                                  • log
                                                                  • (
                                                                      • "
                                                                      • hello
                                                                      • ,
                                                                      • world
                                                                      • "
                                                                  • )
                                                                • ;
                                                              1
                                                                  • console
                                                                  • .
                                                                  • log
                                                                  • (
                                                                      • "
                                                                      • hello
                                                                      • ,
                                                                      • world
                                                                      • "
                                                                  • )
                                                                • ;
                                                              • +
                                                              1
                                                                  • console
                                                                  • .
                                                                  • log
                                                                  • (
                                                                      • "
                                                                      • hello
                                                                      • ,
                                                                      • world
                                                                      • "
                                                                  • )
                                                                • ;
                                                              2
                                                                2
                                                                  +
                                                                2
                                                                  2
                                                                  3
                                                                      • console
                                                                      • .
                                                                      • log
                                                                      • (
                                                                          • "
                                                                          • insertion
                                                                          • "
                                                                      • )
                                                                    • ;
                                                                  • +
                                                                  3
                                                                      • console
                                                                      • .
                                                                      • log
                                                                      • (
                                                                          • "
                                                                          • insertion
                                                                          • "
                                                                      • )
                                                                    • ;
                                                                  4
                                                                    4
                                                                      \ No newline at end of file diff --git a/test/diffs/no-newline-at-eof.split.js b/test/diffs/no-newline-at-eof.split.js index bc15089d8..135895541 100644 --- a/test/diffs/no-newline-at-eof.split.js +++ b/test/diffs/no-newline-at-eof.split.js @@ -1,13 +1,13 @@ - -
                                                                      1
                                                                          • console
                                                                          • .
                                                                          • log
                                                                          • (
                                                                              • "
                                                                              • hello
                                                                              • ,
                                                                              • world
                                                                              • "
                                                                          • )
                                                                        • ;
                                                                      1
                                                                          • console
                                                                          • .
                                                                          • log
                                                                          • (
                                                                              • "
                                                                              • hello
                                                                              • ,
                                                                              • world
                                                                              • "
                                                                          • )
                                                                        • ;
                                                                      • + + - - +
                                                                        1
                                                                            • console
                                                                            • .
                                                                            • log
                                                                            • (
                                                                                • "
                                                                                • hello
                                                                                • ,
                                                                                • world
                                                                                • "
                                                                            • )
                                                                          • ;
                                                                        1
                                                                            • console
                                                                            • .
                                                                            • log
                                                                            • (
                                                                                • "
                                                                                • hello
                                                                                • ,
                                                                                • world
                                                                                • "
                                                                            • )
                                                                          • ;
                                                                        2
                                                                          +
                                                                        2
                                                                        3
                                                                            • console
                                                                            • .
                                                                            • log
                                                                            • (
                                                                                • "
                                                                                • insertion
                                                                                • "
                                                                            • )
                                                                          • ;
                                                                        3
                                                                            • console
                                                                            • .
                                                                            • log
                                                                            • (
                                                                                • "
                                                                                • insertion
                                                                                • "
                                                                            • )
                                                                          • ;
                                                                        \ No newline at end of file diff --git a/test/diffs/reformat.split.js b/test/diffs/reformat.split.js index 6e146f8b2..95f6033f6 100644 --- a/test/diffs/reformat.split.js +++ b/test/diffs/reformat.split.js @@ -1,13 +1,13 @@ - - - + - +
                                                                        1
                                                                            • [ +
                                                                        1
                                                                            • [
                                                                        1
                                                                            • [
                                                                            • bar
                                                                            • ]
                                                                          • ;
                                                                        2
                                                                            • bar
                                                                            • +
                                                                        1
                                                                            • [
                                                                            • bar
                                                                            • ]
                                                                          • ;
                                                                        2
                                                                            • bar
                                                                        3
                                                                            • ]
                                                                          • ;
                                                                        3
                                                                            • ]
                                                                          • ;
                                                                        \ No newline at end of file