Skip to content

Commit d3a6d95

Browse files
committed
refactor(parser): Enhance type safety and modularity in patch parsing
Refactors the git patch parsing logic to improve type safety and code organization. Key improvements include: - Centralizing parsing-related constants. - Introducing generic types for ParseOptions and ParsedCommit to allow dynamic type inference based on options, leading to more precise type checking. - Updating the parseGitPatch function to leverage these enhanced types for better accuracy.
1 parent 2ea4bb4 commit d3a6d95

4 files changed

Lines changed: 49 additions & 68 deletions

File tree

src/consts.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
export const HEADERS = {
2+
FROM: "From: ",
3+
DATE: "Date: ",
4+
SUBJECT: "Subject: ",
5+
};
6+
7+
export const REGEX = {
8+
FROM: /^From\s+([0-9a-f]{40})\s/,
9+
AUTHOR_EMAIL: /<(.*)>/,
10+
PATCH_HEADER: /^\[PATCH[^\]]*\]\s*/,
11+
};

src/logics/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
export * from "./parse/gitPatch";
1+
export * from "./parse/patch";

src/logics/parse/patch.ts

Lines changed: 23 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,17 @@
1-
import { ParseOptions, ParsedCommit, FileChange } from "../../types";
1+
import { ParseOptions, ParsedCommit } from "../../types";
22
import { parseDiffToStructured } from "./diffToStructured";
3+
import { HEADERS, REGEX } from "../../consts";
34

4-
const HEADERS = {
5-
FROM: "From: ",
6-
DATE: "Date: ",
7-
SUBJECT: "Subject: ",
8-
};
9-
10-
const REGEX = {
11-
FROM: /^From\s+([0-9a-f]{40})\s/,
12-
AUTHOR_EMAIL: /<(.*)>/,
13-
PATCH_HEADER: /^\[PATCH[^\]]*\]\s*/,
14-
};
15-
16-
export function parseGitPatch(
5+
export function parseGitPatch<
6+
O extends ParseOptions<any, any> = ParseOptions<false, false>
7+
>(
178
patch: string,
18-
options: ParseOptions = {}
19-
): ParsedCommit[] {
9+
options: O = { parseDates: false, structuredDiff: false } as O
10+
): ParsedCommit<O>[] {
11+
type DateType = ParsedCommit<O>["date"];
12+
type DiffType = ParsedCommit<O>["diff"];
2013
const lines = patch.split("\n");
21-
const commits: ParsedCommit[] = [];
14+
const commits: ParsedCommit<O>[] = [];
2215

2316
let currentSha = "";
2417
let currentAuthorName = "";
@@ -28,40 +21,26 @@ export function parseGitPatch(
2821
let currentDiffLines: string[] = [];
2922
let inMessageSection = false;
3023
let inDiffSection = false;
31-
let foundDiffStart = false; // To track when we've hit `diff --git`
24+
let foundDiffStart = false;
3225

3326
const finalizeCommit = () => {
34-
if (!currentSha) return; // No commit started yet
27+
if (!currentSha) return;
3528

36-
const message = currentMessageLines.join("\n").trimEnd(); // trimEnd to preserve leading/internal newlines
37-
let date: string | Date = currentDate;
38-
// Join lines, then trim trailing newlines that might have been added if the original patch ended with multiple blank lines.
39-
// Then, ensure a single trailing newline if there's content.
29+
const message = currentMessageLines.join("\n").trimEnd();
4030
let diffString = currentDiffLines.join("\n").replace(/\n+$/, "");
4131
if (diffString.length > 0) {
4232
diffString += "\n";
4333
}
44-
45-
let diff: string | FileChange[] = diffString;
46-
47-
// Process based on options
48-
if (options.parseDates && currentDate) {
49-
try {
50-
date = new Date(currentDate);
51-
} catch (e) {
52-
// Keep original string if date parsing fails
53-
console.warn(`Failed to parse date: ${currentDate}`);
54-
}
55-
}
5634

57-
// Process structured diff
58-
if (
59-
options.structuredDiff &&
60-
typeof diff === "string" &&
61-
diff.trim().length > 0
62-
) {
63-
diff = parseDiffToStructured(diff);
64-
}
35+
const date = (
36+
options.parseDates && currentDate ? new Date(currentDate) : currentDate
37+
) as DateType;
38+
39+
const shouldStructurizeDiff =
40+
options.structuredDiff && diffString.trim().length > 0;
41+
const diff = (
42+
shouldStructurizeDiff ? parseDiffToStructured(diffString) : diffString
43+
) as DiffType;
6544

6645
commits.push({
6746
sha: currentSha,
@@ -72,7 +51,6 @@ export function parseGitPatch(
7251
diff,
7352
});
7453

75-
// Reset for next commit
7654
resetCommitState();
7755
};
7856

@@ -89,15 +67,13 @@ export function parseGitPatch(
8967
};
9068

9169
for (const line of lines) {
92-
// Detect the start of a new commit
9370
const fromMatch = line.match(REGEX.FROM);
9471
if (fromMatch) {
9572
finalizeCommit();
9673
currentSha = fromMatch[1];
9774
continue;
9875
}
9976

100-
// Parse author line: From: Name <email>
10177
if (line.startsWith(HEADERS.FROM)) {
10278
const authorLine = line.slice(HEADERS.FROM.length).trim();
10379
const emailMatch = authorLine.match(REGEX.AUTHOR_EMAIL);
@@ -110,52 +86,39 @@ export function parseGitPatch(
11086
continue;
11187
}
11288

113-
// Parse date line: Date: ...
11489
if (line.startsWith(HEADERS.DATE)) {
11590
currentDate = line.slice(HEADERS.DATE.length).trim();
11691
continue;
11792
}
11893

119-
// Parse subject line
12094
if (line.startsWith(HEADERS.SUBJECT)) {
12195
let subject = line.slice(HEADERS.SUBJECT.length).trim();
122-
// Remove leading "[PATCH ...]" if present
12396
subject = subject.replace(REGEX.PATCH_HEADER, "");
12497
currentMessageLines.push(subject);
12598
inMessageSection = true;
12699
continue;
127100
}
128101

129-
// Check if we are transitioning to diff section
130102
if (inMessageSection && line.trim() === "---") {
131103
inMessageSection = false;
132104
inDiffSection = true;
133105
continue;
134106
}
135107

136-
// If we are in the message section, just append lines to message
137108
if (inMessageSection) {
138-
// For subject lines, they are already pushed.
139-
// For subsequent message lines, they might have leading spaces from the patch format.
140-
// We should preserve these as they are part of the message.
141109
currentMessageLines.push(line);
142110
continue;
143111
}
144112

145-
// If we are in the diff section but haven't found `diff --git` yet
146113
if (inDiffSection && !foundDiffStart) {
147-
// Look for the start of the actual diff
148114
if (line.startsWith("diff --git ")) {
149115
foundDiffStart = true;
150116
currentDiffLines.push(line);
151117
}
152-
// Ignore everything until we find `diff --git`
153118
continue;
154119
}
155120

156-
// If we are in diff section and already found `diff --git`
157121
if (inDiffSection && foundDiffStart) {
158-
// Stop capturing when we hit a line that, after trimming, is `--`
159122
if (line.trim() === "--") {
160123
inDiffSection = false;
161124
foundDiffStart = false;
@@ -169,4 +132,4 @@ export function parseGitPatch(
169132
finalizeCommit();
170133

171134
return commits;
172-
}
135+
}

src/types.ts

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1-
export interface ParseOptions {
2-
parseDates?: boolean;
3-
structuredDiff?: boolean;
1+
export interface ParseOptions<
2+
PDate extends boolean = boolean, // Allow any boolean for the base definition
3+
SDiff extends boolean = boolean // Allow any boolean for the base definition
4+
> {
5+
parseDates?: PDate;
6+
structuredDiff?: SDiff;
47
}
58

69
export interface FileChange {
@@ -21,11 +24,15 @@ export interface DiffLine {
2124
content: string;
2225
}
2326

24-
export interface ParsedCommit {
27+
type SelectIfTrue<T extends boolean | undefined, U, V> = T extends true ? U : V;
28+
29+
export interface ParsedCommit<
30+
O extends ParseOptions<boolean, boolean> = ParseOptions<false, false>
31+
> {
2532
sha: string;
2633
authorName: string;
2734
authorEmail: string;
28-
date: string | Date;
35+
date: SelectIfTrue<O["parseDates"], Date, string>;
2936
message: string;
30-
diff: string | FileChange[];
31-
}
37+
diff: SelectIfTrue<O["structuredDiff"], FileChange, string>;
38+
}

0 commit comments

Comments
 (0)