diff --git a/package.json b/package.json
index 27263ad..638ec66 100644
--- a/package.json
+++ b/package.json
@@ -63,6 +63,7 @@
"devDependencies": {
"@types/diff": "4.0.2",
"@types/highlight.js": "9.12.3",
+ "@types/hogan.js": "^3.0.0",
"@types/jest": "24.0.18",
"@types/mkdirp": "0.5.2",
"@types/node": "12.7.2",
diff --git a/src/__tests__/diff2html-tests.ts b/src/__tests__/diff2html-tests.ts
index 4b74110..d53af91 100644
--- a/src/__tests__/diff2html-tests.ts
+++ b/src/__tests__/diff2html-tests.ts
@@ -1,5 +1,5 @@
import { parse, html } from "../diff2html";
-import { DiffFile, LineType } from "../render-utils";
+import { DiffFile, LineType, OutputFormatType } from "../types";
const diffExample1 =
"diff --git a/sample b/sample\n" +
@@ -271,17 +271,17 @@ describe("Diff2Html", () => {
});
it("should generate pretty side by side html from diff", () => {
- const result = html(diffExample1, { outputFormat: "side-by-side", drawFileList: false });
+ const result = html(diffExample1, { outputFormat: OutputFormatType.SIDE_BY_SIDE, drawFileList: false });
expect(result).toEqual(htmlSideExample1);
});
it("should generate pretty side by side html from json", () => {
- const result = html(jsonExample1, { outputFormat: "side-by-side", drawFileList: false });
+ const result = html(jsonExample1, { outputFormat: OutputFormatType.SIDE_BY_SIDE, drawFileList: false });
expect(result).toEqual(htmlSideExample1);
});
it("should generate pretty side by side html from diff 2", () => {
- const result = html(diffExample1, { outputFormat: "side-by-side", drawFileList: true });
+ const result = html(diffExample1, { outputFormat: OutputFormatType.SIDE_BY_SIDE, drawFileList: true });
expect(result).toEqual(htmlSideExample1WithFilesSummary);
});
diff --git a/src/__tests__/line-by-line-tests.ts b/src/__tests__/line-by-line-tests.ts
index 0c840bd..147eb1d 100644
--- a/src/__tests__/line-by-line-tests.ts
+++ b/src/__tests__/line-by-line-tests.ts
@@ -1,6 +1,7 @@
import LineByLineRenderer from "../line-by-line-renderer";
import HoganJsUtils from "../hoganjs-utils";
-import { LineType, CSSLineClass, DiffLine, DiffFile } from "../render-utils";
+import { LineType, DiffLine, DiffFile, LineMatchingType } from "../types";
+import { CSSLineClass } from "../render-utils";
describe("LineByLineRenderer", () => {
describe("_generateEmptyDiff", () => {
@@ -378,7 +379,7 @@ describe("LineByLineRenderer", () => {
const hoganUtils = new HoganJsUtils({});
const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {
- matching: "lines"
+ matching: LineMatchingType.LINES
});
const html = lineByLineRenderer.render(exampleJson);
const expected =
diff --git a/src/__tests__/printer-utils-tests.ts b/src/__tests__/printer-utils-tests.ts
index dce72c9..b76f249 100644
--- a/src/__tests__/printer-utils-tests.ts
+++ b/src/__tests__/printer-utils-tests.ts
@@ -1,4 +1,5 @@
import * as renderUtils from "../render-utils";
+import { DiffStyleType, LineMatchingType } from "../types";
describe("Utils", function() {
describe("getHtmlId", function() {
@@ -73,7 +74,7 @@ describe("Utils", function() {
describe("diffHighlight", function() {
it("should highlight two lines", function() {
const result = renderUtils.diffHighlight("-var myVar = 2;", "+var myVariable = 3;", false, {
- matching: "words"
+ matching: LineMatchingType.WORDS
});
expect(result).toEqual({
@@ -88,7 +89,9 @@ describe("Utils", function() {
});
});
it("should highlight two lines char by char", function() {
- const result = renderUtils.diffHighlight("-var myVar = 2;", "+var myVariable = 3;", false, { diffStyle: "char" });
+ const result = renderUtils.diffHighlight("-var myVar = 2;", "+var myVariable = 3;", false, {
+ diffStyle: DiffStyleType.CHAR
+ });
expect({
oldLine: {
@@ -103,8 +106,8 @@ describe("Utils", function() {
});
it("should highlight combined diff lines", function() {
const result = renderUtils.diffHighlight(" -var myVar = 2;", " +var myVariable = 3;", true, {
- diffStyle: "word",
- matching: "words",
+ diffStyle: DiffStyleType.WORD,
+ matching: LineMatchingType.WORDS,
matchWordsThreshold: 1.0
});
diff --git a/src/__tests__/side-by-side-printer-tests.ts b/src/__tests__/side-by-side-printer-tests.ts
index 1ec2341..9aaa1de 100644
--- a/src/__tests__/side-by-side-printer-tests.ts
+++ b/src/__tests__/side-by-side-printer-tests.ts
@@ -1,6 +1,7 @@
import SideBySideRenderer from "../side-by-side-renderer";
import HoganJsUtils from "../hoganjs-utils";
-import { LineType, CSSLineClass, DiffLine, DiffFile } from "../render-utils";
+import { LineType, DiffLine, DiffFile, LineMatchingType } from "../types";
+import { CSSLineClass } from "../render-utils";
describe("SideBySideRenderer", () => {
describe("generateEmptyDiff", () => {
@@ -238,7 +239,7 @@ describe("SideBySideRenderer", () => {
];
const hoganUtils = new HoganJsUtils({});
- const sideBySideRenderer = new SideBySideRenderer(hoganUtils, { matching: "lines" });
+ const sideBySideRenderer = new SideBySideRenderer(hoganUtils, { matching: LineMatchingType.LINES });
const html = sideBySideRenderer.render(exampleJson);
const expected =
'
\n' +
@@ -386,7 +387,7 @@ describe("SideBySideRenderer", () => {
];
const hoganUtils = new HoganJsUtils({});
- const sideBySideRenderer = new SideBySideRenderer(hoganUtils, { matching: "lines" });
+ const sideBySideRenderer = new SideBySideRenderer(hoganUtils, { matching: LineMatchingType.LINES });
const html = sideBySideRenderer.processLines(false, oldLines, newLines);
const expectedLeft =
"
\n" +
diff --git a/src/__tests__/utils-tests.ts b/src/__tests__/utils-tests.ts
index f64e0e2..4548cf6 100644
--- a/src/__tests__/utils-tests.ts
+++ b/src/__tests__/utils-tests.ts
@@ -1,23 +1,64 @@
-import { escapeForHtml } from "../utils";
+import { escapeForRegExp, escapeForHtml, unifyPath, hashCode } from "../utils";
-describe("Utils", function() {
- describe("escape", function() {
- it("should escape & with &", function() {
+describe("Utils", () => {
+ describe("escapeForRegExp", () => {
+ it("should escape markdown link text", () => {
+ const result = escapeForRegExp("[Link](https://diff2html.xyz)");
+ expect(result).toEqual("\\[Link\\]\\(https:\\/\\/diff2html\\.xyz\\)");
+ });
+ it("should escape all dangerous characters", () => {
+ const result = escapeForRegExp("-[]/{}()*+?.\\^$|");
+ expect(result).toEqual("\\-\\[\\]\\/\\{\\}\\(\\)\\*\\+\\?\\.\\\\\\^\\$\\|");
+ });
+ });
+
+ describe("escapeForHtml", () => {
+ it("should escape & with &", () => {
const result = escapeForHtml("&");
- expect("&").toEqual(result);
+ expect(result).toEqual("&");
});
- it("should escape < with <", function() {
+ it("should escape < with <", () => {
const result = escapeForHtml("<");
- expect("<").toEqual(result);
+ expect(result).toEqual("<");
});
- it("should escape > with >", function() {
+ it("should escape > with >", () => {
const result = escapeForHtml(">");
- expect(">").toEqual(result);
+ expect(result).toEqual(">");
});
- it("should escape a string with multiple problematic characters", function() {
- const result = escapeForHtml('\tlink text');
- const expected = "<a href="#">\tlink text</a>";
- expect(expected).toEqual(result);
+ it('should escape " with "', () => {
+ const result = escapeForHtml('"');
+ expect(result).toEqual(""");
+ });
+ it("should escape ' with '", () => {
+ const result = escapeForHtml("'");
+ expect(result).toEqual("'");
+ });
+ it("should escape / with /", () => {
+ const result = escapeForHtml("/");
+ expect(result).toEqual("/");
+ });
+ it("should escape a string containing HTML code", () => {
+ const result = escapeForHtml(`Search 'Diff2Html'`);
+ expect(result).toEqual(
+ "<a href="/search?q=diff2html">Search 'Diff2Html'</a>"
+ );
+ });
+
+ describe("unifyPath", () => {
+ it("should unify windows style path", () => {
+ const result = unifyPath("\\Users\\Downloads\\diff.html");
+ expect(result).toEqual("/Users/Downloads/diff.html");
+ });
+ });
+
+ describe("hashCode", () => {
+ it("should create consistent hash for a text piece", () => {
+ const string = "/home/diff2html/diff.html";
+ expect(hashCode(string)).toEqual(hashCode(string));
+ });
+ it("should create different hash for different text pieces", () => {
+ expect(hashCode("/home/diff2html/diff1.html")).not.toEqual(hashCode("/home/diff2html/diff2.html"));
+ });
});
});
});
diff --git a/src/diff-parser.ts b/src/diff-parser.ts
index 872e765..6c32637 100644
--- a/src/diff-parser.ts
+++ b/src/diff-parser.ts
@@ -1,4 +1,4 @@
-import { DiffFile, DiffBlock, DiffLine, LineType } from "./render-utils";
+import { DiffFile, DiffBlock, DiffLine, LineType } from "./types";
import { escapeForRegExp } from "./utils";
export interface DiffParserConfig {
diff --git a/src/diff2html.ts b/src/diff2html.ts
index 3861bb2..2b22b9c 100644
--- a/src/diff2html.ts
+++ b/src/diff2html.ts
@@ -2,11 +2,9 @@ import * as DiffParser from "./diff-parser";
import * as fileListPrinter from "./file-list-renderer";
import LineByLineRenderer, { LineByLineRendererConfig, defaultLineByLineRendererConfig } from "./line-by-line-renderer";
import SideBySideRenderer, { SideBySideRendererConfig, defaultSideBySideRendererConfig } from "./side-by-side-renderer";
-import { DiffFile } from "./render-utils";
+import { DiffFile, OutputFormatType } from "./types";
import HoganJsUtils, { HoganJsUtilsConfig } from "./hoganjs-utils";
-type OutputFormatType = "line-by-line" | "side-by-side";
-
export interface Diff2HtmlConfig
extends DiffParser.DiffParserConfig,
LineByLineRendererConfig,
@@ -19,7 +17,7 @@ export interface Diff2HtmlConfig
export const defaultDiff2HtmlConfig = {
...defaultLineByLineRendererConfig,
...defaultSideBySideRendererConfig,
- outputFormat: "line-by-line" as OutputFormatType,
+ outputFormat: OutputFormatType.LINE_BY_LINE,
drawFileList: true
};
diff --git a/src/file-list-renderer.ts b/src/file-list-renderer.ts
index 6fd0a6c..8d2b2d8 100644
--- a/src/file-list-renderer.ts
+++ b/src/file-list-renderer.ts
@@ -1,10 +1,11 @@
import * as renderUtils from "./render-utils";
import HoganJsUtils from "./hoganjs-utils";
+import { DiffFile } from "./types";
const baseTemplatesPath = "file-summary";
const iconsBaseTemplatesPath = "icon";
-export function render(diffFiles: renderUtils.DiffFile[], hoganUtils: HoganJsUtils): string {
+export function render(diffFiles: DiffFile[], hoganUtils: HoganJsUtils): string {
const files = diffFiles
.map(file =>
hoganUtils.render(
diff --git a/src/line-by-line-renderer.ts b/src/line-by-line-renderer.ts
index 062e8f0..c7d5163 100644
--- a/src/line-by-line-renderer.ts
+++ b/src/line-by-line-renderer.ts
@@ -2,6 +2,7 @@ import * as utils from "./utils";
import HoganJsUtils from "./hoganjs-utils";
import * as Rematch from "./rematch";
import * as renderUtils from "./render-utils";
+import { DiffFile, DiffBlock, DiffLine, LineType } from "./types";
export interface LineByLineRendererConfig extends renderUtils.RenderConfig {
renderNothingWhenEmpty?: boolean;
@@ -25,12 +26,12 @@ export default class LineByLineRenderer {
private readonly hoganUtils: HoganJsUtils;
private readonly config: typeof defaultLineByLineRendererConfig;
- constructor(hoganUtils: HoganJsUtils, config: LineByLineRendererConfig) {
+ constructor(hoganUtils: HoganJsUtils, config: LineByLineRendererConfig = {}) {
this.hoganUtils = hoganUtils;
this.config = { ...defaultLineByLineRendererConfig, ...config };
}
- render(diffFiles: renderUtils.DiffFile[]): string | undefined {
+ render(diffFiles: DiffFile[]): string | undefined {
const htmlDiffs = diffFiles.map(file => {
let diffs;
if (file.blocks.length) {
@@ -45,7 +46,7 @@ export default class LineByLineRenderer {
}
// TODO: Make this private after improving tests
- makeFileDiffHtml(file: renderUtils.DiffFile, diffs: string): string {
+ makeFileDiffHtml(file: DiffFile, diffs: string): string {
if (this.config.renderNothingWhenEmpty && Array.isArray(file.blocks) && file.blocks.length === 0) return "";
const fileDiffTemplate = this.hoganUtils.template(baseTemplatesPath, "file-diff");
@@ -75,7 +76,7 @@ export default class LineByLineRenderer {
}
// TODO: Make this private after improving tests
- makeColumnLineNumberHtml(block: renderUtils.DiffBlock): string {
+ makeColumnLineNumberHtml(block: DiffBlock): string {
return this.hoganUtils.render(genericTemplatesPath, "column-line-number", {
CSSLineClass: renderUtils.CSSLineClass,
blockHeader: utils.escapeForHtml(block.header),
@@ -104,7 +105,7 @@ export default class LineByLineRenderer {
if (!prefix) {
const lineWithPrefix = renderUtils.deconstructLine(content, isCombined);
prefix = lineWithPrefix.prefix;
- lineWithoutPrefix = lineWithPrefix.line;
+ lineWithoutPrefix = lineWithPrefix.content;
}
if (prefix === " ") {
@@ -130,7 +131,7 @@ export default class LineByLineRenderer {
}
// TODO: Make this private after improving tests
- processLines(isCombined: boolean, oldLines: renderUtils.DiffLine[], newLines: renderUtils.DiffLine[]): string {
+ processLines(isCombined: boolean, oldLines: DiffLine[], newLines: DiffLine[]): string {
let lines = "";
for (let i = 0; i < oldLines.length; i++) {
@@ -161,16 +162,17 @@ export default class LineByLineRenderer {
}
// TODO: Make this private after improving tests
- generateFileHtml(file: renderUtils.DiffFile): string {
- const prefixSize = renderUtils.prefixLength(file.isCombined);
- const distance = Rematch.newDistanceFn((e: renderUtils.DiffLine) => e.content.substring(prefixSize));
+ generateFileHtml(file: DiffFile): string {
+ const distance = Rematch.newDistanceFn(
+ (e: DiffLine) => renderUtils.deconstructLine(e.content, file.isCombined).content
+ );
const matcher = Rematch.newMatcherFn(distance);
return file.blocks
.map(block => {
let lines = this.makeColumnLineNumberHtml(block);
- let oldLines: renderUtils.DiffLine[] = [];
- let newLines: renderUtils.DiffLine[] = [];
+ let oldLines: DiffLine[] = [];
+ let newLines: DiffLine[] = [];
const processChangeBlock = (): void => {
let matches;
@@ -243,17 +245,17 @@ export default class LineByLineRenderer {
for (let i = 0; i < block.lines.length; i++) {
const diffLine = block.lines[i];
- const { prefix, line } = renderUtils.deconstructLine(diffLine.content, file.isCombined);
+ const { prefix, content: line } = renderUtils.deconstructLine(diffLine.content, file.isCombined);
const escapedLine = utils.escapeForHtml(line);
if (
- diffLine.type !== renderUtils.LineType.INSERT &&
- (newLines.length > 0 || (diffLine.type !== renderUtils.LineType.DELETE && oldLines.length > 0))
+ diffLine.type !== LineType.INSERT &&
+ (newLines.length > 0 || (diffLine.type !== LineType.DELETE && oldLines.length > 0))
) {
processChangeBlock();
}
- if (diffLine.type === renderUtils.LineType.CONTEXT) {
+ if (diffLine.type === LineType.CONTEXT) {
lines += this.makeLineHtml(
file.isCombined,
renderUtils.toCSSClass(diffLine.type),
@@ -262,7 +264,7 @@ export default class LineByLineRenderer {
diffLine.newNumber,
prefix
);
- } else if (diffLine.type === renderUtils.LineType.INSERT && !oldLines.length) {
+ } else if (diffLine.type === LineType.INSERT && !oldLines.length) {
lines += this.makeLineHtml(
file.isCombined,
renderUtils.toCSSClass(diffLine.type),
@@ -271,9 +273,9 @@ export default class LineByLineRenderer {
diffLine.newNumber,
prefix
);
- } else if (diffLine.type === renderUtils.LineType.DELETE) {
+ } else if (diffLine.type === LineType.DELETE) {
oldLines.push(diffLine);
- } else if (diffLine.type === renderUtils.LineType.INSERT && Boolean(oldLines.length)) {
+ } else if (diffLine.type === LineType.INSERT && Boolean(oldLines.length)) {
newLines.push(diffLine);
} else {
console.error("Unknown state in html line-by-line generator");
diff --git a/src/render-utils.ts b/src/render-utils.ts
index b2219bc..b823fbe 100644
--- a/src/render-utils.ts
+++ b/src/render-utils.ts
@@ -2,11 +2,7 @@ import * as jsDiff from "diff";
import { unifyPath, escapeForHtml, hashCode } from "./utils";
import * as rematch from "./rematch";
-
-export type DiffLineParts = {
- prefix: string;
- line: string;
-};
+import { LineMatchingType, DiffStyleType, LineType, DiffLineParts, DiffFile, DiffFileName } from "./types";
export enum CSSLineClass {
INSERTS = "d2h-ins",
@@ -17,73 +13,17 @@ export enum CSSLineClass {
DELETE_CHANGES = "d2h-del d2h-change"
}
-export enum LineType {
- INSERT = "insert",
- DELETE = "delete",
- CONTEXT = "context"
-}
-
-interface DiffLineDeleted {
- type: LineType.DELETE;
- oldNumber: number;
- newNumber: undefined;
-}
-
-interface DiffLineInserted {
- type: LineType.INSERT;
- oldNumber: undefined;
- newNumber: number;
-}
-
-interface DiffLineContext {
- type: LineType.CONTEXT;
- oldNumber: number;
- newNumber: number;
-}
-
-export type DiffLine = (DiffLineDeleted | DiffLineInserted | DiffLineContext) & {
- content: string;
+export type HighlightedLines = {
+ oldLine: {
+ prefix: string;
+ content: string;
+ };
+ newLine: {
+ prefix: string;
+ content: string;
+ };
};
-export interface DiffBlock {
- oldStartLine: number;
- oldStartLine2?: number;
- newStartLine: number;
- header: string;
- lines: DiffLine[];
-}
-
-interface DiffFileName {
- oldName: string;
- newName: string;
-}
-
-export interface DiffFile extends DiffFileName {
- addedLines: number;
- deletedLines: number;
- isCombined: boolean;
- isGitDiff: boolean;
- language: string;
- blocks: DiffBlock[];
- oldMode?: string | string[];
- newMode?: string;
- deletedFileMode?: string;
- newFileMode?: string;
- isDeleted?: boolean;
- isNew?: boolean;
- isCopy?: boolean;
- isRename?: boolean;
- isBinary?: boolean;
- unchangedPercentage?: number;
- changedPercentage?: number;
- checksumBefore?: string | string[];
- checksumAfter?: string;
- mode?: string;
-}
-
-export type LineMatchingType = "lines" | "words" | "none";
-export type DiffStyleType = "word" | "char";
-
export interface RenderConfig {
matching?: LineMatchingType;
matchWordsThreshold?: number;
@@ -92,21 +32,10 @@ export interface RenderConfig {
}
export const defaultRenderConfig = {
- matching: "none" as LineMatchingType,
+ matching: LineMatchingType.NONE,
matchWordsThreshold: 0.25,
maxLineLengthHighlight: 10000,
- diffStyle: "word" as DiffStyleType
-};
-
-type HighlightedLines = {
- oldLine: {
- prefix: string;
- content: string;
- };
- newLine: {
- prefix: string;
- content: string;
- };
+ diffStyle: DiffStyleType.WORD
};
const separator = "/";
@@ -142,7 +71,7 @@ export function toCSSClass(lineType: LineType): CSSLineClass {
/**
* Prefix length of the hunk lines in the diff
*/
-export function prefixLength(isCombined: boolean): number {
+function prefixLength(isCombined: boolean): number {
return isCombined ? 2 : 1;
}
@@ -153,7 +82,7 @@ export function deconstructLine(line: string, isCombined: boolean): DiffLinePart
const indexToSplit = prefixLength(isCombined);
return {
prefix: line.substring(0, indexToSplit),
- line: line.substring(indexToSplit)
+ content: line.substring(indexToSplit)
};
}
@@ -263,40 +192,36 @@ export function getFileIcon(file: DiffFile): string {
}
/**
- * Generates a unique string numerical identifier based on the names of the file diff
+ * Highlight differences between @diffLine1 and @diffLine2 using and tags
*/
export function diffHighlight(
diffLine1: string,
diffLine2: string,
isCombined: boolean,
- config: RenderConfig
+ config: RenderConfig = {}
): HighlightedLines {
const { matching, maxLineLengthHighlight, matchWordsThreshold, diffStyle } = { ...defaultRenderConfig, ...config };
- const prefixLengthVal = prefixLength(isCombined);
- const linePrefix1 = diffLine1.substr(0, prefixLengthVal);
- const unprefixedLine1 = diffLine1.substr(prefixLengthVal);
+ const line1 = deconstructLine(diffLine1, isCombined);
+ const line2 = deconstructLine(diffLine2, isCombined);
- const linePrefix2 = diffLine2.substr(0, prefixLengthVal);
- const unprefixedLine2 = diffLine2.substr(prefixLengthVal);
-
- if (unprefixedLine1.length > maxLineLengthHighlight || unprefixedLine2.length > maxLineLengthHighlight) {
+ if (line1.content.length > maxLineLengthHighlight || line2.content.length > maxLineLengthHighlight) {
return {
oldLine: {
- prefix: linePrefix1,
- content: escapeForHtml(unprefixedLine1)
+ prefix: line1.prefix,
+ content: escapeForHtml(line1.content)
},
newLine: {
- prefix: linePrefix2,
- content: escapeForHtml(unprefixedLine2)
+ prefix: line2.prefix,
+ content: escapeForHtml(line2.content)
}
};
}
const diff =
diffStyle === "char"
- ? jsDiff.diffChars(unprefixedLine1, unprefixedLine2)
- : jsDiff.diffWordsWithSpace(unprefixedLine1, unprefixedLine2);
+ ? jsDiff.diffChars(line1.content, line2.content)
+ : jsDiff.diffWordsWithSpace(line1.content, line2.content);
const changedWords: jsDiff.Change[] = [];
if (diffStyle === "word" && matching === "words") {
@@ -332,11 +257,11 @@ export function diffHighlight(
return {
oldLine: {
- prefix: linePrefix1,
+ prefix: line1.prefix,
content: removeInsElements(highlightedLine)
},
newLine: {
- prefix: linePrefix2,
+ prefix: line2.prefix,
content: removeDelElements(highlightedLine)
}
};
diff --git a/src/side-by-side-renderer.ts b/src/side-by-side-renderer.ts
index 7af8edd..c890662 100644
--- a/src/side-by-side-renderer.ts
+++ b/src/side-by-side-renderer.ts
@@ -2,6 +2,7 @@ import * as utils from "./utils";
import HoganJsUtils from "./hoganjs-utils";
import * as Rematch from "./rematch";
import * as renderUtils from "./render-utils";
+import { DiffLine, LineType, DiffFile } from "./types";
export interface SideBySideRendererConfig extends renderUtils.RenderConfig {
renderNothingWhenEmpty?: boolean;
@@ -30,12 +31,12 @@ export default class SideBySideRenderer {
private readonly hoganUtils: HoganJsUtils;
private readonly config: typeof defaultSideBySideRendererConfig;
- constructor(hoganUtils: HoganJsUtils, config: SideBySideRendererConfig) {
+ constructor(hoganUtils: HoganJsUtils, config: SideBySideRendererConfig = {}) {
this.hoganUtils = hoganUtils;
this.config = { ...defaultSideBySideRendererConfig, ...config };
}
- render(diffFiles: renderUtils.DiffFile[]): string | undefined {
+ render(diffFiles: DiffFile[]): string | undefined {
const content = diffFiles
.map(file => {
let diffs;
@@ -65,7 +66,7 @@ export default class SideBySideRenderer {
}
// TODO: Make this private after improving tests
- makeDiffHtml(file: renderUtils.DiffFile, diffs: FileHtml): string {
+ makeDiffHtml(file: DiffFile, diffs: FileHtml): string {
const fileDiffTemplate = this.hoganUtils.template(baseTemplatesPath, "file-diff");
const filePathTemplate = this.hoganUtils.template(genericTemplatesPath, "file-path");
const fileIconTemplate = this.hoganUtils.template(iconsBaseTemplatesPath, "file");
@@ -98,9 +99,10 @@ export default class SideBySideRenderer {
}
// TODO: Make this private after improving tests
- generateSideBySideFileHtml(file: renderUtils.DiffFile): FileHtml {
- const prefixSize = renderUtils.prefixLength(file.isCombined);
- const distance = Rematch.newDistanceFn((e: renderUtils.DiffLine) => e.content.substring(prefixSize));
+ generateSideBySideFileHtml(file: DiffFile): FileHtml {
+ const distance = Rematch.newDistanceFn(
+ (e: DiffLine) => renderUtils.deconstructLine(e.content, file.isCombined).content
+ );
const matcher = Rematch.newMatcherFn(distance);
const fileHtml = {
@@ -112,8 +114,8 @@ export default class SideBySideRenderer {
fileHtml.left += this.makeSideHtml(block.header);
fileHtml.right += this.makeSideHtml("");
- let oldLines: renderUtils.DiffLine[] = [];
- let newLines: renderUtils.DiffLine[] = [];
+ let oldLines: DiffLine[] = [];
+ let newLines: DiffLine[] = [];
const processChangeBlock = (): void => {
let matches;
@@ -184,17 +186,17 @@ export default class SideBySideRenderer {
for (let i = 0; i < block.lines.length; i++) {
const diffLine = block.lines[i];
- const { prefix, line } = renderUtils.deconstructLine(diffLine.content, file.isCombined);
+ const { prefix, content: line } = renderUtils.deconstructLine(diffLine.content, file.isCombined);
const escapedLine = utils.escapeForHtml(line);
if (
- diffLine.type !== renderUtils.LineType.INSERT &&
- (newLines.length > 0 || (diffLine.type !== renderUtils.LineType.DELETE && oldLines.length > 0))
+ diffLine.type !== LineType.INSERT &&
+ (newLines.length > 0 || (diffLine.type !== LineType.DELETE && oldLines.length > 0))
) {
processChangeBlock();
}
- if (diffLine.type === renderUtils.LineType.CONTEXT) {
+ if (diffLine.type === LineType.CONTEXT) {
fileHtml.left += this.generateSingleLineHtml(
file.isCombined,
renderUtils.toCSSClass(diffLine.type),
@@ -209,7 +211,7 @@ export default class SideBySideRenderer {
diffLine.newNumber,
prefix
);
- } else if (diffLine.type === renderUtils.LineType.INSERT && !oldLines.length) {
+ } else if (diffLine.type === LineType.INSERT && !oldLines.length) {
fileHtml.left += this.generateSingleLineHtml(file.isCombined, renderUtils.CSSLineClass.CONTEXT, "");
fileHtml.right += this.generateSingleLineHtml(
file.isCombined,
@@ -218,9 +220,9 @@ export default class SideBySideRenderer {
diffLine.newNumber,
prefix
);
- } else if (diffLine.type === renderUtils.LineType.DELETE) {
+ } else if (diffLine.type === LineType.DELETE) {
oldLines.push(diffLine);
- } else if (diffLine.type === renderUtils.LineType.INSERT && Boolean(oldLines.length)) {
+ } else if (diffLine.type === LineType.INSERT && Boolean(oldLines.length)) {
newLines.push(diffLine);
} else {
console.error("unknown state in html side-by-side generator");
@@ -235,7 +237,7 @@ export default class SideBySideRenderer {
}
// TODO: Make this private after improving tests
- processLines(isCombined: boolean, oldLines: renderUtils.DiffLine[], newLines: renderUtils.DiffLine[]): FileHtml {
+ processLines(isCombined: boolean, oldLines: DiffLine[], newLines: DiffLine[]): FileHtml {
const fileHtml = {
right: "",
left: ""
@@ -252,7 +254,7 @@ export default class SideBySideRenderer {
let newPrefix;
if (oldLine) {
- const { prefix, line } = renderUtils.deconstructLine(oldLine.content, isCombined);
+ const { prefix, content: line } = renderUtils.deconstructLine(oldLine.content, isCombined);
oldContent = utils.escapeForHtml(line);
oldPrefix = prefix;
} else {
@@ -261,7 +263,7 @@ export default class SideBySideRenderer {
}
if (newLine) {
- const { prefix, line } = renderUtils.deconstructLine(newLine.content, isCombined);
+ const { prefix, content: line } = renderUtils.deconstructLine(newLine.content, isCombined);
newContent = utils.escapeForHtml(line);
newPrefix = prefix;
} else {
@@ -333,7 +335,7 @@ export default class SideBySideRenderer {
} else if (!prefix) {
const lineWithPrefix = renderUtils.deconstructLine(content, isCombined);
prefix = lineWithPrefix.prefix;
- lineWithoutPrefix = lineWithPrefix.line;
+ lineWithoutPrefix = lineWithPrefix.content;
}
if (prefix === " ") {
diff --git a/src/types.ts b/src/types.ts
new file mode 100644
index 0000000..6128f6e
--- /dev/null
+++ b/src/types.ts
@@ -0,0 +1,84 @@
+export type DiffLineParts = {
+ prefix: string;
+ content: string;
+};
+
+export enum LineType {
+ INSERT = "insert",
+ DELETE = "delete",
+ CONTEXT = "context"
+}
+
+export interface DiffLineDeleted {
+ type: LineType.DELETE;
+ oldNumber: number;
+ newNumber: undefined;
+}
+
+export interface DiffLineInserted {
+ type: LineType.INSERT;
+ oldNumber: undefined;
+ newNumber: number;
+}
+
+export interface DiffLineContext {
+ type: LineType.CONTEXT;
+ oldNumber: number;
+ newNumber: number;
+}
+
+export type DiffLine = (DiffLineDeleted | DiffLineInserted | DiffLineContext) & {
+ content: string;
+};
+
+export interface DiffBlock {
+ oldStartLine: number;
+ oldStartLine2?: number;
+ newStartLine: number;
+ header: string;
+ lines: DiffLine[];
+}
+
+export interface DiffFileName {
+ oldName: string;
+ newName: string;
+}
+
+export interface DiffFile extends DiffFileName {
+ addedLines: number;
+ deletedLines: number;
+ isCombined: boolean;
+ isGitDiff: boolean;
+ language: string;
+ blocks: DiffBlock[];
+ oldMode?: string | string[];
+ newMode?: string;
+ deletedFileMode?: string;
+ newFileMode?: string;
+ isDeleted?: boolean;
+ isNew?: boolean;
+ isCopy?: boolean;
+ isRename?: boolean;
+ isBinary?: boolean;
+ unchangedPercentage?: number;
+ changedPercentage?: number;
+ checksumBefore?: string | string[];
+ checksumAfter?: string;
+ mode?: string;
+}
+
+export enum OutputFormatType {
+ LINE_BY_LINE = "line-by-line",
+ SIDE_BY_SIDE = "side-by-side"
+}
+
+export enum LineMatchingType {
+ LINES = "lines",
+ WORDS = "words",
+ NONE = "none"
+}
+
+export enum DiffStyleType {
+ WORD = "word",
+ CHAR = "char"
+}
diff --git a/src/ui/js/diff2html-ui.ts b/src/ui/js/diff2html-ui.ts
index 15a4932..d64bc87 100644
--- a/src/ui/js/diff2html-ui.ts
+++ b/src/ui/js/diff2html-ui.ts
@@ -1,7 +1,7 @@
import HighlightJS from "highlight.js";
import * as HighlightJSInternals from "./highlight.js-internals";
import { html, Diff2HtmlConfig, defaultDiff2HtmlConfig } from "../../diff2html";
-import { DiffFile } from "../../render-utils";
+import { DiffFile } from "../../types";
export interface Diff2HtmlUIConfig extends Diff2HtmlConfig {
synchronisedScroll?: boolean;
diff --git a/typings/hoganjs.d.ts b/typings/hoganjs.d.ts
deleted file mode 100644
index 4958cd8..0000000
--- a/typings/hoganjs.d.ts
+++ /dev/null
@@ -1,93 +0,0 @@
-// Type definitions for hogan.js 3.0
-// Project: http://twitter.github.com/hogan.js/
-// Definitions by: Andrew Leedham
-// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
-// TypeScript Version: 2.2
-
-declare module "hogan.js" {
- export interface Context {
- [key: string]: any;
- }
-
- export interface SectionTags {
- o: string;
- c: string;
- }
-
- export interface HoganOptions {
- asString?: boolean;
- sectionTags?: ReadonlyArray;
- delimiters?: string;
- disableLambda?: boolean;
- }
-
- export interface Token {
- tag: string;
- otag?: string;
- ctag?: string;
- i?: number;
- n?: string;
- text?: string;
- }
-
- export interface Leaf extends Token {
- end: number;
- nodes: Token[];
- }
-
- export type Tree = Leaf[];
-
- export interface Partials {
- [symbol: string]: HoganTemplate;
- }
-
- export interface HoganConstructor {
- code: (context: any, partials: object, indent: string) => string;
- partials: object;
- subs: object;
- }
-
- export class HoganTemplate {
- constructor(codeObject: HoganConstructor);
-
- /**
- * Renders the template to a string.
- *
- * @param context - The data to render the template with.
- * @param partials - The partials to render the template with.
- * @param indent - The string to indent when rendering the template.
- * @returns A rendered template.
- */
- render(context: Context, partials?: Partials, indent?: string): string;
- }
-
- export { HoganTemplate as Template, HoganTemplate as template };
-
- export function compile(text: string, options?: HoganOptions & { asString: false }): HoganTemplate;
- export function compile(text: string, options?: HoganOptions & { asString: true }): string;
- /**
- * Compiles templates to HoganTemplate objects, which have a render method.
- *
- * @param text - Raw mustache string to compile.
- * @param options - Options to use when compiling. See https://github.com/twitter/hogan.js#compilation-options.
- * @returns A HoganTemplate.
- */
- export function compile(text: string, options?: HoganOptions): HoganTemplate | string;
- /**
- * Scans templates returning an array of found tokens.
- *
- * @param text - Raw mustache string to scan.
- * @param delimiters - A string that overrides the default delimiters. Example: "<% %>".
- * @returns Found tokens.
- */
- export function scan(text: string, delimiters?: string): Token[];
- /**
- * Structures tokens into a tree.
- *
- * @param tokens - An array of scanned tokens.
- * @param text - Unused pass undefined.
- * @param options - Options to use when parsing. See https://github.com/twitter/hogan.js#compilation-options.
- * @returns The tree structure of the given tokens.
- */
- export function parse(tokens: ReadonlyArray, text?: undefined, options?: HoganOptions): Tree;
-}
diff --git a/yarn.lock b/yarn.lock
index 3a1c9cb..ad6344c 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -361,6 +361,11 @@
resolved "https://registry.yarnpkg.com/@types/highlight.js/-/highlight.js-9.12.3.tgz#b672cfaac25cbbc634a0fd92c515f66faa18dbca"
integrity sha512-pGF/zvYOACZ/gLGWdQH8zSwteQS1epp68yRcVLJMgUck/MjEn/FBYmPub9pXT8C1e4a8YZfHo1CKyV8q1vKUnQ==
+"@types/hogan.js@^3.0.0":
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/@types/hogan.js/-/hogan.js-3.0.0.tgz#bf26560f39a38224ab6d0491b06f72c8fbe0953d"
+ integrity sha512-djkvb/AN43c3lIGCojNQ1FBS9VqqKhcTns5RQnHw4xBT/csy0jAssAsOiJ8NfaaioZaeKYE7XkVRxE5NeSZcaA==
+
"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0":
version "2.0.1"
resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz#42995b446db9a48a11a07ec083499a860e9138ff"