refactor: Move types and use enums
This commit is contained in:
parent
f72ee2ea46
commit
4200bd7a3b
16 changed files with 236 additions and 265 deletions
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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 =
|
||||
|
|
|
|||
|
|
@ -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
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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" +
|
||||
|
|
|
|||
|
|
@ -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('<a href="#">\tlink text</a>');
|
||||
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(`<a href="/search?q=diff2html">Search 'Diff2Html'</a>`);
|
||||
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"));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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 <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)
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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
84
src/types.ts
Normal 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"
|
||||
}
|
||||
|
|
@ -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
93
typings/hoganjs.d.ts
vendored
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
Loading…
Reference in a new issue