From a3a895d1072cb8b60076d24befaefb3d6a5940db Mon Sep 17 00:00:00 2001 From: Timothy Clem Date: Wed, 8 Feb 2017 10:57:13 -0800 Subject: [PATCH 01/11] Another iterator for go method toc summaries --- src/Renderer/TOC.hs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Renderer/TOC.hs b/src/Renderer/TOC.hs index f5bb7ba58..c78b038d5 100644 --- a/src/Renderer/TOC.hs +++ b/src/Renderer/TOC.hs @@ -153,9 +153,14 @@ toTermName :: forall leaf fields. DefaultFields fields => Source Char -> SyntaxT toTermName source term = case unwrap term of S.Function identifier _ _ _ -> toTermName' identifier S.Method identifier Nothing _ _ _ -> toTermName' identifier - S.Method identifier (Just receiver) _ _ _ -> toTermName' receiver <> "." <> toTermName' identifier + S.Method identifier (Just receiver) _ _ _ -> case unwrap receiver of + S.Indexed [receiverParams] -> case unwrap receiverParams of + S.ParameterDecl (Just ty) _ -> "(" <> toTermName' ty <> ") " <> toTermName' identifier + _ -> toMethodNameWithReceiver receiver identifier + _ -> toMethodNameWithReceiver receiver identifier _ -> termNameFromSource term where + toMethodNameWithReceiver receiver name = toTermName' receiver <> "." <> toTermName' name toTermName' = toTermName source termNameFromSource term = termNameFromRange (range term) termNameFromRange range = toText $ Source.slice range source From 31519dda4260991811cccf9a62782a1032db12f7 Mon Sep 17 00:00:00 2001 From: Timothy Clem Date: Mon, 13 Feb 2017 16:15:38 -0800 Subject: [PATCH 02/11] Remove dups, including matching method names --- src/Renderer/TOC.hs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/Renderer/TOC.hs b/src/Renderer/TOC.hs index 8e77e8a3f..c28e11a3b 100644 --- a/src/Renderer/TOC.hs +++ b/src/Renderer/TOC.hs @@ -5,6 +5,7 @@ import Category as C import Data.Aeson import Data.Functor.Both hiding (fst, snd) import qualified Data.Functor.Both as Both +import Data.Text (toLower) import Data.Record import Diff import Info @@ -69,8 +70,16 @@ diffTOC blobs diff = do toJSONSummaries noDupes where removeDupes :: [TOCSummary DiffInfo] -> [TOCSummary DiffInfo] - removeDupes [] = [] - removeDupes xs = (fmap unsafeHead . List.groupBy (\a b -> parentInfo a == parentInfo b)) xs + removeDupes = foldl' (\xs x -> if x `isInSummaries` xs then xs else xs <> [x]) [] + isInSummaries summary = List.any (\x -> + let + a = parentInfo x + b = parentInfo summary + in + case (a, b) of + (Summarizable _ nameA _ _, Summarizable _ nameB _ _) -> a == b || toLower nameA == toLower nameB + (_, _) -> a == b + ) diffToTOCSummaries :: (StringConv leaf Text, DefaultFields fields) => Both Source -> SyntaxDiff leaf fields -> [TOCSummary DiffInfo] diffToTOCSummaries sources = para $ \diff -> From d68633f6430d1df63d88d4905b040e7bbd1ee61d Mon Sep 17 00:00:00 2001 From: Timothy Clem Date: Mon, 13 Feb 2017 17:18:25 -0800 Subject: [PATCH 03/11] WIP - identify similar methods/functions --- src/Renderer/TOC.hs | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/src/Renderer/TOC.hs b/src/Renderer/TOC.hs index c28e11a3b..d8fb26a26 100644 --- a/src/Renderer/TOC.hs +++ b/src/Renderer/TOC.hs @@ -15,10 +15,10 @@ import qualified Data.List as List import qualified Data.Map as Map hiding (null) import Renderer import Source hiding (null) +import SourceSpan import Syntax as S import Term import Patch -import Unsafe (unsafeHead) data JSONSummary = JSONSummary { info :: Summarizable } | ErrorSummary { error :: Text, errorSpan :: SourceSpan } @@ -70,16 +70,23 @@ diffTOC blobs diff = do toJSONSummaries noDupes where removeDupes :: [TOCSummary DiffInfo] -> [TOCSummary DiffInfo] - removeDupes = foldl' (\xs x -> if x `isInSummaries` xs then xs else xs <> [x]) [] - isInSummaries summary = List.any (\x -> - let - a = parentInfo x - b = parentInfo summary - in - case (a, b) of - (Summarizable _ nameA _ _, Summarizable _ nameB _ _) -> a == b || toLower nameA == toLower nameB - (_, _) -> a == b - ) + removeDupes = foldl' (\xs x -> case (x `elem` xs, x `findSimilarSummaryIndex` xs) of + (True, Nothing) -> xs + (False, Nothing) -> xs <> [x] + (_, Just n) -> + let + (a, _ : b) = List.splitAt n xs + name = case parentInfo x of + (Summarizable _ name _ _) -> name + _ -> "xx" + item = x { parentInfo = Summarizable C.Function name SourceSpan.emptySourceSpan "modified" } + in + a <> (item : b) + ) [] + findSimilarSummaryIndex summary = List.findIndex (isSimilarSummary summary) + isSimilarSummary a b = case (parentInfo a, parentInfo b) of + (Summarizable _ nameA _ _, Summarizable _ nameB _ _) -> toLower nameA == toLower nameB + (_, _) -> False diffToTOCSummaries :: (StringConv leaf Text, DefaultFields fields) => Both Source -> SyntaxDiff leaf fields -> [TOCSummary DiffInfo] diffToTOCSummaries sources = para $ \diff -> From 277054019115fa644c64bbb7744dc074146aff0d Mon Sep 17 00:00:00 2001 From: Timothy Clem Date: Mon, 13 Feb 2017 17:30:33 -0800 Subject: [PATCH 04/11] Make sure we have a real Summarizable to replace with --- src/Renderer/TOC.hs | 16 ++++++--------- test.A.js | 43 +++++++++++++++++++++++++++++++++++++++ test.B.js | 49 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 98 insertions(+), 10 deletions(-) create mode 100644 test.A.js create mode 100644 test.B.js diff --git a/src/Renderer/TOC.hs b/src/Renderer/TOC.hs index d8fb26a26..059af615b 100644 --- a/src/Renderer/TOC.hs +++ b/src/Renderer/TOC.hs @@ -15,7 +15,6 @@ import qualified Data.List as List import qualified Data.Map as Map hiding (null) import Renderer import Source hiding (null) -import SourceSpan import Syntax as S import Term import Patch @@ -65,9 +64,7 @@ toc blobs diff = TOCOutput $ Map.fromList [ summaries = diffTOC blobs diff diffTOC :: (StringConv leaf Text, DefaultFields fields) => Both SourceBlob -> SyntaxDiff leaf fields -> [JSONSummary] -diffTOC blobs diff = do - noDupes <- removeDupes (diffToTOCSummaries (source <$> blobs) diff) - toJSONSummaries noDupes +diffTOC blobs diff = removeDupes (diffToTOCSummaries (source <$> blobs) diff) >>= toJSONSummaries where removeDupes :: [TOCSummary DiffInfo] -> [TOCSummary DiffInfo] removeDupes = foldl' (\xs x -> case (x `elem` xs, x `findSimilarSummaryIndex` xs) of @@ -75,16 +72,15 @@ diffTOC blobs diff = do (False, Nothing) -> xs <> [x] (_, Just n) -> let - (a, _ : b) = List.splitAt n xs - name = case parentInfo x of - (Summarizable _ name _ _) -> name - _ -> "xx" - item = x { parentInfo = Summarizable C.Function name SourceSpan.emptySourceSpan "modified" } + (a, existingItem : b) = List.splitAt n xs + (Summarizable category name sourceSpan _) = parentInfo existingItem + replacement = x { parentInfo = Summarizable category name sourceSpan "modified" } in - a <> (item : b) + a <> (replacement : b) ) [] findSimilarSummaryIndex summary = List.findIndex (isSimilarSummary summary) isSimilarSummary a b = case (parentInfo a, parentInfo b) of + -- TODO: Only do this for methods in the same source file? (Summarizable _ nameA _ _, Summarizable _ nameB _ _) -> toLower nameA == toLower nameB (_, _) -> False diff --git a/test.A.js b/test.A.js new file mode 100644 index 000000000..6c4442952 --- /dev/null +++ b/test.A.js @@ -0,0 +1,43 @@ +/* @flow */ + +import $ from '../jquery' +import cast from '../typecast' +import {on} from 'delegated-events' +import {submit} from '../form' + +let allowSubmit = false + +function performHealthcheck() { + const repoName = cast(document.getElementById('showcase_item_name_with_owner'), HTMLInputElement).value + const submitButton = cast(document.getElementById('submit'), HTMLButtonElement) + const hiddenForm = cast(document.getElementsByClassName('js-submit-health-check')[0], HTMLFormElement) + const targetRepoName = cast(document.getElementById('repo_name'), HTMLInputElement) + document.getElementById("js-health").innerHTML = "Performing health check..." + targetRepoName.value = repoName + submitButton.disabled = false + allowSubmit = true + submit(hiddenForm) +} + +function test() { + console.log("hi"); + return 0; +} + +on('submit', '#new_showcase_item', function(e) { + if (!allowSubmit) { e.preventDefault() } +}) + +$(document).on('ajaxSuccess', '.js-health', function(event, xhr, settings, data) { + this.innerHTML = data +}) + +on('focusout', '#showcase_item_name_with_owner', function() { + performHealthcheck() +}) + +on('focusin', '#showcase_item_body', function() { + if (cast(document.getElementById('showcase_item_name_with_owner'), HTMLInputElement).type === 'hidden') { + performHealthcheck() + } +}) diff --git a/test.B.js b/test.B.js new file mode 100644 index 000000000..8c2d76650 --- /dev/null +++ b/test.B.js @@ -0,0 +1,49 @@ +/* @flow */ + +import cast from '../typecast' +import {changeValue} from '../form' +import {fetchSafeDocumentFragment} from '../fetch' +import {observe} from '../observe' + +function performHealthCheck(container, repoName) { + const formCheck = cast(document.querySelector('.js-repo-health-check'), HTMLFormElement) + const nameToCheck = cast(formCheck.querySelector('.js-repo-health-name'), HTMLInputElement) + nameToCheck.value = repoName + + const completedIndicator = cast(container.querySelector('.js-repo-health-check-completed'), HTMLInputElement) + changeValue(completedIndicator, '') + container.classList.remove('d-none') + container.classList.add('is-loading') + + return fetchSafeDocumentFragment(document, formCheck.action, { + method: 'POST', + body: new FormData(formCheck), + }).then(html => { + const results = cast(container.querySelector('.js-repo-health-results'), HTMLElement) + results.innerHTML = '' + results.appendChild(html) + + container.classList.remove('is-loading') + changeValue(completedIndicator, '1') + }) +} + +function test() { + return 0; +} + +observe('.js-repo-health', function(container: HTMLElement) { + const form = cast(container.closest('form'), HTMLFormElement) + const repoInput = cast(form.querySelector('.js-repo-name'), HTMLInputElement) + + if (repoInput.type === 'hidden') { + const description = cast(form.querySelector('.js-comment-field'), HTMLTextAreaElement) + description.addEventListener('focus', () => { + performHealthCheck(container, repoInput.value) + }) + } else { + repoInput.addEventListener('change', () => { + performHealthCheck(container, repoInput.value) + }) + } +}) From 8bc4da76c92de4d604fc00dbb350609aac48ce2a Mon Sep 17 00:00:00 2001 From: Timothy Clem Date: Tue, 14 Feb 2017 08:27:00 -0800 Subject: [PATCH 05/11] Have to check parentInfo for duplicates --- src/Renderer/TOC.hs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Renderer/TOC.hs b/src/Renderer/TOC.hs index 059af615b..defa514eb 100644 --- a/src/Renderer/TOC.hs +++ b/src/Renderer/TOC.hs @@ -67,7 +67,7 @@ diffTOC :: (StringConv leaf Text, DefaultFields fields) => Both SourceBlob -> Sy diffTOC blobs diff = removeDupes (diffToTOCSummaries (source <$> blobs) diff) >>= toJSONSummaries where removeDupes :: [TOCSummary DiffInfo] -> [TOCSummary DiffInfo] - removeDupes = foldl' (\xs x -> case (x `elem` xs, x `findSimilarSummaryIndex` xs) of + removeDupes = foldl' (\xs x -> case (x `anyDuplicateSummary` xs, x `findSimilarSummaryIndex` xs) of (True, Nothing) -> xs (False, Nothing) -> xs <> [x] (_, Just n) -> @@ -78,10 +78,10 @@ diffTOC blobs diff = removeDupes (diffToTOCSummaries (source <$> blobs) diff) >> in a <> (replacement : b) ) [] + anyDuplicateSummary a = List.any (\b -> parentInfo a == parentInfo b) findSimilarSummaryIndex summary = List.findIndex (isSimilarSummary summary) isSimilarSummary a b = case (parentInfo a, parentInfo b) of - -- TODO: Only do this for methods in the same source file? - (Summarizable _ nameA _ _, Summarizable _ nameB _ _) -> toLower nameA == toLower nameB + (Summarizable catA nameA _ _, Summarizable catB nameB _ _) -> catA == catB && toLower nameA == toLower nameB (_, _) -> False diffToTOCSummaries :: (StringConv leaf Text, DefaultFields fields) => Both Source -> SyntaxDiff leaf fields -> [TOCSummary DiffInfo] From 4e336520bc963ab2c3ec9dfd3403484b0e009eaf Mon Sep 17 00:00:00 2001 From: Timothy Clem Date: Tue, 14 Feb 2017 08:35:10 -0800 Subject: [PATCH 06/11] Remove test files --- test.A.js | 43 ------------------------------------------- test.B.js | 49 ------------------------------------------------- 2 files changed, 92 deletions(-) delete mode 100644 test.A.js delete mode 100644 test.B.js diff --git a/test.A.js b/test.A.js deleted file mode 100644 index 6c4442952..000000000 --- a/test.A.js +++ /dev/null @@ -1,43 +0,0 @@ -/* @flow */ - -import $ from '../jquery' -import cast from '../typecast' -import {on} from 'delegated-events' -import {submit} from '../form' - -let allowSubmit = false - -function performHealthcheck() { - const repoName = cast(document.getElementById('showcase_item_name_with_owner'), HTMLInputElement).value - const submitButton = cast(document.getElementById('submit'), HTMLButtonElement) - const hiddenForm = cast(document.getElementsByClassName('js-submit-health-check')[0], HTMLFormElement) - const targetRepoName = cast(document.getElementById('repo_name'), HTMLInputElement) - document.getElementById("js-health").innerHTML = "Performing health check..." - targetRepoName.value = repoName - submitButton.disabled = false - allowSubmit = true - submit(hiddenForm) -} - -function test() { - console.log("hi"); - return 0; -} - -on('submit', '#new_showcase_item', function(e) { - if (!allowSubmit) { e.preventDefault() } -}) - -$(document).on('ajaxSuccess', '.js-health', function(event, xhr, settings, data) { - this.innerHTML = data -}) - -on('focusout', '#showcase_item_name_with_owner', function() { - performHealthcheck() -}) - -on('focusin', '#showcase_item_body', function() { - if (cast(document.getElementById('showcase_item_name_with_owner'), HTMLInputElement).type === 'hidden') { - performHealthcheck() - } -}) diff --git a/test.B.js b/test.B.js deleted file mode 100644 index 8c2d76650..000000000 --- a/test.B.js +++ /dev/null @@ -1,49 +0,0 @@ -/* @flow */ - -import cast from '../typecast' -import {changeValue} from '../form' -import {fetchSafeDocumentFragment} from '../fetch' -import {observe} from '../observe' - -function performHealthCheck(container, repoName) { - const formCheck = cast(document.querySelector('.js-repo-health-check'), HTMLFormElement) - const nameToCheck = cast(formCheck.querySelector('.js-repo-health-name'), HTMLInputElement) - nameToCheck.value = repoName - - const completedIndicator = cast(container.querySelector('.js-repo-health-check-completed'), HTMLInputElement) - changeValue(completedIndicator, '') - container.classList.remove('d-none') - container.classList.add('is-loading') - - return fetchSafeDocumentFragment(document, formCheck.action, { - method: 'POST', - body: new FormData(formCheck), - }).then(html => { - const results = cast(container.querySelector('.js-repo-health-results'), HTMLElement) - results.innerHTML = '' - results.appendChild(html) - - container.classList.remove('is-loading') - changeValue(completedIndicator, '1') - }) -} - -function test() { - return 0; -} - -observe('.js-repo-health', function(container: HTMLElement) { - const form = cast(container.closest('form'), HTMLFormElement) - const repoInput = cast(form.querySelector('.js-repo-name'), HTMLInputElement) - - if (repoInput.type === 'hidden') { - const description = cast(form.querySelector('.js-comment-field'), HTMLTextAreaElement) - description.addEventListener('focus', () => { - performHealthCheck(container, repoInput.value) - }) - } else { - repoInput.addEventListener('change', () => { - performHealthCheck(container, repoInput.value) - }) - } -}) From 1facca0b50a8f61ee49029ce7fdf84032921546c Mon Sep 17 00:00:00 2001 From: Timothy Clem Date: Tue, 14 Feb 2017 11:29:24 -0800 Subject: [PATCH 07/11] Add failing toc summary test and refactor removeDupes --- semantic-diff.cabal | 1 + src/Renderer/TOC.hs | 33 ++++++++++---------- test/Spec.hs | 2 ++ test/TOCSpec.hs | 74 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 93 insertions(+), 17 deletions(-) create mode 100644 test/TOCSpec.hs diff --git a/semantic-diff.cabal b/semantic-diff.cabal index 8da7988c6..b6364ef71 100644 --- a/semantic-diff.cabal +++ b/semantic-diff.cabal @@ -172,6 +172,7 @@ test-suite test , RangeSpec , Source.Spec , TermSpec + , TOCSpec , Test.Hspec.LeanCheck build-depends: array , base diff --git a/src/Renderer/TOC.hs b/src/Renderer/TOC.hs index defa514eb..555b24a48 100644 --- a/src/Renderer/TOC.hs +++ b/src/Renderer/TOC.hs @@ -1,5 +1,5 @@ {-# LANGUAGE ScopedTypeVariables #-} -module Renderer.TOC (toc) where +module Renderer.TOC (toc, diffTOC, JSONSummary(..), Summarizable(..)) where import Category as C import Data.Aeson @@ -67,22 +67,21 @@ diffTOC :: (StringConv leaf Text, DefaultFields fields) => Both SourceBlob -> Sy diffTOC blobs diff = removeDupes (diffToTOCSummaries (source <$> blobs) diff) >>= toJSONSummaries where removeDupes :: [TOCSummary DiffInfo] -> [TOCSummary DiffInfo] - removeDupes = foldl' (\xs x -> case (x `anyDuplicateSummary` xs, x `findSimilarSummaryIndex` xs) of - (True, Nothing) -> xs - (False, Nothing) -> xs <> [x] - (_, Just n) -> - let - (a, existingItem : b) = List.splitAt n xs - (Summarizable category name sourceSpan _) = parentInfo existingItem - replacement = x { parentInfo = Summarizable category name sourceSpan "modified" } - in - a <> (replacement : b) - ) [] - anyDuplicateSummary a = List.any (\b -> parentInfo a == parentInfo b) - findSimilarSummaryIndex summary = List.findIndex (isSimilarSummary summary) - isSimilarSummary a b = case (parentInfo a, parentInfo b) of - (Summarizable catA nameA _ _, Summarizable catB nameB _ _) -> catA == catB && toLower nameA == toLower nameB - (_, _) -> False + removeDupes = foldl' go [] + where + go xs x | (_, _ : _) <- find exactMatch x xs = xs + | (front, existingItem : back) <- find similarMatch x xs = + let + (Summarizable category name sourceSpan _) = parentInfo existingItem + replacement = x { parentInfo = Summarizable category name sourceSpan "modified" } + in + front <> (replacement : back) + | otherwise = xs <> [x] + find p x = List.break (p x) + exactMatch a b = parentInfo a == parentInfo b + similarMatch a b = case (parentInfo a, parentInfo b) of + (Summarizable catA nameA _ _, Summarizable catB nameB _ _) -> catA == catB && toLower nameA == toLower nameB + (_, _) -> False diffToTOCSummaries :: (StringConv leaf Text, DefaultFields fields) => Both Source -> SyntaxDiff leaf fields -> [TOCSummary DiffInfo] diffToTOCSummaries sources = para $ \diff -> diff --git a/test/Spec.hs b/test/Spec.hs index 71480f5b5..66c1283f8 100644 --- a/test/Spec.hs +++ b/test/Spec.hs @@ -12,6 +12,7 @@ import qualified PatchOutputSpec import qualified RangeSpec import qualified Source.Spec import qualified TermSpec +import qualified TOCSpec import Test.Hspec main :: IO () @@ -27,3 +28,4 @@ main = hspec . parallel $ do describe "Range" RangeSpec.spec describe "Source" Source.Spec.spec describe "Term" TermSpec.spec + describe "TOC" TOCSpec.spec diff --git a/test/TOCSpec.hs b/test/TOCSpec.hs new file mode 100644 index 000000000..9109259d9 --- /dev/null +++ b/test/TOCSpec.hs @@ -0,0 +1,74 @@ +{-# LANGUAGE DataKinds #-} +module TOCSpec where + +import Category +import Data.Functor.Both +-- import Data.Functor.Listable +-- import Data.List (partition) +import Data.RandomWalkSimilarity +import Data.Record +import Diff +import Renderer.TOC +import Info +-- import Interpreter +import Patch +import Prologue hiding (fst, snd) +import Source +import Syntax +-- import Term +import Test.Hspec (Spec, describe, it, parallel) +import Test.Hspec.Expectations.Pretty +import Data.These +import Parse +import Diffing +import Interpreter + +spec :: Spec +spec = parallel $ do + describe "tocSummaries" $ do + it "blank if there are no methods" $ + diffTOC blobs blankDiff `shouldBe` [ ] + + it "summarizes methods" $ do + sourceBlobs <- blobsForPaths (both "/Users/tclem/github/semantic-diff/test.A.js" "/Users/tclem/github/semantic-diff/test.B.js") + diff <- testDiff sourceBlobs + diffTOC sourceBlobs diff `shouldBe` [ ] + +testDiff :: Both SourceBlob -> IO (Diff (Syntax Text) (Record '[Cost, Range, Category, SourceSpan])) +testDiff sourceBlobs = do + terms <- traverse (fmap (defaultFeatureVectorDecorator getLabel) . parser) sourceBlobs + pure $! stripDiff (diffTerms' terms sourceBlobs) + where + parser = parserWithCost (path . fst $ sourceBlobs) + diffTerms' terms blobs = case runBothWith areNullOids blobs of + (True, False) -> pure $ Insert (snd terms) + (False, True) -> pure $ Delete (fst terms) + (_, _) -> runBothWith (diffTerms construct compareCategoryEq diffCostWithCachedTermCosts) terms + areNullOids a b = (hasNullOid a, hasNullOid b) + hasNullOid blob = oid blob == nullOid || Source.null (source blob) + 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 + Pure patch -> uncurry both (fromThese 0 0 (unPatch (cost . extract <$> patch))) + +blobsForPaths :: Both FilePath -> IO (Both SourceBlob) +blobsForPaths paths = do + sources <- sequence $ readAndTranscodeFile <$> paths + pure $ SourceBlob <$> sources <*> pure mempty <*> paths <*> pure (Just Source.defaultPlainBlob) + + +sourceSpanBetween :: (Int, Int) -> (Int, Int) -> SourceSpan +sourceSpanBetween (s1, e1) (s2, e2) = SourceSpan (SourcePos s1 e1) (SourcePos s2 e2) + +arrayInfo :: Record '[Category, Range, SourceSpan] +arrayInfo = ArrayLiteral :. Range 0 3 :. sourceSpanBetween (1, 1) (1, 5) :. Nil + +literalInfo :: Record '[Category, Range, SourceSpan] +literalInfo = StringLiteral :. Range 1 2 :. sourceSpanBetween (1, 2) (1, 4) :. Nil + +blankDiff :: Diff (Syntax Text) (Record '[Category, Range, SourceSpan]) +blankDiff = free $ Free (pure arrayInfo :< Indexed [ free $ Pure (Insert (cofree $ literalInfo :< Leaf "\"a\"")) ]) + +blobs :: Both SourceBlob +blobs = both (SourceBlob (fromText "[]") nullOid "a.js" (Just defaultPlainBlob)) (SourceBlob (fromText "[a]") nullOid "b.js" (Just defaultPlainBlob)) From 94f606da8128bce2192800d31707d9a300e04937 Mon Sep 17 00:00:00 2001 From: Timothy Clem Date: Tue, 14 Feb 2017 11:53:25 -0800 Subject: [PATCH 08/11] Make the dedupe tests pass --- test/TOCSpec.hs | 11 ++++-- test/corpus/toc/javascript/dupParent.A.js | 3 ++ test/corpus/toc/javascript/dupParent.B.js | 6 +++ test/corpus/toc/javascript/test.A.js | 38 +++++++++++++++++++ test/corpus/toc/javascript/test.B.js | 45 +++++++++++++++++++++++ 5 files changed, 100 insertions(+), 3 deletions(-) create mode 100644 test/corpus/toc/javascript/dupParent.A.js create mode 100644 test/corpus/toc/javascript/dupParent.B.js create mode 100644 test/corpus/toc/javascript/test.A.js create mode 100644 test/corpus/toc/javascript/test.B.js diff --git a/test/TOCSpec.hs b/test/TOCSpec.hs index 9109259d9..a5614a568 100644 --- a/test/TOCSpec.hs +++ b/test/TOCSpec.hs @@ -29,10 +29,15 @@ spec = parallel $ do it "blank if there are no methods" $ diffTOC blobs blankDiff `shouldBe` [ ] - it "summarizes methods" $ do - sourceBlobs <- blobsForPaths (both "/Users/tclem/github/semantic-diff/test.A.js" "/Users/tclem/github/semantic-diff/test.B.js") + it "dedupes changes in same parent method" $ do + sourceBlobs <- blobsForPaths (both "test/corpus/toc/javascript/dupParent.A.js" "test/corpus/toc/javascript/dupParent.B.js") diff <- testDiff sourceBlobs - diffTOC sourceBlobs diff `shouldBe` [ ] + diffTOC sourceBlobs diff `shouldBe` [ JSONSummary $ InSummarizable Category.Function "myFunction" (sourceSpanBetween (1, 1) (6, 2)) ] + + it "dedupes similar methods" $ do + sourceBlobs <- blobsForPaths (both "test/corpus/toc/javascript/test.A.js" "test/corpus/toc/javascript/test.B.js") + diff <- testDiff sourceBlobs + diffTOC sourceBlobs diff `shouldBe` [ JSONSummary $ Summarizable Category.Function "performHealthCheck" (sourceSpanBetween (8, 1) (29, 2)) "modified" ] testDiff :: Both SourceBlob -> IO (Diff (Syntax Text) (Record '[Cost, Range, Category, SourceSpan])) testDiff sourceBlobs = do diff --git a/test/corpus/toc/javascript/dupParent.A.js b/test/corpus/toc/javascript/dupParent.A.js new file mode 100644 index 000000000..1f3e01f84 --- /dev/null +++ b/test/corpus/toc/javascript/dupParent.A.js @@ -0,0 +1,3 @@ +function myFunction() { + return 0; +} diff --git a/test/corpus/toc/javascript/dupParent.B.js b/test/corpus/toc/javascript/dupParent.B.js new file mode 100644 index 000000000..3145f834f --- /dev/null +++ b/test/corpus/toc/javascript/dupParent.B.js @@ -0,0 +1,6 @@ +function myFunction() { + if (true) { + console.log(); + } + return 1; +} diff --git a/test/corpus/toc/javascript/test.A.js b/test/corpus/toc/javascript/test.A.js new file mode 100644 index 000000000..251e61ef0 --- /dev/null +++ b/test/corpus/toc/javascript/test.A.js @@ -0,0 +1,38 @@ +/* @flow */ + +import $ from '../jquery' +import cast from '../typecast' +import {on} from 'delegated-events' +import {submit} from '../form' + +let allowSubmit = false + +function performHealthcheck() { + const repoName = cast(document.getElementById('showcase_item_name_with_owner'), HTMLInputElement).value + const submitButton = cast(document.getElementById('submit'), HTMLButtonElement) + const hiddenForm = cast(document.getElementsByClassName('js-submit-health-check')[0], HTMLFormElement) + const targetRepoName = cast(document.getElementById('repo_name'), HTMLInputElement) + document.getElementById("js-health").innerHTML = "Performing health check..." + targetRepoName.value = repoName + submitButton.disabled = false + allowSubmit = true + submit(hiddenForm) +} + +on('submit', '#new_showcase_item', function(e) { + if (!allowSubmit) { e.preventDefault() } +}) + +$(document).on('ajaxSuccess', '.js-health', function(event, xhr, settings, data) { + this.innerHTML = data +}) + +on('focusout', '#showcase_item_name_with_owner', function() { + performHealthcheck() +}) + +on('focusin', '#showcase_item_body', function() { + if (cast(document.getElementById('showcase_item_name_with_owner'), HTMLInputElement).type === 'hidden') { + performHealthcheck() + } +}) diff --git a/test/corpus/toc/javascript/test.B.js b/test/corpus/toc/javascript/test.B.js new file mode 100644 index 000000000..b91cdd649 --- /dev/null +++ b/test/corpus/toc/javascript/test.B.js @@ -0,0 +1,45 @@ +/* @flow */ + +import cast from '../typecast' +import {changeValue} from '../form' +import {fetchSafeDocumentFragment} from '../fetch' +import {observe} from '../observe' + +function performHealthCheck(container, repoName) { + const formCheck = cast(document.querySelector('.js-repo-health-check'), HTMLFormElement) + const nameToCheck = cast(formCheck.querySelector('.js-repo-health-name'), HTMLInputElement) + nameToCheck.value = repoName + + const completedIndicator = cast(container.querySelector('.js-repo-health-check-completed'), HTMLInputElement) + changeValue(completedIndicator, '') + container.classList.remove('d-none') + container.classList.add('is-loading') + + return fetchSafeDocumentFragment(document, formCheck.action, { + method: 'POST', + body: new FormData(formCheck), + }).then(html => { + const results = cast(container.querySelector('.js-repo-health-results'), HTMLElement) + results.innerHTML = '' + results.appendChild(html) + + container.classList.remove('is-loading') + changeValue(completedIndicator, '1') + }) +} + +observe('.js-repo-health', function(container: HTMLElement) { + const form = cast(container.closest('form'), HTMLFormElement) + const repoInput = cast(form.querySelector('.js-repo-name'), HTMLInputElement) + + if (repoInput.type === 'hidden') { + const description = cast(form.querySelector('.js-comment-field'), HTMLTextAreaElement) + description.addEventListener('focus', () => { + performHealthCheck(container, repoInput.value) + }) + } else { + repoInput.addEventListener('change', () => { + performHealthCheck(container, repoInput.value) + }) + } +}) From 64119b3fc3aa41f13c54e0fff4696d57cc74c72a Mon Sep 17 00:00:00 2001 From: Timothy Clem Date: Tue, 14 Feb 2017 11:54:32 -0800 Subject: [PATCH 09/11] Remove unused imports and sort --- test/TOCSpec.hs | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/test/TOCSpec.hs b/test/TOCSpec.hs index a5614a568..449237939 100644 --- a/test/TOCSpec.hs +++ b/test/TOCSpec.hs @@ -3,25 +3,20 @@ module TOCSpec where import Category import Data.Functor.Both --- import Data.Functor.Listable --- import Data.List (partition) import Data.RandomWalkSimilarity import Data.Record +import Data.These import Diff -import Renderer.TOC +import Diffing import Info --- import Interpreter -import Patch +import Interpreter +import Parse import Prologue hiding (fst, snd) +import Renderer.TOC import Source import Syntax --- import Term import Test.Hspec (Spec, describe, it, parallel) import Test.Hspec.Expectations.Pretty -import Data.These -import Parse -import Diffing -import Interpreter spec :: Spec spec = parallel $ do From be88ebb9f6dca515c082cbb9f1b61eb1d805a8c9 Mon Sep 17 00:00:00 2001 From: Timothy Clem Date: Tue, 14 Feb 2017 12:48:34 -0800 Subject: [PATCH 10/11] Import Patch --- test/TOCSpec.hs | 1 + 1 file changed, 1 insertion(+) diff --git a/test/TOCSpec.hs b/test/TOCSpec.hs index 449237939..6e69e3f62 100644 --- a/test/TOCSpec.hs +++ b/test/TOCSpec.hs @@ -11,6 +11,7 @@ import Diffing import Info import Interpreter import Parse +import Patch import Prologue hiding (fst, snd) import Renderer.TOC import Source From 51ad73bb4d2de83f3330221bc51daa7b8c098431 Mon Sep 17 00:00:00 2001 From: Timothy Clem Date: Tue, 14 Feb 2017 13:02:25 -0800 Subject: [PATCH 11/11] Better names for these test fixtures --- test/TOCSpec.hs | 2 +- .../toc/javascript/{test.A.js => erroneousDupMethod.A.js} | 0 .../toc/javascript/{test.B.js => erroneousDupMethod.B.js} | 0 3 files changed, 1 insertion(+), 1 deletion(-) rename test/corpus/toc/javascript/{test.A.js => erroneousDupMethod.A.js} (100%) rename test/corpus/toc/javascript/{test.B.js => erroneousDupMethod.B.js} (100%) diff --git a/test/TOCSpec.hs b/test/TOCSpec.hs index 6e69e3f62..644b32d9d 100644 --- a/test/TOCSpec.hs +++ b/test/TOCSpec.hs @@ -31,7 +31,7 @@ spec = parallel $ do diffTOC sourceBlobs diff `shouldBe` [ JSONSummary $ InSummarizable Category.Function "myFunction" (sourceSpanBetween (1, 1) (6, 2)) ] it "dedupes similar methods" $ do - sourceBlobs <- blobsForPaths (both "test/corpus/toc/javascript/test.A.js" "test/corpus/toc/javascript/test.B.js") + sourceBlobs <- blobsForPaths (both "test/corpus/toc/javascript/erroneousDupMethod.A.js" "test/corpus/toc/javascript/erroneousDupMethod.B.js") diff <- testDiff sourceBlobs diffTOC sourceBlobs diff `shouldBe` [ JSONSummary $ Summarizable Category.Function "performHealthCheck" (sourceSpanBetween (8, 1) (29, 2)) "modified" ] diff --git a/test/corpus/toc/javascript/test.A.js b/test/corpus/toc/javascript/erroneousDupMethod.A.js similarity index 100% rename from test/corpus/toc/javascript/test.A.js rename to test/corpus/toc/javascript/erroneousDupMethod.A.js diff --git a/test/corpus/toc/javascript/test.B.js b/test/corpus/toc/javascript/erroneousDupMethod.B.js similarity index 100% rename from test/corpus/toc/javascript/test.B.js rename to test/corpus/toc/javascript/erroneousDupMethod.B.js