From d8d739d56b02d4dbbe772bc432b8aca9fd4b6de4 Mon Sep 17 00:00:00 2001 From: YeonJuan Date: Mon, 19 May 2025 01:12:19 +0900 Subject: [PATCH 1/2] fix: handle consecutive-empty-files --- src/__fixtures__/consecutive-empty-files | 14 ++++++ src/__tests__/__snapshots__/31.test.ts.snap | 7 ++- .../consecutive-empty-files.test.ts.snap | 48 +++++++++++++++++++ src/__tests__/consecutive-empty-files.test.ts | 10 ++++ src/parse-git-diff.ts | 3 +- 5 files changed, 79 insertions(+), 3 deletions(-) create mode 100644 src/__fixtures__/consecutive-empty-files create mode 100644 src/__tests__/__snapshots__/consecutive-empty-files.test.ts.snap create mode 100644 src/__tests__/consecutive-empty-files.test.ts diff --git a/src/__fixtures__/consecutive-empty-files b/src/__fixtures__/consecutive-empty-files new file mode 100644 index 0000000..98a3f8e --- /dev/null +++ b/src/__fixtures__/consecutive-empty-files @@ -0,0 +1,14 @@ +diff --git a/content b/content +new file mode 100644 +index 0000000..6b584e8 +--- /dev/null ++++ b/content +@@ -0,0 +1 @@ ++content +\ No newline at end of file +diff --git a/empty b/empty +new file mode 100644 +index 0000000..e69de29 +diff --git a/empty2 b/empty2 +new file mode 100644 +index 0000000..e69de29 \ No newline at end of file diff --git a/src/__tests__/__snapshots__/31.test.ts.snap b/src/__tests__/__snapshots__/31.test.ts.snap index 0df8961..074b94b 100644 --- a/src/__tests__/__snapshots__/31.test.ts.snap +++ b/src/__tests__/__snapshots__/31.test.ts.snap @@ -3,6 +3,11 @@ exports[`issue 31 parse \`31\` 1`] = ` { "files": [ + { + "chunks": [], + "path": "var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/epicshop/diff/advanced-react-apis/04.01.solution/7h2jowvfi2q/index.test.tsx", + "type": "AddedFile", + }, { "chunks": [ { @@ -406,7 +411,7 @@ exports[`issue 31 parse \`31\` 1`] = ` }, ], "path": "var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/epicshop/diff/advanced-react-apis/04.01.solution/7h2jowvfi2q/index.tsx", - "type": "AddedFile", + "type": "ChangedFile", }, ], "type": "GitDiff", diff --git a/src/__tests__/__snapshots__/consecutive-empty-files.test.ts.snap b/src/__tests__/__snapshots__/consecutive-empty-files.test.ts.snap new file mode 100644 index 0000000..c58a934 --- /dev/null +++ b/src/__tests__/__snapshots__/consecutive-empty-files.test.ts.snap @@ -0,0 +1,48 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`consecutive-empty-files parse \`consecutive-empty-files\` 1`] = ` +{ + "files": [ + { + "chunks": [ + { + "changes": [ + { + "content": "content", + "lineAfter": 1, + "type": "AddedLine", + }, + { + "content": "No newline at end of file", + "type": "MessageLine", + }, + ], + "context": undefined, + "fromFileRange": { + "lines": 0, + "start": 0, + }, + "toFileRange": { + "lines": 1, + "start": 1, + }, + "type": "Chunk", + }, + ], + "path": "content", + "type": "AddedFile", + }, + { + "chunks": [], + "path": "empty", + "type": "AddedFile", + }, + { + "chunks": [], + "path": "empty2", + "type": "AddedFile", + }, + ], + "type": "GitDiff", +} +`; diff --git a/src/__tests__/consecutive-empty-files.test.ts b/src/__tests__/consecutive-empty-files.test.ts new file mode 100644 index 0000000..b862d96 --- /dev/null +++ b/src/__tests__/consecutive-empty-files.test.ts @@ -0,0 +1,10 @@ +import { getFixture } from './test-utils'; +import parseGitDiff from '../parse-git-diff'; + +describe('consecutive-empty-files', () => { + const fixture = getFixture('consecutive-empty-files'); + + it('parse `consecutive-empty-files`', () => { + expect(parseGitDiff(fixture)).toMatchSnapshot(); + }); +}); diff --git a/src/parse-git-diff.ts b/src/parse-git-diff.ts index cd8c27d..cb0655a 100644 --- a/src/parse-git-diff.ts +++ b/src/parse-git-diff.ts @@ -210,7 +210,7 @@ function parseChunk(context: Context): AnyChunk | undefined { function parseExtendedHeader(ctx: Context) { if (isComparisonInputLine(ctx.getCurLine())) { - ctx.nextLine(); + return null; } const line = ctx.getCurLine(); const type = ExtendedHeaderValues.find((v) => line.startsWith(v)); @@ -279,7 +279,6 @@ function parseChunkHeader(ctx: Context) { toFileRange: getRange(addStart, addLines), } as const; } - const [all, delStart, delLines, addStart, addLines, context] = normalChunkExec; ctx.nextLine(); From 5eba3663f5eb38d63edcd2db7276233cbe1bcb05 Mon Sep 17 00:00:00 2001 From: YeonJuan Date: Mon, 19 May 2025 01:27:23 +0900 Subject: [PATCH 2/2] fix: handle no-prefix --- src/__fixtures__/31-no-prefix | 235 ++++ src/__tests__/31-no-prefix.test.ts | 10 + src/__tests__/31.test.ts | 2 +- .../__snapshots__/31-no-prefix.test.ts.snap | 1186 +++++++++++++++++ src/parse-git-diff.ts | 5 +- 5 files changed, 1433 insertions(+), 5 deletions(-) create mode 100644 src/__fixtures__/31-no-prefix create mode 100644 src/__tests__/31-no-prefix.test.ts create mode 100644 src/__tests__/__snapshots__/31-no-prefix.test.ts.snap diff --git a/src/__fixtures__/31-no-prefix b/src/__fixtures__/31-no-prefix new file mode 100644 index 0000000..209812f --- /dev/null +++ b/src/__fixtures__/31-no-prefix @@ -0,0 +1,235 @@ +diff --git var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/epicshop/diff/advanced-react-apis/09.03.solution/dn2ncwjsbmo/index.test.ts var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/epicshop/diff/advanced-react-apis/09.03.solution/dn2ncwjsbmo/index.test.ts +new file mode 100644 +index 0000000..e69de29 +diff --git var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/epicshop/diff/advanced-react-apis/playground/dn2ncwjsbmo/index.tsx var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/epicshop/diff/advanced-react-apis/09.03.solution/dn2ncwjsbmo/index.tsx +index 4d68325..fd576f7 100644 +--- var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/epicshop/diff/advanced-react-apis/playground/dn2ncwjsbmo/index.tsx ++++ var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/epicshop/diff/advanced-react-apis/09.03.solution/dn2ncwjsbmo/index.tsx +@@ -1,190 +1,54 @@ +-import { createContext, useEffect, useState, use, useCallback } from 'react' ++import { Suspense, useSyncExternalStore } from 'react' + import * as ReactDOM from 'react-dom/client' +-import { +- type BlogPost, +- generateGradient, +- getMatchingPosts, +-} from '#shared/blog-posts' +-import { setGlobalSearchParams } from '#shared/utils' + +-type SearchParamsTuple = readonly [ +- URLSearchParams, +- typeof setGlobalSearchParams, +-] +-const SearchParamsContext = createContext([ +- new URLSearchParams(window.location.search), +- setGlobalSearchParams, +-]) +- +-function SearchParamsProvider({ children }: { children: React.ReactNode }) { +- const [searchParams, setSearchParamsState] = useState( +- () => new URLSearchParams(window.location.search), +- ) ++export function makeMediaQueryStore(mediaQuery: string) { ++ function getSnapshot() { ++ return window.matchMedia(mediaQuery).matches ++ } + +- useEffect(() => { +- function updateSearchParams() { +- setSearchParamsState((prevParams) => { +- const newParams = new URLSearchParams(window.location.search) +- return prevParams.toString() === newParams.toString() +- ? prevParams +- : newParams +- }) ++ function subscribe(callback: () => void) { ++ const mediaQueryList = window.matchMedia(mediaQuery) ++ mediaQueryList.addEventListener('change', callback) ++ return () => { ++ mediaQueryList.removeEventListener('change', callback) + } +- window.addEventListener('popstate', updateSearchParams) +- return () => window.removeEventListener('popstate', updateSearchParams) +- }, []) +- +- const setSearchParams = useCallback( +- (...args: Parameters) => { +- const searchParams = setGlobalSearchParams(...args) +- setSearchParamsState((prevParams) => { +- return prevParams.toString() === searchParams.toString() +- ? prevParams +- : searchParams +- }) +- return searchParams +- }, +- [], +- ) +- +- const searchParamsTuple = [searchParams, setSearchParams] as const +- +- return ( +- +- {children} +- +- ) +-} +- +-function useSearchParams() { +- return use(SearchParamsContext) +-} +- +-const getQueryParam = (params: URLSearchParams) => params.get('query') ?? '' +- +-function App() { +- return ( +- +-
+-
+- +-
+-
+- ) +-} +- +-function Form() { +- const [searchParams, setSearchParams] = useSearchParams() +- const query = getQueryParam(searchParams) +- +- const words = query.split(' ').map((w) => w.trim()) +- +- const dogChecked = words.includes('dog') +- const catChecked = words.includes('cat') +- const caterpillarChecked = words.includes('caterpillar') +- +- function handleCheck(tag: string, checked: boolean) { +- const newWords = checked ? [...words, tag] : words.filter((w) => w !== tag) +- setSearchParams( +- { query: newWords.filter(Boolean).join(' ').trim() }, +- { replace: true }, +- ) + } + +- return ( +- e.preventDefault()}> +-
+- +- +- setSearchParams({ query: e.currentTarget.value }, { replace: true }) +- } +- /> +-
+-
+- +- +- +-
+- +- ) ++ return function useMediaQuery() { ++ return useSyncExternalStore(subscribe, getSnapshot) ++ } + } + +-function MatchingPosts() { +- const [searchParams] = useSearchParams() +- const query = getQueryParam(searchParams) +- const matchingPosts = getMatchingPosts(query) ++const useNarrowMediaQuery = makeMediaQueryStore('(max-width: 600px)') + +- return ( +-
    +- {matchingPosts.map((post) => ( +- +- ))} +-
+- ) ++function NarrowScreenNotifier() { ++ const isNarrow = useNarrowMediaQuery() ++ return isNarrow ? 'You are on a narrow screen' : 'You are on a wide screen' + } + +-function Card({ post }: { post: BlogPost }) { +- const [isFavorited, setIsFavorited] = useState(false) ++function App() { + return ( +-
  • +- {isFavorited ? ( +- +- ) : ( +- +- )} +-
  • ++
    ++
    This is your narrow screen state:
    ++ ++ ++ ++
    + ) + } + + const rootEl = document.createElement('div') + document.body.append(rootEl) +-ReactDOM.createRoot(rootEl).render() ++// 🦉 here's how we pretend we're server-rendering ++rootEl.innerHTML = (await import('react-dom/server')).renderToString() ++ ++// 🦉 here's how we simulate a delay in hydrating with client-side js ++await new Promise((resolve) => setTimeout(resolve, 1000)) ++ ++ReactDOM.hydrateRoot(rootEl, , { ++ onRecoverableError(error) { ++ if (String(error).includes('Missing getServerSnapshot')) return ++ ++ console.error(error) ++ }, ++}) \ No newline at end of file diff --git a/src/__tests__/31-no-prefix.test.ts b/src/__tests__/31-no-prefix.test.ts new file mode 100644 index 0000000..f620690 --- /dev/null +++ b/src/__tests__/31-no-prefix.test.ts @@ -0,0 +1,10 @@ +import { getFixture } from './test-utils'; +import parseGitDiff from '../parse-git-diff'; + +describe('issue 31-no-prefix', () => { + const fixture = getFixture('31-no-prefix'); + + it('parse `31-no-prefix`', () => { + expect(parseGitDiff(fixture, { noPrefix: true })).toMatchSnapshot(); + }); +}); diff --git a/src/__tests__/31.test.ts b/src/__tests__/31.test.ts index b073b88..4c77c1b 100644 --- a/src/__tests__/31.test.ts +++ b/src/__tests__/31.test.ts @@ -1,7 +1,7 @@ import { getFixture } from './test-utils'; import parseGitDiff from '../parse-git-diff'; -describe.only('issue 31', () => { +describe('issue 31', () => { const fixture = getFixture('31'); it('parse `31`', () => { diff --git a/src/__tests__/__snapshots__/31-no-prefix.test.ts.snap b/src/__tests__/__snapshots__/31-no-prefix.test.ts.snap new file mode 100644 index 0000000..f6f0ad0 --- /dev/null +++ b/src/__tests__/__snapshots__/31-no-prefix.test.ts.snap @@ -0,0 +1,1186 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`issue 31-no-prefix parse \`31-no-prefix\` 1`] = ` +{ + "files": [ + { + "chunks": [], + "path": "var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/epicshop/diff/advanced-react-apis/09.03.solution/dn2ncwjsbmo/index.test.ts", + "type": "AddedFile", + }, + { + "chunks": [ + { + "changes": [ + { + "content": "import { createContext, useEffect, useState, use, useCallback } from 'react'", + "lineBefore": 1, + "type": "DeletedLine", + }, + { + "content": "import { Suspense, useSyncExternalStore } from 'react'", + "lineAfter": 1, + "type": "AddedLine", + }, + { + "content": "import * as ReactDOM from 'react-dom/client'", + "lineAfter": 2, + "lineBefore": 2, + "type": "UnchangedLine", + }, + { + "content": "import {", + "lineBefore": 3, + "type": "DeletedLine", + }, + { + "content": " type BlogPost,", + "lineBefore": 4, + "type": "DeletedLine", + }, + { + "content": " generateGradient,", + "lineBefore": 5, + "type": "DeletedLine", + }, + { + "content": " getMatchingPosts,", + "lineBefore": 6, + "type": "DeletedLine", + }, + { + "content": "} from '#shared/blog-posts'", + "lineBefore": 7, + "type": "DeletedLine", + }, + { + "content": "import { setGlobalSearchParams } from '#shared/utils'", + "lineBefore": 8, + "type": "DeletedLine", + }, + { + "content": "", + "lineAfter": 3, + "lineBefore": 9, + "type": "UnchangedLine", + }, + { + "content": "type SearchParamsTuple = readonly [", + "lineBefore": 10, + "type": "DeletedLine", + }, + { + "content": " URLSearchParams,", + "lineBefore": 11, + "type": "DeletedLine", + }, + { + "content": " typeof setGlobalSearchParams,", + "lineBefore": 12, + "type": "DeletedLine", + }, + { + "content": "]", + "lineBefore": 13, + "type": "DeletedLine", + }, + { + "content": "const SearchParamsContext = createContext([", + "lineBefore": 14, + "type": "DeletedLine", + }, + { + "content": " new URLSearchParams(window.location.search),", + "lineBefore": 15, + "type": "DeletedLine", + }, + { + "content": " setGlobalSearchParams,", + "lineBefore": 16, + "type": "DeletedLine", + }, + { + "content": "])", + "lineBefore": 17, + "type": "DeletedLine", + }, + { + "content": "", + "lineBefore": 18, + "type": "DeletedLine", + }, + { + "content": "function SearchParamsProvider({ children }: { children: React.ReactNode }) {", + "lineBefore": 19, + "type": "DeletedLine", + }, + { + "content": " const [searchParams, setSearchParamsState] = useState(", + "lineBefore": 20, + "type": "DeletedLine", + }, + { + "content": " () => new URLSearchParams(window.location.search),", + "lineBefore": 21, + "type": "DeletedLine", + }, + { + "content": " )", + "lineBefore": 22, + "type": "DeletedLine", + }, + { + "content": "export function makeMediaQueryStore(mediaQuery: string) {", + "lineAfter": 4, + "type": "AddedLine", + }, + { + "content": " function getSnapshot() {", + "lineAfter": 5, + "type": "AddedLine", + }, + { + "content": " return window.matchMedia(mediaQuery).matches", + "lineAfter": 6, + "type": "AddedLine", + }, + { + "content": " }", + "lineAfter": 7, + "type": "AddedLine", + }, + { + "content": "", + "lineAfter": 8, + "lineBefore": 23, + "type": "UnchangedLine", + }, + { + "content": " useEffect(() => {", + "lineBefore": 24, + "type": "DeletedLine", + }, + { + "content": " function updateSearchParams() {", + "lineBefore": 25, + "type": "DeletedLine", + }, + { + "content": " setSearchParamsState((prevParams) => {", + "lineBefore": 26, + "type": "DeletedLine", + }, + { + "content": " const newParams = new URLSearchParams(window.location.search)", + "lineBefore": 27, + "type": "DeletedLine", + }, + { + "content": " return prevParams.toString() === newParams.toString()", + "lineBefore": 28, + "type": "DeletedLine", + }, + { + "content": " ? prevParams", + "lineBefore": 29, + "type": "DeletedLine", + }, + { + "content": " : newParams", + "lineBefore": 30, + "type": "DeletedLine", + }, + { + "content": " })", + "lineBefore": 31, + "type": "DeletedLine", + }, + { + "content": " function subscribe(callback: () => void) {", + "lineAfter": 9, + "type": "AddedLine", + }, + { + "content": " const mediaQueryList = window.matchMedia(mediaQuery)", + "lineAfter": 10, + "type": "AddedLine", + }, + { + "content": " mediaQueryList.addEventListener('change', callback)", + "lineAfter": 11, + "type": "AddedLine", + }, + { + "content": " return () => {", + "lineAfter": 12, + "type": "AddedLine", + }, + { + "content": " mediaQueryList.removeEventListener('change', callback)", + "lineAfter": 13, + "type": "AddedLine", + }, + { + "content": " }", + "lineAfter": 14, + "lineBefore": 32, + "type": "UnchangedLine", + }, + { + "content": " window.addEventListener('popstate', updateSearchParams)", + "lineBefore": 33, + "type": "DeletedLine", + }, + { + "content": " return () => window.removeEventListener('popstate', updateSearchParams)", + "lineBefore": 34, + "type": "DeletedLine", + }, + { + "content": " }, [])", + "lineBefore": 35, + "type": "DeletedLine", + }, + { + "content": "", + "lineBefore": 36, + "type": "DeletedLine", + }, + { + "content": " const setSearchParams = useCallback(", + "lineBefore": 37, + "type": "DeletedLine", + }, + { + "content": " (...args: Parameters) => {", + "lineBefore": 38, + "type": "DeletedLine", + }, + { + "content": " const searchParams = setGlobalSearchParams(...args)", + "lineBefore": 39, + "type": "DeletedLine", + }, + { + "content": " setSearchParamsState((prevParams) => {", + "lineBefore": 40, + "type": "DeletedLine", + }, + { + "content": " return prevParams.toString() === searchParams.toString()", + "lineBefore": 41, + "type": "DeletedLine", + }, + { + "content": " ? prevParams", + "lineBefore": 42, + "type": "DeletedLine", + }, + { + "content": " : searchParams", + "lineBefore": 43, + "type": "DeletedLine", + }, + { + "content": " })", + "lineBefore": 44, + "type": "DeletedLine", + }, + { + "content": " return searchParams", + "lineBefore": 45, + "type": "DeletedLine", + }, + { + "content": " },", + "lineBefore": 46, + "type": "DeletedLine", + }, + { + "content": " [],", + "lineBefore": 47, + "type": "DeletedLine", + }, + { + "content": " )", + "lineBefore": 48, + "type": "DeletedLine", + }, + { + "content": "", + "lineBefore": 49, + "type": "DeletedLine", + }, + { + "content": " const searchParamsTuple = [searchParams, setSearchParams] as const", + "lineBefore": 50, + "type": "DeletedLine", + }, + { + "content": "", + "lineBefore": 51, + "type": "DeletedLine", + }, + { + "content": " return (", + "lineBefore": 52, + "type": "DeletedLine", + }, + { + "content": " ", + "lineBefore": 53, + "type": "DeletedLine", + }, + { + "content": " {children}", + "lineBefore": 54, + "type": "DeletedLine", + }, + { + "content": " ", + "lineBefore": 55, + "type": "DeletedLine", + }, + { + "content": " )", + "lineBefore": 56, + "type": "DeletedLine", + }, + { + "content": "}", + "lineBefore": 57, + "type": "DeletedLine", + }, + { + "content": "", + "lineBefore": 58, + "type": "DeletedLine", + }, + { + "content": "function useSearchParams() {", + "lineBefore": 59, + "type": "DeletedLine", + }, + { + "content": " return use(SearchParamsContext)", + "lineBefore": 60, + "type": "DeletedLine", + }, + { + "content": "}", + "lineBefore": 61, + "type": "DeletedLine", + }, + { + "content": "", + "lineBefore": 62, + "type": "DeletedLine", + }, + { + "content": "const getQueryParam = (params: URLSearchParams) => params.get('query') ?? ''", + "lineBefore": 63, + "type": "DeletedLine", + }, + { + "content": "", + "lineBefore": 64, + "type": "DeletedLine", + }, + { + "content": "function App() {", + "lineBefore": 65, + "type": "DeletedLine", + }, + { + "content": " return (", + "lineBefore": 66, + "type": "DeletedLine", + }, + { + "content": " ", + "lineBefore": 67, + "type": "DeletedLine", + }, + { + "content": "
    ", + "lineBefore": 68, + "type": "DeletedLine", + }, + { + "content": "
    ", + "lineBefore": 69, + "type": "DeletedLine", + }, + { + "content": " ", + "lineBefore": 70, + "type": "DeletedLine", + }, + { + "content": "
    ", + "lineBefore": 71, + "type": "DeletedLine", + }, + { + "content": "
    ", + "lineBefore": 72, + "type": "DeletedLine", + }, + { + "content": " )", + "lineBefore": 73, + "type": "DeletedLine", + }, + { + "content": "}", + "lineBefore": 74, + "type": "DeletedLine", + }, + { + "content": "", + "lineBefore": 75, + "type": "DeletedLine", + }, + { + "content": "function Form() {", + "lineBefore": 76, + "type": "DeletedLine", + }, + { + "content": " const [searchParams, setSearchParams] = useSearchParams()", + "lineBefore": 77, + "type": "DeletedLine", + }, + { + "content": " const query = getQueryParam(searchParams)", + "lineBefore": 78, + "type": "DeletedLine", + }, + { + "content": "", + "lineBefore": 79, + "type": "DeletedLine", + }, + { + "content": " const words = query.split(' ').map((w) => w.trim())", + "lineBefore": 80, + "type": "DeletedLine", + }, + { + "content": "", + "lineBefore": 81, + "type": "DeletedLine", + }, + { + "content": " const dogChecked = words.includes('dog')", + "lineBefore": 82, + "type": "DeletedLine", + }, + { + "content": " const catChecked = words.includes('cat')", + "lineBefore": 83, + "type": "DeletedLine", + }, + { + "content": " const caterpillarChecked = words.includes('caterpillar')", + "lineBefore": 84, + "type": "DeletedLine", + }, + { + "content": "", + "lineBefore": 85, + "type": "DeletedLine", + }, + { + "content": " function handleCheck(tag: string, checked: boolean) {", + "lineBefore": 86, + "type": "DeletedLine", + }, + { + "content": " const newWords = checked ? [...words, tag] : words.filter((w) => w !== tag)", + "lineBefore": 87, + "type": "DeletedLine", + }, + { + "content": " setSearchParams(", + "lineBefore": 88, + "type": "DeletedLine", + }, + { + "content": " { query: newWords.filter(Boolean).join(' ').trim() },", + "lineBefore": 89, + "type": "DeletedLine", + }, + { + "content": " { replace: true },", + "lineBefore": 90, + "type": "DeletedLine", + }, + { + "content": " )", + "lineBefore": 91, + "type": "DeletedLine", + }, + { + "content": " }", + "lineAfter": 15, + "lineBefore": 92, + "type": "UnchangedLine", + }, + { + "content": "", + "lineAfter": 16, + "lineBefore": 93, + "type": "UnchangedLine", + }, + { + "content": " return (", + "lineBefore": 94, + "type": "DeletedLine", + }, + { + "content": " e.preventDefault()}>", + "lineBefore": 95, + "type": "DeletedLine", + }, + { + "content": "
    ", + "lineBefore": 96, + "type": "DeletedLine", + }, + { + "content": " ", + "lineBefore": 97, + "type": "DeletedLine", + }, + { + "content": " ", + "lineBefore": 103, + "type": "DeletedLine", + }, + { + "content": " setSearchParams({ query: e.currentTarget.value }, { replace: true })", + "lineBefore": 104, + "type": "DeletedLine", + }, + { + "content": " }", + "lineBefore": 105, + "type": "DeletedLine", + }, + { + "content": " />", + "lineBefore": 106, + "type": "DeletedLine", + }, + { + "content": "
    ", + "lineBefore": 107, + "type": "DeletedLine", + }, + { + "content": "
    ", + "lineBefore": 108, + "type": "DeletedLine", + }, + { + "content": " ", + "lineBefore": 116, + "type": "DeletedLine", + }, + { + "content": " ", + "lineBefore": 124, + "type": "DeletedLine", + }, + { + "content": " ", + "lineBefore": 134, + "type": "DeletedLine", + }, + { + "content": "
    ", + "lineBefore": 135, + "type": "DeletedLine", + }, + { + "content": " ", + "lineBefore": 136, + "type": "DeletedLine", + }, + { + "content": " )", + "lineBefore": 137, + "type": "DeletedLine", + }, + { + "content": " return function useMediaQuery() {", + "lineAfter": 17, + "type": "AddedLine", + }, + { + "content": " return useSyncExternalStore(subscribe, getSnapshot)", + "lineAfter": 18, + "type": "AddedLine", + }, + { + "content": " }", + "lineAfter": 19, + "type": "AddedLine", + }, + { + "content": "}", + "lineAfter": 20, + "lineBefore": 138, + "type": "UnchangedLine", + }, + { + "content": "", + "lineAfter": 21, + "lineBefore": 139, + "type": "UnchangedLine", + }, + { + "content": "function MatchingPosts() {", + "lineBefore": 140, + "type": "DeletedLine", + }, + { + "content": " const [searchParams] = useSearchParams()", + "lineBefore": 141, + "type": "DeletedLine", + }, + { + "content": " const query = getQueryParam(searchParams)", + "lineBefore": 142, + "type": "DeletedLine", + }, + { + "content": " const matchingPosts = getMatchingPosts(query)", + "lineBefore": 143, + "type": "DeletedLine", + }, + { + "content": "const useNarrowMediaQuery = makeMediaQueryStore('(max-width: 600px)')", + "lineAfter": 22, + "type": "AddedLine", + }, + { + "content": "", + "lineAfter": 23, + "lineBefore": 144, + "type": "UnchangedLine", + }, + { + "content": " return (", + "lineBefore": 145, + "type": "DeletedLine", + }, + { + "content": "
      ", + "lineBefore": 146, + "type": "DeletedLine", + }, + { + "content": " {matchingPosts.map((post) => (", + "lineBefore": 147, + "type": "DeletedLine", + }, + { + "content": " ", + "lineBefore": 148, + "type": "DeletedLine", + }, + { + "content": " ))}", + "lineBefore": 149, + "type": "DeletedLine", + }, + { + "content": "
    ", + "lineBefore": 150, + "type": "DeletedLine", + }, + { + "content": " )", + "lineBefore": 151, + "type": "DeletedLine", + }, + { + "content": "function NarrowScreenNotifier() {", + "lineAfter": 24, + "type": "AddedLine", + }, + { + "content": " const isNarrow = useNarrowMediaQuery()", + "lineAfter": 25, + "type": "AddedLine", + }, + { + "content": " return isNarrow ? 'You are on a narrow screen' : 'You are on a wide screen'", + "lineAfter": 26, + "type": "AddedLine", + }, + { + "content": "}", + "lineAfter": 27, + "lineBefore": 152, + "type": "UnchangedLine", + }, + { + "content": "", + "lineAfter": 28, + "lineBefore": 153, + "type": "UnchangedLine", + }, + { + "content": "function Card({ post }: { post: BlogPost }) {", + "lineBefore": 154, + "type": "DeletedLine", + }, + { + "content": " const [isFavorited, setIsFavorited] = useState(false)", + "lineBefore": 155, + "type": "DeletedLine", + }, + { + "content": "function App() {", + "lineAfter": 29, + "type": "AddedLine", + }, + { + "content": " return (", + "lineAfter": 30, + "lineBefore": 156, + "type": "UnchangedLine", + }, + { + "content": "
  • ", + "lineBefore": 157, + "type": "DeletedLine", + }, + { + "content": " {isFavorited ? (", + "lineBefore": 158, + "type": "DeletedLine", + }, + { + "content": " setIsFavorited(false)}", + "lineBefore": 161, + "type": "DeletedLine", + }, + { + "content": " >", + "lineBefore": 162, + "type": "DeletedLine", + }, + { + "content": " ❤️", + "lineBefore": 163, + "type": "DeletedLine", + }, + { + "content": " ", + "lineBefore": 164, + "type": "DeletedLine", + }, + { + "content": " ) : (", + "lineBefore": 165, + "type": "DeletedLine", + }, + { + "content": " ", + "lineBefore": 168, + "type": "DeletedLine", + }, + { + "content": " )}", + "lineBefore": 169, + "type": "DeletedLine", + }, + { + "content": " ", + "lineBefore": 173, + "type": "DeletedLine", + }, + { + "content": " {", + "lineBefore": 176, + "type": "DeletedLine", + }, + { + "content": " event.preventDefault()", + "lineBefore": 177, + "type": "DeletedLine", + }, + { + "content": " alert(\`Great! Let's go to \${post.id}!\`)", + "lineBefore": 178, + "type": "DeletedLine", + }, + { + "content": " }}", + "lineBefore": 179, + "type": "DeletedLine", + }, + { + "content": " >", + "lineBefore": 180, + "type": "DeletedLine", + }, + { + "content": "

    {post.title}

    ", + "lineBefore": 181, + "type": "DeletedLine", + }, + { + "content": "

    {post.description}

    ", + "lineBefore": 182, + "type": "DeletedLine", + }, + { + "content": " ", + "lineBefore": 183, + "type": "DeletedLine", + }, + { + "content": "
  • ", + "lineBefore": 184, + "type": "DeletedLine", + }, + { + "content": "
    ", + "lineAfter": 31, + "type": "AddedLine", + }, + { + "content": "
    This is your narrow screen state:
    ", + "lineAfter": 32, + "type": "AddedLine", + }, + { + "content": " ", + "lineAfter": 33, + "type": "AddedLine", + }, + { + "content": " ", + "lineAfter": 34, + "type": "AddedLine", + }, + { + "content": " ", + "lineAfter": 35, + "type": "AddedLine", + }, + { + "content": "
    ", + "lineAfter": 36, + "type": "AddedLine", + }, + { + "content": " )", + "lineAfter": 37, + "lineBefore": 185, + "type": "UnchangedLine", + }, + { + "content": "}", + "lineAfter": 38, + "lineBefore": 186, + "type": "UnchangedLine", + }, + { + "content": "", + "lineAfter": 39, + "lineBefore": 187, + "type": "UnchangedLine", + }, + { + "content": "const rootEl = document.createElement('div')", + "lineAfter": 40, + "lineBefore": 188, + "type": "UnchangedLine", + }, + { + "content": "document.body.append(rootEl)", + "lineAfter": 41, + "lineBefore": 189, + "type": "UnchangedLine", + }, + { + "content": "ReactDOM.createRoot(rootEl).render()", + "lineBefore": 190, + "type": "DeletedLine", + }, + { + "content": "// 🦉 here's how we pretend we're server-rendering", + "lineAfter": 42, + "type": "AddedLine", + }, + { + "content": "rootEl.innerHTML = (await import('react-dom/server')).renderToString()", + "lineAfter": 43, + "type": "AddedLine", + }, + { + "content": "", + "lineAfter": 44, + "type": "AddedLine", + }, + { + "content": "// 🦉 here's how we simulate a delay in hydrating with client-side js", + "lineAfter": 45, + "type": "AddedLine", + }, + { + "content": "await new Promise((resolve) => setTimeout(resolve, 1000))", + "lineAfter": 46, + "type": "AddedLine", + }, + { + "content": "", + "lineAfter": 47, + "type": "AddedLine", + }, + { + "content": "ReactDOM.hydrateRoot(rootEl, , {", + "lineAfter": 48, + "type": "AddedLine", + }, + { + "content": " onRecoverableError(error) {", + "lineAfter": 49, + "type": "AddedLine", + }, + { + "content": " if (String(error).includes('Missing getServerSnapshot')) return", + "lineAfter": 50, + "type": "AddedLine", + }, + { + "content": "", + "lineAfter": 51, + "type": "AddedLine", + }, + { + "content": " console.error(error)", + "lineAfter": 52, + "type": "AddedLine", + }, + { + "content": " },", + "lineAfter": 53, + "type": "AddedLine", + }, + { + "content": "})", + "lineAfter": 54, + "type": "AddedLine", + }, + ], + "context": undefined, + "fromFileRange": { + "lines": 190, + "start": 1, + }, + "toFileRange": { + "lines": 54, + "start": 1, + }, + "type": "Chunk", + }, + ], + "path": "var/folders/kt/zd3bfncd0c3gjx25hbcq483c0000gn/T/epicshop/diff/advanced-react-apis/09.03.solution/dn2ncwjsbmo/index.tsx", + "type": "ChangedFile", + }, + ], + "type": "GitDiff", +} +`; diff --git a/src/parse-git-diff.ts b/src/parse-git-diff.ts index cb0655a..31e6849 100644 --- a/src/parse-git-diff.ts +++ b/src/parse-git-diff.ts @@ -44,7 +44,6 @@ function parseFileChange(ctx: Context): AnyFileChange | undefined { return; } const comparisonLineParsed = parseComparisonInputLine(ctx); - let isDeleted = false; let isNew = false; let isRename = false; @@ -136,9 +135,7 @@ function parseComparisonInputLine( ctx: Context ): { from: string; to: string } | null { const line = ctx.getCurLine(); - const splitted = line.split(' ').reverse(); - const to = splitted.find((p) => p.startsWith('b/')); - const from = splitted.find((p) => p.startsWith('a/')); + const [to, from] = line.split(' ').reverse(); ctx.nextLine(); if (to && from) { return {