1
1
mirror of https://github.com/github/semantic.git synced 2024-12-03 00:16:52 +03:00

Merge branch 'master' into push-benchmarks-to-graphite

This commit is contained in:
Rob Rix 2016-06-06 09:22:36 -04:00
commit bfc28ec4a6
41 changed files with 333 additions and 238 deletions

View File

@ -63,7 +63,7 @@ library
, comonad , comonad
, protolude , protolude
default-language: Haskell2010 default-language: Haskell2010
default-extensions: DeriveFunctor, DeriveFoldable, DeriveTraversable, DeriveGeneric, OverloadedStrings, NoImplicitPrelude default-extensions: DeriveFunctor, DeriveFoldable, DeriveTraversable, DeriveGeneric, FlexibleInstances, OverloadedStrings, NoImplicitPrelude, RecordWildCards
ghc-options: -Wall -fno-warn-name-shadowing -O2 -threaded -fprof-auto "-with-rtsopts=-N -p -s -h -i0.1" -j ghc-options: -Wall -fno-warn-name-shadowing -O2 -threaded -fprof-auto "-with-rtsopts=-N -p -s -h -i0.1" -j
test-suite semantic-diff-test test-suite semantic-diff-test
@ -99,7 +99,7 @@ test-suite semantic-diff-test
else else
ghc-options: -threaded -rtsopts -with-rtsopts=-N -j -pgml=script/g++ ghc-options: -threaded -rtsopts -with-rtsopts=-N -j -pgml=script/g++
default-language: Haskell2010 default-language: Haskell2010
default-extensions: DeriveFunctor, DeriveGeneric, OverloadedStrings, NoImplicitPrelude default-extensions: DeriveFunctor, DeriveGeneric, FlexibleInstances, OverloadedStrings, NoImplicitPrelude, RecordWildCards
if os(darwin) if os(darwin)
extra-libraries: stdc++ icuuc icudata icui18n extra-libraries: stdc++ icuuc icudata icui18n
if os(darwin) if os(darwin)

View File

@ -104,10 +104,10 @@ alignBranch getRange children ranges = case intersectingChildren of
line $ alignBranch getRange (remaining ++ symmetricalChildren ++ nonIntersectingChildren) (modifyJoin (advanceBy (drop 1)) ranges) line $ alignBranch getRange (remaining ++ symmetricalChildren ++ nonIntersectingChildren) (modifyJoin (advanceBy (drop 1)) ranges)
lineAndRemaining _ Nothing = (identity, []) lineAndRemaining _ Nothing = (identity, [])
lineAndRemaining children (Just ranges) = let (intersections, remaining) = alignChildren getRange children ranges in lineAndRemaining children (Just ranges) = let (intersections, remaining) = alignChildren getRange children ranges in
((:) $ (,) <$> ranges `applyToBoth` intersections, remaining) ((:) $ (,) <$> ranges `applyToBoth` (sortBy (compare `on` getRange) <$> intersections), remaining)
-- | Given a list of aligned children, produce lists of their intersecting first lines, and a list of the remaining lines/nonintersecting first lines. -- | Given a list of aligned children, produce lists of their intersecting first lines, and a list of the remaining lines/nonintersecting first lines.
alignChildren :: (term -> Range) -> [Join These (term)] -> Join These Range -> (Both [term], [Join These term]) alignChildren :: (term -> Range) -> [Join These term] -> Join These Range -> (Both [term], [Join These term])
alignChildren _ [] _ = (both [] [], []) alignChildren _ [] _ = (both [] [], [])
alignChildren getRange (first:rest) headRanges alignChildren getRange (first:rest) headRanges
| ~(l, r) <- splitThese first | ~(l, r) <- splitThese first

View File

@ -1,10 +1,7 @@
{-# LANGUAGE FlexibleInstances #-}
module Category where module Category where
import Prologue import Prologue
import Data.String import Data.String
import Data.Set
import Term
-- | A standardized category of AST node. Used to determine the semantics for -- | A standardized category of AST node. Used to determine the semantics for
-- | semantic diffing and define comparability of nodes. -- | semantic diffing and define comparability of nodes.
@ -28,17 +25,3 @@ data Category =
-- | A non-standard category, which can be used for comparability. -- | A non-standard category, which can be used for comparability.
| Other String | Other String
deriving (Eq, Show, Ord) deriving (Eq, Show, Ord)
-- | The class of types that have categories.
class Categorizable a where
categories :: a -> Set Category
instance Categorizable annotation => Categorizable (Term a annotation) where
categories term | (annotation :< _) <- runCofree term = categories annotation
-- | Test whether the categories from the categorizables intersect.
comparable :: Categorizable a => a -> a -> Bool
comparable a b = catsA == catsB || (not . Data.Set.null $ intersection catsA catsB)
where
catsA = categories a
catsB = categories b

View File

@ -1,7 +1,6 @@
{-# LANGUAGE FlexibleInstances #-} module Data.Functor.Both (Both,both, runBothWith, fst, snd, module X) where
module Data.Functor.Both where
import Data.Bifunctor.Join import Data.Bifunctor.Join as X
import Prologue hiding (zipWith, fst, snd) import Prologue hiding (zipWith, fst, snd)
import qualified Prologue import qualified Prologue

View File

@ -1,4 +1,4 @@
{-# LANGUAGE TypeFamilies, TypeSynonymInstances, FlexibleInstances #-} {-# LANGUAGE TypeFamilies, TypeSynonymInstances #-}
module Diff where module Diff where
import Prologue import Prologue

View File

@ -1,11 +1,11 @@
{-# LANGUAGE DataKinds, TypeFamilies, ScopedTypeVariables, FlexibleInstances, RecordWildCards #-} {-# LANGUAGE DataKinds, TypeFamilies, ScopedTypeVariables #-}
module DiffSummary (DiffSummary(..), diffSummary, DiffInfo(..)) where module DiffSummary (DiffSummary(..), diffSummary, DiffInfo(..)) where
import Prologue hiding (fst, snd) import Prologue hiding (fst, snd)
import Data.String import Data.String
import Data.Maybe (fromJust) import Data.Maybe (fromJust)
import Diff import Diff
import Info import Info (Info, category)
import Patch import Patch
import Term import Term
import Syntax import Syntax
@ -21,8 +21,8 @@ maybeTermName :: HasCategory leaf => Term leaf Info -> Maybe String
maybeTermName term = case runCofree term of maybeTermName term = case runCofree term of
(_ :< Leaf leaf) -> Just (toCategoryName leaf) (_ :< Leaf leaf) -> Just (toCategoryName leaf)
(_ :< Keyed children) -> Just (unpack . mconcat $ keys children) (_ :< Keyed children) -> Just (unpack . mconcat $ keys children)
(_ :< Indexed children) -> toCategoryName . toCategory <$> head (extract <$> children) (_ :< Indexed children) -> toCategoryName . category <$> head (extract <$> children)
(_ :< Fixed children) -> toCategoryName . toCategory <$> head (extract <$> children) (_ :< Fixed children) -> toCategoryName . category <$> head (extract <$> children)
class HasCategory a where class HasCategory a where
toCategoryName :: a -> String toCategoryName :: a -> String
@ -46,14 +46,14 @@ instance HasCategory Category where
(Other s) -> s (Other s) -> s
instance HasCategory leaf => HasCategory (Term leaf Info) where instance HasCategory leaf => HasCategory (Term leaf Info) where
toCategoryName = toCategoryName . toCategory . extract toCategoryName = toCategoryName . category . extract
data DiffSummary a = DiffSummary { data DiffSummary a = DiffSummary {
patch :: Patch DiffInfo, patch :: Patch a,
parentAnnotations :: [DiffInfo] parentAnnotations :: [a]
} deriving (Eq, Functor) } deriving (Eq, Functor)
instance Show a => Show (DiffSummary a) where instance Show (DiffSummary DiffInfo) where
showsPrec _ DiffSummary{..} s = (++s) $ case patch of showsPrec _ DiffSummary{..} s = (++s) $ case patch of
(Insert termInfo) -> "Added the " ++ "'" ++ fromJust (termName termInfo) ++ "' " ++ categoryName termInfo (Insert termInfo) -> "Added the " ++ "'" ++ fromJust (termName termInfo) ++ "' " ++ categoryName termInfo
++ maybeParentContext parentAnnotations ++ maybeParentContext parentAnnotations
@ -70,15 +70,12 @@ diffSummary :: HasCategory leaf => Diff leaf Info -> [DiffSummary DiffInfo]
diffSummary = cata diffSummary' where diffSummary = cata diffSummary' where
diffSummary' :: HasCategory leaf => Base (Diff leaf Info) [DiffSummary DiffInfo] -> [DiffSummary DiffInfo] diffSummary' :: HasCategory leaf => Base (Diff leaf Info) [DiffSummary DiffInfo] -> [DiffSummary DiffInfo]
diffSummary' (Free (_ :< Leaf _)) = [] -- Skip leaves since they don't have any changes diffSummary' (Free (_ :< Leaf _)) = [] -- Skip leaves since they don't have any changes
diffSummary' (Free (infos :< Indexed children)) = prependSummary (DiffInfo (toCategoryName . toCategory $ snd infos) Nothing) <$> join children diffSummary' (Free (infos :< Indexed children)) = prependSummary (DiffInfo (toCategoryName . category $ snd infos) Nothing) <$> join children
diffSummary' (Free (infos :< Fixed children)) = prependSummary (DiffInfo (toCategoryName . toCategory $ snd infos) Nothing) <$> join children diffSummary' (Free (infos :< Fixed children)) = prependSummary (DiffInfo (toCategoryName . category $ snd infos) Nothing) <$> join children
diffSummary' (Free (infos :< Keyed children)) = prependSummary (DiffInfo (toCategoryName . toCategory $ snd infos) Nothing) <$> join (Prologue.toList children) diffSummary' (Free (infos :< Keyed children)) = prependSummary (DiffInfo (toCategoryName . category $ snd infos) Nothing) <$> join (Prologue.toList children)
diffSummary' (Pure (Insert term)) = [DiffSummary (Insert (DiffInfo (toCategoryName term) (maybeTermName term))) []] diffSummary' (Pure (Insert term)) = [DiffSummary (Insert (DiffInfo (toCategoryName term) (maybeTermName term))) []]
diffSummary' (Pure (Delete term)) = [DiffSummary (Delete (DiffInfo (toCategoryName term) (maybeTermName term))) []] diffSummary' (Pure (Delete term)) = [DiffSummary (Delete (DiffInfo (toCategoryName term) (maybeTermName term))) []]
diffSummary' (Pure (Replace t1 t2)) = [DiffSummary (Replace (DiffInfo (toCategoryName t1) (maybeTermName t1)) (DiffInfo (toCategoryName t2) (maybeTermName t2))) []] diffSummary' (Pure (Replace t1 t2)) = [DiffSummary (Replace (DiffInfo (toCategoryName t1) (maybeTermName t1)) (DiffInfo (toCategoryName t2) (maybeTermName t2))) []]
prependSummary :: DiffInfo -> DiffSummary DiffInfo -> DiffSummary DiffInfo prependSummary :: DiffInfo -> DiffSummary DiffInfo -> DiffSummary DiffInfo
prependSummary annotation summary = summary { parentAnnotations = annotation : parentAnnotations summary } prependSummary annotation summary = summary { parentAnnotations = annotation : parentAnnotations summary }
toCategory :: Info -> Category
toCategory info = fromMaybe (Other "Unknown") (maybeFirstCategory info)

View File

@ -1,18 +1,20 @@
module Diffing where module Diffing where
import Prologue import Prologue hiding (fst, snd)
import Data.Bifunctor.Join
import qualified Data.ByteString.Char8 as B1 import qualified Data.ByteString.Char8 as B1
import Data.Functor.Both import Data.Functor.Both
import Data.Functor.Foldable import Data.Functor.Foldable
import qualified Data.Text as T import qualified Data.Text as T
import qualified Data.Text.ICU.Detect as Detect import qualified Data.Text.ICU.Detect as Detect
import qualified Data.Text.ICU.Convert as Convert import qualified Data.Text.ICU.Convert as Convert
import Data.These
import Diff import Diff
import Info import Info
import Category
import Interpreter import Interpreter
import Language import Language
import Parser import Parser
import Patch
import Range import Range
import Renderer import Renderer
import Source hiding ((++)) import Source hiding ((++))
@ -36,8 +38,9 @@ lineByLineParser input = pure . cofree . root $ case foldl' annotateLeaves ([],
(leaves, _) -> cofree <$> leaves (leaves, _) -> cofree <$> leaves
where where
lines = actualLines input lines = actualLines input
root children = Info (Range 0 $ length input) mempty (1 + fromIntegral (length children)) :< Indexed children root children = let size = 1 + fromIntegral (length children) in
leaf charIndex line = Info (Range charIndex $ charIndex + T.length line) mempty 1 :< Leaf line Info (Range 0 $ length input) (Other "program") size size :< Indexed children
leaf charIndex line = Info (Range charIndex $ charIndex + T.length line) (Other "program") 1 1 :< Leaf line
annotateLeaves (accum, charIndex) line = annotateLeaves (accum, charIndex) line =
(accum ++ [ leaf charIndex (toText line) ] (accum ++ [ leaf charIndex (toText line) ]
, charIndex + length line) , charIndex + length line)
@ -52,14 +55,13 @@ breakDownLeavesByWord :: Source Char -> Term T.Text Info -> Term T.Text Info
breakDownLeavesByWord source = cata replaceIn breakDownLeavesByWord source = cata replaceIn
where where
replaceIn :: TermF T.Text Info (Term T.Text Info) -> Term T.Text Info replaceIn :: TermF T.Text Info (Term T.Text Info) -> Term T.Text Info
replaceIn (Info range categories _ :< Leaf _) replaceIn (info :< syntax) = let size' = 1 + sum (size . extract <$> syntax') in cofree $ info { size = size', cost = size' } :< syntax'
| ranges <- rangesAndWordsInSource range where syntax' = case (ranges, syntax) of
, length ranges > 1 (_:_:_, Leaf _) -> Indexed (makeLeaf info <$> ranges)
= cofree $ Info range categories (1 + fromIntegral (length ranges)) :< Indexed (makeLeaf categories <$> ranges) _ -> syntax
replaceIn (info :< syntax) ranges = rangesAndWordsInSource (characterRange info)
= cofree $ info { size = 1 + sum (size . extract <$> syntax) } :< syntax
rangesAndWordsInSource range = rangesAndWordsFrom (start range) (toString $ slice range source) rangesAndWordsInSource range = rangesAndWordsFrom (start range) (toString $ slice range source)
makeLeaf categories (range, substring) = cofree $ Info range categories 1 :< Leaf (T.pack substring) makeLeaf info (range, substring) = cofree $ info { characterRange = range } :< Leaf (T.pack substring)
-- | Transcode a file to a unicode source. -- | Transcode a file to a unicode source.
transcode :: B1.ByteString -> IO (Source Char) transcode :: B1.ByteString -> IO (Source Char)
@ -82,15 +84,25 @@ diffFiles :: Parser -> Renderer -> Both SourceBlob -> IO T.Text
diffFiles parser renderer sourceBlobs = do diffFiles parser renderer sourceBlobs = do
let sources = source <$> sourceBlobs let sources = source <$> sourceBlobs
terms <- sequence $ parser <$> sources terms <- sequence $ parser <$> sources
let replaceLeaves = breakDownLeavesByWord <$> sources let replaceLeaves = breakDownLeavesByWord <$> sources
pure $! renderer (runBothWith (diffTerms diffCostWithAbsoluteDifferenceOfCachedDiffSizes) $ replaceLeaves <*> terms) sourceBlobs let areNullOids = runJoin $ (== nullOid) . oid <$> sourceBlobs
let textDiff = case areNullOids of
(True, False) -> pure $ Insert (snd terms)
(False, True) -> pure $ Delete (fst terms)
(_, _) -> runBothWith (diffTerms construct ((==) `on` category . extract) diffCostWithCachedTermSizes) $ 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))
setCost info cost = info { cost = cost }
sumCost = fmap getSum . foldMap (fmap Sum . getCost)
getCost diff = case runFree diff of
Free (info :< _) -> cost <$> info
Pure patch -> uncurry both (fromThese 0 0 (unPatch (cost . extract <$> patch)))
-- | The sum of the node count of the diffs patches. -- | The sum of the node count of the diffs patches.
diffCostWithCachedTermSizes :: Diff a Info -> Integer diffCostWithCachedTermSizes :: Diff a Info -> Integer
diffCostWithCachedTermSizes = diffSum (getSum . foldMap (Sum . size . extract)) diffCostWithCachedTermSizes diff = case runFree diff of
Free (info :< _) -> sum (cost <$> info)
-- | The absolute difference between the node counts of a diff. Pure patch -> sum (cost . extract <$> patch)
diffCostWithAbsoluteDifferenceOfCachedDiffSizes :: Diff a Info -> Integer
diffCostWithAbsoluteDifferenceOfCachedDiffSizes term = case runFree term of
Free (Join (before, after) :< _) -> abs $ size before - size after
Pure patch -> sum $ size . extract <$> patch

View File

@ -6,11 +6,5 @@ import Range
-- | An annotation for a source file, including the source range and semantic -- | An annotation for a source file, including the source range and semantic
-- | categories. -- | categories.
data Info = Info { characterRange :: !Range, categories :: !(Set Category), size :: !Integer } data Info = Info { characterRange :: !Range, category :: !Category, size :: !Integer, cost :: !Integer }
deriving (Eq, Show) deriving (Eq, Show)
instance Categorizable Info where
categories = Info.categories
maybeFirstCategory :: (Categorizable a) => a -> Maybe Category
maybeFirstCategory term = listToMaybe . toList $ Category.categories term

View File

@ -1,7 +1,6 @@
module Interpreter (interpret, Comparable, diffTerms) where module Interpreter (Comparable, DiffConstructor, diffTerms) where
import Algorithm import Algorithm
import Category
import Data.Functor.Foldable import Data.Functor.Foldable
import Data.Functor.Both import Data.Functor.Both
import qualified Data.OrderedMap as Map import qualified Data.OrderedMap as Map
@ -19,53 +18,52 @@ import Term
-- | Returns whether two terms are comparable -- | Returns whether two terms are comparable
type Comparable a annotation = Term a annotation -> Term a annotation -> Bool type Comparable a annotation = Term a annotation -> Term a annotation -> Bool
-- | Diff two terms, given the default Categorizable.comparable function and a function computing the cost of a given diff. -- | Constructs a diff from the CofreeF containing its annotation and syntax. This function has the opportunity to, for example, cache properties in the annotation.
diffTerms :: (Eq a, Eq annotation, Categorizable annotation) => Cost a annotation -> Term a annotation -> Term a annotation -> Diff a annotation type DiffConstructor leaf annotation = CofreeF (Syntax leaf) (Both annotation) (Diff leaf annotation) -> Diff leaf annotation
diffTerms cost = interpret comparable cost
-- | Diff two terms, given a function that determines whether two terms can be compared. -- | Diff two terms, given a function that determines whether two terms can be compared and a cost function.
interpret :: (Eq a, Eq annotation) => Comparable a annotation -> Cost a annotation -> Term a annotation -> Term a annotation -> Diff a annotation diffTerms :: (Eq a, Eq annotation) => DiffConstructor a annotation -> Comparable a annotation -> Cost (Diff a annotation) -> Term a annotation -> Term a annotation -> Diff a annotation
interpret comparable cost a b = fromMaybe (pure $ Replace a b) $ constructAndRun comparable cost a b diffTerms construct comparable cost a b = fromMaybe (pure $ Replace a b) $ constructAndRun construct comparable cost a b
-- | Constructs an algorithm and runs it -- | Constructs an algorithm and runs it
constructAndRun :: (Eq a, Eq annotation) => Comparable a annotation -> Cost a annotation -> Term a annotation -> Term a annotation -> Maybe (Diff a annotation) constructAndRun :: (Eq a, Eq annotation) => DiffConstructor a annotation -> Comparable a annotation -> Cost (Diff a annotation) -> Term a annotation -> Term a annotation -> Maybe (Diff a annotation)
constructAndRun _ _ a b | a == b = hylo (free . Free) runCofree <$> zipTerms a b constructAndRun _ comparable _ a b | not $ comparable a b = Nothing
constructAndRun comparable _ a b | not $ comparable a b = Nothing constructAndRun construct _ _ a b | (() <$ a) == (() <$ b) = hylo construct runCofree <$> zipTerms a b
constructAndRun comparable cost t1 t2 = constructAndRun construct comparable cost t1 t2 =
run comparable cost $ algorithm a b where run construct comparable cost $ algorithm a b where
algorithm (Indexed a') (Indexed b') = free . Free $ ByIndex a' b' (annotate . Indexed) algorithm (Indexed a') (Indexed b') = free . Free $ ByIndex a' b' (annotate . Indexed)
algorithm (Keyed a') (Keyed b') = free . Free $ ByKey a' b' (annotate . Keyed) algorithm (Keyed a') (Keyed b') = free . Free $ ByKey a' b' (annotate . Keyed)
algorithm (Leaf a') (Leaf b') | a' == b' = annotate $ Leaf b' algorithm (Leaf a') (Leaf b') | a' == b' = annotate $ Leaf b'
algorithm a' b' = free . Free $ Recursive (cofree (annotation1 :< a')) (cofree (annotation2 :< b')) pure algorithm a' b' = free . Free $ Recursive (cofree (annotation1 :< a')) (cofree (annotation2 :< b')) pure
(annotation1 :< a, annotation2 :< b) = (runCofree t1, runCofree t2) (annotation1 :< a, annotation2 :< b) = (runCofree t1, runCofree t2)
annotate = pure . free . Free . (both annotation1 annotation2 :<) annotate = pure . construct . (both annotation1 annotation2 :<)
-- | Runs the diff algorithm -- | Runs the diff algorithm
run :: (Eq a, Eq annotation) => Comparable a annotation -> Cost a annotation -> Algorithm a annotation (Diff a annotation) -> Maybe (Diff a annotation) run :: (Eq a, Eq annotation) => DiffConstructor a annotation -> Comparable a annotation -> Cost (Diff a annotation) -> Algorithm a annotation (Diff a annotation) -> Maybe (Diff a annotation)
run comparable cost algorithm = case runFree algorithm of run construct comparable cost algorithm = case runFree algorithm of
Pure diff -> Just diff Pure diff -> Just diff
Free (Recursive t1 t2 f) -> run comparable cost . f $ recur a b where Free (Recursive t1 t2 f) -> run construct comparable cost . f $ recur a b where
(annotation1 :< a, annotation2 :< b) = (runCofree t1, runCofree t2) (annotation1 :< a, annotation2 :< b) = (runCofree t1, runCofree t2)
annotate = free . Free . (both annotation1 annotation2 :<) annotate = construct . (both annotation1 annotation2 :<)
recur (Indexed a') (Indexed b') | length a' == length b' = annotate . Indexed $ zipWith (interpret comparable cost) a' b' recur (Indexed a') (Indexed b') | length a' == length b' = annotate . Indexed $ zipWith (diffTerms construct comparable cost) a' b'
recur (Fixed a') (Fixed b') | length a' == length b' = annotate . Fixed $ zipWith (interpret comparable cost) a' b' recur (Fixed a') (Fixed b') | length a' == length b' = annotate . Fixed $ zipWith (diffTerms construct comparable cost) a' b'
recur (Keyed a') (Keyed b') | Map.keys a' == bKeys = annotate . Keyed . Map.fromList . fmap repack $ bKeys where recur (Keyed a') (Keyed b') | Map.keys a' == bKeys = annotate . Keyed . Map.fromList . fmap repack $ bKeys where
bKeys = Map.keys b' bKeys = Map.keys b'
repack key = (key, interpretInBoth key a' b') repack key = (key, interpretInBoth key a' b')
interpretInBoth key x y = interpret comparable cost (x ! key) (y ! key) interpretInBoth key x y = diffTerms construct comparable cost (x ! key) (y ! key)
recur _ _ = pure $ Replace (cofree (annotation1 :< a)) (cofree (annotation2 :< b)) recur _ _ = pure $ Replace (cofree (annotation1 :< a)) (cofree (annotation2 :< b))
Free (ByKey a b f) -> run comparable cost $ f byKey where Free (ByKey a b f) -> run construct comparable cost $ f byKey where
byKey = Map.fromList $ toKeyValue <$> List.union aKeys bKeys byKey = Map.fromList $ toKeyValue <$> List.union aKeys bKeys
toKeyValue key | key `List.elem` deleted = (key, pure . Delete $ a ! key) toKeyValue key | key `List.elem` deleted = (key, pure . Delete $ a ! key)
toKeyValue key | key `List.elem` inserted = (key, pure . Insert $ b ! key) toKeyValue key | key `List.elem` inserted = (key, pure . Insert $ b ! key)
toKeyValue key = (key, interpret comparable cost (a ! key) (b ! key)) toKeyValue key = (key, diffTerms construct comparable cost (a ! key) (b ! key))
aKeys = Map.keys a aKeys = Map.keys a
bKeys = Map.keys b bKeys = Map.keys b
deleted = aKeys \\ bKeys deleted = aKeys \\ bKeys
inserted = bKeys \\ aKeys inserted = bKeys \\ aKeys
Free (ByIndex a b f) -> run comparable cost . f $ ses (constructAndRun comparable cost) cost a b Free (ByIndex a b f) -> run construct comparable cost . f $ ses (constructAndRun construct comparable cost) cost a b

View File

@ -1,11 +1,9 @@
module Parser where module Parser where
import Prologue hiding (Constructor) import Prologue hiding (Constructor)
import Data.String
import Data.Text (pack) import Data.Text (pack)
import Category import Category
import Info import Info
import Range
import Syntax import Syntax
import Term import Term
import qualified Data.OrderedMap as Map import qualified Data.OrderedMap as Map
@ -17,9 +15,8 @@ import Source
-- | and aren't pure. -- | and aren't pure.
type Parser = Source Char -> IO (Term Text Info) type Parser = Source Char -> IO (Term Text Info)
-- | Given a source string, the term's range, production name, and -- | A function which constructs a term from a source string, annotation, and children.
-- | production/child pairs, construct the term. type Constructor = Source Char -> Info -> [Term Text Info] -> Term Text Info
type Constructor = Source Char -> Range -> String -> [Term Text Info] -> Term Text Info
-- | Categories that are treated as keyed nodes. -- | Categories that are treated as keyed nodes.
keyedCategories :: Set.Set Category keyedCategories :: Set.Set Category
@ -30,24 +27,25 @@ fixedCategories :: Set.Set Category
fixedCategories = Set.fromList [ BinaryOperator, Pair ] fixedCategories = Set.fromList [ BinaryOperator, Pair ]
-- | Should these categories be treated as keyed nodes? -- | Should these categories be treated as keyed nodes?
isKeyed :: Set.Set Category -> Bool isKeyed :: Category -> Bool
isKeyed = not . Set.null . Set.intersection keyedCategories isKeyed = flip Set.member keyedCategories
-- | Should these categories be treated as fixed nodes? -- | Should these categories be treated as fixed nodes?
isFixed :: Set.Set Category -> Bool isFixed :: Category -> Bool
isFixed = not . Set.null . Set.intersection fixedCategories isFixed = flip Set.member fixedCategories
-- | Given a function that maps production names to sets of categories, produce -- | Given a function that maps production names to sets of categories, produce
-- | a Constructor. -- | a Constructor.
termConstructor :: (String -> Set.Set Category) -> Constructor termConstructor :: Constructor
termConstructor mapping source range name children = cofree (Info range categories (1 + sum (size . extract <$> children)) :< construct children) termConstructor source info children = cofree (info :< syntax)
where where
categories = mapping name syntax = construct children
construct :: [Term Text Info] -> Syntax Text (Term Text Info) construct :: [Term Text Info] -> Syntax Text (Term Text Info)
construct [] = Leaf . pack . toString $ slice range source construct [] = Leaf . pack . toString $ slice (characterRange info) source
construct children | isFixed categories = Fixed children construct children | isFixed (category info) = Fixed children
construct children | isKeyed categories = Keyed . Map.fromList $ assignKey <$> children construct children | isKeyed (category info) = Keyed . Map.fromList $ assignKey <$> children
construct children = Indexed children construct children = Indexed children
assignKey node | Info _ categories _ :< Fixed (key : _) <- runCofree node, Set.member Pair categories = (getSubstring key, node) assignKey node = case runCofree node of
assignKey node = (getSubstring node, node) info :< Fixed (key : _) | Pair == category info -> (getSubstring key, node)
getSubstring term | Info range _ _ :< _ <- runCofree term = pack . toString $ slice range source _ -> (getSubstring node, node)
getSubstring term = pack . toString $ slice (characterRange (extract term)) source

View File

@ -1,4 +1,3 @@
{-# LANGUAGE FlexibleInstances #-}
module Range where module Range where
import qualified Data.Char as Char import qualified Data.Char as Char
@ -50,8 +49,7 @@ maybeLastIndex (Range _ end) = Just $ end - 1
-- | Test two ranges for intersection. -- | Test two ranges for intersection.
intersectsRange :: Range -> Range -> Bool intersectsRange :: Range -> Range -> Bool
intersectsRange range1 range2 = isWellFormedAndNonEmpty $ intersectionRange range1 range2 intersectsRange range1 range2 = start range1 < end range2 && start range2 < end range1
where isWellFormedAndNonEmpty range = start range < end range
-- Return the (possibly empty, possibly ill-formed) intersection of two ranges. -- Return the (possibly empty, possibly ill-formed) intersection of two ranges.
intersectionRange :: Range -> Range -> Range intersectionRange :: Range -> Range -> Range

View File

@ -1,4 +1,4 @@
{-# LANGUAGE FlexibleInstances, OverloadedStrings, TypeSynonymInstances #-} {-# LANGUAGE OverloadedStrings, TypeSynonymInstances #-}
{-# OPTIONS_GHC -fno-warn-orphans #-} {-# OPTIONS_GHC -fno-warn-orphans #-}
module Renderer.JSON ( module Renderer.JSON (
json json
@ -66,7 +66,7 @@ lineFields n term range = [ "number" .= n
] ]
termFields :: (ToJSON recur, KeyValue kv) => Info -> Syntax leaf recur -> [kv] termFields :: (ToJSON recur, KeyValue kv) => Info -> Syntax leaf recur -> [kv]
termFields (Info range categories _) syntax = "range" .= range : "categories" .= categories : case syntax of termFields Info{..} syntax = "range" .= characterRange : "category" .= category : case syntax of
Leaf _ -> [] Leaf _ -> []
Indexed c -> childrenFields c Indexed c -> childrenFields c
Fixed c -> childrenFields c Fixed c -> childrenFields c

View File

@ -1,4 +1,3 @@
{-# LANGUAGE FlexibleInstances #-}
module Renderer.Split where module Renderer.Split where
import Data.String import Data.String
@ -31,8 +30,8 @@ maybeFirst = foldr (const . Just) Nothing
-- | Add the first category from a Foldable of categories as a class name as a -- | Add the first category from a Foldable of categories as a class name as a
-- | class name on the markup, prefixed by `category-`. -- | class name on the markup, prefixed by `category-`.
classifyMarkup :: Prologue.Foldable f => f Category -> Markup -> Markup classifyMarkup :: Category -> Markup -> Markup
classifyMarkup categories element = maybe element ((element !) . A.class_ . stringValue . styleName) $ maybeFirst categories classifyMarkup category element = (element !) . A.class_ . stringValue $ styleName category
-- | Return the appropriate style name for the given category. -- | Return the appropriate style name for the given category.
styleName :: Category -> String styleName :: Category -> String
@ -92,11 +91,11 @@ split diff blobs = TL.toStrict . renderHtml
newtype Renderable a = Renderable a newtype Renderable a = Renderable a
instance ToMarkup f => ToMarkup (Renderable (Source Char, Info, Syntax a (f, Range))) where instance ToMarkup f => ToMarkup (Renderable (Source Char, Info, Syntax a (f, Range))) where
toMarkup (Renderable (source, Info range categories size, syntax)) = (! A.data_ (stringValue (show size))) . classifyMarkup categories $ case syntax of toMarkup (Renderable (source, Info {..}, syntax)) = (! A.data_ (stringValue (show size))) . classifyMarkup category $ case syntax of
Leaf _ -> span . string . toString $ slice range source Leaf _ -> span . string . toString $ slice characterRange source
Indexed children -> ul . mconcat $ wrapIn li <$> contentElements source range children Indexed children -> ul . mconcat $ wrapIn li <$> contentElements source characterRange children
Fixed children -> ul . mconcat $ wrapIn li <$> contentElements source range children Fixed children -> ul . mconcat $ wrapIn li <$> contentElements source characterRange children
Keyed children -> dl . mconcat $ wrapIn dd <$> contentElements source range children Keyed children -> dl . mconcat $ wrapIn dd <$> contentElements source characterRange children
contentElements :: (Foldable t, ToMarkup f) => Source Char -> Range -> t (f, Range) -> [Markup] 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 contentElements source range children = let (elements, next) = foldr' (markupForContextAndChild source) ([], end range) children in
@ -113,13 +112,13 @@ wrapIn _ l@Blaze.Comment{} = l
wrapIn f p = f p wrapIn f p = f p
instance ToMarkup (Renderable (Source Char, Term a Info)) where instance ToMarkup (Renderable (Source Char, Term a Info)) where
toMarkup (Renderable (source, term)) = Prologue.fst $ cata (\ (info@(Info range _ _) :< syntax) -> (toMarkup $ Renderable (source, info, syntax), range)) term toMarkup (Renderable (source, term)) = Prologue.fst $ cata (\ (info@(Info{..}) :< syntax) -> (toMarkup $ Renderable (source, info, syntax), characterRange)) term
instance ToMarkup (Renderable (Source Char, SplitDiff a Info)) where instance ToMarkup (Renderable (Source Char, SplitDiff a Info)) where
toMarkup (Renderable (source, diff)) = Prologue.fst $ iter (\ (info@(Info range _ _) :< syntax) -> (toMarkup $ Renderable (source, info, syntax), range)) $ toMarkupAndRange <$> diff toMarkup (Renderable (source, diff)) = Prologue.fst $ iter (\ (info@(Info{..}) :< syntax) -> (toMarkup $ Renderable (source, info, syntax), characterRange)) $ toMarkupAndRange <$> diff
where toMarkupAndRange :: SplitPatch (Term a Info) -> (Markup, Range) where toMarkupAndRange :: SplitPatch (Term a Info) -> (Markup, Range)
toMarkupAndRange patch = let term@(Info range _ size :< _) = runCofree $ getSplitTerm patch in toMarkupAndRange patch = let term@(Info{..} :< _) = runCofree $ getSplitTerm patch in
((div ! A.class_ (splitPatchToClassName patch) ! A.data_ (stringValue (show size))) . toMarkup $ Renderable (source, cofree term), range) ((div ! A.class_ (splitPatchToClassName patch) ! A.data_ (stringValue (show size))) . toMarkup $ Renderable (source, cofree term), characterRange)
instance ToMarkup a => ToMarkup (Renderable (Bool, Int, a)) where instance ToMarkup a => ToMarkup (Renderable (Bool, Int, a)) where
toMarkup (Renderable (hasChanges, num, line)) = toMarkup (Renderable (hasChanges, num, line)) =

View File

@ -3,7 +3,9 @@ module Renderer.Summary where
import Prologue import Prologue
import Renderer import Renderer
import DiffSummary import DiffSummary
import Data.Aeson
import Data.ByteString.Builder
import Data.Text (pack) import Data.Text (pack)
summary :: Renderer summary :: Renderer
summary diff sources = pack . show $ diffSummary diff summary diff _ = toS . toLazyByteString . fromEncoding . foldable $ pack . show <$> diffSummary diff

View File

@ -2,29 +2,26 @@ module SES where
import Prologue import Prologue
import Patch import Patch
import Diff
import Term
import qualified Data.Map as Map import qualified Data.Map as Map
-- | A function that maybe creates a diff from two terms. -- | Edit constructor for two terms, if comparable. Otherwise returns Nothing.
type Compare a annotation = Term a annotation -> Term a annotation -> Maybe (Diff a annotation) type Compare term edit = term -> term -> Maybe edit
-- | A function that computes the cost of a diff. -- | A function that computes the cost of an edit.
type Cost a annotation = Diff a annotation -> Integer type Cost edit = edit -> Integer
-- | Find the shortest edit script (diff) between two terms given a function to compute the cost. -- | Find the shortest edit script (diff) between two terms given a function to compute the cost.
ses :: Compare a annotation -> Cost a annotation -> [Term a annotation] -> [Term a annotation] -> [Diff a annotation] ses :: Applicative edit => Compare term (edit (Patch term)) -> Cost (edit (Patch term)) -> [term] -> [term] -> [edit (Patch term)]
ses diffTerms cost as bs = fst <$> evalState diffState Map.empty where ses diffTerms cost as bs = fst <$> evalState diffState Map.empty where
diffState = diffAt diffTerms cost (0, 0) as bs diffState = diffAt diffTerms cost (0, 0) as bs
-- | Find the shortest edit script between two terms at a given vertex in the edit graph. -- | Find the shortest edit script between two terms at a given vertex in the edit graph.
diffAt :: Compare a annotation -> Cost a annotation -> (Integer, Integer) -> [Term a annotation] -> [Term a annotation] -> State (Map.Map (Integer, Integer) [(Diff a annotation, Integer)]) [(Diff a annotation, Integer)] diffAt :: Applicative edit => Compare term (edit (Patch term)) -> Cost (edit (Patch term)) -> (Integer, Integer) -> [term] -> [term] -> State (Map.Map (Integer, Integer) [(edit (Patch term), Integer)]) [(edit (Patch term), Integer)]
diffAt _ _ _ [] [] = pure [] diffAt diffTerms cost (i, j) as bs
diffAt _ cost _ [] bs = pure $ foldr toInsertions [] bs where | null as, null bs = pure []
toInsertions each = consWithCost cost (free . Pure . Insert $ each) | null as = pure $ foldr insert [] bs
diffAt _ cost _ as [] = pure $ foldr toDeletions [] as where | null bs = pure $ foldr delete [] as
toDeletions each = consWithCost cost (free . Pure . Delete $ each) | (a : as) <- as, (b : bs) <- bs = do
diffAt diffTerms cost (i, j) (a : as) (b : bs) = do
cachedDiffs <- get cachedDiffs <- get
case Map.lookup (i, j) cachedDiffs of case Map.lookup (i, j) cachedDiffs of
Just diffs -> pure diffs Just diffs -> pure diffs
@ -34,19 +31,19 @@ diffAt diffTerms cost (i, j) (a : as) (b : bs) = do
nomination <- fmap best $ case diffTerms a b of nomination <- fmap best $ case diffTerms a b of
Just diff -> do Just diff -> do
diagonal <- recur (succ i, succ j) as bs diagonal <- recur (succ i, succ j) as bs
pure [ delete down, insert right, consWithCost cost diff diagonal ] pure [ delete a down, insert b right, consWithCost cost diff diagonal ]
Nothing -> pure [ delete down, insert right ] Nothing -> pure [ delete a down, insert b right ]
cachedDiffs' <- get cachedDiffs' <- get
put $ Map.insert (i, j) nomination cachedDiffs' put $ Map.insert (i, j) nomination cachedDiffs'
pure nomination pure nomination
where where
delete = consWithCost cost (free . Pure . Delete $ a) delete = consWithCost cost . pure . Delete
insert = consWithCost cost (free . Pure . Insert $ b) insert = consWithCost cost . pure . Insert
costOf [] = 0 costOf [] = 0
costOf ((_, c) : _) = c costOf ((_, c) : _) = c
best = minimumBy (comparing costOf) best = minimumBy (comparing costOf)
recur = diffAt diffTerms cost recur = diffAt diffTerms cost
-- | Prepend a diff to the list with the cumulative cost. -- | Prepend an edit script and the cumulative cost onto the edit script.
consWithCost :: Cost a annotation -> Diff a annotation -> [(Diff a annotation, Integer)] -> [(Diff a annotation, Integer)] consWithCost :: Cost edit -> edit -> [(edit, Integer)] -> [(edit, Integer)]
consWithCost cost diff rest = (diff, cost diff + maybe 0 snd (fst <$> uncons rest)) : rest consWithCost cost edit rest = (edit, cost edit + maybe 0 snd (fst <$> uncons rest)) : rest

View File

@ -30,8 +30,8 @@ newtype Source a = Source { getVector :: Vector.Vector a }
-- | Map blobs with Nothing blobKind to empty blobs. -- | Map blobs with Nothing blobKind to empty blobs.
idOrEmptySourceBlob :: SourceBlob -> SourceBlob idOrEmptySourceBlob :: SourceBlob -> SourceBlob
idOrEmptySourceBlob blob = if isNothing (Source.blobKind blob) idOrEmptySourceBlob blob = if isNothing (blobKind blob)
then (blob { Source.oid = nullOid, Source.blobKind = Nothing }) then blob { oid = nullOid, blobKind = Nothing }
else blob else blob
nullOid :: String nullOid :: String

View File

@ -1,4 +1,4 @@
{-# LANGUAGE TypeFamilies, TypeSynonymInstances, FlexibleInstances #-} {-# LANGUAGE TypeFamilies, TypeSynonymInstances #-}
module Term where module Term where
import Prologue import Prologue

View File

@ -3,11 +3,11 @@ module TreeSitter where
import Prologue hiding (Constructor) import Prologue hiding (Constructor)
import Data.String import Data.String
import Category import Category
import Info
import Language import Language
import Parser import Parser
import Range import Range
import Source import Source
import qualified Data.Set as Set
import Foreign import Foreign
import Foreign.C.String import Foreign.C.String
import Text.Parser.TreeSitter hiding (Language(..)) import Text.Parser.TreeSitter hiding (Language(..))
@ -21,33 +21,33 @@ treeSitterParser language grammar contents = do
withCString (toString contents) (\source -> do withCString (toString contents) (\source -> do
ts_document_set_input_string document source ts_document_set_input_string document source
ts_document_parse document ts_document_parse document
term <- documentToTerm (termConstructor $ categoriesForLanguage language) document contents term <- documentToTerm language document contents
ts_document_free document ts_document_free document
pure term) pure term)
-- Given a language and a node name, return the correct categories. -- Given a language and a node name, return the correct categories.
categoriesForLanguage :: Language -> String -> Set.Set Category categoriesForLanguage :: Language -> String -> Category
categoriesForLanguage language name = case (language, name) of categoriesForLanguage language name = case (language, name) of
(JavaScript, "object") -> Set.singleton DictionaryLiteral (JavaScript, "object") -> DictionaryLiteral
(JavaScript, "rel_op") -> Set.singleton BinaryOperator -- relational operator, e.g. >, <, <=, >=, ==, != (JavaScript, "rel_op") -> BinaryOperator -- relational operator, e.g. >, <, <=, >=, ==, !=
(Ruby, "hash") -> Set.singleton DictionaryLiteral (Ruby, "hash") -> DictionaryLiteral
_ -> defaultCategoryForNodeName name _ -> defaultCategoryForNodeName name
-- | Given a node name from TreeSitter, return the correct categories. -- | Given a node name from TreeSitter, return the correct categories.
defaultCategoryForNodeName :: String -> Set.Set Category defaultCategoryForNodeName :: String -> Category
defaultCategoryForNodeName name = case name of defaultCategoryForNodeName name = case name of
"function_call" -> Set.singleton FunctionCall "function_call" -> FunctionCall
"pair" -> Set.singleton Pair "pair" -> Pair
"string" -> Set.singleton StringLiteral "string" -> StringLiteral
"integer" -> Set.singleton IntegerLiteral "integer" -> IntegerLiteral
"symbol" -> Set.singleton SymbolLiteral "symbol" -> SymbolLiteral
"array" -> Set.singleton ArrayLiteral "array" -> ArrayLiteral
_ -> Set.singleton (Other name) _ -> (Other name)
-- | Given a constructor and a tree sitter document, return a parser. -- | Return a parser for a tree sitter language & document.
documentToTerm :: Constructor -> Ptr Document -> Parser documentToTerm :: Language -> Ptr Document -> Parser
documentToTerm constructor document contents = alloca $ \ root -> do documentToTerm language document contents = alloca $ \ root -> do
ts_document_root_node_p document root ts_document_root_node_p document root
toTerm root toTerm root
where toTerm node = do where toTerm node = do
@ -58,7 +58,9 @@ documentToTerm constructor document contents = alloca $ \ root -> do
-- Note: The strict application here is semantically important. Without it, we may not evaluate the range until after weve exited the scope that `node` was allocated within, meaning `alloca` will free it & other stack data may overwrite it. -- Note: The strict application here is semantically important. Without it, we may not evaluate the range until after weve 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 } range <- pure $! Range { start = fromIntegral $ ts_node_p_start_char node, end = fromIntegral $ ts_node_p_end_char node }
pure $! constructor contents range name children let size' = 1 + sum (size . extract <$> children)
let info = Info range (categoriesForLanguage language name) size' size'
pure $! termConstructor contents info children
getChild node n out = do getChild node n out = do
_ <- ts_node_p_named_child node n out _ <- ts_node_p_named_child node n out
toTerm out toTerm out

View File

@ -1,4 +1,3 @@
{-# LANGUAGE FlexibleInstances #-}
module AlignmentSpec where module AlignmentSpec where
import Alignment import Alignment
@ -22,6 +21,7 @@ import Range
import qualified Source import qualified Source
import SplitDiff import SplitDiff
import Syntax import Syntax
import Category
import Term import Term
import Test.Hspec import Test.Hspec
import Test.Hspec.QuickCheck import Test.Hspec.QuickCheck
@ -257,7 +257,7 @@ align :: Both (Source.Source Char) -> ConstructibleFree (Patch (Term String Info
align sources = PrettyDiff sources . fmap (fmap (getRange &&& identity)) . alignDiff sources . deconstruct align sources = PrettyDiff sources . fmap (fmap (getRange &&& identity)) . alignDiff sources . deconstruct
info :: Int -> Int -> Info info :: Int -> Int -> Info
info = ((\ r -> Info r mempty 0) .) . Range info start end = Info (Range start end) StringLiteral 0 0
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 Info)) Info)] -> PrettyDiff (SplitDiff String Info)
prettyDiff sources = PrettyDiff sources . fmap (fmap ((getRange &&& identity) . deconstruct)) prettyDiff sources = PrettyDiff sources . fmap (fmap ((getRange &&& identity) . deconstruct))

View File

@ -1,4 +1,3 @@
{-# LANGUAGE FlexibleInstances #-}
{-# OPTIONS_GHC -fno-warn-orphans #-} {-# OPTIONS_GHC -fno-warn-orphans #-}
module ArbitraryTerm where module ArbitraryTerm where
@ -8,7 +7,6 @@ import Data.Functor.Both
import Data.Functor.Foldable import Data.Functor.Foldable
import qualified Data.OrderedMap as Map import qualified Data.OrderedMap as Map
import qualified Data.List as List import qualified Data.List as List
import qualified Data.Set as Set
import Data.Text.Arbitrary () import Data.Text.Arbitrary ()
import Data.These import Data.These
import Info import Info
@ -46,12 +44,6 @@ instance (Eq a, Eq annotation, Arbitrary a, Arbitrary annotation) => Arbitrary (
data CategorySet = A | B | C | D deriving (Eq, Show) data CategorySet = A | B | C | D deriving (Eq, Show)
instance Categorizable CategorySet where
categories A = Set.fromList [ Other "a" ]
categories B = Set.fromList [ Other "b" ]
categories C = Set.fromList [ Other "c" ]
categories D = Set.fromList [ Other "d" ]
instance Arbitrary CategorySet where instance Arbitrary CategorySet where
arbitrary = elements [ A, B, C, D ] arbitrary = elements [ A, B, C, D ]
@ -80,4 +72,4 @@ instance Arbitrary a => Arbitrary (Source a) where
arbitraryLeaf :: Gen (Source Char, Info, Syntax (Source Char) f) arbitraryLeaf :: Gen (Source Char, Info, Syntax (Source Char) f)
arbitraryLeaf = toTuple <$> arbitrary arbitraryLeaf = toTuple <$> arbitrary
where toTuple string = (string, Info (Range 0 $ length string) mempty 1, Leaf string) where toTuple string = (string, Info (Range 0 $ length string) StringLiteral 1 0, Leaf string)

View File

@ -1,3 +1,4 @@
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
module CorpusSpec where module CorpusSpec where
import System.IO import System.IO
@ -32,7 +33,7 @@ spec = parallel $ do
examples "test/diffs/" `shouldNotReturn` [] examples "test/diffs/" `shouldNotReturn` []
where where
runTestsIn :: String -> (T.Text -> T.Text -> Expectation) -> SpecWith () runTestsIn :: FilePath -> (Verbatim -> Verbatim -> Expectation) -> SpecWith ()
runTestsIn directory matcher = do runTestsIn directory matcher = do
paths <- runIO $ examples directory paths <- runIO $ examples directory
let tests = correctTests =<< paths let tests = correctTests =<< paths
@ -57,7 +58,7 @@ examples directory = do
let keys = Set.unions $ keysSet <$> [as, bs] let keys = Set.unions $ keysSet <$> [as, bs]
pure $ (\name -> (both (as ! name) (bs ! name), Map.lookup name jsons, Map.lookup name patches, Map.lookup name splits)) <$> sort (Set.toList keys) pure $ (\name -> (both (as ! name) (bs ! name), Map.lookup name jsons, Map.lookup name patches, Map.lookup name splits)) <$> sort (Set.toList keys)
where where
globFor :: String -> IO [FilePath] globFor :: FilePath -> IO [FilePath]
globFor p = globDir1 (compile p) directory globFor p = globDir1 (compile p) directory
toDict list = Map.fromList ((normalizeName <$> list) `zip` list) toDict list = Map.fromList ((normalizeName <$> list) `zip` list)
@ -68,14 +69,21 @@ normalizeName path = addExtension (dropExtension $ dropExtension path) (takeExte
-- | Given file paths for A, B, and, optionally, a diff, return whether diffing -- | 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 -- | the files will produce the diff. If no diff is provided, then the result
-- | is true, but the diff will still be calculated. -- | is true, but the diff will still be calculated.
testDiff :: Renderer -> Both FilePath -> Maybe FilePath -> (T.Text -> T.Text -> Expectation) -> Expectation testDiff :: Renderer -> Both FilePath -> Maybe FilePath -> (Verbatim -> Verbatim -> Expectation) -> Expectation
testDiff renderer paths diff matcher = do testDiff renderer paths diff matcher = do
sources <- sequence $ readAndTranscodeFile <$> paths sources <- sequence $ readAndTranscodeFile <$> paths
actual <- diffFiles parser renderer (sourceBlobs sources) actual <- Verbatim <$> diffFiles parser renderer (sourceBlobs sources)
case diff of case diff of
Nothing -> matcher actual actual Nothing -> matcher actual actual
Just file -> do Just file -> do
expected <- T.pack <$> readFile file expected <- Verbatim . T.pack <$> readFile file
matcher actual expected matcher actual expected
where parser = parserForFilepath (fst paths) where parser = parserForFilepath (fst paths)
sourceBlobs sources = pure S.SourceBlob <*> sources <*> pure mempty <*> paths <*> pure (Just S.defaultPlainBlob) sourceBlobs sources = pure S.SourceBlob <*> sources <*> pure mempty <*> paths <*> pure (Just S.defaultPlainBlob)
-- | A wrapper around `Text` with a more readable `Show` instance.
newtype Verbatim = Verbatim Text
deriving (Eq, NFData)
instance Show Verbatim where
showsPrec _ (Verbatim text) = ('\n':) . (T.unpack text ++)

View File

@ -9,22 +9,21 @@ import Syntax
import Patch import Patch
import Range import Range
import Category import Category
import Data.Set
import DiffSummary import DiffSummary
arrayInfo :: Info arrayInfo :: Info
arrayInfo = Info (rangeAt 0) (singleton ArrayLiteral) 2 arrayInfo = Info (rangeAt 0) ArrayLiteral 2 0
literalInfo :: Info literalInfo :: Info
literalInfo = Info (rangeAt 1) (singleton StringLiteral) 1 literalInfo = Info (rangeAt 1) StringLiteral 1 0
testDiff :: Diff String Info testDiff :: Diff String Info
testDiff = free $ Free (pure arrayInfo :< Indexed [ free $ Pure (Insert (cofree $ literalInfo :< Leaf "a")) ]) testDiff = free $ Free (pure arrayInfo :< Indexed [ free $ Pure (Insert (cofree $ literalInfo :< Leaf "a")) ])
testSummary :: DiffSummary Char testSummary :: DiffSummary DiffInfo
testSummary = DiffSummary { patch = Insert (DiffInfo "string" (Just "a")), parentAnnotations = [] } testSummary = DiffSummary { patch = Insert (DiffInfo "string" (Just "a")), parentAnnotations = [] }
replacementSummary :: DiffSummary Char replacementSummary :: DiffSummary DiffInfo
replacementSummary = DiffSummary { patch = Replace (DiffInfo "string" (Just "a")) (DiffInfo "symbol" (Just "b")), parentAnnotations = [ (DiffInfo "array" (Just "switch {}")) ] } replacementSummary = DiffSummary { patch = Replace (DiffInfo "string" (Just "a")) (DiffInfo "symbol" (Just "b")), parentAnnotations = [ (DiffInfo "array" (Just "switch {}")) ] }
spec :: Spec spec :: Spec

View File

@ -14,8 +14,8 @@ spec :: Spec
spec = parallel $ spec = parallel $
describe "interpret" $ describe "interpret" $
it "returns a replacement when comparing two unicode equivalent terms" $ it "returns a replacement when comparing two unicode equivalent terms" $
I.interpret comparable diffCost (cofree (Info range mempty 0 :< Leaf "t\776")) (cofree (Info range2 mempty 0 :< Leaf "\7831")) `shouldBe` I.diffTerms (free . Free) ((==) `on` extract) diffCost (cofree (Info range StringLiteral 0 0 :< Leaf "t\776")) (cofree (Info range2 StringLiteral 0 0 :< Leaf "\7831")) `shouldBe`
free (Pure (Replace (cofree (Info range mempty 0 :< Leaf "t\776")) (cofree (Info range2 mempty 0 :< Leaf "\7831")))) free (Pure (Replace (cofree (Info range StringLiteral 0 0 :< Leaf "t\776")) (cofree (Info range2 StringLiteral 0 0 :< Leaf "\7831"))))
where where
range = Range 0 2 range = Range 0 2

View File

@ -2,17 +2,16 @@ module PatchOutputSpec where
import Prologue import Prologue
import Data.Functor.Both import Data.Functor.Both
import Data.String
import Diff
import Info import Info
import Range import Range
import Renderer.Patch import Renderer.Patch
import Source import Source
import Syntax import Syntax
import Category
import Test.Hspec import Test.Hspec
spec :: Spec spec :: Spec
spec = parallel $ spec = parallel $
describe "hunks" $ describe "hunks" $
it "empty diffs have empty hunks" $ it "empty diffs have empty hunks" $
hunks (free . Free $ pure (Info (Range 0 0) mempty 1) :< Leaf "") (both (SourceBlob (fromList "") "abcde" "path2.txt" (Just defaultPlainBlob)) (SourceBlob (fromList "") "xyz" "path2.txt" (Just defaultPlainBlob))) `shouldBe` [Hunk {offset = pure 0, changes = [], trailingContext = []}] hunks (free . Free $ pure (Info (Range 0 0) StringLiteral 1 0) :< Leaf "") (both (SourceBlob (fromList "") "abcde" "path2.txt" (Just defaultPlainBlob)) (SourceBlob (fromList "") "xyz" "path2.txt" (Just defaultPlainBlob))) `shouldBe` [Hunk {offset = pure 0, changes = [], trailingContext = []}]

View File

@ -6,7 +6,6 @@ import Data.Text.Arbitrary ()
import Prologue import Prologue
import Data.String import Data.String
import Category
import Interpreter import Interpreter
import Diff import Diff
import ArbitraryTerm import ArbitraryTerm
@ -19,9 +18,9 @@ spec = parallel $ do
describe "Diff" $ do describe "Diff" $ do
prop "equality is reflexive" $ prop "equality is reflexive" $
\ a b -> let diff = interpret comparable diffCost (unTerm a) (unTerm (b :: ArbitraryTerm String CategorySet)) in \ a b -> let diff = diffTerms (free . Free) ((==) `on` extract) diffCost (unTerm a) (unTerm (b :: ArbitraryTerm String CategorySet)) in
diff == diff diff == diff
prop "equal terms produce identity diffs" $ prop "equal terms produce identity diffs" $
\ a -> let term = unTerm (a :: ArbitraryTerm String CategorySet) in \ a -> let term = unTerm (a :: ArbitraryTerm String CategorySet) in
diffCost (interpret comparable diffCost term term) == 0 diffCost (diffTerms (free . Free) ((==) `on` extract) diffCost term term) == 0

2
test/crashers/573.A.js Normal file
View File

@ -0,0 +1,2 @@
if (a &&
b) {}

1
test/crashers/573.B.js Normal file
View File

@ -0,0 +1 @@
if (a && b) {}

View File

@ -1,14 +0,0 @@
<!DOCTYPE HTML>
<html><head><link rel="stylesheet" href="style.css"></head><body><table class="diff"><colgroup><col width="40"><col><col width="40"><col></colgroup><tr><td class="blob-num">1</td><td class="blob-code"><ul class="category-program"><li><ul class="category-expression_statement"><li><ul class="category-function_call"><li><ul class="category-member_access"><li><span class="category-identifier">console</span></li>.<li><span class="category-identifier">log</span></li></ul></li>(<li><ul class="category-arguments"><li><ul class="category-string"><li><span class="category-string">&#39;</span></li><li><span class="category-string">hello</span></li><li><span class="category-string">&#39;</span></li></ul></li></ul></li>)</ul></li>;</ul></li>
</ul></td>
<td class="blob-num">1</td><td class="blob-code"><ul class="category-program"><li><ul class="category-expression_statement"><li><ul class="category-function_call"><li><ul class="category-member_access"><li><span class="category-identifier">console</span></li>.<li><span class="category-identifier">log</span></li></ul></li>(<li><ul class="category-arguments"><li><ul class="category-string"><li><span class="category-string">&#39;</span></li><li><span class="category-string">hello</span></li><li><span class="category-string">&#39;</span></li></ul></li></ul></li>)</ul></li>;</ul></li>
</ul></td>
</tr><tr><td class="blob-num blob-num-empty empty-cell"></td><td class="blob-code blob-code-empty empty-cell"></td>
<td class="blob-num blob-num-replacement">2</td><td class="blob-code blob-code-replacement"><ul class="category-program"><li><div class="patch insert" data="5"><ul class="category-expression_statement"><li><ul class="category-function_call"><li><ul class="category-member_access"><li><span class="category-identifier">console</span></li>.<li><span class="category-identifier">log</span></li></ul></li>(<li><ul class="category-arguments"><li><ul class="category-string"><li><span class="category-string">&#39;</span></li><li><span class="category-string">world</span></li><li><span class="category-string">&#39;</span></li></ul></li></ul></li>)</ul></li>;</ul></div></li>
</ul></td>
</tr><tr><td class="blob-num">2</td><td class="blob-code"><ul class="category-program"></ul></td>
<td class="blob-num">3</td><td class="blob-code"><ul class="category-program"></ul></td>
</tr></table></body></html>

View File

@ -1,24 +0,0 @@
<!DOCTYPE HTML>
<html><head><link rel="stylesheet" href="style.css"></head><body><table class="diff"><colgroup><col width="40"><col><col width="40"><col></colgroup><tr><td class="blob-num">1</td><td class="blob-code"><ul class="category-program"><li><ul class="category-if_statement">if (<li><span class="category-true">true</span></li>) <li><ul class="category-statement_block">{
</ul></li></ul></li></ul></td>
<td class="blob-num">1</td><td class="blob-code"><ul class="category-program"><li><ul class="category-if_statement">if (<li><span class="category-true">true</span></li>) <li><ul class="category-statement_block">{
</ul></li></ul></li></ul></td>
</tr><tr><td class="blob-num">2</td><td class="blob-code"><ul class="category-program"><li><ul class="category-if_statement"><li><ul class="category-statement_block"> <li><ul class="category-expression_statement"><li><ul class="category-function_call"><li><ul class="category-member_access"><li><span class="category-identifier">console</span></li>.<li><span class="category-identifier">log</span></li></ul></li>(<li><ul class="category-arguments"><li><ul class="category-string"><li><span class="category-string">&#39;</span></li><li><span class="category-string">hello</span></li><li><span class="category-string">&#39;</span></li></ul></li></ul></li>)</ul></li>;</ul></li>
</ul></li></ul></li></ul></td>
<td class="blob-num">2</td><td class="blob-code"><ul class="category-program"><li><ul class="category-if_statement"><li><ul class="category-statement_block"> <li><ul class="category-expression_statement"><li><ul class="category-function_call"><li><ul class="category-member_access"><li><span class="category-identifier">console</span></li>.<li><span class="category-identifier">log</span></li></ul></li>(<li><ul class="category-arguments"><li><ul class="category-string"><li><span class="category-string">&#39;</span></li><li><span class="category-string">hello</span></li><li><span class="category-string">&#39;</span></li></ul></li></ul></li>)</ul></li>;</ul></li>
</ul></li></ul></li></ul></td>
</tr><tr><td class="blob-num blob-num-empty empty-cell"></td><td class="blob-code blob-code-empty empty-cell"></td>
<td class="blob-num blob-num-replacement">3</td><td class="blob-code blob-code-replacement"><ul class="category-program"><li><ul class="category-if_statement"><li><ul class="category-statement_block"> <li><div class="patch insert" data="5"><ul class="category-expression_statement"><li><ul class="category-function_call"><li><ul class="category-member_access"><li><span class="category-identifier">console</span></li>.<li><span class="category-identifier">log</span></li></ul></li>(<li><ul class="category-arguments"><li><ul class="category-string"><li><span class="category-string">&#39;</span></li><li><span class="category-string">world</span></li><li><span class="category-string">&#39;</span></li></ul></li></ul></li>)</ul></li>;</ul></div></li>
</ul></li></ul></li></ul></td>
</tr><tr><td class="blob-num">3</td><td class="blob-code"><ul class="category-program"><li><ul class="category-if_statement"><li><ul class="category-statement_block">}</ul></li><li><ul class="category-statement_block"></ul></li></ul></li>
</ul></td>
<td class="blob-num">4</td><td class="blob-code"><ul class="category-program"><li><ul class="category-if_statement"><li><ul class="category-statement_block">}</ul></li></ul></li>
</ul></td>
</tr><tr><td class="blob-num">4</td><td class="blob-code"><ul class="category-program"></ul></td>
<td class="blob-num">5</td><td class="blob-code"><ul class="category-program"></ul></td>
</tr></table></body></html>

View File

@ -1 +1 @@
{"rows":[[{"number":1,"terms":[{"range":[0,2],"categories":["program"],"children":[{"range":[0,2],"categories":["expression_statement"],"children":[{"range":[0,2],"categories":["DictionaryLiteral"],"children":{}}]}]}],"range":[0,2],"hasChanges":false},{"number":1,"terms":[{"range":[0,2],"categories":["program"],"children":[{"range":[0,2],"categories":["expression_statement"],"children":[{"range":[0,2],"categories":["DictionaryLiteral"],"children":{}}]}]}],"range":[0,2],"hasChanges":false}],[{"number":2,"terms":[{"range":[2,12],"categories":["program"],"children":[{"range":[2,12],"categories":["expression_statement"],"children":[{"range":[2,12],"categories":["DictionaryLiteral"],"children":{"\"b\"":{"range":[4,10],"categories":["Pair"],"children":[{"range":[4,7],"categories":["StringLiteral"],"children":[{"range":[4,5],"categories":["StringLiteral"]},{"range":[5,6],"categories":["StringLiteral"]},{"range":[6,7],"categories":["StringLiteral"]}]},{"patch":"replace","range":[9,10],"categories":["number"]}]}}}]}]}],"range":[2,12],"hasChanges":true},{"number":2,"terms":[{"range":[2,12],"categories":["program"],"children":[{"range":[2,12],"categories":["expression_statement"],"children":[{"range":[2,12],"categories":["DictionaryLiteral"],"children":{"\"b\"":{"range":[4,10],"categories":["Pair"],"children":[{"range":[4,7],"categories":["StringLiteral"],"children":[{"range":[4,5],"categories":["StringLiteral"]},{"range":[5,6],"categories":["StringLiteral"]},{"range":[6,7],"categories":["StringLiteral"]}]},{"patch":"replace","range":[9,10],"categories":["number"]}]}}}]}]}],"range":[2,12],"hasChanges":true}],[{"number":3,"terms":[{"range":[12,21],"categories":["program"],"children":[{"range":[12,21],"categories":["expression_statement"],"children":[{"range":[12,21],"categories":["DictionaryLiteral"],"children":{"\"a\"":{"range":[14,20],"categories":["Pair"],"children":[{"range":[14,17],"categories":["StringLiteral"],"children":[{"range":[14,15],"categories":["StringLiteral"]},{"range":[15,16],"categories":["StringLiteral"]},{"range":[16,17],"categories":["StringLiteral"]}]},{"range":[19,20],"categories":["number"]}]}}}]}]}],"range":[12,21],"hasChanges":false},{"number":3,"terms":[{"range":[12,21],"categories":["program"],"children":[{"range":[12,21],"categories":["expression_statement"],"children":[{"range":[12,21],"categories":["DictionaryLiteral"],"children":{"\"a\"":{"range":[14,20],"categories":["Pair"],"children":[{"range":[14,17],"categories":["StringLiteral"],"children":[{"range":[14,15],"categories":["StringLiteral"]},{"range":[15,16],"categories":["StringLiteral"]},{"range":[16,17],"categories":["StringLiteral"]}]},{"range":[19,20],"categories":["number"]}]}}}]}]}],"range":[12,21],"hasChanges":false}],[{"number":4,"terms":[{"range":[21,23],"categories":["program"],"children":[{"range":[21,23],"categories":["expression_statement"],"children":[{"range":[21,22],"categories":["DictionaryLiteral"],"children":{}}]}]}],"range":[21,23],"hasChanges":false},{"number":4,"terms":[{"range":[21,23],"categories":["program"],"children":[{"range":[21,23],"categories":["expression_statement"],"children":[{"range":[21,22],"categories":["DictionaryLiteral"],"children":{}}]}]}],"range":[21,23],"hasChanges":false}],[{"number":5,"terms":[{"range":[23,23],"categories":["program"],"children":[]}],"range":[23,23],"hasChanges":false},{"number":5,"terms":[{"range":[23,23],"categories":["program"],"children":[]}],"range":[23,23],"hasChanges":false}]],"oids":["",""],"paths":["test/diffs/dictionary.A.js","test/diffs/dictionary.B.js"]} {"rows":[[{"number":1,"terms":[{"range":[0,2],"category":"program","children":[{"range":[0,2],"category":"expression_statement","children":[{"range":[0,2],"category":"DictionaryLiteral","children":{}}]}]}],"range":[0,2],"hasChanges":false},{"number":1,"terms":[{"range":[0,2],"category":"program","children":[{"range":[0,2],"category":"expression_statement","children":[{"range":[0,2],"category":"DictionaryLiteral","children":{}}]}]}],"range":[0,2],"hasChanges":false}],[{"number":2,"terms":[{"range":[2,12],"category":"program","children":[{"range":[2,12],"category":"expression_statement","children":[{"range":[2,12],"category":"DictionaryLiteral","children":{"\"b\"":{"range":[4,10],"category":"Pair","children":[{"range":[4,7],"category":"StringLiteral","children":[{"range":[4,5],"category":"StringLiteral"},{"range":[5,6],"category":"StringLiteral"},{"range":[6,7],"category":"StringLiteral"}]},{"patch":"replace","range":[9,10],"category":"number"}]}}}]}]}],"range":[2,12],"hasChanges":true},{"number":2,"terms":[{"range":[2,12],"category":"program","children":[{"range":[2,12],"category":"expression_statement","children":[{"range":[2,12],"category":"DictionaryLiteral","children":{"\"b\"":{"range":[4,10],"category":"Pair","children":[{"range":[4,7],"category":"StringLiteral","children":[{"range":[4,5],"category":"StringLiteral"},{"range":[5,6],"category":"StringLiteral"},{"range":[6,7],"category":"StringLiteral"}]},{"patch":"replace","range":[9,10],"category":"number"}]}}}]}]}],"range":[2,12],"hasChanges":true}],[{"number":3,"terms":[{"range":[12,21],"category":"program","children":[{"range":[12,21],"category":"expression_statement","children":[{"range":[12,21],"category":"DictionaryLiteral","children":{"\"a\"":{"range":[14,20],"category":"Pair","children":[{"range":[14,17],"category":"StringLiteral","children":[{"range":[14,15],"category":"StringLiteral"},{"range":[15,16],"category":"StringLiteral"},{"range":[16,17],"category":"StringLiteral"}]},{"range":[19,20],"category":"number"}]}}}]}]}],"range":[12,21],"hasChanges":false},{"number":3,"terms":[{"range":[12,21],"category":"program","children":[{"range":[12,21],"category":"expression_statement","children":[{"range":[12,21],"category":"DictionaryLiteral","children":{"\"a\"":{"range":[14,20],"category":"Pair","children":[{"range":[14,17],"category":"StringLiteral","children":[{"range":[14,15],"category":"StringLiteral"},{"range":[15,16],"category":"StringLiteral"},{"range":[16,17],"category":"StringLiteral"}]},{"range":[19,20],"category":"number"}]}}}]}]}],"range":[12,21],"hasChanges":false}],[{"number":4,"terms":[{"range":[21,23],"category":"program","children":[{"range":[21,23],"category":"expression_statement","children":[{"range":[21,22],"category":"DictionaryLiteral","children":{}}]}]}],"range":[21,23],"hasChanges":false},{"number":4,"terms":[{"range":[21,23],"category":"program","children":[{"range":[21,23],"category":"expression_statement","children":[{"range":[21,22],"category":"DictionaryLiteral","children":{}}]}]}],"range":[21,23],"hasChanges":false}],[{"number":5,"terms":[{"range":[23,23],"category":"program","children":[]}],"range":[23,23],"hasChanges":false},{"number":5,"terms":[{"range":[23,23],"category":"program","children":[]}],"range":[23,23],"hasChanges":false}]],"oids":["",""],"paths":["test/diffs/dictionary.A.js","test/diffs/dictionary.B.js"]}

View File

@ -0,0 +1,14 @@
<!DOCTYPE HTML>
<html><head><link rel="stylesheet" href="style.css"></head><body><table class="diff"><colgroup><col width="40"><col><col width="40"><col></colgroup><tr><td class="blob-num">1</td><td class="blob-code"><ul class="category-program" data="11"><li><ul class="category-expression_statement" data="10"><li><ul class="category-function_call" data="9"><li><ul class="category-member_access" data="3"><li><span class="category-identifier" data="1">console</span></li>.<li><span class="category-identifier" data="1">log</span></li></ul></li>(<li><ul class="category-arguments" data="5"><li><ul class="category-string" data="4"><li><span class="category-string" data="1">&#39;</span></li><li><span class="category-string" data="1">hello</span></li><li><span class="category-string" data="1">&#39;</span></li></ul></li></ul></li>)</ul></li>;</ul></li>
</ul></td>
<td class="blob-num">1</td><td class="blob-code"><ul class="category-program" data="21"><li><ul class="category-expression_statement" data="10"><li><ul class="category-function_call" data="9"><li><ul class="category-member_access" data="3"><li><span class="category-identifier" data="1">console</span></li>.<li><span class="category-identifier" data="1">log</span></li></ul></li>(<li><ul class="category-arguments" data="5"><li><ul class="category-string" data="4"><li><span class="category-string" data="1">&#39;</span></li><li><span class="category-string" data="1">hello</span></li><li><span class="category-string" data="1">&#39;</span></li></ul></li></ul></li>)</ul></li>;</ul></li>
</ul></td>
</tr><tr><td class="blob-num blob-num-empty empty-cell"></td><td class="blob-code blob-code-empty empty-cell"></td>
<td class="blob-num blob-num-replacement">2</td><td class="blob-code blob-code-replacement"><ul class="category-program" data="21"><li><div class="patch insert" data="10"><ul class="category-expression_statement" data="10"><li><ul class="category-function_call" data="9"><li><ul class="category-member_access" data="3"><li><span class="category-identifier" data="1">console</span></li>.<li><span class="category-identifier" data="1">log</span></li></ul></li>(<li><ul class="category-arguments" data="5"><li><ul class="category-string" data="4"><li><span class="category-string" data="1">&#39;</span></li><li><span class="category-string" data="1">world</span></li><li><span class="category-string" data="1">&#39;</span></li></ul></li></ul></li>)</ul></li>;</ul></div></li>
</ul></td>
</tr><tr><td class="blob-num">2</td><td class="blob-code"><ul class="category-program" data="11"></ul></td>
<td class="blob-num">3</td><td class="blob-code"><ul class="category-program" data="21"></ul></td>
</tr></table></body></html>

View File

@ -0,0 +1,24 @@
<!DOCTYPE HTML>
<html><head><link rel="stylesheet" href="style.css"></head><body><table class="diff"><colgroup><col width="40"><col><col width="40"><col></colgroup><tr><td class="blob-num">1</td><td class="blob-code"><ul class="category-program" data="14"><li><ul class="category-if_statement" data="13">if (<li><span class="category-true" data="1">true</span></li>) <li><ul class="category-statement_block" data="11">{
</ul></li></ul></li></ul></td>
<td class="blob-num">1</td><td class="blob-code"><ul class="category-program" data="24"><li><ul class="category-if_statement" data="23">if (<li><span class="category-true" data="1">true</span></li>) <li><ul class="category-statement_block" data="21">{
</ul></li></ul></li></ul></td>
</tr><tr><td class="blob-num">2</td><td class="blob-code"><ul class="category-program" data="14"><li><ul class="category-if_statement" data="13"><li><ul class="category-statement_block" data="11"> <li><ul class="category-expression_statement" data="10"><li><ul class="category-function_call" data="9"><li><ul class="category-member_access" data="3"><li><span class="category-identifier" data="1">console</span></li>.<li><span class="category-identifier" data="1">log</span></li></ul></li>(<li><ul class="category-arguments" data="5"><li><ul class="category-string" data="4"><li><span class="category-string" data="1">&#39;</span></li><li><span class="category-string" data="1">hello</span></li><li><span class="category-string" data="1">&#39;</span></li></ul></li></ul></li>)</ul></li>;</ul></li>
</ul></li></ul></li></ul></td>
<td class="blob-num">2</td><td class="blob-code"><ul class="category-program" data="24"><li><ul class="category-if_statement" data="23"><li><ul class="category-statement_block" data="21"> <li><ul class="category-expression_statement" data="10"><li><ul class="category-function_call" data="9"><li><ul class="category-member_access" data="3"><li><span class="category-identifier" data="1">console</span></li>.<li><span class="category-identifier" data="1">log</span></li></ul></li>(<li><ul class="category-arguments" data="5"><li><ul class="category-string" data="4"><li><span class="category-string" data="1">&#39;</span></li><li><span class="category-string" data="1">hello</span></li><li><span class="category-string" data="1">&#39;</span></li></ul></li></ul></li>)</ul></li>;</ul></li>
</ul></li></ul></li></ul></td>
</tr><tr><td class="blob-num blob-num-empty empty-cell"></td><td class="blob-code blob-code-empty empty-cell"></td>
<td class="blob-num blob-num-replacement">3</td><td class="blob-code blob-code-replacement"><ul class="category-program" data="24"><li><ul class="category-if_statement" data="23"><li><ul class="category-statement_block" data="21"> <li><div class="patch insert" data="10"><ul class="category-expression_statement" data="10"><li><ul class="category-function_call" data="9"><li><ul class="category-member_access" data="3"><li><span class="category-identifier" data="1">console</span></li>.<li><span class="category-identifier" data="1">log</span></li></ul></li>(<li><ul class="category-arguments" data="5"><li><ul class="category-string" data="4"><li><span class="category-string" data="1">&#39;</span></li><li><span class="category-string" data="1">world</span></li><li><span class="category-string" data="1">&#39;</span></li></ul></li></ul></li>)</ul></li>;</ul></div></li>
</ul></li></ul></li></ul></td>
</tr><tr><td class="blob-num">3</td><td class="blob-code"><ul class="category-program" data="14"><li><ul class="category-if_statement" data="13"><li><ul class="category-statement_block" data="11">}</ul></li></ul></li>
</ul></td>
<td class="blob-num">4</td><td class="blob-code"><ul class="category-program" data="24"><li><ul class="category-if_statement" data="23"><li><ul class="category-statement_block" data="21">}</ul></li></ul></li>
</ul></td>
</tr><tr><td class="blob-num">4</td><td class="blob-code"><ul class="category-program" data="14"></ul></td>
<td class="blob-num">5</td><td class="blob-code"><ul class="category-program" data="24"></ul></td>
</tr></table></body></html>

View File

@ -1 +1 @@
{"rows":[[{"number":1,"terms":[{"range":[0,29],"categories":["program"],"children":[{"range":[0,28],"categories":["expression_statement"],"children":[{"range":[0,27],"categories":["FunctionCall"],"children":[{"range":[0,11],"categories":["member_access"],"children":[{"range":[0,7],"categories":["identifier"]},{"range":[8,11],"categories":["identifier"]}]},{"range":[12,26],"categories":["arguments"],"children":[{"range":[12,26],"categories":["StringLiteral"],"children":[{"range":[12,13],"categories":["StringLiteral"]},{"range":[13,18],"categories":["StringLiteral"]},{"range":[18,19],"categories":["StringLiteral"]},{"range":[20,25],"categories":["StringLiteral"]},{"range":[25,26],"categories":["StringLiteral"]}]}]}]}]}]}],"range":[0,29],"hasChanges":false},{"number":1,"terms":[{"range":[0,29],"categories":["program"],"children":[{"range":[0,28],"categories":["expression_statement"],"children":[{"range":[0,27],"categories":["FunctionCall"],"children":[{"range":[0,11],"categories":["member_access"],"children":[{"range":[0,7],"categories":["identifier"]},{"range":[8,11],"categories":["identifier"]}]},{"range":[12,26],"categories":["arguments"],"children":[{"range":[12,26],"categories":["StringLiteral"],"children":[{"range":[12,13],"categories":["StringLiteral"]},{"range":[13,18],"categories":["StringLiteral"]},{"range":[18,19],"categories":["StringLiteral"]},{"range":[20,25],"categories":["StringLiteral"]},{"range":[25,26],"categories":["StringLiteral"]}]}]}]}]}]}],"range":[0,29],"hasChanges":false}],[{"number":2,"terms":[{"range":[29,29],"categories":["program"],"children":[]}],"range":[29,29],"hasChanges":false},{"number":2,"terms":[{"range":[29,30],"categories":["program"],"children":[]}],"range":[29,30],"hasChanges":false}],[{"number":3,"terms":[{"range":[30,56],"categories":["program"],"children":[{"patch":"insert","range":[30,55],"categories":["expression_statement"],"children":[{"range":[30,54],"categories":["FunctionCall"],"children":[{"range":[30,41],"categories":["member_access"],"children":[{"range":[30,37],"categories":["identifier"]},{"range":[38,41],"categories":["identifier"]}]},{"range":[42,53],"categories":["arguments"],"children":[{"range":[42,53],"categories":["StringLiteral"],"children":[{"range":[42,43],"categories":["StringLiteral"]},{"range":[43,52],"categories":["StringLiteral"]},{"range":[52,53],"categories":["StringLiteral"]}]}]}]}]}]}],"range":[30,56],"hasChanges":true}],[{"number":4,"terms":[{"range":[56,56],"categories":["program"],"children":[]}],"range":[56,56],"hasChanges":false}]],"oids":["",""],"paths":["test/diffs/newline-at-eof.A.js","test/diffs/newline-at-eof.B.js"]} {"rows":[[{"number":1,"terms":[{"range":[0,29],"category":"program","children":[{"range":[0,28],"category":"expression_statement","children":[{"range":[0,27],"category":"FunctionCall","children":[{"range":[0,11],"category":"member_access","children":[{"range":[0,7],"category":"identifier"},{"range":[8,11],"category":"identifier"}]},{"range":[12,26],"category":"arguments","children":[{"range":[12,26],"category":"StringLiteral","children":[{"range":[12,13],"category":"StringLiteral"},{"range":[13,18],"category":"StringLiteral"},{"range":[18,19],"category":"StringLiteral"},{"range":[20,25],"category":"StringLiteral"},{"range":[25,26],"category":"StringLiteral"}]}]}]}]}]}],"range":[0,29],"hasChanges":false},{"number":1,"terms":[{"range":[0,29],"category":"program","children":[{"range":[0,28],"category":"expression_statement","children":[{"range":[0,27],"category":"FunctionCall","children":[{"range":[0,11],"category":"member_access","children":[{"range":[0,7],"category":"identifier"},{"range":[8,11],"category":"identifier"}]},{"range":[12,26],"category":"arguments","children":[{"range":[12,26],"category":"StringLiteral","children":[{"range":[12,13],"category":"StringLiteral"},{"range":[13,18],"category":"StringLiteral"},{"range":[18,19],"category":"StringLiteral"},{"range":[20,25],"category":"StringLiteral"},{"range":[25,26],"category":"StringLiteral"}]}]}]}]}]}],"range":[0,29],"hasChanges":false}],[{"number":2,"terms":[{"range":[29,29],"category":"program","children":[]}],"range":[29,29],"hasChanges":false},{"number":2,"terms":[{"range":[29,30],"category":"program","children":[]}],"range":[29,30],"hasChanges":false}],[{"number":3,"terms":[{"range":[30,56],"category":"program","children":[{"patch":"insert","range":[30,55],"category":"expression_statement","children":[{"range":[30,54],"category":"FunctionCall","children":[{"range":[30,41],"category":"member_access","children":[{"range":[30,37],"category":"identifier"},{"range":[38,41],"category":"identifier"}]},{"range":[42,53],"category":"arguments","children":[{"range":[42,53],"category":"StringLiteral","children":[{"range":[42,43],"category":"StringLiteral"},{"range":[43,52],"category":"StringLiteral"},{"range":[52,53],"category":"StringLiteral"}]}]}]}]}]}],"range":[30,56],"hasChanges":true}],[{"number":4,"terms":[{"range":[56,56],"category":"program","children":[]}],"range":[56,56],"hasChanges":false}]],"oids":["",""],"paths":["test/diffs/newline-at-eof.A.js","test/diffs/newline-at-eof.B.js"]}

View File

@ -1 +1 @@
{"rows":[[{"number":1,"terms":[{"range":[0,28],"categories":["program"],"children":[{"range":[0,28],"categories":["expression_statement"],"children":[{"range":[0,27],"categories":["FunctionCall"],"children":[{"range":[0,11],"categories":["member_access"],"children":[{"range":[0,7],"categories":["identifier"]},{"range":[8,11],"categories":["identifier"]}]},{"range":[12,26],"categories":["arguments"],"children":[{"range":[12,26],"categories":["StringLiteral"],"children":[{"range":[12,13],"categories":["StringLiteral"]},{"range":[13,18],"categories":["StringLiteral"]},{"range":[18,19],"categories":["StringLiteral"]},{"range":[20,25],"categories":["StringLiteral"]},{"range":[25,26],"categories":["StringLiteral"]}]}]}]}]}]}],"range":[0,28],"hasChanges":false},{"number":1,"terms":[{"range":[0,29],"categories":["program"],"children":[{"range":[0,28],"categories":["expression_statement"],"children":[{"range":[0,27],"categories":["FunctionCall"],"children":[{"range":[0,11],"categories":["member_access"],"children":[{"range":[0,7],"categories":["identifier"]},{"range":[8,11],"categories":["identifier"]}]},{"range":[12,26],"categories":["arguments"],"children":[{"range":[12,26],"categories":["StringLiteral"],"children":[{"range":[12,13],"categories":["StringLiteral"]},{"range":[13,18],"categories":["StringLiteral"]},{"range":[18,19],"categories":["StringLiteral"]},{"range":[20,25],"categories":["StringLiteral"]},{"range":[25,26],"categories":["StringLiteral"]}]}]}]}]}]}],"range":[0,29],"hasChanges":false}],[{"number":2,"terms":[{"range":[29,30],"categories":["program"],"children":[]}],"range":[29,30],"hasChanges":false}],[{"number":3,"terms":[{"range":[30,55],"categories":["program"],"children":[{"patch":"insert","range":[30,55],"categories":["expression_statement"],"children":[{"range":[30,54],"categories":["FunctionCall"],"children":[{"range":[30,41],"categories":["member_access"],"children":[{"range":[30,37],"categories":["identifier"]},{"range":[38,41],"categories":["identifier"]}]},{"range":[42,53],"categories":["arguments"],"children":[{"range":[42,53],"categories":["StringLiteral"],"children":[{"range":[42,43],"categories":["StringLiteral"]},{"range":[43,52],"categories":["StringLiteral"]},{"range":[52,53],"categories":["StringLiteral"]}]}]}]}]}]}],"range":[30,55],"hasChanges":true}]],"oids":["",""],"paths":["test/diffs/no-newline-at-eof.A.js","test/diffs/no-newline-at-eof.B.js"]} {"rows":[[{"number":1,"terms":[{"range":[0,28],"category":"program","children":[{"range":[0,28],"category":"expression_statement","children":[{"range":[0,27],"category":"FunctionCall","children":[{"range":[0,11],"category":"member_access","children":[{"range":[0,7],"category":"identifier"},{"range":[8,11],"category":"identifier"}]},{"range":[12,26],"category":"arguments","children":[{"range":[12,26],"category":"StringLiteral","children":[{"range":[12,13],"category":"StringLiteral"},{"range":[13,18],"category":"StringLiteral"},{"range":[18,19],"category":"StringLiteral"},{"range":[20,25],"category":"StringLiteral"},{"range":[25,26],"category":"StringLiteral"}]}]}]}]}]}],"range":[0,28],"hasChanges":false},{"number":1,"terms":[{"range":[0,29],"category":"program","children":[{"range":[0,28],"category":"expression_statement","children":[{"range":[0,27],"category":"FunctionCall","children":[{"range":[0,11],"category":"member_access","children":[{"range":[0,7],"category":"identifier"},{"range":[8,11],"category":"identifier"}]},{"range":[12,26],"category":"arguments","children":[{"range":[12,26],"category":"StringLiteral","children":[{"range":[12,13],"category":"StringLiteral"},{"range":[13,18],"category":"StringLiteral"},{"range":[18,19],"category":"StringLiteral"},{"range":[20,25],"category":"StringLiteral"},{"range":[25,26],"category":"StringLiteral"}]}]}]}]}]}],"range":[0,29],"hasChanges":false}],[{"number":2,"terms":[{"range":[29,30],"category":"program","children":[]}],"range":[29,30],"hasChanges":false}],[{"number":3,"terms":[{"range":[30,55],"category":"program","children":[{"patch":"insert","range":[30,55],"category":"expression_statement","children":[{"range":[30,54],"category":"FunctionCall","children":[{"range":[30,41],"category":"member_access","children":[{"range":[30,37],"category":"identifier"},{"range":[38,41],"category":"identifier"}]},{"range":[42,53],"category":"arguments","children":[{"range":[42,53],"category":"StringLiteral","children":[{"range":[42,43],"category":"StringLiteral"},{"range":[43,52],"category":"StringLiteral"},{"range":[52,53],"category":"StringLiteral"}]}]}]}]}]}],"range":[30,55],"hasChanges":true}]],"oids":["",""],"paths":["test/diffs/no-newline-at-eof.A.js","test/diffs/no-newline-at-eof.B.js"]}

@ -1 +1 @@
Subproject commit 40a3d5376f56e76f4530c3fa3a86f857e457c30f Subproject commit 36a5a0fc1ff4990f34842a27bc9c6cb0d181a50e

35
weekly/2015-06-01.md Normal file
View File

@ -0,0 +1,35 @@
# Planning Meeting June 1, 2016
## Observations
@robrix:
- Since Alignment Diff PR was merged, it offers a good chance to step back and look at what would be most valuable in support of Diff Summaries and the 90 day goals.
- Want to triage Diff Summaries and if we should be reprioritizing staff shipping Semantic Diff now that Alignment is merged.
@joshvera:
- Reviewed open issues to prepare for planning meeting while evaluating how they fit into our milestones.
- Thinking about how we can ensure resilience if Semantic Diff times out, or fails and having fall back to traditional diffing.
- Also thinking about how to ensure Semantic Diff can render correctly.
## Focus Points
- Update semantic diff sha in github/github so semantic diff can be updated in github local development environments [PR](https://github.com/github/github/pull/56240)
- Improving Diff Summaries (performance and structure)
- Profiling and algorithm improvements to SES (Performance label)
- Fix Ruby Parser
- Unicorn Timeouts (algorithmic improvement, better fallback (should probably be driven by workflow tools))
- Benchmarking
- Other Features: detecting and rendering moves (as part of DotCom milestone)
## Plan
@robrix & @joshvera:
- Rob to set aside Benchmarking for now, focus on profiling (pair with Rick).
- Josh and Rick to pair on Diff Summary performance while Rob is out.
- Have Rick take a look at the Unicorn timeouts (involving some profiling to identify where things are slowest).
- Rob on vacation starting Tuesday June 7th (for 10 days)
- Schedule mini-summit for week of June 20th.
- Longer term planning including solidifying a road map will be held at the mini-summit.

81
weekly/2016-05-31.md Normal file
View File

@ -0,0 +1,81 @@
# May 31th, 2016
NB: On Tuesday this week since Monday was Memorial Day.
## Agenda
1. Retrospective on last week:
- What went well?
- What was challenging?
- What did you learn?
## What went well?
@joshvera:
- Diff summaries merged.
- Introduced a new prelude.
- Pairing with Rick.
- Alignment!
@rewinfrey:
- Pairing with Josh.
- Was at MoonConf and enjoyed the conference.
- Almost finished with Haskell Tic Tac Toe.
@robrix:
- Alignment resolved!
- Getting diff summaries merged.
## What was challenging?
@joshvera:
- Didn't make as much progress on the structure of diff summaries as desired.
@rewinfrey:
- Continuing to adjust to the project.
- Hit an edge case in Minimax that is tricky.
@robrix:
- Not sure why the line approach in alignment solved the problem.
- In stack 1.1 you cannot rely on it rebuilding internal packages (must clean and rebuild semantic diff tool)
## What did you learn?
@joshvera:
- Learned about the different types of preludes.
- Learned about an extension in GHC 8.0 that introduces Applicative Do syntax, but is tricky to use in parallel computations because of the order of executation.
@rewinfrey:
- Learned about different recursion schemes (zygotmorphism
- The transition from thinking about function application to thinking about function composition was an eye-opener.
- Understanding a lot more about why certain shapes are advantageous.
- http://livestream.com/accounts/16500216/events/5467460 (first video)
- Greg Pfeil's talk: 02:24 Recursion Where FP Hits Bottom
- Amar Shah's talk: 04:24 Point Free or Die
- Ashley Powell's talk: 05:57 Negotiating Salary for Women in Tech
- Patrick Thomson's talk: 06:25 Bracer: Transforming Real-World Languages with Coproducts and Recursion Schemes
@robrix:
- Learned a lot about GHCi debugging.
- Remembered to question assumptions, specifically about alignment property tests that were throwing results off.
- Using Arbitrary for purposes other than QuickCheck.
## Other Items
@robrix:
- Planning this week about the timeline for staffshipping diff summaries.
- Planning for a possible mini-summit in late June.