refactor: Move types and use enums

This commit is contained in:
Rodrigo Fernandes 2019-10-21 23:37:42 +01:00
parent f72ee2ea46
commit 4200bd7a3b
No known key found for this signature in database
GPG key ID: 67157D2E3D4258B4
16 changed files with 236 additions and 265 deletions

View file

@ -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",

View file

@ -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);
});

View file

@ -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 =

View file

@ -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
});

View file

@ -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 =
'<div class="d2h-wrapper">\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 =
"<tr>\n" +

View file

@ -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 &amp;", 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 &amp;", () => {
const result = escapeForHtml("&");
expect("&amp;").toEqual(result);
expect(result).toEqual("&amp;");
});
it("should escape < with &lt;", function() {
it("should escape < with &lt;", () => {
const result = escapeForHtml("<");
expect("&lt;").toEqual(result);
expect(result).toEqual("&lt;");
});
it("should escape > with &gt;", function() {
it("should escape > with &gt;", () => {
const result = escapeForHtml(">");
expect("&gt;").toEqual(result);
expect(result).toEqual("&gt;");
});
it('should escape " with &quot;', () => {
const result = escapeForHtml('"');
expect(result).toEqual("&quot;");
});
it("should escape ' with &#x27;", () => {
const result = escapeForHtml("'");
expect(result).toEqual("&#x27;");
});
it("should escape / with &#x2F;", () => {
const result = escapeForHtml("/");
expect(result).toEqual("&#x2F;");
});
it("should escape a string containing HTML code", () => {
const result = escapeForHtml(`<a href="/search?q=diff2html">Search 'Diff2Html'</a>`);
expect(result).toEqual(
"&lt;a href=&quot;&#x2F;search?q=diff2html&quot;&gt;Search &#x27;Diff2Html&#x27;&lt;&#x2F;a&gt;"
);
});
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"));
});
it("should escape a string with multiple problematic characters", function() {
const result = escapeForHtml('<a href="#">\tlink text</a>');
const expected = "&lt;a href=&quot;#&quot;&gt;\tlink text&lt;&#x2F;a&gt;";
expect(expected).toEqual(result);
});
});
});

View file

@ -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 {

View file

@ -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
};

View file

@ -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(

View file

@ -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");

View file

@ -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,72 +13,16 @@ 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) & {
export type HighlightedLines = {
oldLine: {
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";
newLine: {
prefix: string;
content: string;
};
};
export interface RenderConfig {
matching?: LineMatchingType;
@ -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 <ins> and <del> 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)
}
};

View file

@ -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 === " ") {

84
src/types.ts Normal file
View file

@ -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"
}

View file

@ -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;

93
typings/hoganjs.d.ts vendored
View file

@ -1,93 +0,0 @@
// Type definitions for hogan.js 3.0
// Project: http://twitter.github.com/hogan.js/
// Definitions by: Andrew Leedham <https://github.com/AndrewLeedham>
// 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<SectionTags>;
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<Token>, text?: undefined, options?: HoganOptions): Tree;
}

View file

@ -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"