From 4200bd7a3bd8e290c2c49b2fbb7a4f186cbbd974 Mon Sep 17 00:00:00 2001 From: Rodrigo Fernandes Date: Mon, 21 Oct 2019 23:37:42 +0100 Subject: [PATCH] refactor: Move types and use enums --- package.json | 1 + src/__tests__/diff2html-tests.ts | 8 +- src/__tests__/line-by-line-tests.ts | 5 +- src/__tests__/printer-utils-tests.ts | 11 +- src/__tests__/side-by-side-printer-tests.ts | 7 +- src/__tests__/utils-tests.ts | 67 ++++++++-- src/diff-parser.ts | 2 +- src/diff2html.ts | 6 +- src/file-list-renderer.ts | 3 +- src/line-by-line-renderer.ts | 38 +++--- src/render-utils.ts | 129 ++++---------------- src/side-by-side-renderer.ts | 40 +++--- src/types.ts | 84 +++++++++++++ src/ui/js/diff2html-ui.ts | 2 +- typings/hoganjs.d.ts | 93 -------------- yarn.lock | 5 + 16 files changed, 236 insertions(+), 265 deletions(-) create mode 100644 src/types.ts delete mode 100644 typings/hoganjs.d.ts 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"