wip: Code and Tests working

This commit is contained in:
Rodrigo Fernandes 2019-10-12 22:45:49 +01:00
parent ef08c53ba9
commit 4f607633dd
No known key found for this signature in database
GPG key ID: 67157D2E3D4258B4
48 changed files with 3318 additions and 3224 deletions

View file

@ -3,4 +3,5 @@ coverage/**
build/** build/**
docs/** docs/**
node_modules/** node_modules/**
src/diff2html-templates.js src/diff2html-templates.*
typings/**

2
.gitignore vendored
View file

@ -32,4 +32,4 @@ bower_components/
/docs/ /docs/
/dist/ /dist/
/build/ /build/
/src/diff2html-templates.js /src/diff2html-templates.*

View file

@ -201,7 +201,7 @@ The HTML output accepts a Javascript object with configuration. Possible options
- `matchingMaxComparisons`: perform at most this much comparisons for line matching a block of changes, default is `2500` - `matchingMaxComparisons`: perform at most this much comparisons for line matching a block of changes, default is `2500`
- `maxLineSizeInBlockForComparison`: maximum number os characters of the bigger line in a block to apply comparison, default is `200` - `maxLineSizeInBlockForComparison`: maximum number os characters of the bigger line in a block to apply comparison, default is `200`
- `maxLineLengthHighlight`: only perform diff changes highlight if lines are smaller than this, default is `10000` - `maxLineLengthHighlight`: only perform diff changes highlight if lines are smaller than this, default is `10000`
- `templates`: object with previously compiled templates to replace parts of the html - `compiledTemplates`: object with previously compiled templates to replace parts of the html
- `rawTemplates`: object with raw not compiled templates to replace parts of the html - `rawTemplates`: object with raw not compiled templates to replace parts of the html
- `renderNothingWhenEmpty`: render nothing if the diff shows no change in its comparison: `true` or `false`, default is `false` - `renderNothingWhenEmpty`: render nothing if the diff shows no change in its comparison: `true` or `false`, default is `false`
> For more information regarding the possible templates look into [src/templates](https://github.com/rtfpessoa/diff2html/tree/master/src/templates) > For more information regarding the possible templates look into [src/templates](https://github.com/rtfpessoa/diff2html/tree/master/src/templates)

View file

@ -41,7 +41,7 @@
"coverage": "jest --collectCoverage", "coverage": "jest --collectCoverage",
"coverage-html": "yarn run coverage && open ./coverage/index.html", "coverage-html": "yarn run coverage && open ./coverage/index.html",
"codacy": "cat ./coverage/lcov.info | codacy-coverage", "codacy": "cat ./coverage/lcov.info | codacy-coverage",
"build": "rm -rf build; yarn run build-scripts && yarn run build-css && yarn run build-templates && yarn run build-library && yarn run build-browser-bundle && yarn run build-website", "build": "rm -rf build docs; yarn run build-scripts && yarn run build-css && yarn run build-templates && yarn run build-library && yarn run build-browser-bundle && yarn run build-website",
"build-scripts": "tsc -p tsconfig.scripts.json", "build-scripts": "tsc -p tsconfig.scripts.json",
"build-css": "./scripts/build-css.sh", "build-css": "./scripts/build-css.sh",
"build-templates": "./scripts/build-templates.sh", "build-templates": "./scripts/build-templates.sh",
@ -57,23 +57,22 @@
"fs": false "fs": false
}, },
"dependencies": { "dependencies": {
"diff": "^4.0.1", "diff": "4.0.1",
"hogan.js": "^3.0.2", "hogan.js": "3.0.2"
"merge": "^1.2.1",
"whatwg-fetch": "^3.0.0"
}, },
"devDependencies": { "devDependencies": {
"@types/hogan.js": "^3.0.0", "@types/diff": "4.0.2",
"@types/highlight.js": "9.12.3",
"@types/jest": "24.0.18", "@types/jest": "24.0.18",
"@types/mkdirp": "^0.5.2", "@types/mkdirp": "0.5.2",
"@types/node": "^12.7.2", "@types/node": "12.7.2",
"@types/nopt": "^3.0.29", "@types/nopt": "3.0.29",
"@typescript-eslint/eslint-plugin": "2.0.0", "@typescript-eslint/eslint-plugin": "2.0.0",
"@typescript-eslint/parser": "2.0.0", "@typescript-eslint/parser": "2.0.0",
"autoprefixer": "^9.6.0", "autoprefixer": "9.6.0",
"browserify": "^16.5.0", "browserify": "16.5.0",
"clean-css-cli": "^4.3.0", "clean-css-cli": "4.3.0",
"codacy-coverage": "^3.4.0", "codacy-coverage": "3.4.0",
"eslint": "6.2.2", "eslint": "6.2.2",
"eslint-config-prettier": "6.1.0", "eslint-config-prettier": "6.1.0",
"eslint-config-standard": "14.0.1", "eslint-config-standard": "14.0.1",
@ -81,17 +80,19 @@
"eslint-plugin-jest": "22.15.2", "eslint-plugin-jest": "22.15.2",
"eslint-plugin-node": "9.1.0", "eslint-plugin-node": "9.1.0",
"eslint-plugin-prettier": "3.1.0", "eslint-plugin-prettier": "3.1.0",
"eslint-plugin-promise": "^4.2.1", "eslint-plugin-promise": "4.2.1",
"eslint-plugin-standard": "^4.0.1", "eslint-plugin-standard": "4.0.1",
"fast-html-parser": "^1.0.1", "fast-html-parser": "1.0.1",
"highlight.js": "9.15.10",
"jest": "24.9.0", "jest": "24.9.0",
"mkdirp": "^0.5.1", "mkdirp": "0.5.1",
"nopt": "^4.0.1", "nopt": "4.0.1",
"postcss-cli": "^6.1.3", "postcss-cli": "6.1.3",
"prettier": "1.18.2", "prettier": "1.18.2",
"terser": "^4.3.8", "terser": "4.3.8",
"ts-jest": "24.0.2", "ts-jest": "24.1.0",
"typescript": "^3.6.3" "typescript": "3.6.4",
"whatwg-fetch": "3.0.0"
}, },
"resolutions": { "resolutions": {
"lodash": "4.17.15" "lodash": "4.17.15"

View file

@ -5,6 +5,6 @@ set -e
SCRIPT_DIRECTORY="$( cd "$( dirname "$0" )" && pwd )" SCRIPT_DIRECTORY="$( cd "$( dirname "$0" )" && pwd )"
node ${SCRIPT_DIRECTORY}/../build/scripts/hulk.js \ node ${SCRIPT_DIRECTORY}/../build/scripts/hulk.js \
--wrapper node \ --wrapper ts \
--variable 'browserTemplates' \ --variable 'defaultTemplates' \
${SCRIPT_DIRECTORY}/../src/templates/*.mustache > ${SCRIPT_DIRECTORY}/../src/diff2html-templates.js ${SCRIPT_DIRECTORY}/../src/templates/*.mustache > ${SCRIPT_DIRECTORY}/../src/diff2html-templates.ts

View file

@ -19,7 +19,7 @@ import * as path from "path";
import * as fs from "fs"; import * as fs from "fs";
import * as hogan from "hogan.js"; import * as hogan from "hogan.js";
import * as nopt from "nopt"; import nopt from "nopt";
import * as mkderp from "mkdirp"; import * as mkderp from "mkdirp";
const options = nopt( const options = nopt(
@ -52,8 +52,7 @@ function cyan(text: string): string {
} }
function extractFiles(files: string[]): string[] { function extractFiles(files: string[]): string[] {
const usage = ` const usage = `${cyan(
${cyan(
"USAGE:" "USAGE:"
)} hulk [--wrapper wrapper] [--outputdir outputdir] [--namespace namespace] [--variable variable] FILES )} hulk [--wrapper wrapper] [--outputdir outputdir] [--namespace namespace] [--variable variable] FILES
@ -130,6 +129,8 @@ function wrap(file: string, name: string, openedFile: string): string {
// If we have a template per file the export will expose the template directly // If we have a template per file the export will expose the template directly
return options.outputdir ? `global.${objectStmt};\nmodule.exports = ${objectAccessor};` : `global.${objectStmt}`; return options.outputdir ? `global.${objectStmt};\nmodule.exports = ${objectAccessor};` : `global.${objectStmt}`;
case "ts":
return `// @ts-ignore\n${objectStmt}`;
default: default:
return objectStmt; return objectStmt;
} }
@ -141,19 +142,18 @@ function prepareOutput(content: string): string {
case "amd": case "amd":
return content; return content;
case "node": case "node":
return ( return `(function() {
"(function() {\n" + if (!!!global.${variableName}) global.${variableName} = {};
"if (!!!global." + var Hogan = require("hogan.js");
variableName + ${content}
") global." + ${!options.outputdir ? `module.exports = global.${variableName};\n` : ""})();`;
variableName +
" = {};\n" + case "ts":
'var Hogan = require("hogan.js");' + return `import * as Hogan from "hogan.js";
content + type CompiledTemplates = { [name: string]: Hogan.Template };
"\n" + export const ${variableName}: CompiledTemplates = {};
(!options.outputdir ? "module.exports = global." + variableName + ";\n" : "") + ${content}`;
"})();"
);
default: default:
return "if (!!!" + variableName + ") var " + variableName + " = {};\n" + content; return "if (!!!" + variableName + ") var " + variableName + " = {};\n" + content;
} }
@ -181,7 +181,9 @@ const templates = extractFiles(options.argv.remain)
if (!options.outputdir) return cleanFileContents; if (!options.outputdir) return cleanFileContents;
return fs.writeFileSync(path.join(options.outputdir, `${name}.js`), prepareOutput(cleanFileContents)); const fileExtension = options.wrapper === "ts" ? "ts" : "js";
return fs.writeFileSync(path.join(options.outputdir, `${name}.${fileExtension}`), prepareOutput(cleanFileContents));
}) })
.filter(templateContents => typeof templateContents !== "undefined"); .filter(templateContents => typeof templateContents !== "undefined");

View file

@ -1,19 +1,19 @@
const DiffParser = require("../diff-parser.js").DiffParser; import { parse } from "../diff-parser";
function checkDiffSample(diff) { function checkDiffSample(diff: string): void {
const result = DiffParser.generateDiffJson(diff); const result = parse(diff);
const file1 = result[0]; const file1 = result[0];
expect(1).toEqual(result.length); expect(result.length).toEqual(1);
expect(1).toEqual(file1.addedLines); expect(file1.addedLines).toEqual(1);
expect(1).toEqual(file1.deletedLines); expect(file1.deletedLines).toEqual(1);
expect("sample").toEqual(file1.oldName); expect(file1.oldName).toEqual("sample");
expect("sample").toEqual(file1.newName); expect(file1.newName).toEqual("sample");
expect(1).toEqual(file1.blocks.length); expect(file1.blocks.length).toEqual(1);
} }
describe("DiffParser", function() { describe("DiffParser", () => {
describe("generateDiffJson", function() { describe("generateDiffJson", () => {
it("should parse unix with \n diff", function() { it("should parse unix with \n diff", () => {
const diff = const diff =
"diff --git a/sample b/sample\n" + "diff --git a/sample b/sample\n" +
"index 0000001..0ddf2ba\n" + "index 0000001..0ddf2ba\n" +
@ -25,7 +25,7 @@ describe("DiffParser", function() {
checkDiffSample(diff); checkDiffSample(diff);
}); });
it("should parse windows with \r\n diff", function() { it("should parse windows with \r\n diff", () => {
const diff = const diff =
"diff --git a/sample b/sample\r\n" + "diff --git a/sample b/sample\r\n" +
"index 0000001..0ddf2ba\r\n" + "index 0000001..0ddf2ba\r\n" +
@ -37,7 +37,7 @@ describe("DiffParser", function() {
checkDiffSample(diff); checkDiffSample(diff);
}); });
it("should parse old os x with \r diff", function() { it("should parse old os x with \r diff", () => {
const diff = const diff =
"diff --git a/sample b/sample\r" + "diff --git a/sample b/sample\r" +
"index 0000001..0ddf2ba\r" + "index 0000001..0ddf2ba\r" +
@ -49,7 +49,7 @@ describe("DiffParser", function() {
checkDiffSample(diff); checkDiffSample(diff);
}); });
it("should parse mixed eols diff", function() { it("should parse mixed eols diff", () => {
const diff = const diff =
"diff --git a/sample b/sample\n" + "diff --git a/sample b/sample\n" +
"index 0000001..0ddf2ba\r\n" + "index 0000001..0ddf2ba\r\n" +
@ -61,7 +61,7 @@ describe("DiffParser", function() {
checkDiffSample(diff); checkDiffSample(diff);
}); });
it("should parse diff with special characters", function() { it("should parse diff with special characters", () => {
const diff = const diff =
'diff --git "a/bla with \ttab.scala" "b/bla with \ttab.scala"\n' + 'diff --git "a/bla with \ttab.scala" "b/bla with \ttab.scala"\n' +
"index 4c679d7..e9bd385 100644\n" + "index 4c679d7..e9bd385 100644\n" +
@ -72,17 +72,17 @@ describe("DiffParser", function() {
"+cenas com ananas\n" + "+cenas com ananas\n" +
"+bananas"; "+bananas";
const result = DiffParser.generateDiffJson(diff); const result = parse(diff);
const file1 = result[0]; const file1 = result[0];
expect(1).toEqual(result.length); expect(result.length).toEqual(1);
expect(2).toEqual(file1.addedLines); expect(file1.addedLines).toEqual(2);
expect(1).toEqual(file1.deletedLines); expect(file1.deletedLines).toEqual(1);
expect("bla with \ttab.scala").toEqual(file1.oldName); expect(file1.oldName).toEqual("bla with \ttab.scala");
expect("bla with \ttab.scala").toEqual(file1.newName); expect(file1.newName).toEqual("bla with \ttab.scala");
expect(1).toEqual(file1.blocks.length); expect(file1.blocks.length).toEqual(1);
}); });
it("should parse diff with prefix", function() { it("should parse diff with prefix", () => {
const diff = const diff =
'diff --git "\tbla with \ttab.scala" "\tbla with \ttab.scala"\n' + 'diff --git "\tbla with \ttab.scala" "\tbla with \ttab.scala"\n' +
"index 4c679d7..e9bd385 100644\n" + "index 4c679d7..e9bd385 100644\n" +
@ -93,17 +93,17 @@ describe("DiffParser", function() {
"+cenas com ananas\n" + "+cenas com ananas\n" +
"+bananas"; "+bananas";
const result = DiffParser.generateDiffJson(diff, { srcPrefix: "\t", dstPrefix: "\t" }); const result = parse(diff, { srcPrefix: "\t", dstPrefix: "\t" });
const file1 = result[0]; const file1 = result[0];
expect(1).toEqual(result.length); expect(result.length).toEqual(1);
expect(2).toEqual(file1.addedLines); expect(file1.addedLines).toEqual(2);
expect(1).toEqual(file1.deletedLines); expect(file1.deletedLines).toEqual(1);
expect("bla with \ttab.scala").toEqual(file1.oldName); expect(file1.oldName).toEqual("bla with \ttab.scala");
expect("bla with \ttab.scala").toEqual(file1.newName); expect(file1.newName).toEqual("bla with \ttab.scala");
expect(1).toEqual(file1.blocks.length); expect(file1.blocks.length).toEqual(1);
}); });
it("should parse diff with deleted file", function() { it("should parse diff with deleted file", () => {
const diff = const diff =
"diff --git a/src/var/strundefined.js b/src/var/strundefined.js\n" + "diff --git a/src/var/strundefined.js b/src/var/strundefined.js\n" +
"deleted file mode 100644\n" + "deleted file mode 100644\n" +
@ -111,26 +111,26 @@ describe("DiffParser", function() {
"--- a/src/var/strundefined.js\n" + "--- a/src/var/strundefined.js\n" +
"+++ /dev/null\n" + "+++ /dev/null\n" +
"@@ -1,3 +0,0 @@\n" + "@@ -1,3 +0,0 @@\n" +
"-define(function() {\n" + "-define(() => {\n" +
"- return typeof undefined;\n" + "- return typeof undefined;\n" +
"-});\n"; "-});\n";
const result = DiffParser.generateDiffJson(diff); const result = parse(diff);
expect(1).toEqual(result.length); expect(result.length).toEqual(1);
const file1 = result[0]; const file1 = result[0];
expect(false).toEqual(file1.isCombined); expect(file1.isCombined).toEqual(false);
expect(0).toEqual(file1.addedLines); expect(file1.addedLines).toEqual(0);
expect(3).toEqual(file1.deletedLines); expect(file1.deletedLines).toEqual(3);
expect("src/var/strundefined.js").toEqual(file1.oldName); expect(file1.oldName).toEqual("src/var/strundefined.js");
expect("/dev/null").toEqual(file1.newName); expect(file1.newName).toEqual("/dev/null");
expect(1).toEqual(file1.blocks.length); expect(file1.blocks.length).toEqual(1);
expect(true).toEqual(file1.isDeleted); expect(file1.isDeleted).toEqual(true);
expect("04e16b0").toEqual(file1.checksumBefore); expect(file1.checksumBefore).toEqual("04e16b0");
expect("0000000").toEqual(file1.checksumAfter); expect(file1.checksumAfter).toEqual("0000000");
}); });
it("should parse diff with new file", function() { it("should parse diff with new file", () => {
const diff = const diff =
"diff --git a/test.js b/test.js\n" + "diff --git a/test.js b/test.js\n" +
"new file mode 100644\n" + "new file mode 100644\n" +
@ -144,23 +144,23 @@ describe("DiffParser", function() {
"+\n" + "+\n" +
"+console.log(parser.parsePatchDiffResult(text, patchLineList));\n"; "+console.log(parser.parsePatchDiffResult(text, patchLineList));\n";
const result = DiffParser.generateDiffJson(diff); const result = parse(diff);
expect(1).toEqual(result.length); expect(result.length).toEqual(1);
const file1 = result[0]; const file1 = result[0];
expect(false).toEqual(file1.isCombined); expect(file1.isCombined).toEqual(false);
expect(5).toEqual(file1.addedLines); expect(file1.addedLines).toEqual(5);
expect(0).toEqual(file1.deletedLines); expect(file1.deletedLines).toEqual(0);
expect("/dev/null").toEqual(file1.oldName); expect(file1.oldName).toEqual("/dev/null");
expect("test.js").toEqual(file1.newName); expect(file1.newName).toEqual("test.js");
expect(1).toEqual(file1.blocks.length); expect(file1.blocks.length).toEqual(1);
expect(true).toEqual(file1.isNew); expect(file1.isNew).toEqual(true);
expect("100644").toEqual(file1.newFileMode); expect(file1.newFileMode).toEqual("100644");
expect("0000000").toEqual(file1.checksumBefore); expect(file1.checksumBefore).toEqual("0000000");
expect("e1e22ec").toEqual(file1.checksumAfter); expect(file1.checksumAfter).toEqual("e1e22ec");
}); });
it("should parse diff with nested diff", function() { it("should parse diff with nested diff", () => {
const diff = const diff =
"diff --git a/src/offset.js b/src/offset.js\n" + "diff --git a/src/offset.js b/src/offset.js\n" +
"index cc6ffb4..fa51f18 100644\n" + "index cc6ffb4..fa51f18 100644\n" +
@ -174,22 +174,22 @@ describe("DiffParser", function() {
"+\n" + "+\n" +
"+console.log(parser.parsePatchDiffResult(text, patchLineList));\n"; "+console.log(parser.parsePatchDiffResult(text, patchLineList));\n";
const result = DiffParser.generateDiffJson(diff); const result = parse(diff);
expect(1).toEqual(result.length); expect(result.length).toEqual(1);
const file1 = result[0]; const file1 = result[0];
expect(false).toEqual(file1.isCombined); expect(file1.isCombined).toEqual(false);
expect(6).toEqual(file1.addedLines); expect(file1.addedLines).toEqual(6);
expect(0).toEqual(file1.deletedLines); expect(file1.deletedLines).toEqual(0);
expect("src/offset.js").toEqual(file1.oldName); expect(file1.oldName).toEqual("src/offset.js");
expect("src/offset.js").toEqual(file1.newName); expect(file1.newName).toEqual("src/offset.js");
expect(1).toEqual(file1.blocks.length); expect(file1.blocks.length).toEqual(1);
expect(6).toEqual(file1.blocks[0].lines.length); expect(file1.blocks[0].lines.length).toEqual(6);
expect("cc6ffb4").toEqual(file1.checksumBefore); expect(file1.checksumBefore).toEqual("cc6ffb4");
expect("fa51f18").toEqual(file1.checksumAfter); expect(file1.checksumAfter).toEqual("fa51f18");
}); });
it("should parse diff with multiple blocks", function() { it("should parse diff with multiple blocks", () => {
const diff = const diff =
"diff --git a/src/attributes/classes.js b/src/attributes/classes.js\n" + "diff --git a/src/attributes/classes.js b/src/attributes/classes.js\n" +
"index c617824..c8d1393 100644\n" + "index c617824..c8d1393 100644\n" +
@ -217,23 +217,23 @@ describe("DiffParser", function() {
" // store className if set\n" + " // store className if set\n" +
' dataPriv.set( this, "__className__", this.className );\n'; ' dataPriv.set( this, "__className__", this.className );\n';
const result = DiffParser.generateDiffJson(diff); const result = parse(diff);
expect(1).toEqual(result.length); expect(result.length).toEqual(1);
const file1 = result[0]; const file1 = result[0];
expect(false).toEqual(file1.isCombined); expect(file1.isCombined).toEqual(false);
expect(2).toEqual(file1.addedLines); expect(file1.addedLines).toEqual(2);
expect(3).toEqual(file1.deletedLines); expect(file1.deletedLines).toEqual(3);
expect("src/attributes/classes.js").toEqual(file1.oldName); expect(file1.oldName).toEqual("src/attributes/classes.js");
expect("src/attributes/classes.js").toEqual(file1.newName); expect(file1.newName).toEqual("src/attributes/classes.js");
expect(2).toEqual(file1.blocks.length); expect(file1.blocks.length).toEqual(2);
expect(11).toEqual(file1.blocks[0].lines.length); expect(file1.blocks[0].lines.length).toEqual(11);
expect(8).toEqual(file1.blocks[1].lines.length); expect(file1.blocks[1].lines.length).toEqual(8);
expect("c617824").toEqual(file1.checksumBefore); expect(file1.checksumBefore).toEqual("c617824");
expect("c8d1393").toEqual(file1.checksumAfter); expect(file1.checksumAfter).toEqual("c8d1393");
}); });
it("should parse diff with multiple files", function() { it("should parse diff with multiple files", () => {
const diff = const diff =
"diff --git a/src/core/init.js b/src/core/init.js\n" + "diff --git a/src/core/init.js b/src/core/init.js\n" +
"index e49196a..50f310c 100644\n" + "index e49196a..50f310c 100644\n" +
@ -260,33 +260,33 @@ describe("DiffParser", function() {
' "./var/hasOwn",\n' + ' "./var/hasOwn",\n' +
' "./var/slice",\n'; ' "./var/slice",\n';
const result = DiffParser.generateDiffJson(diff); const result = parse(diff);
expect(2).toEqual(result.length); expect(result.length).toEqual(2);
const file1 = result[0]; const file1 = result[0];
expect(false).toEqual(file1.isCombined); expect(file1.isCombined).toEqual(false);
expect(1).toEqual(file1.addedLines); expect(file1.addedLines).toEqual(1);
expect(1).toEqual(file1.deletedLines); expect(file1.deletedLines).toEqual(1);
expect("src/core/init.js").toEqual(file1.oldName); expect(file1.oldName).toEqual("src/core/init.js");
expect("src/core/init.js").toEqual(file1.newName); expect(file1.newName).toEqual("src/core/init.js");
expect(1).toEqual(file1.blocks.length); expect(file1.blocks.length).toEqual(1);
expect(8).toEqual(file1.blocks[0].lines.length); expect(file1.blocks[0].lines.length).toEqual(8);
expect("e49196a").toEqual(file1.checksumBefore); expect(file1.checksumBefore).toEqual("e49196a");
expect("50f310c").toEqual(file1.checksumAfter); expect(file1.checksumAfter).toEqual("50f310c");
const file2 = result[1]; const file2 = result[1];
expect(false).toEqual(file2.isCombined); expect(file2.isCombined).toEqual(false);
expect(0).toEqual(file2.addedLines); expect(file2.addedLines).toEqual(0);
expect(1).toEqual(file2.deletedLines); expect(file2.deletedLines).toEqual(1);
expect("src/event.js").toEqual(file2.oldName); expect(file2.oldName).toEqual("src/event.js");
expect("src/event.js").toEqual(file2.newName); expect(file2.newName).toEqual("src/event.js");
expect(1).toEqual(file2.blocks.length); expect(file2.blocks.length).toEqual(1);
expect(6).toEqual(file2.blocks[0].lines.length); expect(file2.blocks[0].lines.length).toEqual(6);
expect("7336f4d").toEqual(file2.checksumBefore); expect(file2.checksumBefore).toEqual("7336f4d");
expect("6183f70").toEqual(file2.checksumAfter); expect(file2.checksumAfter).toEqual("6183f70");
}); });
it("should parse combined diff", function() { it("should parse combined diff", () => {
const diff = const diff =
"diff --combined describe.c\n" + "diff --combined describe.c\n" +
"index fabadb8,cc95eb0..4866510\n" + "index fabadb8,cc95eb0..4866510\n" +
@ -316,62 +316,62 @@ describe("DiffParser", function() {
" initialized = 1;\n" + " initialized = 1;\n" +
" for_each_ref(get_name);\n"; " for_each_ref(get_name);\n";
const result = DiffParser.generateDiffJson(diff); const result = parse(diff);
expect(1).toEqual(result.length); expect(result.length).toEqual(1);
const file1 = result[0]; const file1 = result[0];
expect(true).toEqual(file1.isCombined); expect(file1.isCombined).toEqual(true);
expect(9).toEqual(file1.addedLines); expect(9).toEqual(file1.addedLines);
expect(2).toEqual(file1.deletedLines); expect(2).toEqual(file1.deletedLines);
expect("describe.c").toEqual(file1.oldName); expect(file1.oldName).toEqual("describe.c");
expect("describe.c").toEqual(file1.newName); expect(file1.newName).toEqual("describe.c");
expect(1).toEqual(file1.blocks.length); expect(file1.blocks.length).toEqual(1);
expect(22).toEqual(file1.blocks[0].lines.length); expect(file1.blocks[0].lines.length).toEqual(22);
expect(["4866510", "cc95eb0"].sort()).toEqual(file1.checksumBefore.sort()); expect(file1.checksumBefore).toEqual(["cc95eb0", "4866510"]);
expect("fabadb8").toEqual(file1.checksumAfter); expect(file1.checksumAfter).toEqual("fabadb8");
}); });
it("should parse diffs with copied files", function() { it("should parse diffs with copied files", () => {
const diff = const diff =
"diff --git a/index.js b/more-index.js\n" + "diff --git a/index.js b/more-index.js\n" +
"dissimilarity index 5%\n" + "dissimilarity index 5%\n" +
"copy from index.js\n" + "copy from index.js\n" +
"copy to more-index.js\n"; "copy to more-index.js\n";
const result = DiffParser.generateDiffJson(diff); const result = parse(diff);
expect(1).toEqual(result.length); expect(result.length).toEqual(1);
const file1 = result[0]; const file1 = result[0];
expect(0).toEqual(file1.addedLines); expect(file1.addedLines).toEqual(0);
expect(0).toEqual(file1.deletedLines); expect(file1.deletedLines).toEqual(0);
expect("index.js").toEqual(file1.oldName); expect(file1.oldName).toEqual("index.js");
expect("more-index.js").toEqual(file1.newName); expect(file1.newName).toEqual("more-index.js");
expect(0).toEqual(file1.blocks.length); expect(file1.blocks.length).toEqual(0);
expect(true).toEqual(file1.isCopy); expect(file1.isCopy).toEqual(true);
expect("5").toEqual(file1.changedPercentage); expect(file1.changedPercentage).toEqual(5);
}); });
it("should parse diffs with moved files", function() { it("should parse diffs with moved files", () => {
const diff = const diff =
"diff --git a/more-index.js b/other-index.js\n" + "diff --git a/more-index.js b/other-index.js\n" +
"similarity index 86%\n" + "similarity index 86%\n" +
"rename from more-index.js\n" + "rename from more-index.js\n" +
"rename to other-index.js\n"; "rename to other-index.js\n";
const result = DiffParser.generateDiffJson(diff); const result = parse(diff);
expect(1).toEqual(result.length); expect(result.length).toEqual(1);
const file1 = result[0]; const file1 = result[0];
expect(0).toEqual(file1.addedLines); expect(file1.addedLines).toEqual(0);
expect(0).toEqual(file1.deletedLines); expect(file1.deletedLines).toEqual(0);
expect("more-index.js").toEqual(file1.oldName); expect(file1.oldName).toEqual("more-index.js");
expect("other-index.js").toEqual(file1.newName); expect(file1.newName).toEqual("other-index.js");
expect(0).toEqual(file1.blocks.length); expect(file1.blocks.length).toEqual(0);
expect(true).toEqual(file1.isRename); expect(file1.isRename).toEqual(true);
expect("86").toEqual(file1.unchangedPercentage); expect(file1.unchangedPercentage).toEqual(86);
}); });
it("should parse diffs correct line numbers", function() { it("should parse diffs correct line numbers", () => {
const diff = const diff =
"diff --git a/sample b/sample\n" + "diff --git a/sample b/sample\n" +
"index 0000001..0ddf2ba\n" + "index 0000001..0ddf2ba\n" +
@ -381,23 +381,23 @@ describe("DiffParser", function() {
"-test\n" + "-test\n" +
"+test1r\n"; "+test1r\n";
const result = DiffParser.generateDiffJson(diff); const result = parse(diff);
expect(1).toEqual(result.length); expect(result.length).toEqual(1);
const file1 = result[0]; const file1 = result[0];
expect(1).toEqual(file1.addedLines); expect(file1.addedLines).toEqual(1);
expect(1).toEqual(file1.deletedLines); expect(file1.deletedLines).toEqual(1);
expect("sample").toEqual(file1.oldName); expect(file1.oldName).toEqual("sample");
expect("sample").toEqual(file1.newName); expect(file1.newName).toEqual("sample");
expect(1).toEqual(file1.blocks.length); expect(file1.blocks.length).toEqual(1);
expect(2).toEqual(file1.blocks[0].lines.length); expect(file1.blocks[0].lines.length).toEqual(2);
expect(1).toEqual(file1.blocks[0].lines[0].oldNumber); expect(file1.blocks[0].lines[0].oldNumber).toEqual(1);
expect(null).toEqual(file1.blocks[0].lines[0].newNumber); expect(file1.blocks[0].lines[0].newNumber).toBeUndefined();
expect(null).toEqual(file1.blocks[0].lines[1].oldNumber); expect(file1.blocks[0].lines[1].oldNumber).toBeUndefined();
expect(1).toEqual(file1.blocks[0].lines[1].newNumber); expect(file1.blocks[0].lines[1].newNumber).toEqual(1);
}); });
it("should parse unified non git diff and strip timestamps off the headers", function() { it("should parse unified non git diff and strip timestamps off the headers", () => {
const diffs = [ const diffs = [
// 2 hours ahead of GMT // 2 hours ahead of GMT
"--- a/sample.js 2016-10-25 11:37:14.000000000 +0200\n" + "--- a/sample.js 2016-10-25 11:37:14.000000000 +0200\n" +
@ -416,42 +416,42 @@ describe("DiffParser", function() {
]; ];
diffs.forEach(function(diff) { diffs.forEach(function(diff) {
const result = DiffParser.generateDiffJson(diff); const result = parse(diff);
const file1 = result[0]; const file1 = result[0];
expect(1).toEqual(result.length); expect(result.length).toEqual(1);
expect(2).toEqual(file1.addedLines); expect(file1.addedLines).toEqual(2);
expect(1).toEqual(file1.deletedLines); expect(file1.deletedLines).toEqual(1);
expect("sample.js").toEqual(file1.oldName); expect(file1.oldName).toEqual("sample.js");
expect("sample.js").toEqual(file1.newName); expect(file1.newName).toEqual("sample.js");
expect(1).toEqual(file1.blocks.length); expect(file1.blocks.length).toEqual(1);
const linesContent = file1.blocks[0].lines.map(function(line) { const linesContent = file1.blocks[0].lines.map(function(line) {
return line.content; return line.content;
}); });
expect(linesContent).toEqual(["-test", "+test1r", "+test2r"]); expect(["-test", "+test1r", "+test2r"]).toEqual(linesContent);
}); });
}); });
it("should parse unified non git diff", function() { it("should parse unified non git diff", () => {
const diff = const diff =
"--- a/sample.js\n" + "+++ b/sample.js\n" + "@@ -1 +1,2 @@\n" + "-test\n" + "+test1r\n" + "+test2r\n"; "--- a/sample.js\n" + "+++ b/sample.js\n" + "@@ -1 +1,2 @@\n" + "-test\n" + "+test1r\n" + "+test2r\n";
const result = DiffParser.generateDiffJson(diff); const result = parse(diff);
const file1 = result[0]; const file1 = result[0];
expect(1).toEqual(result.length); expect(result.length).toEqual(1);
expect(2).toEqual(file1.addedLines); expect(file1.addedLines).toEqual(2);
expect(1).toEqual(file1.deletedLines); expect(file1.deletedLines).toEqual(1);
expect("sample.js").toEqual(file1.oldName); expect(file1.oldName).toEqual("sample.js");
expect("sample.js").toEqual(file1.newName); expect(file1.newName).toEqual("sample.js");
expect(1).toEqual(file1.blocks.length); expect(file1.blocks.length).toEqual(1);
const linesContent = file1.blocks[0].lines.map(function(line) { const linesContent = file1.blocks[0].lines.map(function(line) {
return line.content; return line.content;
}); });
expect(linesContent).toEqual(["-test", "+test1r", "+test2r"]); expect(["-test", "+test1r", "+test2r"]).toEqual(linesContent);
}); });
it("should parse unified diff with multiple hunks and files", function() { it("should parse unified diff with multiple hunks and files", () => {
const diff = const diff =
"--- sample.js\n" + "--- sample.js\n" +
"+++ sample.js\n" + "+++ sample.js\n" +
@ -464,40 +464,40 @@ describe("DiffParser", function() {
"@@ -1 +1,2 @@\n" + "@@ -1 +1,2 @@\n" +
"+test1"; "+test1";
const result = DiffParser.generateDiffJson(diff); const result = parse(diff);
expect(2).toEqual(result.length); expect(result.length).toEqual(2);
const file1 = result[0]; const file1 = result[0];
expect(1).toEqual(file1.addedLines); expect(file1.addedLines).toEqual(1);
expect(1).toEqual(file1.deletedLines); expect(file1.deletedLines).toEqual(1);
expect("sample.js").toEqual(file1.oldName); expect(file1.oldName).toEqual("sample.js");
expect("sample.js").toEqual(file1.newName); expect(file1.newName).toEqual("sample.js");
expect(2).toEqual(file1.blocks.length); expect(file1.blocks.length).toEqual(2);
const linesContent1 = file1.blocks[0].lines.map(function(line) { const linesContent1 = file1.blocks[0].lines.map(function(line) {
return line.content; return line.content;
}); });
expect(linesContent1).toEqual(["-test"]); expect(["-test"]).toEqual(linesContent1);
const linesContent2 = file1.blocks[1].lines.map(function(line) { const linesContent2 = file1.blocks[1].lines.map(function(line) {
return line.content; return line.content;
}); });
expect(linesContent2).toEqual(["+test"]); expect(["+test"]).toEqual(linesContent2);
const file2 = result[1]; const file2 = result[1];
expect(1).toEqual(file2.addedLines); expect(file2.addedLines).toEqual(1);
expect(0).toEqual(file2.deletedLines); expect(file2.deletedLines).toEqual(0);
expect("sample1.js").toEqual(file2.oldName); expect(file2.oldName).toEqual("sample1.js");
expect("sample1.js").toEqual(file2.newName); expect(file2.newName).toEqual("sample1.js");
expect(1).toEqual(file2.blocks.length); expect(file2.blocks.length).toEqual(1);
const linesContent = file2.blocks[0].lines.map(function(line) { const linesContent = file2.blocks[0].lines.map(function(line) {
return line.content; return line.content;
}); });
expect(linesContent).toEqual(["+test1"]); expect(["+test1"]).toEqual(linesContent);
}); });
it("should parse diff with --- and +++ in the context lines", function() { it("should parse diff with --- and +++ in the context lines", () => {
const diff = const diff =
"--- sample.js\n" + "--- sample.js\n" +
"+++ sample.js\n" + "+++ sample.js\n" +
@ -512,40 +512,40 @@ describe("DiffParser", function() {
"+++ 2\n" + "+++ 2\n" +
"++++ 2"; "++++ 2";
const result = DiffParser.generateDiffJson(diff); const result = parse(diff);
const file1 = result[0]; const file1 = result[0];
expect(1).toEqual(result.length); expect(result.length).toEqual(1);
expect(3).toEqual(file1.addedLines); expect(file1.addedLines).toEqual(3);
expect(3).toEqual(file1.deletedLines); expect(file1.deletedLines).toEqual(3);
expect("sample.js").toEqual(file1.oldName); expect(file1.oldName).toEqual("sample.js");
expect("sample.js").toEqual(file1.newName); expect(file1.newName).toEqual("sample.js");
expect(1).toEqual(file1.blocks.length); expect(file1.blocks.length).toEqual(1);
const linesContent = file1.blocks[0].lines.map(function(line) { const linesContent = file1.blocks[0].lines.map(function(line) {
return line.content; return line.content;
}); });
expect(linesContent).toEqual([" test", " ", "-- 1", "--- 1", "---- 1", " ", "++ 2", "+++ 2", "++++ 2"]); expect([" test", " ", "-- 1", "--- 1", "---- 1", " ", "++ 2", "+++ 2", "++++ 2"]).toEqual(linesContent);
}); });
it("should parse diff without proper hunk headers", function() { it("should parse diff without proper hunk headers", () => {
const diff = "--- sample.js\n" + "+++ sample.js\n" + "@@ @@\n" + " test"; const diff = "--- sample.js\n" + "+++ sample.js\n" + "@@ @@\n" + " test";
const result = DiffParser.generateDiffJson(diff); const result = parse(diff);
const file1 = result[0]; const file1 = result[0];
expect(1).toEqual(result.length); expect(result.length).toEqual(1);
expect(0).toEqual(file1.addedLines); expect(file1.addedLines).toEqual(0);
expect(0).toEqual(file1.deletedLines); expect(file1.deletedLines).toEqual(0);
expect("sample.js").toEqual(file1.oldName); expect(file1.oldName).toEqual("sample.js");
expect("sample.js").toEqual(file1.newName); expect(file1.newName).toEqual("sample.js");
expect(1).toEqual(file1.blocks.length); expect(file1.blocks.length).toEqual(1);
const linesContent = file1.blocks[0].lines.map(function(line) { const linesContent = file1.blocks[0].lines.map(function(line) {
return line.content; return line.content;
}); });
expect(linesContent).toEqual([" test"]); expect([" test"]).toEqual(linesContent);
}); });
it("should parse binary file diff", function() { it("should parse binary file diff", () => {
const diff = const diff =
"diff --git a/last-changes-config.png b/last-changes-config.png\n" + "diff --git a/last-changes-config.png b/last-changes-config.png\n" +
"index 322248b..56fc1f2 100644\n" + "index 322248b..56fc1f2 100644\n" +
@ -553,19 +553,19 @@ describe("DiffParser", function() {
"+++ b/last-changes-config.png\n" + "+++ b/last-changes-config.png\n" +
"Binary files differ"; "Binary files differ";
const result = DiffParser.generateDiffJson(diff); const result = parse(diff);
const file1 = result[0]; const file1 = result[0];
expect(1).toEqual(result.length); expect(result.length).toEqual(1);
expect(0).toEqual(file1.addedLines); expect(file1.addedLines).toEqual(0);
expect(0).toEqual(file1.deletedLines); expect(file1.deletedLines).toEqual(0);
expect("last-changes-config.png").toEqual(file1.oldName); expect(file1.oldName).toEqual("last-changes-config.png");
expect("last-changes-config.png").toEqual(file1.newName); expect(file1.newName).toEqual("last-changes-config.png");
expect(1).toEqual(file1.blocks.length); expect(file1.blocks.length).toEqual(1);
expect(0).toEqual(file1.blocks[0].lines.length); expect(file1.blocks[0].lines.length).toEqual(0);
expect("Binary files differ").toEqual(file1.blocks[0].header); expect(file1.blocks[0].header).toEqual("Binary files differ");
}); });
it("should parse diff with --find-renames", function() { it("should parse diff with --find-renames", () => {
const diff = const diff =
"diff --git a/src/test-bar.js b/src/test-baz.js\n" + "diff --git a/src/test-bar.js b/src/test-baz.js\n" +
"similarity index 98%\n" + "similarity index 98%\n" +
@ -581,22 +581,22 @@ describe("DiffParser", function() {
" }\n" + " }\n" +
" "; " ";
const result = DiffParser.generateDiffJson(diff); const result = parse(diff);
const file1 = result[0]; const file1 = result[0];
expect(1).toEqual(result.length); expect(result.length).toEqual(1);
expect(1).toEqual(file1.addedLines); expect(file1.addedLines).toEqual(1);
expect(1).toEqual(file1.deletedLines); expect(file1.deletedLines).toEqual(1);
expect("src/test-bar.js").toEqual(file1.oldName); expect(file1.oldName).toEqual("src/test-bar.js");
expect("src/test-baz.js").toEqual(file1.newName); expect(file1.newName).toEqual("src/test-baz.js");
expect(1).toEqual(file1.blocks.length); expect(file1.blocks.length).toEqual(1);
expect(5).toEqual(file1.blocks[0].lines.length); expect(file1.blocks[0].lines.length).toEqual(5);
const linesContent = file1.blocks[0].lines.map(function(line) { const linesContent = file1.blocks[0].lines.map(function(line) {
return line.content; return line.content;
}); });
expect(linesContent).toEqual([" function foo() {", '-var bar = "Whoops!";', '+var baz = "Whoops!";', " }", " "]); expect([" function foo() {", '-var bar = "Whoops!";', '+var baz = "Whoops!";', " }", " "]).toEqual(linesContent);
}); });
it("should parse diff with prefix 2", function() { it("should parse diff with prefix 2", () => {
const diff = const diff =
'diff --git "\tTest.scala" "\tScalaTest.scala"\n' + 'diff --git "\tTest.scala" "\tScalaTest.scala"\n' +
"similarity index 88%\n" + "similarity index 88%\n" +
@ -640,36 +640,36 @@ describe("DiffParser", function() {
" }\n" + " }\n" +
" "; " ";
const result = DiffParser.generateDiffJson(diff, { srcPrefix: "\t", dstPrefix: "\t" }); const result = parse(diff, { srcPrefix: "\t", dstPrefix: "\t" });
expect(3).toEqual(result.length); expect(result.length).toEqual(3);
const file1 = result[0]; const file1 = result[0];
expect(2).toEqual(file1.addedLines); expect(file1.addedLines).toEqual(2);
expect(1).toEqual(file1.deletedLines); expect(file1.deletedLines).toEqual(1);
expect("Test.scala").toEqual(file1.oldName); expect(file1.oldName).toEqual("Test.scala");
expect("ScalaTest.scala").toEqual(file1.newName); expect(file1.newName).toEqual("ScalaTest.scala");
expect(2).toEqual(file1.blocks.length); expect(file1.blocks.length).toEqual(2);
expect(8).toEqual(file1.blocks[0].lines.length); expect(file1.blocks[0].lines.length).toEqual(8);
expect(7).toEqual(file1.blocks[1].lines.length); expect(file1.blocks[1].lines.length).toEqual(7);
const file2 = result[1]; const file2 = result[1];
expect("/dev/null").toEqual(file2.oldName); expect(file2.oldName).toEqual("/dev/null");
expect("tardis.png").toEqual(file2.newName); expect(file2.newName).toEqual("tardis.png");
const file3 = result[2]; const file3 = result[2];
expect(1).toEqual(file3.addedLines); expect(file3.addedLines).toEqual(1);
expect(1).toEqual(file3.deletedLines); expect(file3.deletedLines).toEqual(1);
expect("src/test-bar.js").toEqual(file3.oldName); expect(file3.oldName).toEqual("src/test-bar.js");
expect("src/test-baz.js").toEqual(file3.newName); expect(file3.newName).toEqual("src/test-baz.js");
expect(1).toEqual(file3.blocks.length); expect(file3.blocks.length).toEqual(1);
expect(5).toEqual(file3.blocks[0].lines.length); expect(file3.blocks[0].lines.length).toEqual(5);
const linesContent = file3.blocks[0].lines.map(function(line) { const linesContent = file3.blocks[0].lines.map(function(line) {
return line.content; return line.content;
}); });
expect(linesContent).toEqual([" function foo() {", '-var bar = "Whoops!";', '+var baz = "Whoops!";', " }", " "]); expect([" function foo() {", '-var bar = "Whoops!";', '+var baz = "Whoops!";', " }", " "]).toEqual(linesContent);
}); });
it("should parse binary with content", function() { it("should parse binary with content", () => {
const diff = const diff =
"diff --git a/favicon.png b/favicon.png\n" + "diff --git a/favicon.png b/favicon.png\n" +
"deleted file mode 100644\n" + "deleted file mode 100644\n" +
@ -703,26 +703,26 @@ describe("DiffParser", function() {
" }\n" + " }\n" +
" "; " ";
const result = DiffParser.generateDiffJson(diff); const result = parse(diff);
expect(2).toEqual(result.length); expect(result.length).toEqual(2);
const file1 = result[0]; const file1 = result[0];
expect("favicon.png").toEqual(file1.oldName); expect(file1.oldName).toEqual("favicon.png");
expect("favicon.png").toEqual(file1.newName); expect(file1.newName).toEqual("favicon.png");
expect(1).toEqual(file1.blocks.length); expect(file1.blocks.length).toEqual(1);
expect(0).toEqual(file1.blocks[0].lines.length); expect(file1.blocks[0].lines.length).toEqual(0);
const file2 = result[1]; const file2 = result[1];
expect(1).toEqual(file2.addedLines); expect(file2.addedLines).toEqual(1);
expect(1).toEqual(file2.deletedLines); expect(file2.deletedLines).toEqual(1);
expect("src/test-bar.js").toEqual(file2.oldName); expect(file2.oldName).toEqual("src/test-bar.js");
expect("src/test-baz.js").toEqual(file2.newName); expect(file2.newName).toEqual("src/test-baz.js");
expect(1).toEqual(file2.blocks.length); expect(file2.blocks.length).toEqual(1);
expect(5).toEqual(file2.blocks[0].lines.length); expect(file2.blocks[0].lines.length).toEqual(5);
const linesContent = file2.blocks[0].lines.map(function(line) { const linesContent = file2.blocks[0].lines.map(function(line) {
return line.content; return line.content;
}); });
expect(linesContent).toEqual([" function foo() {", '-var bar = "Whoops!";', '+var baz = "Whoops!";', " }", " "]); expect([" function foo() {", '-var bar = "Whoops!";', '+var baz = "Whoops!";', " }", " "]).toEqual(linesContent);
}); });
}); });
}); });

View file

@ -1,4 +1,5 @@
const Diff2Html = require("../diff2html.js").Diff2Html; import { parse, html } from "../diff2html";
import { DiffFile, LineType } from "../render-utils";
const diffExample1 = const diffExample1 =
"diff --git a/sample b/sample\n" + "diff --git a/sample b/sample\n" +
@ -9,27 +10,27 @@ const diffExample1 =
"-test\n" + "-test\n" +
"+test1\n"; "+test1\n";
const jsonExample1 = [ const jsonExample1: DiffFile[] = [
{ {
blocks: [ blocks: [
{ {
lines: [ lines: [
{ {
content: "-test", content: "-test",
type: "d2h-del", type: LineType.DELETE,
oldNumber: 1, oldNumber: 1,
newNumber: null newNumber: undefined
}, },
{ {
content: "+test1", content: "+test1",
type: "d2h-ins", type: LineType.INSERT,
oldNumber: null, oldNumber: undefined,
newNumber: 1 newNumber: 1
} }
], ],
oldStartLine: "1", oldStartLine: 1,
oldStartLine2: null, oldStartLine2: undefined,
newStartLine: "1", newStartLine: 1,
header: "@@ -1 +1 @@" header: "@@ -1 +1 @@"
} }
], ],
@ -38,9 +39,10 @@ const jsonExample1 = [
checksumBefore: "0000001", checksumBefore: "0000001",
checksumAfter: "0ddf2ba", checksumAfter: "0ddf2ba",
oldName: "sample", oldName: "sample",
language: undefined,
newName: "sample", newName: "sample",
isCombined: false language: "",
isCombined: false,
isGitDiff: true
} }
]; ];
@ -183,9 +185,9 @@ const htmlSideExample1 =
const htmlSideExample1WithFilesSummary = filesExample1 + htmlSideExample1; const htmlSideExample1WithFilesSummary = filesExample1 + htmlSideExample1;
describe("Diff2Html", function() { describe("Diff2Html", () => {
describe("getJsonFromDiff", function() { describe("getJsonFromDiff", () => {
it("should parse simple diff to json", function() { it("should parse simple diff to json", () => {
const diff = const diff =
"diff --git a/sample b/sample\n" + "diff --git a/sample b/sample\n" +
"index 0000001..0ddf2ba\n" + "index 0000001..0ddf2ba\n" +
@ -194,19 +196,19 @@ describe("Diff2Html", function() {
"@@ -1 +1 @@\n" + "@@ -1 +1 @@\n" +
"-test\n" + "-test\n" +
"+test1\n"; "+test1\n";
const result = Diff2Html.getJsonFromDiff(diff); const result = parse(diff);
const file1 = result[0]; const file1 = result[0];
expect(1).toEqual(result.length); expect(result.length).toEqual(1);
expect(1).toEqual(file1.addedLines); expect(file1.addedLines).toEqual(1);
expect(1).toEqual(file1.deletedLines); expect(file1.deletedLines).toEqual(1);
expect("sample").toEqual(file1.oldName); expect(file1.oldName).toEqual("sample");
expect("sample").toEqual(file1.newName); expect(file1.newName).toEqual("sample");
expect(1).toEqual(file1.blocks.length); expect(file1.blocks.length).toEqual(1);
}); });
// Test case for issue #49 // Test case for issue #49
it("should parse diff with added EOF", function() { it("should parse diff with added EOF", () => {
const diff = const diff =
"diff --git a/sample.scala b/sample.scala\n" + "diff --git a/sample.scala b/sample.scala\n" +
"index b583263..8b2fc3e 100644\n" + "index b583263..8b2fc3e 100644\n" +
@ -223,67 +225,67 @@ describe("Diff2Html", function() {
"+ IndexLock, RepositoryError, NotValidRepo, PullRequestNotMergeable, BranchError,\n" + "+ IndexLock, RepositoryError, NotValidRepo, PullRequestNotMergeable, BranchError,\n" +
"+ PluginError, CodeParserError, EngineError = Value\n" + "+ PluginError, CodeParserError, EngineError = Value\n" +
"+}\n"; "+}\n";
const result = Diff2Html.getJsonFromDiff(diff); const result = parse(diff);
expect(50).toEqual(result[0].blocks[0].lines[0].oldNumber); expect(result[0].blocks[0].lines[0].oldNumber).toEqual(50);
expect(50).toEqual(result[0].blocks[0].lines[0].newNumber); expect(result[0].blocks[0].lines[0].newNumber).toEqual(50);
expect(51).toEqual(result[0].blocks[0].lines[1].oldNumber); expect(result[0].blocks[0].lines[1].oldNumber).toEqual(51);
expect(51).toEqual(result[0].blocks[0].lines[1].newNumber); expect(result[0].blocks[0].lines[1].newNumber).toEqual(51);
expect(52).toEqual(result[0].blocks[0].lines[2].oldNumber); expect(result[0].blocks[0].lines[2].oldNumber).toEqual(52);
expect(52).toEqual(result[0].blocks[0].lines[2].newNumber); expect(result[0].blocks[0].lines[2].newNumber).toEqual(52);
expect(53).toEqual(result[0].blocks[0].lines[3].oldNumber); expect(result[0].blocks[0].lines[3].oldNumber).toEqual(53);
expect(null).toEqual(result[0].blocks[0].lines[3].newNumber); expect(result[0].blocks[0].lines[3].newNumber).toBeUndefined();
expect(54).toEqual(result[0].blocks[0].lines[4].oldNumber); expect(result[0].blocks[0].lines[4].oldNumber).toEqual(54);
expect(null).toEqual(result[0].blocks[0].lines[4].newNumber); expect(result[0].blocks[0].lines[4].newNumber).toBeUndefined();
expect(null).toEqual(result[0].blocks[0].lines[5].oldNumber); expect(result[0].blocks[0].lines[5].oldNumber).toBeUndefined();
expect(53).toEqual(result[0].blocks[0].lines[5].newNumber); expect(result[0].blocks[0].lines[5].newNumber).toEqual(53);
expect(null).toEqual(result[0].blocks[0].lines[6].oldNumber); expect(result[0].blocks[0].lines[6].oldNumber).toBeUndefined();
expect(54).toEqual(result[0].blocks[0].lines[6].newNumber); expect(result[0].blocks[0].lines[6].newNumber).toEqual(54);
expect(null).toEqual(result[0].blocks[0].lines[7].oldNumber); expect(result[0].blocks[0].lines[7].oldNumber).toBeUndefined();
expect(55).toEqual(result[0].blocks[0].lines[7].newNumber); expect(result[0].blocks[0].lines[7].newNumber).toEqual(55);
expect(null).toEqual(result[0].blocks[0].lines[8].oldNumber); expect(result[0].blocks[0].lines[8].oldNumber).toBeUndefined();
expect(56).toEqual(result[0].blocks[0].lines[8].newNumber); expect(result[0].blocks[0].lines[8].newNumber).toEqual(56);
}); });
it("should generate pretty line by line html from diff", function() { it("should generate pretty line by line html from diff", () => {
const result = Diff2Html.getPrettyHtmlFromDiff(diffExample1); const result = html(diffExample1);
expect(htmlLineExample1).toEqual(result); expect(result).toEqual(htmlLineExample1);
}); });
it("should generate pretty line by line html from json", function() { it("should generate pretty line by line html from json", () => {
const result = Diff2Html.getPrettyHtmlFromJson(jsonExample1); const result = html(jsonExample1);
expect(htmlLineExample1).toEqual(result); expect(result).toEqual(htmlLineExample1);
}); });
it("should generate pretty diff with files summary", function() { it("should generate pretty diff with files summary", () => {
const result = Diff2Html.getPrettyHtmlFromDiff(diffExample1, { showFiles: true }); const result = html(diffExample1, { showFiles: true });
expect(htmlLineExample1WithFilesSummary).toEqual(result); expect(result).toEqual(htmlLineExample1WithFilesSummary);
}); });
it("should generate pretty side by side html from diff", function() { it("should generate pretty side by side html from diff", () => {
const result = Diff2Html.getPrettySideBySideHtmlFromDiff(diffExample1); const result = html(diffExample1, { outputFormat: "side-by-side" });
expect(htmlSideExample1).toEqual(result); expect(result).toEqual(htmlSideExample1);
}); });
it("should generate pretty side by side html from json", function() { it("should generate pretty side by side html from json", () => {
const result = Diff2Html.getPrettySideBySideHtmlFromJson(jsonExample1); const result = html(jsonExample1, { outputFormat: "side-by-side" });
expect(htmlSideExample1).toEqual(result); expect(result).toEqual(htmlSideExample1);
}); });
it("should generate pretty side by side html from diff 2", function() { it("should generate pretty side by side html from diff 2", () => {
const result = Diff2Html.getPrettySideBySideHtmlFromDiff(diffExample1, { showFiles: true }); const result = html(diffExample1, { outputFormat: "side-by-side", showFiles: true });
expect(htmlSideExample1WithFilesSummary).toEqual(result); expect(result).toEqual(htmlSideExample1WithFilesSummary);
}); });
it("should generate pretty side by side html from diff with html on headers", function() { it("should generate pretty side by side html from diff with html on headers", () => {
const diffExample2 = const diffExample2 =
"diff --git a/CHANGELOG.md b/CHANGELOG.md\n" + "diff --git a/CHANGELOG.md b/CHANGELOG.md\n" +
"index fc3e3f4..b486d10 100644\n" + "index fc3e3f4..b486d10 100644\n" +
@ -516,8 +518,8 @@ describe("Diff2Html", function() {
"</div>\n" + "</div>\n" +
"</div>"; "</div>";
const result = Diff2Html.getPrettyHtmlFromDiff(diffExample2); const result = html(diffExample2);
expect(result).toEqual(htmlExample2); expect(htmlExample2).toEqual(result);
}); });
}); });
}); });

View file

@ -1,17 +1,30 @@
const FileListPrinter = require("../file-list-printer.js").FileListPrinter; import { render } from "../file-list-renderer";
import HoganJsUtils from "../hoganjs-utils";
describe("FileListPrinter", function() { describe("FileListPrinter", () => {
describe("generateFileList", function() { describe("generateFileList", () => {
it("should expose old and new files to templates", function() { it("should expose old and new files to templates", () => {
const hoganUtils = new HoganJsUtils({
rawTemplates: {
"file-summary-wrapper": "{{{files}}}",
"file-summary-line": "{{oldName}}, {{newName}}, {{fileName}}"
}
});
const files = [ const files = [
{ {
addedlines: 12, isCombined: false,
deletedlines: 41, isGitDiff: false,
blocks: [],
addedLines: 12,
deletedLines: 41,
language: "js", language: "js",
oldName: "my/file/name.js", oldName: "my/file/name.js",
newName: "my/file/name.js" newName: "my/file/name.js"
}, },
{ {
isCombined: false,
isGitDiff: false,
blocks: [],
addedLines: 12, addedLines: 12,
deletedLines: 41, deletedLines: 41,
language: "js", language: "js",
@ -19,6 +32,9 @@ describe("FileListPrinter", function() {
newName: "my/file/name2.js" newName: "my/file/name2.js"
}, },
{ {
isCombined: false,
isGitDiff: false,
blocks: [],
addedLines: 12, addedLines: 12,
deletedLines: 0, deletedLines: 0,
language: "js", language: "js",
@ -27,6 +43,9 @@ describe("FileListPrinter", function() {
isNew: true isNew: true
}, },
{ {
isCombined: false,
isGitDiff: false,
blocks: [],
addedLines: 0, addedLines: 0,
deletedLines: 41, deletedLines: 41,
language: "js", language: "js",
@ -36,26 +55,23 @@ describe("FileListPrinter", function() {
} }
]; ];
const fileListPrinter = new FileListPrinter({ const fileHtml = render(files, hoganUtils);
rawTemplates: {
"file-summary-wrapper": "{{{files}}}",
"file-summary-line": "{{oldName}}, {{newName}}, {{fileName}}"
}
});
const fileHtml = fileListPrinter.generateFileList(files);
const expected = const expected =
"my/file/name.js, my/file/name.js, my/file/name.js\n" + "my/file/name.js, my/file/name.js, my/file/name.js\n" +
"my/file/name1.js, my/file/name2.js, my/file/{name1.js → name2.js}\n" + "my/file/name1.js, my/file/name2.js, my/file/{name1.js → name2.js}\n" +
"dev/null, my/file/name.js, my/file/name.js\n" + "dev/null, my/file/name.js, my/file/name.js\n" +
"my/file/name.js, dev/null, my/file/name.js"; "my/file/name.js, dev/null, my/file/name.js";
expect(expected).toEqual(fileHtml); expect(fileHtml).toEqual(expected);
}); });
it("should work for all kinds of files", function() { it("should work for all kinds of files", () => {
const hoganUtils = new HoganJsUtils({});
const files = [ const files = [
{ {
isCombined: false,
isGitDiff: false,
blocks: [],
addedLines: 12, addedLines: 12,
deletedLines: 41, deletedLines: 41,
language: "js", language: "js",
@ -63,6 +79,9 @@ describe("FileListPrinter", function() {
newName: "my/file/name.js" newName: "my/file/name.js"
}, },
{ {
isCombined: false,
isGitDiff: false,
blocks: [],
addedLines: 12, addedLines: 12,
deletedLines: 41, deletedLines: 41,
language: "js", language: "js",
@ -70,6 +89,9 @@ describe("FileListPrinter", function() {
newName: "my/file/name2.js" newName: "my/file/name2.js"
}, },
{ {
isCombined: false,
isGitDiff: false,
blocks: [],
addedLines: 12, addedLines: 12,
deletedLines: 0, deletedLines: 0,
language: "js", language: "js",
@ -78,6 +100,9 @@ describe("FileListPrinter", function() {
isNew: true isNew: true
}, },
{ {
isCombined: false,
isGitDiff: false,
blocks: [],
addedLines: 0, addedLines: 0,
deletedLines: 41, deletedLines: 41,
language: "js", language: "js",
@ -87,8 +112,7 @@ describe("FileListPrinter", function() {
} }
]; ];
const fileListPrinter = new FileListPrinter(); const fileHtml = render(files, hoganUtils);
const fileHtml = fileListPrinter.generateFileList(files);
const expected = const expected =
'<div class="d2h-file-list-wrapper">\n' + '<div class="d2h-file-list-wrapper">\n' +
@ -149,7 +173,7 @@ describe("FileListPrinter", function() {
" </ol>\n" + " </ol>\n" +
"</div>"; "</div>";
expect(expected).toEqual(fileHtml); expect(fileHtml).toEqual(expected);
}); });
}); });
}); });

View file

@ -1,73 +0,0 @@
const HoganJsUtils = new (require("../hoganjs-utils.js")).HoganJsUtils();
const diffParser = require("../diff-parser.js").DiffParser;
describe("HoganJsUtils", function() {
describe("render", function() {
const emptyDiffHtml =
"<tr>\n" +
' <td class="d2h-info">\n' +
' <div class="d2h-code-line d2h-info">\n' +
" File without changes\n" +
" </div>\n" +
" </td>\n" +
"</tr>";
it("should render view", function() {
const result = HoganJsUtils.render("generic", "empty-diff", {
contentClass: "d2h-code-line",
diffParser: diffParser
});
expect(emptyDiffHtml).toEqual(result);
});
it("should render view without cache", function() {
const result = HoganJsUtils.render(
"generic",
"empty-diff",
{
contentClass: "d2h-code-line",
diffParser: diffParser
},
{ noCache: true }
);
expect(emptyDiffHtml).toEqual(result);
});
it("should return null if template is missing", function() {
const hoganUtils = new (require("../hoganjs-utils.js")).HoganJsUtils({ noCache: true });
const result = hoganUtils.render("generic", "missing-template", {});
expect(null).toEqual(result);
});
it("should allow templates to be overridden with compiled templates", function() {
const emptyDiffTemplate = HoganJsUtils.compile("<p>{{myName}}</p>");
const config = { templates: { "generic-empty-diff": emptyDiffTemplate } };
const hoganUtils = new (require("../hoganjs-utils.js")).HoganJsUtils(config);
const result = hoganUtils.render("generic", "empty-diff", { myName: "Rodrigo Fernandes" });
expect("<p>Rodrigo Fernandes</p>").toEqual(result);
});
it("should allow templates to be overridden with uncompiled templates", function() {
const emptyDiffTemplate = "<p>{{myName}}</p>";
const config = { rawTemplates: { "generic-empty-diff": emptyDiffTemplate } };
const hoganUtils = new (require("../hoganjs-utils.js")).HoganJsUtils(config);
const result = hoganUtils.render("generic", "empty-diff", { myName: "Rodrigo Fernandes" });
expect("<p>Rodrigo Fernandes</p>").toEqual(result);
});
it("should allow templates to be overridden giving priority to compiled templates", function() {
const emptyDiffTemplate = HoganJsUtils.compile("<p>{{myName}}</p>");
const emptyDiffTemplateUncompiled = "<p>Not used!</p>";
const config = {
templates: { "generic-empty-diff": emptyDiffTemplate },
rawTemplates: { "generic-empty-diff": emptyDiffTemplateUncompiled }
};
const hoganUtils = new (require("../hoganjs-utils.js")).HoganJsUtils(config);
const result = hoganUtils.render("generic", "empty-diff", { myName: "Rodrigo Fernandes" });
expect("<p>Rodrigo Fernandes</p>").toEqual(result);
});
});
});

View file

@ -0,0 +1,66 @@
import HoganJsUtils from "../hoganjs-utils";
import { CSSLineClass } from "../render-utils";
describe("HoganJsUtils", () => {
describe("render", () => {
const emptyDiffHtml =
"<tr>\n" +
' <td class="d2h-info">\n' +
' <div class="d2h-code-line d2h-info">\n' +
" File without changes\n" +
" </div>\n" +
" </td>\n" +
"</tr>";
it("should render view", () => {
const hoganJsUtils = new HoganJsUtils({});
const result = hoganJsUtils.render("generic", "empty-diff", {
contentClass: "d2h-code-line",
CSSLineClass: CSSLineClass
});
expect(result).toEqual(emptyDiffHtml);
});
it("should render view without cache", () => {
const hoganJsUtils = new HoganJsUtils({});
const result = hoganJsUtils.render("generic", "empty-diff", {
contentClass: "d2h-code-line",
CSSLineClass: CSSLineClass
});
expect(result).toEqual(emptyDiffHtml);
});
it("should throw exception if template is missing", () => {
const hoganJsUtils = new HoganJsUtils({});
expect(() => hoganJsUtils.render("generic", "missing-template", {})).toThrow(Error);
});
it("should allow templates to be overridden with compiled templates", () => {
const emptyDiffTemplate = HoganJsUtils.compile("<p>{{myName}}</p>");
const hoganJsUtils = new HoganJsUtils({ compiledTemplates: { "generic-empty-diff": emptyDiffTemplate } });
const result = hoganJsUtils.render("generic", "empty-diff", { myName: "Rodrigo Fernandes" });
expect(result).toEqual("<p>Rodrigo Fernandes</p>");
});
it("should allow templates to be overridden with uncompiled templates", () => {
const emptyDiffTemplate = "<p>{{myName}}</p>";
const hoganJsUtils = new HoganJsUtils({ rawTemplates: { "generic-empty-diff": emptyDiffTemplate } });
const result = hoganJsUtils.render("generic", "empty-diff", { myName: "Rodrigo Fernandes" });
expect(result).toEqual("<p>Rodrigo Fernandes</p>");
});
it("should allow templates to be overridden giving priority to raw templates", () => {
const emptyDiffTemplate = HoganJsUtils.compile("<p>Not used!</p>");
const emptyDiffTemplateUncompiled = "<p>{{myName}}</p>";
const hoganJsUtils = new HoganJsUtils({
compiledTemplates: { "generic-empty-diff": emptyDiffTemplate },
rawTemplates: { "generic-empty-diff": emptyDiffTemplateUncompiled }
});
const result = hoganJsUtils.render("generic", "empty-diff", { myName: "Rodrigo Fernandes" });
expect(result).toEqual("<p>Rodrigo Fernandes</p>");
});
});
});

View file

@ -1,10 +1,13 @@
const LineByLinePrinter = require("../line-by-line-printer.js").LineByLinePrinter; import LineByLineRenderer from "../line-by-line-renderer";
import HoganJsUtils from "../hoganjs-utils";
import { LineType, CSSLineClass, DiffLine, DiffFile } from "../render-utils";
describe("LineByLinePrinter", function() { describe("LineByLineRenderer", () => {
describe("_generateEmptyDiff", function() { describe("_generateEmptyDiff", () => {
it("should return an empty diff", function() { it("should return an empty diff", () => {
const lineByLinePrinter = new LineByLinePrinter({}); const hoganUtils = new HoganJsUtils({});
const fileHtml = lineByLinePrinter._generateEmptyDiff(); const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {});
const fileHtml = lineByLineRenderer.generateEmptyDiff();
const expected = const expected =
"<tr>\n" + "<tr>\n" +
' <td class="d2h-info">\n' + ' <td class="d2h-info">\n' +
@ -14,15 +17,15 @@ describe("LineByLinePrinter", function() {
" </td>\n" + " </td>\n" +
"</tr>"; "</tr>";
expect(expected).toEqual(fileHtml); expect(fileHtml).toEqual(expected);
}); });
}); });
describe("makeLineHtml", function() { describe("makeLineHtml", () => {
it("should work for insertions", function() { it("should work for insertions", () => {
const diffParser = require("../diff-parser.js").DiffParser; const hoganUtils = new HoganJsUtils({});
const lineByLinePrinter = new LineByLinePrinter({}); const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {});
let fileHtml = lineByLinePrinter.makeLineHtml(false, diffParser.LINE_TYPE.INSERTS, "", 30, "test", "+"); let fileHtml = lineByLineRenderer.makeLineHtml(false, CSSLineClass.INSERTS, "test", undefined, 30, "+");
fileHtml = fileHtml.replace(/\n\n+/g, "\n"); fileHtml = fileHtml.replace(/\n\n+/g, "\n");
const expected = const expected =
"<tr>\n" + "<tr>\n" +
@ -38,13 +41,13 @@ describe("LineByLinePrinter", function() {
" </td>\n" + " </td>\n" +
"</tr>"; "</tr>";
expect(expected).toEqual(fileHtml); expect(fileHtml).toEqual(expected);
}); });
it("should work for deletions", function() { it("should work for deletions", () => {
const diffParser = require("../diff-parser.js").DiffParser; const hoganUtils = new HoganJsUtils({});
const lineByLinePrinter = new LineByLinePrinter({}); const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {});
let fileHtml = lineByLinePrinter.makeLineHtml(false, diffParser.LINE_TYPE.DELETES, 30, "", "test", "-"); let fileHtml = lineByLineRenderer.makeLineHtml(false, CSSLineClass.DELETES, "test", 30, undefined, "-");
fileHtml = fileHtml.replace(/\n\n+/g, "\n"); fileHtml = fileHtml.replace(/\n\n+/g, "\n");
const expected = const expected =
"<tr>\n" + "<tr>\n" +
@ -60,13 +63,13 @@ describe("LineByLinePrinter", function() {
" </td>\n" + " </td>\n" +
"</tr>"; "</tr>";
expect(expected).toEqual(fileHtml); expect(fileHtml).toEqual(expected);
}); });
it("should convert indents into non breakin spaces (2 white spaces)", function() { it("should convert indents into non breakin spaces (2 white spaces)", () => {
const diffParser = require("../diff-parser.js").DiffParser; const hoganUtils = new HoganJsUtils({});
const lineByLinePrinter = new LineByLinePrinter({}); const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {});
let fileHtml = lineByLinePrinter.makeLineHtml(false, diffParser.LINE_TYPE.INSERTS, "", 30, " test", "+"); let fileHtml = lineByLineRenderer.makeLineHtml(false, CSSLineClass.INSERTS, " test", undefined, 30, "+");
fileHtml = fileHtml.replace(/\n\n+/g, "\n"); fileHtml = fileHtml.replace(/\n\n+/g, "\n");
const expected = const expected =
"<tr>\n" + "<tr>\n" +
@ -82,13 +85,13 @@ describe("LineByLinePrinter", function() {
" </td>\n" + " </td>\n" +
"</tr>"; "</tr>";
expect(expected).toEqual(fileHtml); expect(fileHtml).toEqual(expected);
}); });
it("should convert indents into non breakin spaces (4 white spaces)", function() { it("should convert indents into non breakin spaces (4 white spaces)", () => {
const diffParser = require("../diff-parser.js").DiffParser; const hoganUtils = new HoganJsUtils({});
const lineByLinePrinter = new LineByLinePrinter({}); const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {});
let fileHtml = lineByLinePrinter.makeLineHtml(false, diffParser.LINE_TYPE.INSERTS, "", 30, " test", "+"); let fileHtml = lineByLineRenderer.makeLineHtml(false, CSSLineClass.INSERTS, " test", undefined, 30, "+");
fileHtml = fileHtml.replace(/\n\n+/g, "\n"); fileHtml = fileHtml.replace(/\n\n+/g, "\n");
const expected = const expected =
"<tr>\n" + "<tr>\n" +
@ -104,13 +107,13 @@ describe("LineByLinePrinter", function() {
" </td>\n" + " </td>\n" +
"</tr>"; "</tr>";
expect(expected).toEqual(fileHtml); expect(fileHtml).toEqual(expected);
}); });
it("should preserve tabs", function() { it("should preserve tabs", () => {
const diffParser = require("../diff-parser.js").DiffParser; const hoganUtils = new HoganJsUtils({});
const lineByLinePrinter = new LineByLinePrinter({}); const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {});
let fileHtml = lineByLinePrinter.makeLineHtml(false, diffParser.LINE_TYPE.INSERTS, "", 30, "\ttest", "+"); let fileHtml = lineByLineRenderer.makeLineHtml(false, CSSLineClass.INSERTS, "\ttest", undefined, 30, "+");
fileHtml = fileHtml.replace(/\n\n+/g, "\n"); fileHtml = fileHtml.replace(/\n\n+/g, "\n");
const expected = const expected =
"<tr>\n" + "<tr>\n" +
@ -127,24 +130,28 @@ describe("LineByLinePrinter", function() {
" </td>\n" + " </td>\n" +
"</tr>"; "</tr>";
expect(expected).toEqual(fileHtml); expect(fileHtml).toEqual(expected);
}); });
}); });
describe("makeFileDiffHtml", function() { describe("makeFileDiffHtml", () => {
it("should work for simple file", function() { it("should work for simple file", () => {
const lineByLinePrinter = new LineByLinePrinter({}); const hoganUtils = new HoganJsUtils({});
const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {});
const file = { const file = {
addedLines: 12, addedLines: 12,
deletedLines: 41, deletedLines: 41,
language: "js", language: "js",
oldName: "my/file/name.js", oldName: "my/file/name.js",
newName: "my/file/name.js" newName: "my/file/name.js",
isCombined: false,
isGitDiff: false,
blocks: []
}; };
const diffs = "<span>Random Html</span>"; const diffs = "<span>Random Html</span>";
const fileHtml = lineByLinePrinter.makeFileDiffHtml(file, diffs); const fileHtml = lineByLineRenderer.makeFileDiffHtml(file, diffs);
const expected = const expected =
'<div id="d2h-781444" class="d2h-file-wrapper" data-lang="js">\n' + '<div id="d2h-781444" class="d2h-file-wrapper" data-lang="js">\n' +
@ -166,10 +173,11 @@ describe("LineByLinePrinter", function() {
" </div>\n" + " </div>\n" +
"</div>"; "</div>";
expect(expected).toEqual(fileHtml); expect(fileHtml).toEqual(expected);
}); });
it("should work for simple added file", function() { it("should work for simple added file", () => {
const lineByLinePrinter = new LineByLinePrinter({}); const hoganUtils = new HoganJsUtils({});
const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {});
const file = { const file = {
addedLines: 12, addedLines: 12,
@ -177,11 +185,14 @@ describe("LineByLinePrinter", function() {
language: "js", language: "js",
oldName: "dev/null", oldName: "dev/null",
newName: "my/file/name.js", newName: "my/file/name.js",
isNew: true isNew: true,
isCombined: false,
isGitDiff: false,
blocks: []
}; };
const diffs = "<span>Random Html</span>"; const diffs = "<span>Random Html</span>";
const fileHtml = lineByLinePrinter.makeFileDiffHtml(file, diffs); const fileHtml = lineByLineRenderer.makeFileDiffHtml(file, diffs);
const expected = const expected =
'<div id="d2h-781444" class="d2h-file-wrapper" data-lang="js">\n' + '<div id="d2h-781444" class="d2h-file-wrapper" data-lang="js">\n' +
@ -203,10 +214,11 @@ describe("LineByLinePrinter", function() {
" </div>\n" + " </div>\n" +
"</div>"; "</div>";
expect(expected).toEqual(fileHtml); expect(fileHtml).toEqual(expected);
}); });
it("should work for simple deleted file", function() { it("should work for simple deleted file", () => {
const lineByLinePrinter = new LineByLinePrinter({}); const hoganUtils = new HoganJsUtils({});
const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {});
const file = { const file = {
addedLines: 0, addedLines: 0,
@ -214,11 +226,14 @@ describe("LineByLinePrinter", function() {
language: "js", language: "js",
oldName: "my/file/name.js", oldName: "my/file/name.js",
newName: "dev/null", newName: "dev/null",
isDeleted: true isDeleted: true,
isCombined: false,
isGitDiff: false,
blocks: []
}; };
const diffs = "<span>Random Html</span>"; const diffs = "<span>Random Html</span>";
const fileHtml = lineByLinePrinter.makeFileDiffHtml(file, diffs); const fileHtml = lineByLineRenderer.makeFileDiffHtml(file, diffs);
const expected = const expected =
'<div id="d2h-781444" class="d2h-file-wrapper" data-lang="js">\n' + '<div id="d2h-781444" class="d2h-file-wrapper" data-lang="js">\n' +
@ -240,10 +255,11 @@ describe("LineByLinePrinter", function() {
" </div>\n" + " </div>\n" +
"</div>"; "</div>";
expect(expected).toEqual(fileHtml); expect(fileHtml).toEqual(expected);
}); });
it("should work for simple renamed file", function() { it("should work for simple renamed file", () => {
const lineByLinePrinter = new LineByLinePrinter({}); const hoganUtils = new HoganJsUtils({});
const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {});
const file = { const file = {
addedLines: 12, addedLines: 12,
@ -251,11 +267,14 @@ describe("LineByLinePrinter", function() {
language: "js", language: "js",
oldName: "my/file/name1.js", oldName: "my/file/name1.js",
newName: "my/file/name2.js", newName: "my/file/name2.js",
isRename: true isRename: true,
isCombined: false,
isGitDiff: false,
blocks: []
}; };
const diffs = "<span>Random Html</span>"; const diffs = "<span>Random Html</span>";
const fileHtml = lineByLinePrinter.makeFileDiffHtml(file, diffs); const fileHtml = lineByLineRenderer.makeFileDiffHtml(file, diffs);
const expected = const expected =
'<div id="d2h-662683" class="d2h-file-wrapper" data-lang="js">\n' + '<div id="d2h-662683" class="d2h-file-wrapper" data-lang="js">\n' +
@ -277,61 +296,71 @@ describe("LineByLinePrinter", function() {
" </div>\n" + " </div>\n" +
"</div>"; "</div>";
expect(expected).toEqual(fileHtml); expect(fileHtml).toEqual(expected);
}); });
it("should return empty when option renderNothingWhenEmpty is true and file blocks not present", function() { it("should return empty when option renderNothingWhenEmpty is true and file blocks not present", () => {
const lineByLinePrinter = new LineByLinePrinter({ const hoganUtils = new HoganJsUtils({});
const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {
renderNothingWhenEmpty: true renderNothingWhenEmpty: true
}); });
const file = { const file = {
addedLines: 0,
deletedLines: 0,
language: "js",
oldName: "my/file/name1.js",
newName: "my/file/name2.js",
isRename: true,
isCombined: false,
isGitDiff: false,
blocks: [] blocks: []
}; };
const diffs = "<span>Random Html</span>"; const diffs = "<span>Random Html</span>";
const fileHtml = lineByLinePrinter.makeFileDiffHtml(file, diffs); const fileHtml = lineByLineRenderer.makeFileDiffHtml(file, diffs);
const expected = ""; const expected = "";
expect(expected).toEqual(fileHtml); expect(fileHtml).toEqual(expected);
}); });
}); });
describe("makeLineByLineHtmlWrapper", function() { describe("makeLineByLineHtmlWrapper", () => {
it("should work for simple content", function() { it("should work for simple content", () => {
const lineByLinePrinter = new LineByLinePrinter({}); const hoganUtils = new HoganJsUtils({});
const fileHtml = lineByLinePrinter.makeLineByLineHtmlWrapper("<span>Random Html</span>"); const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {});
const fileHtml = lineByLineRenderer.makeLineByLineHtmlWrapper("<span>Random Html</span>");
const expected = '<div class="d2h-wrapper">\n' + " <span>Random Html</span>\n" + "</div>"; const expected = '<div class="d2h-wrapper">\n' + " <span>Random Html</span>\n" + "</div>";
expect(expected).toEqual(fileHtml); expect(fileHtml).toEqual(expected);
}); });
}); });
describe("generateLineByLineJsonHtml", function() { describe("generateLineByLineJsonHtml", () => {
it("should work for list of files", function() { it("should work for list of files", () => {
const exampleJson = [ const exampleJson: DiffFile[] = [
{ {
blocks: [ blocks: [
{ {
lines: [ lines: [
{ {
content: "-test", content: "-test",
type: "d2h-del", type: LineType.DELETE,
oldNumber: 1, oldNumber: 1,
newNumber: null newNumber: undefined
}, },
{ {
content: "+test1r", content: "+test1r",
type: "d2h-ins", type: LineType.INSERT,
oldNumber: null, oldNumber: undefined,
newNumber: 1 newNumber: 1
} }
], ],
oldStartLine: "1", oldStartLine: 1,
oldStartLine2: null, oldStartLine2: undefined,
newStartLine: "1", newStartLine: 1,
header: "@@ -1 +1 @@" header: "@@ -1 +1 @@"
} }
], ],
@ -340,17 +369,21 @@ describe("LineByLinePrinter", function() {
checksumBefore: "0000001", checksumBefore: "0000001",
checksumAfter: "0ddf2ba", checksumAfter: "0ddf2ba",
oldName: "sample", oldName: "sample",
language: undefined,
newName: "sample", newName: "sample",
isCombined: false language: "txt",
isCombined: false,
isGitDiff: true
} }
]; ];
const lineByLinePrinter = new LineByLinePrinter({ matching: "lines" }); const hoganUtils = new HoganJsUtils({});
const html = lineByLinePrinter.generateLineByLineJsonHtml(exampleJson); const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {
matching: "lines"
});
const html = lineByLineRenderer.render(exampleJson);
const expected = const expected =
'<div class="d2h-wrapper">\n' + '<div class="d2h-wrapper">\n' +
' <div id="d2h-675094" class="d2h-file-wrapper" data-lang="">\n' + ' <div id="d2h-675094" class="d2h-file-wrapper" data-lang="txt">\n' +
' <div class="d2h-file-header">\n' + ' <div class="d2h-file-header">\n' +
' <span class="d2h-file-name-wrapper">\n' + ' <span class="d2h-file-name-wrapper">\n' +
' <svg aria-hidden="true" class="d2h-icon" height="16" version="1.1" viewBox="0 0 12 16" width="12">\n' + ' <svg aria-hidden="true" class="d2h-icon" height="16" version="1.1" viewBox="0 0 12 16" width="12">\n' +
@ -368,23 +401,23 @@ describe("LineByLinePrinter", function() {
' <div class="d2h-code-line d2h-info">@@ -1 +1 @@</div>\n' + ' <div class="d2h-code-line d2h-info">@@ -1 +1 @@</div>\n' +
" </td>\n" + " </td>\n" +
"</tr><tr>\n" + "</tr><tr>\n" +
' <td class="d2h-code-linenumber d2h-del">\n' + ' <td class="d2h-code-linenumber d2h-del d2h-change">\n' +
' <div class="line-num1">1</div>\n' + ' <div class="line-num1">1</div>\n' +
'<div class="line-num2"></div>\n' + '<div class="line-num2"></div>\n' +
" </td>\n" + " </td>\n" +
' <td class="d2h-del">\n' + ' <td class="d2h-del d2h-change">\n' +
' <div class="d2h-code-line d2h-del">\n' + ' <div class="d2h-code-line d2h-del d2h-change">\n' +
' <span class="d2h-code-line-prefix">-</span>\n' + ' <span class="d2h-code-line-prefix">-</span>\n' +
' <span class="d2h-code-line-ctn"><del>test</del></span>\n' + ' <span class="d2h-code-line-ctn"><del>test</del></span>\n' +
" </div>\n" + " </div>\n" +
" </td>\n" + " </td>\n" +
"</tr><tr>\n" + "</tr><tr>\n" +
' <td class="d2h-code-linenumber d2h-ins">\n' + ' <td class="d2h-code-linenumber d2h-ins d2h-change">\n' +
' <div class="line-num1"></div>\n' + ' <div class="line-num1"></div>\n' +
'<div class="line-num2">1</div>\n' + '<div class="line-num2">1</div>\n' +
" </td>\n" + " </td>\n" +
' <td class="d2h-ins">\n' + ' <td class="d2h-ins d2h-change">\n' +
' <div class="d2h-code-line d2h-ins">\n' + ' <div class="d2h-code-line d2h-ins d2h-change">\n' +
' <span class="d2h-code-line-prefix">+</span>\n' + ' <span class="d2h-code-line-prefix">+</span>\n' +
' <span class="d2h-code-line-ctn"><ins>test1r</ins></span>\n' + ' <span class="d2h-code-line-ctn"><ins>test1r</ins></span>\n' +
" </div>\n" + " </div>\n" +
@ -397,10 +430,10 @@ describe("LineByLinePrinter", function() {
"</div>\n" + "</div>\n" +
"</div>"; "</div>";
expect(expected).toEqual(html); expect(html).toEqual(expected);
}); });
it("should work for empty blocks", function() { it("should work for empty blocks", () => {
const exampleJson = [ const exampleJson = [
{ {
blocks: [], blocks: [],
@ -409,12 +442,16 @@ describe("LineByLinePrinter", function() {
oldName: "sample", oldName: "sample",
language: "js", language: "js",
newName: "sample", newName: "sample",
isCombined: false isCombined: false,
isGitDiff: false
} }
]; ];
const lineByLinePrinter = new LineByLinePrinter({ renderNothingWhenEmpty: false }); const hoganUtils = new HoganJsUtils({});
const html = lineByLinePrinter.generateLineByLineJsonHtml(exampleJson); const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {
renderNothingWhenEmpty: false
});
const html = lineByLineRenderer.render(exampleJson);
const expected = const expected =
'<div class="d2h-wrapper">\n' + '<div class="d2h-wrapper">\n' +
' <div id="d2h-675094" class="d2h-file-wrapper" data-lang="js">\n' + ' <div id="d2h-675094" class="d2h-file-wrapper" data-lang="js">\n' +
@ -443,31 +480,32 @@ describe("LineByLinePrinter", function() {
"</div>\n" + "</div>\n" +
"</div>"; "</div>";
expect(expected).toEqual(html); expect(html).toEqual(expected);
}); });
}); });
describe("_processLines", function() { describe("_processLines", () => {
it("should work for simple block header", function() { it("should work for simple block header", () => {
const lineByLinePrinter = new LineByLinePrinter({}); const hoganUtils = new HoganJsUtils({});
const oldLines = [ const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {});
const oldLines: DiffLine[] = [
{ {
content: "-test", content: "-test",
type: "d2h-del", type: LineType.DELETE,
oldNumber: 1, oldNumber: 1,
newNumber: null newNumber: undefined
} }
]; ];
const newLines = [ const newLines: DiffLine[] = [
{ {
content: "+test1r", content: "+test1r",
type: "d2h-ins", type: LineType.INSERT,
oldNumber: null, oldNumber: undefined,
newNumber: 1 newNumber: 1
} }
]; ];
const html = lineByLinePrinter._processLines(false, oldLines, newLines); const html = lineByLineRenderer.processLines(false, oldLines, newLines);
const expected = const expected =
"<tr>\n" + "<tr>\n" +
@ -494,45 +532,46 @@ describe("LineByLinePrinter", function() {
" </td>\n" + " </td>\n" +
"</tr>"; "</tr>";
expect(expected).toEqual(html); expect(html).toEqual(expected);
}); });
}); });
describe("_generateFileHtml", function() { describe("_generateFileHtml", () => {
it("should work for simple file", function() { it("should work for simple file", () => {
const lineByLinePrinter = new LineByLinePrinter({}); const hoganUtils = new HoganJsUtils({});
const file = { const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {});
const file: DiffFile = {
blocks: [ blocks: [
{ {
lines: [ lines: [
{ {
content: " one context line", content: " one context line",
type: "d2h-cntx", type: LineType.CONTEXT,
oldNumber: 1, oldNumber: 1,
newNumber: 1 newNumber: 1
}, },
{ {
content: "-test", content: "-test",
type: "d2h-del", type: LineType.DELETE,
oldNumber: 2, oldNumber: 2,
newNumber: null newNumber: undefined
}, },
{ {
content: "+test1r", content: "+test1r",
type: "d2h-ins", type: LineType.INSERT,
oldNumber: null, oldNumber: undefined,
newNumber: 2 newNumber: 2
}, },
{ {
content: "+test2r", content: "+test2r",
type: "d2h-ins", type: LineType.INSERT,
oldNumber: null, oldNumber: undefined,
newNumber: 3 newNumber: 3
} }
], ],
oldStartLine: "1", oldStartLine: 1,
oldStartLine2: null, oldStartLine2: undefined,
newStartLine: "1", newStartLine: 1,
header: "@@ -1 +1 @@" header: "@@ -1 +1 @@"
} }
], ],
@ -541,12 +580,13 @@ describe("LineByLinePrinter", function() {
checksumBefore: "0000001", checksumBefore: "0000001",
checksumAfter: "0ddf2ba", checksumAfter: "0ddf2ba",
oldName: "sample", oldName: "sample",
language: undefined, language: "txt",
newName: "sample", newName: "sample",
isCombined: false isCombined: false,
isGitDiff: true
}; };
const html = lineByLinePrinter._generateFileHtml(file); const html = lineByLineRenderer.generateFileHtml(file);
const expected = const expected =
"<tr>\n" + "<tr>\n" +
@ -600,7 +640,7 @@ describe("LineByLinePrinter", function() {
" </td>\n" + " </td>\n" +
"</tr>"; "</tr>";
expect(expected).toEqual(html); expect(html).toEqual(expected);
}); });
}); });
}); });

View file

@ -1,16 +1,16 @@
const PrinterUtils = require("../printer-utils.js").PrinterUtils; import * as renderUtils from "../render-utils";
describe("Utils", function() { describe("Utils", function() {
describe("getHtmlId", function() { describe("getHtmlId", function() {
it("should generate file unique id", function() { it("should generate file unique id", function() {
const result = PrinterUtils.getHtmlId({ const result = renderUtils.getHtmlId({
oldName: "sample.js", oldName: "sample.js",
newName: "sample.js" newName: "sample.js"
}); });
expect("d2h-960013").toEqual(result); expect("d2h-960013").toEqual(result);
}); });
it("should generate file unique id for empty hashes", function() { it("should generate file unique id for empty hashes", function() {
const result = PrinterUtils.getHtmlId({ const result = renderUtils.getHtmlId({
oldName: "sample.js", oldName: "sample.js",
newName: "sample.js" newName: "sample.js"
}); });
@ -20,105 +20,102 @@ describe("Utils", function() {
describe("getDiffName", function() { describe("getDiffName", function() {
it("should generate the file name for a changed file", function() { it("should generate the file name for a changed file", function() {
const result = PrinterUtils.getDiffName({ const result = renderUtils.filenameDiff({
oldName: "sample.js", oldName: "sample.js",
newName: "sample.js" newName: "sample.js"
}); });
expect("sample.js").toEqual(result); expect("sample.js").toEqual(result);
}); });
it("should generate the file name for a changed file and full rename", function() { it("should generate the file name for a changed file and full rename", function() {
const result = PrinterUtils.getDiffName({ const result = renderUtils.filenameDiff({
oldName: "sample1.js", oldName: "sample1.js",
newName: "sample2.js" newName: "sample2.js"
}); });
expect("sample1.js → sample2.js").toEqual(result); expect("sample1.js → sample2.js").toEqual(result);
}); });
it("should generate the file name for a changed file and prefix rename", function() { it("should generate the file name for a changed file and prefix rename", function() {
const result = PrinterUtils.getDiffName({ const result = renderUtils.filenameDiff({
oldName: "src/path/sample.js", oldName: "src/path/sample.js",
newName: "source/path/sample.js" newName: "source/path/sample.js"
}); });
expect("{src → source}/path/sample.js").toEqual(result); expect("{src → source}/path/sample.js").toEqual(result);
}); });
it("should generate the file name for a changed file and suffix rename", function() { it("should generate the file name for a changed file and suffix rename", function() {
const result = PrinterUtils.getDiffName({ const result = renderUtils.filenameDiff({
oldName: "src/path/sample1.js", oldName: "src/path/sample1.js",
newName: "src/path/sample2.js" newName: "src/path/sample2.js"
}); });
expect("src/path/{sample1.js → sample2.js}").toEqual(result); expect("src/path/{sample1.js → sample2.js}").toEqual(result);
}); });
it("should generate the file name for a changed file and middle rename", function() { it("should generate the file name for a changed file and middle rename", function() {
const result = PrinterUtils.getDiffName({ const result = renderUtils.filenameDiff({
oldName: "src/really/big/path/sample.js", oldName: "src/really/big/path/sample.js",
newName: "src/small/path/sample.js" newName: "src/small/path/sample.js"
}); });
expect("src/{really/big → small}/path/sample.js").toEqual(result); expect("src/{really/big → small}/path/sample.js").toEqual(result);
}); });
it("should generate the file name for a deleted file", function() { it("should generate the file name for a deleted file", function() {
const result = PrinterUtils.getDiffName({ const result = renderUtils.filenameDiff({
oldName: "src/my/file.js", oldName: "src/my/file.js",
newName: "/dev/null" newName: "/dev/null"
}); });
expect("src/my/file.js").toEqual(result); expect("src/my/file.js").toEqual(result);
}); });
it("should generate the file name for a new file", function() { it("should generate the file name for a new file", function() {
const result = PrinterUtils.getDiffName({ const result = renderUtils.filenameDiff({
oldName: "/dev/null", oldName: "/dev/null",
newName: "src/my/file.js" newName: "src/my/file.js"
}); });
expect("src/my/file.js").toEqual(result); expect("src/my/file.js").toEqual(result);
}); });
it("should generate handle undefined filename", function() {
const result = PrinterUtils.getDiffName({});
expect("unknown/file/path").toEqual(result);
});
}); });
describe("diffHighlight", function() { describe("diffHighlight", function() {
it("should highlight two lines", function() { it("should highlight two lines", function() {
const result = PrinterUtils.diffHighlight("-var myVar = 2;", "+var myVariable = 3;", { matching: "words" }); const result = renderUtils.diffHighlight("-var myVar = 2;", "+var myVariable = 3;", false, {
matching: "words"
});
expect({ expect(result).toEqual({
first: { oldLine: {
prefix: "-", prefix: "-",
line: "var <del>myVar</del> = <del>2</del>;" content: "var <del>myVar</del> = <del>2</del>;"
}, },
second: { newLine: {
prefix: "+", prefix: "+",
line: "var <ins>myVariable</ins> = <ins>3</ins>;" content: "var <ins>myVariable</ins> = <ins>3</ins>;"
} }
}).toEqual(result); });
}); });
it("should highlight two lines char by char", function() { it("should highlight two lines char by char", function() {
const result = PrinterUtils.diffHighlight("-var myVar = 2;", "+var myVariable = 3;", { diffStyle: "char" }); const result = renderUtils.diffHighlight("-var myVar = 2;", "+var myVariable = 3;", false, { diffStyle: "char" });
expect({ expect({
first: { oldLine: {
prefix: "-", prefix: "-",
line: "var myVar = <del>2</del>;" content: "var myVar = <del>2</del>;"
}, },
second: { newLine: {
prefix: "+", prefix: "+",
line: "var myVar<ins>iable</ins> = <ins>3</ins>;" content: "var myVar<ins>iable</ins> = <ins>3</ins>;"
} }
}).toEqual(result); }).toEqual(result);
}); });
it("should highlight combined diff lines", function() { it("should highlight combined diff lines", function() {
const result = PrinterUtils.diffHighlight(" -var myVar = 2;", " +var myVariable = 3;", { const result = renderUtils.diffHighlight(" -var myVar = 2;", " +var myVariable = 3;", true, {
diffStyle: "word", diffStyle: "word",
isCombined: true,
matching: "words", matching: "words",
matchWordsThreshold: 1.0 matchWordsThreshold: 1.0
}); });
expect({ expect({
first: { oldLine: {
prefix: " -", prefix: " -",
line: 'var <del class="d2h-change">myVar</del> = <del class="d2h-change">2</del>;' content: 'var <del class="d2h-change">myVar</del> = <del class="d2h-change">2</del>;'
}, },
second: { newLine: {
prefix: " +", prefix: " +",
line: 'var <ins class="d2h-change">myVariable</ins> = <ins class="d2h-change">3</ins>;' content: 'var <ins class="d2h-change">myVariable</ins> = <ins class="d2h-change">3</ins>;'
} }
}).toEqual(result); }).toEqual(result);
}); });

View file

@ -1,10 +1,13 @@
const SideBySidePrinter = require("../side-by-side-printer.js").SideBySidePrinter; import SideBySideRenderer from "../side-by-side-renderer";
import HoganJsUtils from "../hoganjs-utils";
import { LineType, CSSLineClass, DiffLine, DiffFile } from "../render-utils";
describe("SideBySidePrinter", function() { describe("SideBySideRenderer", () => {
describe("generateEmptyDiff", function() { describe("generateEmptyDiff", () => {
it("should return an empty diff", function() { it("should return an empty diff", () => {
const sideBySidePrinter = new SideBySidePrinter({}); const hoganUtils = new HoganJsUtils({});
const fileHtml = sideBySidePrinter.generateEmptyDiff(); const sideBySideRenderer = new SideBySideRenderer(hoganUtils, {});
const fileHtml = sideBySideRenderer.generateEmptyDiff();
const expectedRight = ""; const expectedRight = "";
const expectedLeft = const expectedLeft =
"<tr>\n" + "<tr>\n" +
@ -15,51 +18,53 @@ describe("SideBySidePrinter", function() {
" </td>\n" + " </td>\n" +
"</tr>"; "</tr>";
expect(expectedRight).toEqual(fileHtml.right); expect(fileHtml.right).toEqual(expectedRight);
expect(expectedLeft).toEqual(fileHtml.left); expect(fileHtml.left).toEqual(expectedLeft);
}); });
}); });
describe("generateSideBySideFileHtml", function() { describe("generateSideBySideFileHtml", () => {
it("should generate lines with the right prefixes", function() { it("should generate lines with the right prefixes", () => {
const sideBySidePrinter = new SideBySidePrinter({}); const hoganUtils = new HoganJsUtils({});
const sideBySideRenderer = new SideBySideRenderer(hoganUtils, {});
const file = { const file: DiffFile = {
isGitDiff: true,
blocks: [ blocks: [
{ {
lines: [ lines: [
{ {
content: " context", content: " context",
type: "d2h-cntx", type: LineType.CONTEXT,
oldNumber: 19, oldNumber: 19,
newNumber: 19 newNumber: 19
}, },
{ {
content: "-removed", content: "-removed",
type: "d2h-del", type: LineType.DELETE,
oldNumber: 20, oldNumber: 20,
newNumber: null newNumber: undefined
}, },
{ {
content: "+added", content: "+added",
type: "d2h-ins", type: LineType.INSERT,
oldNumber: null, oldNumber: undefined,
newNumber: 20 newNumber: 20
}, },
{ {
content: "+another added", content: "+another added",
type: "d2h-ins", type: LineType.INSERT,
oldNumber: null, oldNumber: undefined,
newNumber: 21 newNumber: 21
} }
], ],
oldStartLine: "19", oldStartLine: 19,
newStartLine: "19", newStartLine: 19,
header: "@@ -19,7 +19,7 @@" header: "@@ -19,7 +19,7 @@"
} }
], ],
deletedLines: 1, deletedLines: 1,
addedLines: 1, addedLines: 2,
checksumBefore: "fc56817", checksumBefore: "fc56817",
checksumAfter: "e8e7e49", checksumAfter: "e8e7e49",
mode: "100644", mode: "100644",
@ -69,7 +74,7 @@ describe("SideBySidePrinter", function() {
isCombined: false isCombined: false
}; };
const fileHtml = sideBySidePrinter.generateSideBySideFileHtml(file); const fileHtml = sideBySideRenderer.generateSideBySideFileHtml(file);
const expectedLeft = const expectedLeft =
"<tr>\n" + "<tr>\n" +
@ -148,16 +153,16 @@ describe("SideBySidePrinter", function() {
" </td>\n" + " </td>\n" +
"</tr>"; "</tr>";
expect(expectedLeft).toEqual(fileHtml.left); expect(fileHtml.left).toEqual(expectedLeft);
expect(expectedRight).toEqual(fileHtml.right); expect(fileHtml.right).toEqual(expectedRight);
}); });
}); });
describe("generateSingleLineHtml", function() { describe("generateSingleLineHtml", () => {
it("should work for insertions", function() { it("should work for insertions", () => {
const diffParser = require("../diff-parser.js").DiffParser; const hoganUtils = new HoganJsUtils({});
const sideBySidePrinter = new SideBySidePrinter({}); const sideBySideRenderer = new SideBySideRenderer(hoganUtils, {});
const fileHtml = sideBySidePrinter.generateSingleLineHtml(false, diffParser.LINE_TYPE.INSERTS, 30, "test", "+"); const fileHtml = sideBySideRenderer.generateSingleLineHtml(false, CSSLineClass.INSERTS, "test", 30, "+");
const expected = const expected =
"<tr>\n" + "<tr>\n" +
' <td class="d2h-code-side-linenumber d2h-ins">\n' + ' <td class="d2h-code-side-linenumber d2h-ins">\n' +
@ -171,12 +176,12 @@ describe("SideBySidePrinter", function() {
" </td>\n" + " </td>\n" +
"</tr>"; "</tr>";
expect(expected).toEqual(fileHtml); expect(fileHtml).toEqual(expected);
}); });
it("should work for deletions", function() { it("should work for deletions", () => {
const diffParser = require("../diff-parser.js").DiffParser; const hoganUtils = new HoganJsUtils({});
const sideBySidePrinter = new SideBySidePrinter({}); const sideBySideRenderer = new SideBySideRenderer(hoganUtils, {});
const fileHtml = sideBySidePrinter.generateSingleLineHtml(false, diffParser.LINE_TYPE.DELETES, 30, "test", "-"); const fileHtml = sideBySideRenderer.generateSingleLineHtml(false, CSSLineClass.DELETES, "test", 30, "-");
const expected = const expected =
"<tr>\n" + "<tr>\n" +
' <td class="d2h-code-side-linenumber d2h-del">\n' + ' <td class="d2h-code-side-linenumber d2h-del">\n' +
@ -190,33 +195,33 @@ describe("SideBySidePrinter", function() {
" </td>\n" + " </td>\n" +
"</tr>"; "</tr>";
expect(expected).toEqual(fileHtml); expect(fileHtml).toEqual(expected);
}); });
}); });
describe("generateSideBySideJsonHtml", function() { describe("generateSideBySideJsonHtml", () => {
it("should work for list of files", function() { it("should work for list of files", () => {
const exampleJson = [ const exampleJson: DiffFile[] = [
{ {
blocks: [ blocks: [
{ {
lines: [ lines: [
{ {
content: "-test", content: "-test",
type: "d2h-del", type: LineType.DELETE,
oldNumber: 1, oldNumber: 1,
newNumber: null newNumber: undefined
}, },
{ {
content: "+test1r", content: "+test1r",
type: "d2h-ins", type: LineType.INSERT,
oldNumber: null, oldNumber: undefined,
newNumber: 1 newNumber: 1
} }
], ],
oldStartLine: "1", oldStartLine: 1,
oldStartLine2: null, oldStartLine2: undefined,
newStartLine: "1", newStartLine: 1,
header: "@@ -1 +1 @@" header: "@@ -1 +1 @@"
} }
], ],
@ -225,17 +230,19 @@ describe("SideBySidePrinter", function() {
checksumBefore: "0000001", checksumBefore: "0000001",
checksumAfter: "0ddf2ba", checksumAfter: "0ddf2ba",
oldName: "sample", oldName: "sample",
language: undefined, language: "txt",
newName: "sample", newName: "sample",
isCombined: false isCombined: false,
isGitDiff: true
} }
]; ];
const sideBySidePrinter = new SideBySidePrinter({ matching: "lines" }); const hoganUtils = new HoganJsUtils({});
const html = sideBySidePrinter.generateSideBySideJsonHtml(exampleJson); const sideBySideRenderer = new SideBySideRenderer(hoganUtils, { matching: "lines" });
const html = sideBySideRenderer.render(exampleJson);
const expected = const expected =
'<div class="d2h-wrapper">\n' + '<div class="d2h-wrapper">\n' +
' <div id="d2h-675094" class="d2h-file-wrapper" data-lang="">\n' + ' <div id="d2h-675094" class="d2h-file-wrapper" data-lang="txt">\n' +
' <div class="d2h-file-header">\n' + ' <div class="d2h-file-header">\n' +
' <span class="d2h-file-name-wrapper">\n' + ' <span class="d2h-file-name-wrapper">\n' +
' <svg aria-hidden="true" class="d2h-icon" height="16" version="1.1" viewBox="0 0 12 16" width="12">\n' + ' <svg aria-hidden="true" class="d2h-icon" height="16" version="1.1" viewBox="0 0 12 16" width="12">\n' +
@ -254,11 +261,11 @@ describe("SideBySidePrinter", function() {
' <div class="d2h-code-side-line d2h-info">@@ -1 +1 @@</div>\n' + ' <div class="d2h-code-side-line d2h-info">@@ -1 +1 @@</div>\n' +
" </td>\n" + " </td>\n" +
"</tr><tr>\n" + "</tr><tr>\n" +
' <td class="d2h-code-side-linenumber d2h-del">\n' + ' <td class="d2h-code-side-linenumber d2h-del d2h-change">\n' +
" 1\n" + " 1\n" +
" </td>\n" + " </td>\n" +
' <td class="d2h-del">\n' + ' <td class="d2h-del d2h-change">\n' +
' <div class="d2h-code-side-line d2h-del">\n' + ' <div class="d2h-code-side-line d2h-del d2h-change">\n' +
' <span class="d2h-code-line-prefix">-</span>\n' + ' <span class="d2h-code-line-prefix">-</span>\n' +
' <span class="d2h-code-line-ctn"><del>test</del></span>\n' + ' <span class="d2h-code-line-ctn"><del>test</del></span>\n' +
" </div>\n" + " </div>\n" +
@ -278,11 +285,11 @@ describe("SideBySidePrinter", function() {
' <div class="d2h-code-side-line d2h-info"></div>\n' + ' <div class="d2h-code-side-line d2h-info"></div>\n' +
" </td>\n" + " </td>\n" +
"</tr><tr>\n" + "</tr><tr>\n" +
' <td class="d2h-code-side-linenumber d2h-ins">\n' + ' <td class="d2h-code-side-linenumber d2h-ins d2h-change">\n' +
" 1\n" + " 1\n" +
" </td>\n" + " </td>\n" +
' <td class="d2h-ins">\n' + ' <td class="d2h-ins d2h-change">\n' +
' <div class="d2h-code-side-line d2h-ins">\n' + ' <div class="d2h-code-side-line d2h-ins d2h-change">\n' +
' <span class="d2h-code-line-prefix">+</span>\n' + ' <span class="d2h-code-line-prefix">+</span>\n' +
' <span class="d2h-code-line-ctn"><ins>test1r</ins></span>\n' + ' <span class="d2h-code-line-ctn"><ins>test1r</ins></span>\n' +
" </div>\n" + " </div>\n" +
@ -296,21 +303,25 @@ describe("SideBySidePrinter", function() {
"</div>\n" + "</div>\n" +
"</div>"; "</div>";
expect(expected).toEqual(html); expect(html).toEqual(expected);
}); });
it("should work for files without blocks", function() { it("should work for files without blocks", () => {
const exampleJson = [ const exampleJson: DiffFile[] = [
{ {
blocks: [], blocks: [],
oldName: "sample", oldName: "sample",
language: "js", language: "js",
newName: "sample", newName: "sample",
isCombined: false isCombined: false,
addedLines: 0,
deletedLines: 0,
isGitDiff: false
} }
]; ];
const sideBySidePrinter = new SideBySidePrinter(); const hoganUtils = new HoganJsUtils({});
const html = sideBySidePrinter.generateSideBySideJsonHtml(exampleJson); const sideBySideRenderer = new SideBySideRenderer(hoganUtils, {});
const html = sideBySideRenderer.render(exampleJson);
const expected = const expected =
'<div class="d2h-wrapper">\n' + '<div class="d2h-wrapper">\n' +
' <div id="d2h-675094" class="d2h-file-wrapper" data-lang="js">\n' + ' <div id="d2h-675094" class="d2h-file-wrapper" data-lang="js">\n' +
@ -350,32 +361,33 @@ describe("SideBySidePrinter", function() {
"</div>\n" + "</div>\n" +
"</div>"; "</div>";
expect(expected).toEqual(html); expect(html).toEqual(expected);
}); });
}); });
describe("processLines", function() { describe("processLines", () => {
it("should process file lines", function() { it("should process file lines", () => {
const oldLines = [ const oldLines: DiffLine[] = [
{ {
content: "-test", content: "-test",
type: "d2h-del", type: LineType.DELETE,
oldNumber: 1, oldNumber: 1,
newNumber: null newNumber: undefined
} }
]; ];
const newLines = [ const newLines: DiffLine[] = [
{ {
content: "+test1r", content: "+test1r",
type: "d2h-ins", type: LineType.INSERT,
oldNumber: null, oldNumber: undefined,
newNumber: 1 newNumber: 1
} }
]; ];
const sideBySidePrinter = new SideBySidePrinter({ matching: "lines" }); const hoganUtils = new HoganJsUtils({});
const html = sideBySidePrinter.processLines(false, oldLines, newLines); const sideBySideRenderer = new SideBySideRenderer(hoganUtils, { matching: "lines" });
const html = sideBySideRenderer.processLines(false, oldLines, newLines);
const expectedLeft = const expectedLeft =
"<tr>\n" + "<tr>\n" +
' <td class="d2h-code-side-linenumber d2h-del">\n' + ' <td class="d2h-code-side-linenumber d2h-del">\n' +
@ -402,8 +414,8 @@ describe("SideBySidePrinter", function() {
" </td>\n" + " </td>\n" +
"</tr>"; "</tr>";
expect(expectedLeft).toEqual(html.left); expect(html.left).toEqual(expectedLeft);
expect(expectedRight).toEqual(html.right); expect(html.right).toEqual(expectedRight);
}); });
}); });
}); });

View file

@ -1,21 +1,21 @@
const Utils = require("../utils.js").Utils; import { escapeForHtml } from "../utils";
describe("Utils", function() { describe("Utils", function() {
describe("escape", function() { describe("escape", function() {
it("should escape & with &amp;", function() { it("should escape & with &amp;", function() {
const result = Utils.escape("&"); const result = escapeForHtml("&");
expect("&amp;").toEqual(result); expect("&amp;").toEqual(result);
}); });
it("should escape < with &lt;", function() { it("should escape < with &lt;", function() {
const result = Utils.escape("<"); const result = escapeForHtml("<");
expect("&lt;").toEqual(result); expect("&lt;").toEqual(result);
}); });
it("should escape > with &gt;", function() { it("should escape > with &gt;", function() {
const result = Utils.escape(">"); const result = escapeForHtml(">");
expect("&gt;").toEqual(result); expect("&gt;").toEqual(result);
}); });
it("should escape a string with multiple problematic characters", function() { it("should escape a string with multiple problematic characters", function() {
const result = Utils.escape('<a href="#">\tlink text</a>'); const result = escapeForHtml('<a href="#">\tlink text</a>');
const expected = "&lt;a href=&quot;#&quot;&gt;\tlink text&lt;&#x2F;a&gt;"; const expected = "&lt;a href=&quot;#&quot;&gt;\tlink text&lt;&#x2F;a&gt;";
expect(expected).toEqual(result); expect(expected).toEqual(result);
}); });

View file

@ -1,448 +0,0 @@
/*
*
* Diff Parser (diff-parser.js)
* Author: rtfpessoa
*
*/
(function() {
const utils = require("./utils.js").Utils;
const LINE_TYPE = {
INSERTS: "d2h-ins",
DELETES: "d2h-del",
INSERT_CHANGES: "d2h-ins d2h-change",
DELETE_CHANGES: "d2h-del d2h-change",
CONTEXT: "d2h-cntx",
INFO: "d2h-info"
};
function DiffParser() {}
DiffParser.prototype.LINE_TYPE = LINE_TYPE;
DiffParser.prototype.generateDiffJson = function(diffInput, configuration) {
const config = configuration || {};
const files = [];
let currentFile = null;
let currentBlock = null;
let oldLine = null;
let oldLine2 = null; // Used for combined diff
let newLine = null;
let possibleOldName;
let possibleNewName;
/* Diff Header */
const oldFileNameHeader = "--- ";
const newFileNameHeader = "+++ ";
const hunkHeaderPrefix = "@@";
/* Add previous block(if exists) before start a new file */
function saveBlock() {
if (currentBlock) {
currentFile.blocks.push(currentBlock);
currentBlock = null;
}
}
/*
* Add previous file(if exists) before start a new one
* if it has name (to avoid binary files errors)
*/
function saveFile() {
if (currentFile) {
if (!currentFile.oldName) {
currentFile.oldName = possibleOldName;
}
if (!currentFile.newName) {
currentFile.newName = possibleNewName;
}
if (currentFile.newName) {
files.push(currentFile);
currentFile = null;
}
}
possibleOldName = undefined;
possibleNewName = undefined;
}
/* Create file structure */
function startFile() {
saveBlock();
saveFile();
currentFile = {};
currentFile.blocks = [];
currentFile.deletedLines = 0;
currentFile.addedLines = 0;
}
function startBlock(line) {
saveBlock();
let values;
/**
* From Range:
* -<start line>[,<number of lines>]
*
* To Range:
* +<start line>[,<number of lines>]
*
* @@ from-file-range to-file-range @@
*
* @@@ from-file-range from-file-range to-file-range @@@
*
* number of lines is optional, if omited consider 0
*/
if ((values = /^@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@.*/.exec(line))) {
currentFile.isCombined = false;
oldLine = values[1];
newLine = values[2];
} else if ((values = /^@@@ -(\d+)(?:,\d+)? -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@@.*/.exec(line))) {
currentFile.isCombined = true;
oldLine = values[1];
oldLine2 = values[2];
newLine = values[3];
} else {
if (utils.startsWith(line, hunkHeaderPrefix)) {
console.error("Failed to parse lines, starting in 0!");
}
oldLine = 0;
newLine = 0;
currentFile.isCombined = false;
}
/* Create block metadata */
currentBlock = {};
currentBlock.lines = [];
currentBlock.oldStartLine = oldLine;
currentBlock.oldStartLine2 = oldLine2;
currentBlock.newStartLine = newLine;
currentBlock.header = line;
}
function createLine(line) {
const currentLine = {};
currentLine.content = line;
const newLinePrefixes = !currentFile.isCombined ? ["+"] : ["+", " +"];
const delLinePrefixes = !currentFile.isCombined ? ["-"] : ["-", " -"];
/* Fill the line data */
if (utils.startsWith(line, newLinePrefixes)) {
currentFile.addedLines++;
currentLine.type = LINE_TYPE.INSERTS;
currentLine.oldNumber = null;
currentLine.newNumber = newLine++;
currentBlock.lines.push(currentLine);
} else if (utils.startsWith(line, delLinePrefixes)) {
currentFile.deletedLines++;
currentLine.type = LINE_TYPE.DELETES;
currentLine.oldNumber = oldLine++;
currentLine.newNumber = null;
currentBlock.lines.push(currentLine);
} else {
currentLine.type = LINE_TYPE.CONTEXT;
currentLine.oldNumber = oldLine++;
currentLine.newNumber = newLine++;
currentBlock.lines.push(currentLine);
}
}
/*
* Checks if there is a hunk header coming before a new file starts
*
* Hunk header is a group of three lines started by ( `--- ` , `+++ ` , `@@` )
*/
function existHunkHeader(line, lineIdx) {
let idx = lineIdx;
while (idx < diffLines.length - 3) {
if (utils.startsWith(line, "diff")) {
return false;
}
if (
utils.startsWith(diffLines[idx], oldFileNameHeader) &&
utils.startsWith(diffLines[idx + 1], newFileNameHeader) &&
utils.startsWith(diffLines[idx + 2], hunkHeaderPrefix)
) {
return true;
}
idx++;
}
return false;
}
var diffLines = diffInput
.replace(/\\ No newline at end of file/g, "")
.replace(/\r\n?/g, "\n")
.split("\n");
/* Diff */
const oldMode = /^old mode (\d{6})/;
const newMode = /^new mode (\d{6})/;
const deletedFileMode = /^deleted file mode (\d{6})/;
const newFileMode = /^new file mode (\d{6})/;
const copyFrom = /^copy from "?(.+)"?/;
const copyTo = /^copy to "?(.+)"?/;
const renameFrom = /^rename from "?(.+)"?/;
const renameTo = /^rename to "?(.+)"?/;
const similarityIndex = /^similarity index (\d+)%/;
const dissimilarityIndex = /^dissimilarity index (\d+)%/;
const index = /^index ([0-9a-z]+)\.\.([0-9a-z]+)\s*(\d{6})?/;
const binaryFiles = /^Binary files (.*) and (.*) differ/;
const binaryDiff = /^GIT binary patch/;
/* Combined Diff */
const combinedIndex = /^index ([0-9a-z]+),([0-9a-z]+)\.\.([0-9a-z]+)/;
const combinedMode = /^mode (\d{6}),(\d{6})\.\.(\d{6})/;
const combinedNewFile = /^new file mode (\d{6})/;
const combinedDeletedFile = /^deleted file mode (\d{6}),(\d{6})/;
diffLines.forEach(function(line, lineIndex) {
// Unmerged paths, and possibly other non-diffable files
// https://github.com/scottgonzalez/pretty-diff/issues/11
// Also, remove some useless lines
if (!line || utils.startsWith(line, "*")) {
return;
}
// Used to store regex capture groups
let values;
const prevLine = diffLines[lineIndex - 1];
const nxtLine = diffLines[lineIndex + 1];
const afterNxtLine = diffLines[lineIndex + 2];
if (utils.startsWith(line, "diff")) {
startFile();
// diff --git a/blocked_delta_results.png b/blocked_delta_results.png
const gitDiffStart = /^diff --git "?(.+)"? "?(.+)"?/;
if ((values = gitDiffStart.exec(line))) {
possibleOldName = _getFilename(null, values[1], config.dstPrefix);
possibleNewName = _getFilename(null, values[2], config.srcPrefix);
}
currentFile.isGitDiff = true;
return;
}
if (
!currentFile || // If we do not have a file yet, we should crete one
(!currentFile.isGitDiff &&
currentFile && // If we already have some file in progress and
(utils.startsWith(line, oldFileNameHeader) && // If we get to an old file path header line
// And is followed by the new file path header line and the hunk header line
utils.startsWith(nxtLine, newFileNameHeader) &&
utils.startsWith(afterNxtLine, hunkHeaderPrefix)))
) {
startFile();
}
/*
* We need to make sure that we have the three lines of the header.
* This avoids cases like the ones described in:
* - https://github.com/rtfpessoa/diff2html/issues/87
*/
if (
(utils.startsWith(line, oldFileNameHeader) && utils.startsWith(nxtLine, newFileNameHeader)) ||
(utils.startsWith(line, newFileNameHeader) && utils.startsWith(prevLine, oldFileNameHeader))
) {
/*
* --- Date Timestamp[FractionalSeconds] TimeZone
* --- 2002-02-21 23:30:39.942229878 -0800
*/
if (
currentFile &&
!currentFile.oldName &&
utils.startsWith(line, "--- ") &&
(values = getSrcFilename(line, config))
) {
currentFile.oldName = values;
currentFile.language = getExtension(currentFile.oldName, currentFile.language);
return;
}
/*
* +++ Date Timestamp[FractionalSeconds] TimeZone
* +++ 2002-02-21 23:30:39.942229878 -0800
*/
if (
currentFile &&
!currentFile.newName &&
utils.startsWith(line, "+++ ") &&
(values = getDstFilename(line, config))
) {
currentFile.newName = values;
currentFile.language = getExtension(currentFile.newName, currentFile.language);
return;
}
}
if (
(currentFile && utils.startsWith(line, hunkHeaderPrefix)) ||
(currentFile.isGitDiff && currentFile && currentFile.oldName && currentFile.newName && !currentBlock)
) {
startBlock(line);
return;
}
/*
* There are three types of diff lines. These lines are defined by the way they start.
* 1. New line starts with: +
* 2. Old line starts with: -
* 3. Context line starts with: <SPACE>
*/
if (currentBlock && (utils.startsWith(line, "+") || utils.startsWith(line, "-") || utils.startsWith(line, " "))) {
createLine(line);
return;
}
const doesNotExistHunkHeader = !existHunkHeader(line, lineIndex);
/*
* Git diffs provide more information regarding files modes, renames, copies,
* commits between changes and similarity indexes
*/
if ((values = oldMode.exec(line))) {
currentFile.oldMode = values[1];
} else if ((values = newMode.exec(line))) {
currentFile.newMode = values[1];
} else if ((values = deletedFileMode.exec(line))) {
currentFile.deletedFileMode = values[1];
currentFile.isDeleted = true;
} else if ((values = newFileMode.exec(line))) {
currentFile.newFileMode = values[1];
currentFile.isNew = true;
} else if ((values = copyFrom.exec(line))) {
if (doesNotExistHunkHeader) {
currentFile.oldName = values[1];
}
currentFile.isCopy = true;
} else if ((values = copyTo.exec(line))) {
if (doesNotExistHunkHeader) {
currentFile.newName = values[1];
}
currentFile.isCopy = true;
} else if ((values = renameFrom.exec(line))) {
if (doesNotExistHunkHeader) {
currentFile.oldName = values[1];
}
currentFile.isRename = true;
} else if ((values = renameTo.exec(line))) {
if (doesNotExistHunkHeader) {
currentFile.newName = values[1];
}
currentFile.isRename = true;
} else if ((values = binaryFiles.exec(line))) {
currentFile.isBinary = true;
currentFile.oldName = _getFilename(null, values[1], config.srcPrefix);
currentFile.newName = _getFilename(null, values[2], config.dstPrefix);
startBlock("Binary file");
} else if ((values = binaryDiff.exec(line))) {
currentFile.isBinary = true;
startBlock(line);
} else if ((values = similarityIndex.exec(line))) {
currentFile.unchangedPercentage = values[1];
} else if ((values = dissimilarityIndex.exec(line))) {
currentFile.changedPercentage = values[1];
} else if ((values = index.exec(line))) {
currentFile.checksumBefore = values[1];
currentFile.checksumAfter = values[2];
values[3] && (currentFile.mode = values[3]);
} else if ((values = combinedIndex.exec(line))) {
currentFile.checksumBefore = [values[2], values[3]];
currentFile.checksumAfter = values[1];
} else if ((values = combinedMode.exec(line))) {
currentFile.oldMode = [values[2], values[3]];
currentFile.newMode = values[1];
} else if ((values = combinedNewFile.exec(line))) {
currentFile.newFileMode = values[1];
currentFile.isNew = true;
} else if ((values = combinedDeletedFile.exec(line))) {
currentFile.deletedFileMode = values[1];
currentFile.isDeleted = true;
}
});
saveBlock();
saveFile();
return files;
};
function getExtension(filename, language) {
const nameSplit = filename.split(".");
if (nameSplit.length > 1) {
return nameSplit[nameSplit.length - 1];
}
return language;
}
function getSrcFilename(line, cfg) {
return _getFilename("---", line, cfg.srcPrefix);
}
function getDstFilename(line, cfg) {
return _getFilename("\\+\\+\\+", line, cfg.dstPrefix);
}
function _getFilename(linePrefix, line, extraPrefix) {
const prefixes = ["a/", "b/", "i/", "w/", "c/", "o/"];
if (extraPrefix) {
prefixes.push(extraPrefix);
}
let FilenameRegExp;
if (linePrefix) {
FilenameRegExp = new RegExp("^" + linePrefix + ' "?(.+?)"?$');
} else {
FilenameRegExp = new RegExp('^"?(.+?)"?$');
}
let filename;
const values = FilenameRegExp.exec(line);
if (values && values[1]) {
filename = values[1];
const matchingPrefixes = prefixes.filter(function(p) {
return filename.indexOf(p) === 0;
});
if (matchingPrefixes[0]) {
// Remove prefix if exists
filename = filename.slice(matchingPrefixes[0].length);
}
// Cleanup timestamps generated by the unified diff (diff command) as specified in
// https://www.gnu.org/software/diffutils/manual/html_node/Detailed-Unified.html
// Ie: 2016-10-25 11:37:14.000000000 +0200
filename = filename.replace(/\s+\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}(?:\.\d+)? [-+]\d{4}.*$/, "");
}
return filename;
}
module.exports.DiffParser = new DiffParser();
})();

436
src/diff-parser.ts Normal file
View file

@ -0,0 +1,436 @@
import { DiffFile, DiffBlock, DiffLine, LineType } from "./render-utils";
import { escapeForRegExp } from "./utils";
export interface DiffParserConfig {
srcPrefix?: string;
dstPrefix?: string;
}
function getExtension(filename: string, language: string): string {
const filenameParts = filename.split(".");
return filenameParts.length > 1 ? filenameParts[filenameParts.length - 1] : language;
}
function startsWithAny(str: string, prefixes: string[]): boolean {
return prefixes.reduce<boolean>((startsWith, prefix) => startsWith || str.startsWith(prefix), false);
}
const baseDiffFilenamePrefixes = ["a/", "b/", "i/", "w/", "c/", "o/"];
function getFilename(line: string, linePrefix?: string, extraPrefix?: string): string {
const prefixes = extraPrefix !== undefined ? [...baseDiffFilenamePrefixes, extraPrefix] : baseDiffFilenamePrefixes;
const FilenameRegExp = linePrefix
? new RegExp(`^${escapeForRegExp(linePrefix)} "?(.+?)"?$`)
: new RegExp('^"?(.+?)"?$');
const [, filename = ""] = FilenameRegExp.exec(line) || []; // TODO: Check if this is safe
const matchingPrefix = prefixes.find(p => filename.indexOf(p) === 0);
const fnameWithoutPrefix = matchingPrefix ? filename.slice(matchingPrefix.length) : filename;
// Cleanup timestamps generated by the unified diff (diff command) as specified in
// https://www.gnu.org/software/diffutils/manual/html_node/Detailed-Unified.html
// Ie: 2016-10-25 11:37:14.000000000 +0200
return fnameWithoutPrefix.replace(/\s+\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}(?:\.\d+)? [-+]\d{4}.*$/, "");
}
function getSrcFilename(line: string, srcPrefix?: string): string | undefined {
return getFilename(line, "---", srcPrefix);
}
function getDstFilename(line: string, dstPrefix?: string): string | undefined {
return getFilename(line, "+++", dstPrefix);
}
/**
*
* Docs:
* - Unified: https://www.gnu.org/software/diffutils/manual/html_node/Unified-Format.html
* - Git Diff: https://git-scm.com/docs/git-diff-tree#_raw_output_format
* - Git Combined Diff: https://git-scm.com/docs/git-diff-tree#_combined_diff_format
*
*/
export function parse(diffInput: string, config: DiffParserConfig = {}): DiffFile[] {
const files: DiffFile[] = [];
let currentFile: DiffFile | null = null;
let currentBlock: DiffBlock | null = null;
let oldLine: number | null = null;
let oldLine2: number | null = null; // Used for combined diff
let newLine: number | null = null;
let possibleOldName: string | null = null;
let possibleNewName: string | null = null;
/* Diff Header */
const oldFileNameHeader = "--- ";
const newFileNameHeader = "+++ ";
const hunkHeaderPrefix = "@@";
/* Diff */
const oldMode = /^old mode (\d{6})/;
const newMode = /^new mode (\d{6})/;
const deletedFileMode = /^deleted file mode (\d{6})/;
const newFileMode = /^new file mode (\d{6})/;
const copyFrom = /^copy from "?(.+)"?/;
const copyTo = /^copy to "?(.+)"?/;
const renameFrom = /^rename from "?(.+)"?/;
const renameTo = /^rename to "?(.+)"?/;
const similarityIndex = /^similarity index (\d+)%/;
const dissimilarityIndex = /^dissimilarity index (\d+)%/;
const index = /^index ([0-9a-z]+)\.\.([0-9a-z]+)\s*(\d{6})?/;
const binaryFiles = /^Binary files (.*) and (.*) differ/;
const binaryDiff = /^GIT binary patch/;
/* Combined Diff */
const combinedIndex = /^index ([0-9a-z]+),([0-9a-z]+)\.\.([0-9a-z]+)/;
const combinedMode = /^mode (\d{6}),(\d{6})\.\.(\d{6})/;
const combinedNewFile = /^new file mode (\d{6})/;
const combinedDeletedFile = /^deleted file mode (\d{6}),(\d{6})/;
const diffLines = diffInput
.replace(/\\ No newline at end of file/g, "")
.replace(/\r\n?/g, "\n")
.split("\n");
/* Add previous block(if exists) before start a new file */
function saveBlock(): void {
if (currentBlock !== null && currentFile !== null) {
currentFile.blocks.push(currentBlock);
currentBlock = null;
}
}
/*
* Add previous file(if exists) before start a new one
* if it has name (to avoid binary files errors)
*/
function saveFile(): void {
if (currentFile !== null) {
if (!currentFile.oldName && possibleOldName !== null) {
currentFile.oldName = possibleOldName;
}
if (!currentFile.newName && possibleNewName !== null) {
currentFile.newName = possibleNewName;
}
if (currentFile.newName) {
files.push(currentFile);
currentFile = null;
}
}
possibleOldName = null;
possibleNewName = null;
}
/* Create file structure */
function startFile(): void {
saveBlock();
saveFile();
// TODO: Avoid disabling types
// eslint-disable-next-line
// @ts-ignore
currentFile = {
blocks: [],
deletedLines: 0,
addedLines: 0
};
}
function startBlock(line: string): void {
saveBlock();
let values;
/**
* From Range:
* -<start line>[,<number of lines>]
*
* To Range:
* +<start line>[,<number of lines>]
*
* @@ from-file-range to-file-range @@
*
* @@@ from-file-range from-file-range to-file-range @@@
*
* number of lines is optional, if omited consider 0
*/
if (currentFile !== null) {
if ((values = /^@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@.*/.exec(line))) {
currentFile.isCombined = false;
oldLine = parseInt(values[1], 10);
newLine = parseInt(values[2], 10);
} else if ((values = /^@@@ -(\d+)(?:,\d+)? -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@@.*/.exec(line))) {
currentFile.isCombined = true;
oldLine = parseInt(values[1], 10);
oldLine2 = parseInt(values[2], 10);
newLine = parseInt(values[3], 10);
} else {
if (line.startsWith(hunkHeaderPrefix)) {
console.error("Failed to parse lines, starting in 0!");
}
oldLine = 0;
newLine = 0;
currentFile.isCombined = false;
}
}
/* Create block metadata */
// TODO: Avoid disabling types
// eslint-disable-next-line
// @ts-ignore
currentBlock = {
lines: [],
oldStartLine: oldLine,
oldStartLine2: oldLine2,
newStartLine: newLine,
header: line
};
}
function createLine(line: string): void {
if (currentFile === null || currentBlock === null || oldLine === null || newLine === null) return;
// TODO: Avoid disabling types
// eslint-disable-next-line
// @ts-ignore
const currentLine: DiffLine = {
content: line
};
const addedPrefixes = currentFile.isCombined ? ["+ ", " +", "++"] : ["+"];
const deletedPrefixes = currentFile.isCombined ? ["- ", " -", "--"] : ["-"];
// TODO: Check if this makes sense for combined diff
if (startsWithAny(line, addedPrefixes)) {
currentFile.addedLines++;
currentLine.type = LineType.INSERT;
currentLine.oldNumber = undefined;
currentLine.newNumber = newLine++;
} else if (startsWithAny(line, deletedPrefixes)) {
currentFile.deletedLines++;
currentLine.type = LineType.DELETE;
currentLine.oldNumber = oldLine++;
currentLine.newNumber = undefined;
} else {
currentLine.type = LineType.CONTEXT;
currentLine.oldNumber = oldLine++;
currentLine.newNumber = newLine++;
}
currentBlock.lines.push(currentLine);
}
/*
* Checks if there is a hunk header coming before a new file starts
*
* Hunk header is a group of three lines started by ( `--- ` , `+++ ` , `@@` )
*/
function existHunkHeader(line: string, lineIdx: number): boolean {
let idx = lineIdx;
while (idx < diffLines.length - 3) {
if (line.startsWith("diff")) {
return false;
}
if (
diffLines[idx].startsWith(oldFileNameHeader) &&
diffLines[idx + 1].startsWith(newFileNameHeader) &&
diffLines[idx + 2].startsWith(hunkHeaderPrefix)
) {
return true;
}
idx++;
}
return false;
}
diffLines.forEach(function(line, lineIndex) {
// Unmerged paths, and possibly other non-diffable files
// https://github.com/scottgonzalez/pretty-diff/issues/11
// Also, remove some useless lines
if (!line || line.startsWith("*")) {
return;
}
// Used to store regex capture groups
let values;
const prevLine = diffLines[lineIndex - 1];
const nxtLine = diffLines[lineIndex + 1];
const afterNxtLine = diffLines[lineIndex + 2];
if (line.startsWith("diff")) {
startFile();
// diff --git a/blocked_delta_results.png b/blocked_delta_results.png
const gitDiffStart = /^diff --git "?(.+)"? "?(.+)"?/;
if ((values = gitDiffStart.exec(line))) {
possibleOldName = getFilename(values[1], undefined, config.dstPrefix);
possibleNewName = getFilename(values[2], undefined, config.srcPrefix);
}
if (currentFile === null) {
throw new Error("Where is my file !!!");
}
currentFile.isGitDiff = true;
return;
}
if (
!currentFile || // If we do not have a file yet, we should crete one
(!currentFile.isGitDiff &&
currentFile && // If we already have some file in progress and
(line.startsWith(oldFileNameHeader) && // If we get to an old file path header line
// And is followed by the new file path header line and the hunk header line
nxtLine.startsWith(newFileNameHeader) &&
afterNxtLine.startsWith(hunkHeaderPrefix)))
) {
startFile();
}
/*
* We need to make sure that we have the three lines of the header.
* This avoids cases like the ones described in:
* - https://github.com/rtfpessoa/diff2html/issues/87
*/
if (
(line.startsWith(oldFileNameHeader) && nxtLine.startsWith(newFileNameHeader)) ||
(line.startsWith(newFileNameHeader) && prevLine.startsWith(oldFileNameHeader))
) {
/*
* --- Date Timestamp[FractionalSeconds] TimeZone
* --- 2002-02-21 23:30:39.942229878 -0800
*/
if (
currentFile &&
!currentFile.oldName &&
line.startsWith("--- ") &&
(values = getSrcFilename(line, config.srcPrefix))
) {
currentFile.oldName = values;
currentFile.language = getExtension(currentFile.oldName, currentFile.language);
return;
}
/*
* +++ Date Timestamp[FractionalSeconds] TimeZone
* +++ 2002-02-21 23:30:39.942229878 -0800
*/
if (
currentFile &&
!currentFile.newName &&
line.startsWith("+++ ") &&
(values = getDstFilename(line, config.dstPrefix))
) {
currentFile.newName = values;
currentFile.language = getExtension(currentFile.newName, currentFile.language);
return;
}
}
if (
(currentFile && line.startsWith(hunkHeaderPrefix)) ||
(currentFile && currentFile.isGitDiff && currentFile.oldName && currentFile.newName && !currentBlock)
) {
startBlock(line);
return;
}
/*
* There are three types of diff lines. These lines are defined by the way they start.
* 1. New line starts with: +
* 2. Old line starts with: -
* 3. Context line starts with: <SPACE>
*/
if (currentBlock && (line.startsWith("+") || line.startsWith("-") || line.startsWith(" "))) {
createLine(line);
return;
}
const doesNotExistHunkHeader = !existHunkHeader(line, lineIndex);
if (currentFile === null) {
throw new Error("Where is my file !!!");
}
/*
* Git diffs provide more information regarding files modes, renames, copies,
* commits between changes and similarity indexes
*/
if ((values = oldMode.exec(line))) {
currentFile.oldMode = values[1];
} else if ((values = newMode.exec(line))) {
currentFile.newMode = values[1];
} else if ((values = deletedFileMode.exec(line))) {
currentFile.deletedFileMode = values[1];
currentFile.isDeleted = true;
} else if ((values = newFileMode.exec(line))) {
currentFile.newFileMode = values[1];
currentFile.isNew = true;
} else if ((values = copyFrom.exec(line))) {
if (doesNotExistHunkHeader) {
currentFile.oldName = values[1];
}
currentFile.isCopy = true;
} else if ((values = copyTo.exec(line))) {
if (doesNotExistHunkHeader) {
currentFile.newName = values[1];
}
currentFile.isCopy = true;
} else if ((values = renameFrom.exec(line))) {
if (doesNotExistHunkHeader) {
currentFile.oldName = values[1];
}
currentFile.isRename = true;
} else if ((values = renameTo.exec(line))) {
if (doesNotExistHunkHeader) {
currentFile.newName = values[1];
}
currentFile.isRename = true;
} else if ((values = binaryFiles.exec(line))) {
currentFile.isBinary = true;
currentFile.oldName = getFilename(values[1], undefined, config.srcPrefix);
currentFile.newName = getFilename(values[2], undefined, config.dstPrefix);
startBlock("Binary file");
} else if ((values = binaryDiff.exec(line))) {
currentFile.isBinary = true;
startBlock(line);
} else if ((values = similarityIndex.exec(line))) {
currentFile.unchangedPercentage = parseInt(values[1], 10);
} else if ((values = dissimilarityIndex.exec(line))) {
currentFile.changedPercentage = parseInt(values[1], 10);
} else if ((values = index.exec(line))) {
currentFile.checksumBefore = values[1];
currentFile.checksumAfter = values[2];
values[3] && (currentFile.mode = values[3]);
} else if ((values = combinedIndex.exec(line))) {
currentFile.checksumBefore = [values[2], values[3]];
currentFile.checksumAfter = values[1];
} else if ((values = combinedMode.exec(line))) {
currentFile.oldMode = [values[2], values[3]];
currentFile.newMode = values[1];
} else if ((values = combinedNewFile.exec(line))) {
currentFile.newFileMode = values[1];
currentFile.isNew = true;
} else if ((values = combinedDeletedFile.exec(line))) {
currentFile.deletedFileMode = values[1];
currentFile.isDeleted = true;
}
});
saveBlock();
saveFile();
return files;
}

70
src/diff2html.d.ts vendored
View file

@ -1,70 +0,0 @@
// Type definitions for diff2html
// Project: https://github.com/rtfpessoa/diff2html
// Definitions by: rtfpessoa <https://github.com/rtfpessoa/>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
declare namespace Diff2Html {
export interface Options {
inputFormat?: "diff" | "json";
outputFormat?: "line-by-line" | "side-by-side";
showFiles?: boolean;
diffStyle?: "word" | "char";
matching?: "lines" | "words" | "none";
matchWordsThreshold?: number;
matchingMaxComparisons?: number;
maxLineSizeInBlockForComparison?: number;
maxLineLengthHighlight?: number;
templates?: object;
rawTemplates?: object;
renderNothingWhenEmpty?: boolean;
}
export interface Line {
content: string;
type: string;
oldNumber: number;
newNumber: number;
}
export interface Block {
oldStartLine: number;
oldStartLine2?: number;
newStartLine: number;
header: string;
lines: Line[];
}
export interface Result {
addedLines: number;
deletedLines: number;
isCombined: boolean;
isGitDiff: boolean;
oldName: string;
newName: string;
language: string;
blocks: Block[];
oldMode?: string;
newMode?: string;
deletedFileMode?: string;
newFileMode?: string;
isDeleted?: boolean;
isNew?: boolean;
isCopy?: boolean;
isRename?: boolean;
unchangedPercentage?: number;
changedPercentage?: number;
checksumBefore?: string;
checksumAfter?: string;
mode?: string;
}
export interface Diff2Html {
getJsonFromDiff(input: string, configuration?: Options): Result[];
getPrettyHtml(input: any, configuration?: Options): string;
}
}
declare module "diff2html" {
var d2h: { Diff2Html: Diff2Html.Diff2Html };
export = d2h;
}

View file

@ -1,113 +0,0 @@
/*
*
* Diff to HTML (diff2html.js)
* Author: rtfpessoa
*
*/
(function() {
const diffParser = require("./diff-parser.js").DiffParser;
const htmlPrinter = require("./html-printer.js").HtmlPrinter;
const utils = require("./utils.js").Utils;
function Diff2Html() {}
const defaultConfig = {
inputFormat: "diff",
outputFormat: "line-by-line",
showFiles: false,
diffStyle: "word",
matching: "none",
matchWordsThreshold: 0.25,
matchingMaxComparisons: 2500,
maxLineSizeInBlockForComparison: 200,
maxLineLengthHighlight: 10000,
templates: {},
rawTemplates: {},
renderNothingWhenEmpty: false
};
/*
* Generates json object from string diff input
*/
Diff2Html.prototype.getJsonFromDiff = function(diffInput, config) {
const cfg = utils.safeConfig(config, defaultConfig);
return diffParser.generateDiffJson(diffInput, cfg);
};
/*
* Generates the html diff. The config parameter configures the output/input formats and other options
*/
Diff2Html.prototype.getPrettyHtml = function(diffInput, config) {
const cfg = utils.safeConfig(config, defaultConfig);
let diffJson = diffInput;
if (!cfg.inputFormat || cfg.inputFormat === "diff") {
diffJson = diffParser.generateDiffJson(diffInput, cfg);
}
let fileList = "";
if (cfg.showFiles === true) {
fileList = htmlPrinter.generateFileListSummary(diffJson, cfg);
}
let diffOutput = "";
if (cfg.outputFormat === "side-by-side") {
diffOutput = htmlPrinter.generateSideBySideJsonHtml(diffJson, cfg);
} else {
diffOutput = htmlPrinter.generateLineByLineJsonHtml(diffJson, cfg);
}
return fileList + diffOutput;
};
/*
* Deprecated methods - The following methods exist only to maintain compatibility with previous versions
*/
/*
* Generates pretty html from string diff input
*/
Diff2Html.prototype.getPrettyHtmlFromDiff = function(diffInput, config) {
const cfg = utils.safeConfig(config, defaultConfig);
cfg.inputFormat = "diff";
cfg.outputFormat = "line-by-line";
return this.getPrettyHtml(diffInput, cfg);
};
/*
* Generates pretty html from a json object
*/
Diff2Html.prototype.getPrettyHtmlFromJson = function(diffJson, config) {
const cfg = utils.safeConfig(config, defaultConfig);
cfg.inputFormat = "json";
cfg.outputFormat = "line-by-line";
return this.getPrettyHtml(diffJson, cfg);
};
/*
* Generates pretty side by side html from string diff input
*/
Diff2Html.prototype.getPrettySideBySideHtmlFromDiff = function(diffInput, config) {
const cfg = utils.safeConfig(config, defaultConfig);
cfg.inputFormat = "diff";
cfg.outputFormat = "side-by-side";
return this.getPrettyHtml(diffInput, cfg);
};
/*
* Generates pretty side by side html from a json object
*/
Diff2Html.prototype.getPrettySideBySideHtmlFromJson = function(diffJson, config) {
const cfg = utils.safeConfig(config, defaultConfig);
cfg.inputFormat = "json";
cfg.outputFormat = "side-by-side";
return this.getPrettyHtml(diffJson, cfg);
};
const diffObject = new Diff2Html();
module.exports.Diff2Html = diffObject;
// Expose diff2html in the browser
global.Diff2Html = diffObject;
})();

50
src/diff2html.ts Normal file
View file

@ -0,0 +1,50 @@
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 HoganJsUtils, { HoganJsUtilsConfig } from "./hoganjs-utils";
type OutputFormatType = "line-by-line" | "side-by-side";
export interface Diff2HtmlConfig
extends DiffParser.DiffParserConfig,
LineByLineRendererConfig,
SideBySideRendererConfig,
HoganJsUtilsConfig {
outputFormat?: OutputFormatType;
showFiles?: boolean;
}
export const defaultDiff2HtmlConfig = {
...defaultLineByLineRendererConfig,
...defaultSideBySideRendererConfig,
outputFormat: "line-by-line" as OutputFormatType,
showFiles: false
};
export function parse(diffInput: string, configuration: Diff2HtmlConfig = {}): DiffFile[] {
return DiffParser.parse(diffInput, { ...defaultDiff2HtmlConfig, ...configuration });
}
export function html(diffInput: string | DiffFile[], configuration: Diff2HtmlConfig = {}): string {
const config = { ...defaultDiff2HtmlConfig, ...configuration };
const diffJson = typeof diffInput === "string" ? DiffParser.parse(diffInput, config) : diffInput;
const hoganUtils = new HoganJsUtils(config);
const fileList = config.showFiles ? fileListPrinter.render(diffJson, hoganUtils) : "";
const diffOutput =
config.outputFormat === "side-by-side"
? new SideBySideRenderer(hoganUtils, config).render(diffJson)
: new LineByLineRenderer(hoganUtils, config).render(diffJson);
// TODO: Review error handling
if (diffOutput === undefined) {
throw new Error("OMG we haz no diff. Why???");
}
return fileList + diffOutput;
}

View file

@ -1,54 +0,0 @@
/*
*
* FileListPrinter (file-list-printer.js)
* Author: nmatpt
*
*/
(function() {
const printerUtils = require("./printer-utils.js").PrinterUtils;
let hoganUtils;
const baseTemplatesPath = "file-summary";
const iconsBaseTemplatesPath = "icon";
function FileListPrinter(config) {
this.config = config;
const HoganJsUtils = require("./hoganjs-utils.js").HoganJsUtils;
hoganUtils = new HoganJsUtils(config);
}
FileListPrinter.prototype.generateFileList = function(diffFiles) {
const lineTemplate = hoganUtils.template(baseTemplatesPath, "line");
const files = diffFiles
.map(function(file) {
const fileTypeName = printerUtils.getFileTypeIcon(file);
const iconTemplate = hoganUtils.template(iconsBaseTemplatesPath, fileTypeName);
return lineTemplate.render(
{
fileHtmlId: printerUtils.getHtmlId(file),
oldName: file.oldName,
newName: file.newName,
fileName: printerUtils.getDiffName(file),
deletedLines: "-" + file.deletedLines,
addedLines: "+" + file.addedLines
},
{
fileIcon: iconTemplate
}
);
})
.join("\n");
return hoganUtils.render(baseTemplatesPath, "wrapper", {
filesNumber: diffFiles.length,
files: files
});
};
module.exports.FileListPrinter = FileListPrinter;
})();

32
src/file-list-renderer.ts Normal file
View file

@ -0,0 +1,32 @@
import * as renderUtils from "./render-utils";
import HoganJsUtils from "./hoganjs-utils";
const baseTemplatesPath = "file-summary";
const iconsBaseTemplatesPath = "icon";
export function render(diffFiles: renderUtils.DiffFile[], hoganUtils: HoganJsUtils): string {
const files = diffFiles
.map(file =>
hoganUtils.render(
baseTemplatesPath,
"line",
{
fileHtmlId: renderUtils.getHtmlId(file),
oldName: file.oldName,
newName: file.newName,
fileName: renderUtils.filenameDiff(file),
deletedLines: "-" + file.deletedLines,
addedLines: "+" + file.addedLines
},
{
fileIcon: hoganUtils.template(iconsBaseTemplatesPath, renderUtils.getFileIcon(file))
}
)
)
.join("\n");
return hoganUtils.render(baseTemplatesPath, "wrapper", {
filesNumber: diffFiles.length,
files: files
});
}

View file

@ -1,89 +0,0 @@
/*
*
* Utils (hoganjs-utils.js)
* Author: rtfpessoa
*
*/
(function() {
const fs = require("fs");
const path = require("path");
const hogan = require("hogan.js");
const hoganTemplates = require("./diff2html-templates.js");
let extraTemplates;
function HoganJsUtils(configuration) {
this.config = configuration || {};
extraTemplates = this.config.templates || {};
const rawTemplates = this.config.rawTemplates || {};
for (const templateName in rawTemplates) {
if (rawTemplates.hasOwnProperty(templateName)) {
if (!extraTemplates[templateName]) extraTemplates[templateName] = this.compile(rawTemplates[templateName]);
}
}
}
HoganJsUtils.prototype.render = function(namespace, view, params) {
const template = this.template(namespace, view);
if (template) {
return template.render(params);
}
return null;
};
HoganJsUtils.prototype.template = function(namespace, view) {
const templateKey = this._templateKey(namespace, view);
return this._getTemplate(templateKey);
};
HoganJsUtils.prototype._getTemplate = function(templateKey) {
let template;
if (!this.config.noCache) {
template = this._readFromCache(templateKey);
}
if (!template) {
template = this._loadTemplate(templateKey);
}
return template;
};
HoganJsUtils.prototype._loadTemplate = function(templateKey) {
let template;
try {
if (fs.readFileSync) {
const templatesPath = path.resolve(__dirname, "templates");
const templatePath = path.join(templatesPath, templateKey);
const templateContent = fs.readFileSync(templatePath + ".mustache", "utf8");
template = hogan.compile(templateContent);
hoganTemplates[templateKey] = template;
}
} catch (e) {
console.error("Failed to read (template: " + templateKey + ") from fs: " + e.message);
}
return template;
};
HoganJsUtils.prototype._readFromCache = function(templateKey) {
return extraTemplates[templateKey] || hoganTemplates[templateKey];
};
HoganJsUtils.prototype._templateKey = function(namespace, view) {
return namespace + "-" + view;
};
HoganJsUtils.prototype.compile = function(templateStr) {
return hogan.compile(templateStr);
};
module.exports.HoganJsUtils = HoganJsUtils;
})();

54
src/hoganjs-utils.ts Normal file
View file

@ -0,0 +1,54 @@
import * as Hogan from "hogan.js";
import { defaultTemplates } from "./diff2html-templates";
export interface RawTemplates {
[name: string]: string;
}
export interface CompiledTemplates {
[name: string]: Hogan.Template;
}
export interface HoganJsUtilsConfig {
compiledTemplates?: CompiledTemplates;
rawTemplates?: RawTemplates;
}
export default class HoganJsUtils {
private preCompiledTemplates: CompiledTemplates;
constructor({ compiledTemplates = {}, rawTemplates = {} }: HoganJsUtilsConfig) {
const compiledRawTemplates = Object.entries(rawTemplates).reduce<CompiledTemplates>(
(previousTemplates, [name, templateString]) => {
const compiledTemplate: Hogan.Template = Hogan.compile(templateString, { asString: false });
return { ...previousTemplates, [name]: compiledTemplate };
},
{}
);
this.preCompiledTemplates = { ...defaultTemplates, ...compiledTemplates, ...compiledRawTemplates };
}
static compile(templateString: string): Hogan.Template {
return Hogan.compile(templateString, { asString: false });
}
render(namespace: string, view: string, params: Hogan.Context, partials?: Hogan.Partials, indent?: string): string {
const templateKey = this.templateKey(namespace, view);
try {
const template = this.preCompiledTemplates[templateKey];
return template.render(params, partials, indent);
} catch (e) {
throw new Error(`Could not find template to render '${templateKey}'`);
}
}
template(namespace: string, view: string): Hogan.Template {
return this.preCompiledTemplates[this.templateKey(namespace, view)];
}
private templateKey(namespace: string, view: string): string {
return `${namespace}-${view}`;
}
}

View file

@ -1,31 +0,0 @@
/*
*
* HtmlPrinter (html-printer.js)
* Author: rtfpessoa
*
*/
(function() {
const LineByLinePrinter = require("./line-by-line-printer.js").LineByLinePrinter;
const SideBySidePrinter = require("./side-by-side-printer.js").SideBySidePrinter;
const FileListPrinter = require("./file-list-printer.js").FileListPrinter;
function HtmlPrinter() {}
HtmlPrinter.prototype.generateLineByLineJsonHtml = function(diffFiles, config) {
const lineByLinePrinter = new LineByLinePrinter(config);
return lineByLinePrinter.generateLineByLineJsonHtml(diffFiles);
};
HtmlPrinter.prototype.generateSideBySideJsonHtml = function(diffFiles, config) {
const sideBySidePrinter = new SideBySidePrinter(config);
return sideBySidePrinter.generateSideBySideJsonHtml(diffFiles);
};
HtmlPrinter.prototype.generateFileListSummary = function(diffJson, config) {
const fileListPrinter = new FileListPrinter(config);
return fileListPrinter.generateFileList(diffJson);
};
module.exports.HtmlPrinter = new HtmlPrinter();
})();

View file

@ -1,256 +0,0 @@
/*
*
* LineByLinePrinter (line-by-line-printer.js)
* Author: rtfpessoa
*
*/
(function() {
const diffParser = require("./diff-parser.js").DiffParser;
const printerUtils = require("./printer-utils.js").PrinterUtils;
const utils = require("./utils.js").Utils;
const Rematch = require("./rematch.js").Rematch;
let hoganUtils;
const genericTemplatesPath = "generic";
const baseTemplatesPath = "line-by-line";
const iconsBaseTemplatesPath = "icon";
const tagsBaseTemplatesPath = "tag";
function LineByLinePrinter(config) {
this.config = config;
const HoganJsUtils = require("./hoganjs-utils.js").HoganJsUtils;
hoganUtils = new HoganJsUtils(config);
}
LineByLinePrinter.prototype.makeFileDiffHtml = function(file, diffs) {
if (this.config.renderNothingWhenEmpty && file.blocks && !file.blocks.length) return "";
const fileDiffTemplate = hoganUtils.template(baseTemplatesPath, "file-diff");
const filePathTemplate = hoganUtils.template(genericTemplatesPath, "file-path");
const fileIconTemplate = hoganUtils.template(iconsBaseTemplatesPath, "file");
const fileTagTemplate = hoganUtils.template(tagsBaseTemplatesPath, printerUtils.getFileTypeIcon(file));
return fileDiffTemplate.render({
file: file,
fileHtmlId: printerUtils.getHtmlId(file),
diffs: diffs,
filePath: filePathTemplate.render(
{
fileDiffName: printerUtils.getDiffName(file)
},
{
fileIcon: fileIconTemplate,
fileTag: fileTagTemplate
}
)
});
};
LineByLinePrinter.prototype.makeLineByLineHtmlWrapper = function(content) {
return hoganUtils.render(genericTemplatesPath, "wrapper", { content: content });
};
LineByLinePrinter.prototype.generateLineByLineJsonHtml = function(diffFiles) {
const that = this;
const htmlDiffs = diffFiles.map(function(file) {
let diffs;
if (file.blocks.length) {
diffs = that._generateFileHtml(file);
} else {
diffs = that._generateEmptyDiff();
}
return that.makeFileDiffHtml(file, diffs);
});
return this.makeLineByLineHtmlWrapper(htmlDiffs.join("\n"));
};
const matcher = Rematch.rematch(function(a, b) {
const amod = a.content.substr(1);
const bmod = b.content.substr(1);
return Rematch.distance(amod, bmod);
});
LineByLinePrinter.prototype.makeColumnLineNumberHtml = function(block) {
return hoganUtils.render(genericTemplatesPath, "column-line-number", {
diffParser: diffParser,
blockHeader: utils.escape(block.header),
lineClass: "d2h-code-linenumber",
contentClass: "d2h-code-line"
});
};
LineByLinePrinter.prototype._generateFileHtml = function(file) {
const that = this;
return file.blocks
.map(function(block) {
let lines = that.makeColumnLineNumberHtml(block);
let oldLines = [];
let newLines = [];
function processChangeBlock() {
let matches;
let insertType;
let deleteType;
const comparisons = oldLines.length * newLines.length;
const maxLineSizeInBlock = Math.max.apply(
null,
[0].concat(
oldLines.concat(newLines).map(function(elem) {
return elem.content.length;
})
)
);
const doMatching =
comparisons < that.config.matchingMaxComparisons &&
maxLineSizeInBlock < that.config.maxLineSizeInBlockForComparison &&
(that.config.matching === "lines" || that.config.matching === "words");
if (doMatching) {
matches = matcher(oldLines, newLines);
insertType = diffParser.LINE_TYPE.INSERT_CHANGES;
deleteType = diffParser.LINE_TYPE.DELETE_CHANGES;
} else {
matches = [[oldLines, newLines]];
insertType = diffParser.LINE_TYPE.INSERTS;
deleteType = diffParser.LINE_TYPE.DELETES;
}
matches.forEach(function(match) {
oldLines = match[0];
newLines = match[1];
let processedOldLines = [];
let processedNewLines = [];
const common = Math.min(oldLines.length, newLines.length);
let oldLine, newLine;
for (let j = 0; j < common; j++) {
oldLine = oldLines[j];
newLine = newLines[j];
that.config.isCombined = file.isCombined;
const diff = printerUtils.diffHighlight(oldLine.content, newLine.content, that.config);
processedOldLines += that.makeLineHtml(
file.isCombined,
deleteType,
oldLine.oldNumber,
oldLine.newNumber,
diff.first.line,
diff.first.prefix
);
processedNewLines += that.makeLineHtml(
file.isCombined,
insertType,
newLine.oldNumber,
newLine.newNumber,
diff.second.line,
diff.second.prefix
);
}
lines += processedOldLines + processedNewLines;
lines += that._processLines(file.isCombined, oldLines.slice(common), newLines.slice(common));
});
oldLines = [];
newLines = [];
}
for (let i = 0; i < block.lines.length; i++) {
const line = block.lines[i];
const escapedLine = utils.escape(line.content);
if (
line.type !== diffParser.LINE_TYPE.INSERTS &&
(newLines.length > 0 || (line.type !== diffParser.LINE_TYPE.DELETES && oldLines.length > 0))
) {
processChangeBlock();
}
if (line.type === diffParser.LINE_TYPE.CONTEXT) {
lines += that.makeLineHtml(file.isCombined, line.type, line.oldNumber, line.newNumber, escapedLine);
} else if (line.type === diffParser.LINE_TYPE.INSERTS && !oldLines.length) {
lines += that.makeLineHtml(file.isCombined, line.type, line.oldNumber, line.newNumber, escapedLine);
} else if (line.type === diffParser.LINE_TYPE.DELETES) {
oldLines.push(line);
} else if (line.type === diffParser.LINE_TYPE.INSERTS && Boolean(oldLines.length)) {
newLines.push(line);
} else {
console.error("Unknown state in html line-by-line generator");
processChangeBlock();
}
}
processChangeBlock();
return lines;
})
.join("\n");
};
LineByLinePrinter.prototype._processLines = function(isCombined, oldLines, newLines) {
let lines = "";
for (let i = 0; i < oldLines.length; i++) {
const oldLine = oldLines[i];
const oldEscapedLine = utils.escape(oldLine.content);
lines += this.makeLineHtml(isCombined, oldLine.type, oldLine.oldNumber, oldLine.newNumber, oldEscapedLine);
}
for (let j = 0; j < newLines.length; j++) {
const newLine = newLines[j];
const newEscapedLine = utils.escape(newLine.content);
lines += this.makeLineHtml(isCombined, newLine.type, newLine.oldNumber, newLine.newNumber, newEscapedLine);
}
return lines;
};
LineByLinePrinter.prototype.makeLineHtml = function(isCombined, type, oldNumber, newNumber, content, possiblePrefix) {
const lineNumberTemplate = hoganUtils.render(baseTemplatesPath, "numbers", {
oldNumber: utils.valueOrEmpty(oldNumber),
newNumber: utils.valueOrEmpty(newNumber)
});
let lineWithoutPrefix = content;
let prefix = possiblePrefix;
if (!prefix) {
const lineWithPrefix = printerUtils.separatePrefix(isCombined, content);
prefix = lineWithPrefix.prefix;
lineWithoutPrefix = lineWithPrefix.line;
}
if (prefix === " ") {
prefix = "&nbsp;";
}
return hoganUtils.render(genericTemplatesPath, "line", {
type: type,
lineClass: "d2h-code-linenumber",
contentClass: "d2h-code-line",
prefix: prefix,
content: lineWithoutPrefix,
lineNumber: lineNumberTemplate
});
};
LineByLinePrinter.prototype._generateEmptyDiff = function() {
return hoganUtils.render(genericTemplatesPath, "empty-diff", {
contentClass: "d2h-code-line",
diffParser: diffParser
});
};
module.exports.LineByLinePrinter = LineByLinePrinter;
})();

View file

@ -0,0 +1,290 @@
import * as utils from "./utils";
import HoganJsUtils from "./hoganjs-utils";
import * as Rematch from "./rematch";
import * as renderUtils from "./render-utils";
export interface LineByLineRendererConfig extends renderUtils.RenderConfig {
renderNothingWhenEmpty?: boolean;
matchingMaxComparisons?: number;
maxLineSizeInBlockForComparison?: number;
}
export const defaultLineByLineRendererConfig = {
renderNothingWhenEmpty: false,
matchingMaxComparisons: 2500,
maxLineSizeInBlockForComparison: 200,
...renderUtils.defaultRenderConfig
};
const genericTemplatesPath = "generic";
const baseTemplatesPath = "line-by-line";
const iconsBaseTemplatesPath = "icon";
const tagsBaseTemplatesPath = "tag";
export default class LineByLineRenderer {
private readonly hoganUtils: HoganJsUtils;
private readonly config: typeof defaultLineByLineRendererConfig;
constructor(hoganUtils: HoganJsUtils, config: LineByLineRendererConfig) {
this.hoganUtils = hoganUtils;
this.config = { ...defaultLineByLineRendererConfig, ...config };
}
render(diffFiles: renderUtils.DiffFile[]): string | undefined {
const htmlDiffs = diffFiles.map(file => {
let diffs;
if (file.blocks.length) {
diffs = this.generateFileHtml(file);
} else {
diffs = this.generateEmptyDiff();
}
return this.makeFileDiffHtml(file, diffs);
});
return this.makeLineByLineHtmlWrapper(htmlDiffs.join("\n"));
}
// TODO: Make this private after improving tests
makeFileDiffHtml(file: renderUtils.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");
const filePathTemplate = this.hoganUtils.template(genericTemplatesPath, "file-path");
const fileIconTemplate = this.hoganUtils.template(iconsBaseTemplatesPath, "file");
const fileTagTemplate = this.hoganUtils.template(tagsBaseTemplatesPath, renderUtils.getFileIcon(file));
return fileDiffTemplate.render({
file: file,
fileHtmlId: renderUtils.getHtmlId(file),
diffs: diffs,
filePath: filePathTemplate.render(
{
fileDiffName: renderUtils.filenameDiff(file)
},
{
fileIcon: fileIconTemplate,
fileTag: fileTagTemplate
}
)
});
}
// TODO: Make this private after improving tests
makeLineByLineHtmlWrapper(content: string): string {
return this.hoganUtils.render(genericTemplatesPath, "wrapper", { content: content });
}
// TODO: Make this private after improving tests
makeColumnLineNumberHtml(block: renderUtils.DiffBlock): string {
return this.hoganUtils.render(genericTemplatesPath, "column-line-number", {
CSSLineClass: renderUtils.CSSLineClass,
blockHeader: utils.escapeForHtml(block.header),
lineClass: "d2h-code-linenumber",
contentClass: "d2h-code-line"
});
}
// TODO: Make this private after improving tests
makeLineHtml(
isCombined: boolean,
type: renderUtils.CSSLineClass,
content: string,
oldNumber?: number,
newNumber?: number,
possiblePrefix?: string
): string {
const lineNumberTemplate = this.hoganUtils.render(baseTemplatesPath, "numbers", {
oldNumber: oldNumber || "",
newNumber: newNumber || ""
});
let lineWithoutPrefix = content;
let prefix = possiblePrefix;
if (!prefix) {
const lineWithPrefix = renderUtils.deconstructLine(content, isCombined);
prefix = lineWithPrefix.prefix;
lineWithoutPrefix = lineWithPrefix.line;
}
if (prefix === " ") {
prefix = "&nbsp;";
}
return this.hoganUtils.render(genericTemplatesPath, "line", {
type: type,
lineClass: "d2h-code-linenumber",
contentClass: "d2h-code-line",
prefix: prefix,
content: lineWithoutPrefix,
lineNumber: lineNumberTemplate
});
}
// TODO: Make this private after improving tests
generateEmptyDiff(): string {
return this.hoganUtils.render(genericTemplatesPath, "empty-diff", {
contentClass: "d2h-code-line",
CSSLineClass: renderUtils.CSSLineClass
});
}
// TODO: Make this private after improving tests
processLines(isCombined: boolean, oldLines: renderUtils.DiffLine[], newLines: renderUtils.DiffLine[]): string {
let lines = "";
for (let i = 0; i < oldLines.length; i++) {
const oldLine = oldLines[i];
const oldEscapedLine = utils.escapeForHtml(oldLine.content);
lines += this.makeLineHtml(
isCombined,
renderUtils.toCSSClass(oldLine.type),
oldEscapedLine,
oldLine.oldNumber,
oldLine.newNumber
);
}
for (let j = 0; j < newLines.length; j++) {
const newLine = newLines[j];
const newEscapedLine = utils.escapeForHtml(newLine.content);
lines += this.makeLineHtml(
isCombined,
renderUtils.toCSSClass(newLine.type),
newEscapedLine,
newLine.oldNumber,
newLine.newNumber
);
}
return lines;
}
// 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));
const matcher = Rematch.newMatcherFn(distance);
return file.blocks
.map(block => {
let lines = this.makeColumnLineNumberHtml(block);
let oldLines: renderUtils.DiffLine[] = [];
let newLines: renderUtils.DiffLine[] = [];
const processChangeBlock = (): void => {
let matches;
let insertType: renderUtils.CSSLineClass;
let deleteType: renderUtils.CSSLineClass;
const comparisons = oldLines.length * newLines.length;
const maxLineSizeInBlock = Math.max.apply(
null,
[0].concat(oldLines.concat(newLines).map(elem => elem.content.length))
);
const doMatching =
comparisons < this.config.matchingMaxComparisons &&
maxLineSizeInBlock < this.config.maxLineSizeInBlockForComparison &&
(this.config.matching === "lines" || this.config.matching === "words");
if (doMatching) {
matches = matcher(oldLines, newLines);
insertType = renderUtils.CSSLineClass.INSERT_CHANGES;
deleteType = renderUtils.CSSLineClass.DELETE_CHANGES;
} else {
matches = [[oldLines, newLines]];
insertType = renderUtils.CSSLineClass.INSERTS;
deleteType = renderUtils.CSSLineClass.DELETES;
}
matches.forEach(match => {
oldLines = match[0];
newLines = match[1];
let processedOldLines = "";
let processedNewLines = "";
const common = Math.min(oldLines.length, newLines.length);
let oldLine, newLine;
for (let j = 0; j < common; j++) {
oldLine = oldLines[j];
newLine = newLines[j];
const diff = renderUtils.diffHighlight(oldLine.content, newLine.content, file.isCombined, this.config);
processedOldLines += this.makeLineHtml(
file.isCombined,
deleteType,
diff.oldLine.content,
oldLine.oldNumber,
oldLine.newNumber,
diff.oldLine.prefix
);
processedNewLines += this.makeLineHtml(
file.isCombined,
insertType,
diff.newLine.content,
newLine.oldNumber,
newLine.newNumber,
diff.newLine.prefix
);
}
lines += processedOldLines + processedNewLines;
lines += this.processLines(file.isCombined, oldLines.slice(common), newLines.slice(common));
});
oldLines = [];
newLines = [];
};
for (let i = 0; i < block.lines.length; i++) {
const diffLine = block.lines[i];
const { prefix, 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))
) {
processChangeBlock();
}
if (diffLine.type === renderUtils.LineType.CONTEXT) {
lines += this.makeLineHtml(
file.isCombined,
renderUtils.toCSSClass(diffLine.type),
escapedLine,
diffLine.oldNumber,
diffLine.newNumber,
prefix
);
} else if (diffLine.type === renderUtils.LineType.INSERT && !oldLines.length) {
lines += this.makeLineHtml(
file.isCombined,
renderUtils.toCSSClass(diffLine.type),
escapedLine,
diffLine.oldNumber,
diffLine.newNumber,
prefix
);
} else if (diffLine.type === renderUtils.LineType.DELETE) {
oldLines.push(diffLine);
} else if (diffLine.type === renderUtils.LineType.INSERT && Boolean(oldLines.length)) {
newLines.push(diffLine);
} else {
console.error("Unknown state in html line-by-line generator");
processChangeBlock();
}
}
processChangeBlock();
return lines;
})
.join("\n");
}
}

View file

@ -1,264 +0,0 @@
/*
*
* PrinterUtils (printer-utils.js)
* Author: rtfpessoa
*
*/
(function() {
const jsDiff = require("diff");
const utils = require("./utils.js").Utils;
const Rematch = require("./rematch.js").Rematch;
const separator = "/";
function PrinterUtils() {}
PrinterUtils.prototype.separatePrefix = function(isCombined, line) {
let prefix;
let lineWithoutPrefix;
if (isCombined) {
prefix = line.substring(0, 2);
lineWithoutPrefix = line.substring(2);
} else {
prefix = line.substring(0, 1);
lineWithoutPrefix = line.substring(1);
}
return {
prefix: prefix,
line: lineWithoutPrefix
};
};
PrinterUtils.prototype.getHtmlId = function(file) {
const hashCode = function(text) {
let i, chr, len;
let hash = 0;
for (i = 0, len = text.length; i < len; i++) {
chr = text.charCodeAt(i);
hash = (hash << 5) - hash + chr;
hash |= 0; // Convert to 32bit integer
}
return hash;
};
return (
"d2h-" +
hashCode(this.getDiffName(file))
.toString()
.slice(-6)
);
};
PrinterUtils.prototype.getDiffName = function(file) {
const oldFilename = unifyPath(file.oldName);
const newFilename = unifyPath(file.newName);
if (
oldFilename &&
newFilename &&
oldFilename !== newFilename &&
!isDevNullName(oldFilename) &&
!isDevNullName(newFilename)
) {
const prefixPaths = [];
const suffixPaths = [];
const oldFilenameParts = oldFilename.split(separator);
const newFilenameParts = newFilename.split(separator);
const oldFilenamePartsSize = oldFilenameParts.length;
const newFilenamePartsSize = newFilenameParts.length;
let i = 0;
let j = oldFilenamePartsSize - 1;
let k = newFilenamePartsSize - 1;
while (i < j && i < k) {
if (oldFilenameParts[i] === newFilenameParts[i]) {
prefixPaths.push(newFilenameParts[i]);
i += 1;
} else {
break;
}
}
while (j > i && k > i) {
if (oldFilenameParts[j] === newFilenameParts[k]) {
suffixPaths.unshift(newFilenameParts[k]);
j -= 1;
k -= 1;
} else {
break;
}
}
const finalPrefix = prefixPaths.join(separator);
const finalSuffix = suffixPaths.join(separator);
const oldRemainingPath = oldFilenameParts.slice(i, j + 1).join(separator);
const newRemainingPath = newFilenameParts.slice(i, k + 1).join(separator);
if (finalPrefix.length && finalSuffix.length) {
return (
finalPrefix + separator + "{" + oldRemainingPath + " → " + newRemainingPath + "}" + separator + finalSuffix
);
} else if (finalPrefix.length) {
return finalPrefix + separator + "{" + oldRemainingPath + " → " + newRemainingPath + "}";
} else if (finalSuffix.length) {
return "{" + oldRemainingPath + " → " + newRemainingPath + "}" + separator + finalSuffix;
}
return oldFilename + " → " + newFilename;
} else if (newFilename && !isDevNullName(newFilename)) {
return newFilename;
} else if (oldFilename) {
return oldFilename;
}
return "unknown/file/path";
};
PrinterUtils.prototype.getFileTypeIcon = function(file) {
let templateName = "file-changed";
if (file.isRename) {
templateName = "file-renamed";
} else if (file.isCopy) {
templateName = "file-renamed";
} else if (file.isNew) {
templateName = "file-added";
} else if (file.isDeleted) {
templateName = "file-deleted";
} else if (file.newName !== file.oldName) {
// If file is not Added, not Deleted and the names changed it must be a rename :)
templateName = "file-renamed";
}
return templateName;
};
PrinterUtils.prototype.diffHighlight = function(diffLine1, diffLine2, config) {
let linePrefix1, linePrefix2, unprefixedLine1, unprefixedLine2;
let prefixSize = 1;
if (config.isCombined) {
prefixSize = 2;
}
linePrefix1 = diffLine1.substr(0, prefixSize);
linePrefix2 = diffLine2.substr(0, prefixSize);
unprefixedLine1 = diffLine1.substr(prefixSize);
unprefixedLine2 = diffLine2.substr(prefixSize);
if (
unprefixedLine1.length > config.maxLineLengthHighlight ||
unprefixedLine2.length > config.maxLineLengthHighlight
) {
return {
first: {
prefix: linePrefix1,
line: utils.escape(unprefixedLine1)
},
second: {
prefix: linePrefix2,
line: utils.escape(unprefixedLine2)
}
};
}
let diff;
if (config.diffStyle === "char") {
diff = jsDiff.diffChars(unprefixedLine1, unprefixedLine2);
} else {
diff = jsDiff.diffWordsWithSpace(unprefixedLine1, unprefixedLine2);
}
let highlightedLine = "";
const changedWords = [];
if (config.diffStyle === "word" && config.matching === "words") {
let treshold = 0.25;
if (typeof config.matchWordsThreshold !== "undefined") {
treshold = config.matchWordsThreshold;
}
const matcher = Rematch.rematch(function(a, b) {
const amod = a.value;
const bmod = b.value;
return Rematch.distance(amod, bmod);
});
const removed = diff.filter(function isRemoved(element) {
return element.removed;
});
const added = diff.filter(function isAdded(element) {
return element.added;
});
const chunks = matcher(added, removed);
chunks.forEach(function(chunk) {
if (chunk[0].length === 1 && chunk[1].length === 1) {
const dist = Rematch.distance(chunk[0][0].value, chunk[1][0].value);
if (dist < treshold) {
changedWords.push(chunk[0][0]);
changedWords.push(chunk[1][0]);
}
}
});
}
diff.forEach(function(part) {
const addClass = changedWords.indexOf(part) > -1 ? ' class="d2h-change"' : "";
const elemType = part.added ? "ins" : part.removed ? "del" : null;
const escapedValue = utils.escape(part.value);
if (elemType !== null) {
highlightedLine += "<" + elemType + addClass + ">" + escapedValue + "</" + elemType + ">";
} else {
highlightedLine += escapedValue;
}
});
return {
first: {
prefix: linePrefix1,
line: removeIns(highlightedLine)
},
second: {
prefix: linePrefix2,
line: removeDel(highlightedLine)
}
};
};
function unifyPath(path) {
if (path) {
return path.replace("\\", "/");
}
return path;
}
function isDevNullName(name) {
return name.indexOf("dev/null") !== -1;
}
function removeIns(line) {
return line.replace(/(<ins[^>]*>((.|\n)*?)<\/ins>)/g, "");
}
function removeDel(line) {
return line.replace(/(<del[^>]*>((.|\n)*?)<\/del>)/g, "");
}
module.exports.PrinterUtils = new PrinterUtils();
})();

View file

@ -1,145 +0,0 @@
/*
*
* Rematch (rematch.js)
* Matching two sequences of objects by similarity
* Author: W. Illmeyer, Nexxar GmbH
*
*/
(function() {
const Rematch = {};
/*
Copyright (c) 2011 Andrei Mackenzie
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
function levenshtein(a, b) {
if (a.length === 0) {
return b.length;
}
if (b.length === 0) {
return a.length;
}
const matrix = [];
// Increment along the first column of each row
let i;
for (i = 0; i <= b.length; i++) {
matrix[i] = [i];
}
// Increment each column in the first row
let j;
for (j = 0; j <= a.length; j++) {
matrix[0][j] = j;
}
// Fill in the rest of the matrix
for (i = 1; i <= b.length; i++) {
for (j = 1; j <= a.length; j++) {
if (b.charAt(i - 1) === a.charAt(j - 1)) {
matrix[i][j] = matrix[i - 1][j - 1];
} else {
matrix[i][j] = Math.min(
matrix[i - 1][j - 1] + 1, // Substitution
Math.min(
matrix[i][j - 1] + 1, // Insertion
matrix[i - 1][j] + 1
)
); // Deletion
}
}
}
return matrix[b.length][a.length];
}
Rematch.levenshtein = levenshtein;
Rematch.distance = function distance(x, y) {
x = x.trim();
y = y.trim();
const lev = levenshtein(x, y);
const score = lev / (x.length + y.length);
return score;
};
Rematch.rematch = function rematch(distanceFunction) {
function findBestMatch(a, b, cache) {
let bestMatchDist = Infinity;
let bestMatch;
for (let i = 0; i < a.length; ++i) {
for (let j = 0; j < b.length; ++j) {
const cacheKey = JSON.stringify([a[i], b[j]]);
var md;
if (cache.hasOwnProperty(cacheKey)) {
md = cache[cacheKey];
} else {
md = distanceFunction(a[i], b[j]);
cache[cacheKey] = md;
}
if (md < bestMatchDist) {
bestMatchDist = md;
bestMatch = { indexA: i, indexB: j, score: bestMatchDist };
}
}
}
return bestMatch;
}
function group(a, b, level, cache) {
if (typeof cache === "undefined") {
cache = {};
}
const bm = findBestMatch(a, b, cache);
if (!level) {
level = 0;
}
if (!bm || a.length + b.length < 3) {
return [[a, b]];
}
const a1 = a.slice(0, bm.indexA);
const b1 = b.slice(0, bm.indexB);
const aMatch = [a[bm.indexA]];
const bMatch = [b[bm.indexB]];
const tailA = bm.indexA + 1;
const tailB = bm.indexB + 1;
const a2 = a.slice(tailA);
const b2 = b.slice(tailB);
const group1 = group(a1, b1, level + 1, cache);
const groupMatch = group(aMatch, bMatch, level + 1, cache);
const group2 = group(a2, b2, level + 1, cache);
let result = groupMatch;
if (bm.indexA > 0 || bm.indexB > 0) {
result = group1.concat(result);
}
if (a.length > tailA || b.length > tailB) {
result = result.concat(group2);
}
return result;
}
return group;
};
module.exports.Rematch = Rematch;
})();

137
src/rematch.ts Normal file
View file

@ -0,0 +1,137 @@
/*
* Matching two sequences of objects by similarity
* Author: W. Illmeyer, Nexxar GmbH
*/
export type BestMatch = {
indexA: number;
indexB: number;
score: number;
};
/*
Copyright (c) 2011 Andrei Mackenzie
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
export function levenshtein(a: string, b: string): number {
if (a.length === 0) {
return b.length;
}
if (b.length === 0) {
return a.length;
}
const matrix = [];
// Increment along the first column of each row
let i;
for (i = 0; i <= b.length; i++) {
matrix[i] = [i];
}
// Increment each column in the first row
let j;
for (j = 0; j <= a.length; j++) {
matrix[0][j] = j;
}
// Fill in the rest of the matrix
for (i = 1; i <= b.length; i++) {
for (j = 1; j <= a.length; j++) {
if (b.charAt(i - 1) === a.charAt(j - 1)) {
matrix[i][j] = matrix[i - 1][j - 1];
} else {
matrix[i][j] = Math.min(
matrix[i - 1][j - 1] + 1, // Substitution
Math.min(
matrix[i][j - 1] + 1, // Insertion
matrix[i - 1][j] + 1
)
); // Deletion
}
}
}
return matrix[b.length][a.length];
}
export type DistanceFn<T> = (x: T, y: T) => number;
export function newDistanceFn<T>(str: (value: T) => string): DistanceFn<T> {
return (x: T, y: T): number => {
const xValue = str(x).trim();
const yValue = str(y).trim();
const lev = levenshtein(xValue, yValue);
const score = lev / (xValue.length + yValue.length);
return score;
};
}
export type MatcherFn<T> = (a: T[], b: T[], level?: number, cache?: Map<string, number>) => T[][][];
export function newMatcherFn<T>(distance: (x: T, y: T) => number): MatcherFn<T> {
function findBestMatch(a: T[], b: T[], cache: Map<string, number> = new Map()): BestMatch | undefined {
let bestMatchDist = Infinity;
let bestMatch;
for (let i = 0; i < a.length; ++i) {
for (let j = 0; j < b.length; ++j) {
const cacheKey = JSON.stringify([a[i], b[j]]);
let md;
if (!(cache.has(cacheKey) && (md = cache.get(cacheKey)))) {
md = distance(a[i], b[j]);
cache.set(cacheKey, md);
}
if (md < bestMatchDist) {
bestMatchDist = md;
bestMatch = { indexA: i, indexB: j, score: bestMatchDist };
}
}
}
return bestMatch;
}
function group(a: T[], b: T[], level = 0, cache: Map<string, number> = new Map()): T[][][] {
const bm = findBestMatch(a, b, cache);
if (!bm || a.length + b.length < 3) {
return [[a, b]];
}
const a1 = a.slice(0, bm.indexA);
const b1 = b.slice(0, bm.indexB);
const aMatch = [a[bm.indexA]];
const bMatch = [b[bm.indexB]];
const tailA = bm.indexA + 1;
const tailB = bm.indexB + 1;
const a2 = a.slice(tailA);
const b2 = b.slice(tailB);
const group1 = group(a1, b1, level + 1, cache);
const groupMatch = group(aMatch, bMatch, level + 1, cache);
const group2 = group(a2, b2, level + 1, cache);
let result = groupMatch;
if (bm.indexA > 0 || bm.indexB > 0) {
result = group1.concat(result);
}
if (a.length > tailA || b.length > tailB) {
result = result.concat(group2);
}
return result;
}
return group;
}

343
src/render-utils.ts Normal file
View file

@ -0,0 +1,343 @@
import * as jsDiff from "diff";
import { unifyPath, escapeForHtml, hashCode } from "./utils";
import * as rematch from "./rematch";
export type DiffLineParts = {
prefix: string;
line: string;
};
export enum CSSLineClass {
INSERTS = "d2h-ins",
DELETES = "d2h-del",
CONTEXT = "d2h-cntx",
INFO = "d2h-info",
INSERT_CHANGES = "d2h-ins d2h-change",
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 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;
maxLineLengthHighlight?: number;
diffStyle?: DiffStyleType;
}
export const defaultRenderConfig = {
matching: "none" as LineMatchingType,
matchWordsThreshold: 0.25,
maxLineLengthHighlight: 10000,
diffStyle: "word" as DiffStyleType
};
type HighlightedLines = {
oldLine: {
prefix: string;
content: string;
};
newLine: {
prefix: string;
content: string;
};
};
const separator = "/";
const distance = rematch.newDistanceFn((change: jsDiff.Change) => change.value);
const matcher = rematch.newMatcherFn(distance);
function isDevNullName(name: string): boolean {
return name.indexOf("dev/null") !== -1;
}
function removeInsElements(line: string): string {
return line.replace(/(<ins[^>]*>((.|\n)*?)<\/ins>)/g, "");
}
function removeDelElements(line: string): string {
return line.replace(/(<del[^>]*>((.|\n)*?)<\/del>)/g, "");
}
/**
* Convert from LineType to CSSLineClass
*/
export function toCSSClass(lineType: LineType): CSSLineClass {
switch (lineType) {
case LineType.CONTEXT:
return CSSLineClass.CONTEXT;
case LineType.INSERT:
return CSSLineClass.INSERTS;
case LineType.DELETE:
return CSSLineClass.DELETES;
}
}
/**
* Prefix length of the hunk lines in the diff
*/
export function prefixLength(isCombined: boolean): number {
return isCombined ? 2 : 1;
}
/**
* Deconstructs diff @line by separating the content from the prefix type
*/
export function deconstructLine(line: string, isCombined: boolean): DiffLineParts {
const indexToSplit = prefixLength(isCombined);
return {
prefix: line.substring(0, indexToSplit),
line: line.substring(indexToSplit)
};
}
/**
* Generates pretty filename diffs
*
* e.g.:
* 1. file = { oldName: "my/path/to/file.js", newName: "my/path/to/new-file.js" }
* returns "my/path/to/{file.js → new-file.js}"
* 2. file = { oldName: "my/path/to/file.js", newName: "very/new/path/to/new-file.js" }
* returns "my/path/to/file.js → very/new/path/to/new-file.js"
* 3. file = { oldName: "my/path/to/file.js", newName: "my/path/for/file.js" }
* returns "my/path/{to → for}/file.js"
*/
export function filenameDiff(file: DiffFileName): string {
// TODO: Review this huuuuuge piece of code, do we need this?
// TODO: Move unify path to parsing
const oldFilename = unifyPath(file.oldName);
const newFilename = unifyPath(file.newName);
if (oldFilename !== newFilename && !isDevNullName(oldFilename) && !isDevNullName(newFilename)) {
const prefixPaths = [];
const suffixPaths = [];
const oldFilenameParts = oldFilename.split(separator);
const newFilenameParts = newFilename.split(separator);
const oldFilenamePartsSize = oldFilenameParts.length;
const newFilenamePartsSize = newFilenameParts.length;
let i = 0;
let j = oldFilenamePartsSize - 1;
let k = newFilenamePartsSize - 1;
while (i < j && i < k) {
if (oldFilenameParts[i] === newFilenameParts[i]) {
prefixPaths.push(newFilenameParts[i]);
i += 1;
} else {
break;
}
}
while (j > i && k > i) {
if (oldFilenameParts[j] === newFilenameParts[k]) {
suffixPaths.unshift(newFilenameParts[k]);
j -= 1;
k -= 1;
} else {
break;
}
}
const finalPrefix = prefixPaths.join(separator);
const finalSuffix = suffixPaths.join(separator);
const oldRemainingPath = oldFilenameParts.slice(i, j + 1).join(separator);
const newRemainingPath = newFilenameParts.slice(i, k + 1).join(separator);
if (finalPrefix.length && finalSuffix.length) {
return (
finalPrefix + separator + "{" + oldRemainingPath + " → " + newRemainingPath + "}" + separator + finalSuffix
);
} else if (finalPrefix.length) {
return finalPrefix + separator + "{" + oldRemainingPath + " → " + newRemainingPath + "}";
} else if (finalSuffix.length) {
return "{" + oldRemainingPath + " → " + newRemainingPath + "}" + separator + finalSuffix;
}
return oldFilename + " → " + newFilename;
} else if (!isDevNullName(newFilename)) {
return newFilename;
} else {
return oldFilename;
}
}
/**
* Generates a unique string numerical identifier based on the names of the file diff
*/
export function getHtmlId(file: DiffFileName): string {
return `d2h-${hashCode(filenameDiff(file))
.toString()
.slice(-6)}`;
}
/**
* Selects the correct icon name for the file
*/
export function getFileIcon(file: DiffFile): string {
let templateName = "file-changed";
if (file.isRename) {
templateName = "file-renamed";
} else if (file.isCopy) {
templateName = "file-renamed";
} else if (file.isNew) {
templateName = "file-added";
} else if (file.isDeleted) {
templateName = "file-deleted";
} else if (file.newName !== file.oldName) {
// If file is not Added, not Deleted and the names changed it must be a rename :)
templateName = "file-renamed";
}
return templateName;
}
/**
* Generates a unique string numerical identifier based on the names of the file diff
*/
export function diffHighlight(
diffLine1: string,
diffLine2: string,
isCombined: boolean,
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 linePrefix2 = diffLine2.substr(0, prefixLengthVal);
const unprefixedLine2 = diffLine2.substr(prefixLengthVal);
if (unprefixedLine1.length > maxLineLengthHighlight || unprefixedLine2.length > maxLineLengthHighlight) {
return {
oldLine: {
prefix: linePrefix1,
content: escapeForHtml(unprefixedLine1)
},
newLine: {
prefix: linePrefix2,
content: escapeForHtml(unprefixedLine2)
}
};
}
const diff =
diffStyle === "char"
? jsDiff.diffChars(unprefixedLine1, unprefixedLine2)
: jsDiff.diffWordsWithSpace(unprefixedLine1, unprefixedLine2);
const changedWords: jsDiff.Change[] = [];
if (diffStyle === "word" && matching === "words") {
let treshold = 0.25;
if (typeof matchWordsThreshold !== "undefined") {
treshold = matchWordsThreshold;
}
const removed = diff.filter(element => element.removed);
const added = diff.filter(element => element.added);
const chunks = matcher(added, removed);
chunks.forEach(chunk => {
if (chunk[0].length === 1 && chunk[1].length === 1) {
const dist = distance(chunk[0][0], chunk[1][0]);
if (dist < treshold) {
changedWords.push(chunk[0][0]);
changedWords.push(chunk[1][0]);
}
}
});
}
const highlightedLine = diff.reduce((highlightedLine, part) => {
const elemType = part.added ? "ins" : part.removed ? "del" : null;
const addClass = changedWords.indexOf(part) > -1 ? ' class="d2h-change"' : "";
const escapedValue = escapeForHtml(part.value);
return elemType !== null
? `${highlightedLine}<${elemType}${addClass}>${escapedValue}</${elemType}>`
: `${highlightedLine}${escapedValue}`;
}, "");
return {
oldLine: {
prefix: linePrefix1,
content: removeInsElements(highlightedLine)
},
newLine: {
prefix: linePrefix2,
content: removeDelElements(highlightedLine)
}
};
}

View file

@ -1,329 +0,0 @@
/*
*
* HtmlPrinter (html-printer.js)
* Author: rtfpessoa
*
*/
(function() {
const diffParser = require("./diff-parser.js").DiffParser;
const printerUtils = require("./printer-utils.js").PrinterUtils;
const utils = require("./utils.js").Utils;
const Rematch = require("./rematch.js").Rematch;
let hoganUtils;
const genericTemplatesPath = "generic";
const baseTemplatesPath = "side-by-side";
const iconsBaseTemplatesPath = "icon";
const tagsBaseTemplatesPath = "tag";
const matcher = Rematch.rematch(function(a, b) {
const amod = a.content.substr(1);
const bmod = b.content.substr(1);
return Rematch.distance(amod, bmod);
});
function SideBySidePrinter(config) {
this.config = config;
const HoganJsUtils = require("./hoganjs-utils.js").HoganJsUtils;
hoganUtils = new HoganJsUtils(config);
}
SideBySidePrinter.prototype.makeDiffHtml = function(file, diffs) {
const fileDiffTemplate = hoganUtils.template(baseTemplatesPath, "file-diff");
const filePathTemplate = hoganUtils.template(genericTemplatesPath, "file-path");
const fileIconTemplate = hoganUtils.template(iconsBaseTemplatesPath, "file");
const fileTagTemplate = hoganUtils.template(tagsBaseTemplatesPath, printerUtils.getFileTypeIcon(file));
return fileDiffTemplate.render({
file: file,
fileHtmlId: printerUtils.getHtmlId(file),
diffs: diffs,
filePath: filePathTemplate.render(
{
fileDiffName: printerUtils.getDiffName(file)
},
{
fileIcon: fileIconTemplate,
fileTag: fileTagTemplate
}
)
});
};
SideBySidePrinter.prototype.generateSideBySideJsonHtml = function(diffFiles) {
const that = this;
const content = diffFiles
.map(function(file) {
let diffs;
if (file.blocks.length) {
diffs = that.generateSideBySideFileHtml(file);
} else {
diffs = that.generateEmptyDiff();
}
return that.makeDiffHtml(file, diffs);
})
.join("\n");
return hoganUtils.render(genericTemplatesPath, "wrapper", { content: content });
};
SideBySidePrinter.prototype.makeSideHtml = function(blockHeader) {
return hoganUtils.render(genericTemplatesPath, "column-line-number", {
diffParser: diffParser,
blockHeader: utils.escape(blockHeader),
lineClass: "d2h-code-side-linenumber",
contentClass: "d2h-code-side-line"
});
};
SideBySidePrinter.prototype.generateSideBySideFileHtml = function(file) {
const that = this;
const fileHtml = {};
fileHtml.left = "";
fileHtml.right = "";
file.blocks.forEach(function(block) {
fileHtml.left += that.makeSideHtml(block.header);
fileHtml.right += that.makeSideHtml("");
let oldLines = [];
let newLines = [];
function processChangeBlock() {
let matches;
let insertType;
let deleteType;
const comparisons = oldLines.length * newLines.length;
const maxLineSizeInBlock = Math.max.apply(
null,
oldLines.concat(newLines).map(function(elem) {
return elem.length;
})
);
const doMatching =
comparisons < that.config.matchingMaxComparisons &&
maxLineSizeInBlock < that.config.maxLineSizeInBlockForComparison &&
(that.config.matching === "lines" || that.config.matching === "words");
if (doMatching) {
matches = matcher(oldLines, newLines);
insertType = diffParser.LINE_TYPE.INSERT_CHANGES;
deleteType = diffParser.LINE_TYPE.DELETE_CHANGES;
} else {
matches = [[oldLines, newLines]];
insertType = diffParser.LINE_TYPE.INSERTS;
deleteType = diffParser.LINE_TYPE.DELETES;
}
matches.forEach(function(match) {
oldLines = match[0];
newLines = match[1];
const common = Math.min(oldLines.length, newLines.length);
const max = Math.max(oldLines.length, newLines.length);
for (let j = 0; j < common; j++) {
const oldLine = oldLines[j];
const newLine = newLines[j];
that.config.isCombined = file.isCombined;
const diff = printerUtils.diffHighlight(oldLine.content, newLine.content, that.config);
fileHtml.left += that.generateSingleLineHtml(
file.isCombined,
deleteType,
oldLine.oldNumber,
diff.first.line,
diff.first.prefix
);
fileHtml.right += that.generateSingleLineHtml(
file.isCombined,
insertType,
newLine.newNumber,
diff.second.line,
diff.second.prefix
);
}
if (max > common) {
const oldSlice = oldLines.slice(common);
const newSlice = newLines.slice(common);
const tmpHtml = that.processLines(file.isCombined, oldSlice, newSlice);
fileHtml.left += tmpHtml.left;
fileHtml.right += tmpHtml.right;
}
});
oldLines = [];
newLines = [];
}
for (let i = 0; i < block.lines.length; i++) {
const line = block.lines[i];
const prefix = line.content[0];
const escapedLine = utils.escape(line.content.substr(1));
if (
line.type !== diffParser.LINE_TYPE.INSERTS &&
(newLines.length > 0 || (line.type !== diffParser.LINE_TYPE.DELETES && oldLines.length > 0))
) {
processChangeBlock();
}
if (line.type === diffParser.LINE_TYPE.CONTEXT) {
fileHtml.left += that.generateSingleLineHtml(file.isCombined, line.type, line.oldNumber, escapedLine, prefix);
fileHtml.right += that.generateSingleLineHtml(
file.isCombined,
line.type,
line.newNumber,
escapedLine,
prefix
);
} else if (line.type === diffParser.LINE_TYPE.INSERTS && !oldLines.length) {
fileHtml.left += that.generateSingleLineHtml(file.isCombined, diffParser.LINE_TYPE.CONTEXT, "", "", "");
fileHtml.right += that.generateSingleLineHtml(
file.isCombined,
line.type,
line.newNumber,
escapedLine,
prefix
);
} else if (line.type === diffParser.LINE_TYPE.DELETES) {
oldLines.push(line);
} else if (line.type === diffParser.LINE_TYPE.INSERTS && Boolean(oldLines.length)) {
newLines.push(line);
} else {
console.error("unknown state in html side-by-side generator");
processChangeBlock();
}
}
processChangeBlock();
});
return fileHtml;
};
SideBySidePrinter.prototype.processLines = function(isCombined, oldLines, newLines) {
const that = this;
const fileHtml = {};
fileHtml.left = "";
fileHtml.right = "";
const maxLinesNumber = Math.max(oldLines.length, newLines.length);
for (let i = 0; i < maxLinesNumber; i++) {
const oldLine = oldLines[i];
const newLine = newLines[i];
var oldContent;
var newContent;
var oldPrefix;
var newPrefix;
if (oldLine) {
oldContent = utils.escape(oldLine.content.substr(1));
oldPrefix = oldLine.content[0];
}
if (newLine) {
newContent = utils.escape(newLine.content.substr(1));
newPrefix = newLine.content[0];
}
if (oldLine && newLine) {
fileHtml.left += that.generateSingleLineHtml(
isCombined,
oldLine.type,
oldLine.oldNumber,
oldContent,
oldPrefix
);
fileHtml.right += that.generateSingleLineHtml(
isCombined,
newLine.type,
newLine.newNumber,
newContent,
newPrefix
);
} else if (oldLine) {
fileHtml.left += that.generateSingleLineHtml(
isCombined,
oldLine.type,
oldLine.oldNumber,
oldContent,
oldPrefix
);
fileHtml.right += that.generateSingleLineHtml(isCombined, diffParser.LINE_TYPE.CONTEXT, "", "", "");
} else if (newLine) {
fileHtml.left += that.generateSingleLineHtml(isCombined, diffParser.LINE_TYPE.CONTEXT, "", "", "");
fileHtml.right += that.generateSingleLineHtml(
isCombined,
newLine.type,
newLine.newNumber,
newContent,
newPrefix
);
} else {
console.error("How did it get here?");
}
}
return fileHtml;
};
SideBySidePrinter.prototype.generateSingleLineHtml = function(isCombined, type, number, content, possiblePrefix) {
let lineWithoutPrefix = content;
let prefix = possiblePrefix;
let lineClass = "d2h-code-side-linenumber";
let contentClass = "d2h-code-side-line";
if (!number && !content) {
lineClass += " d2h-code-side-emptyplaceholder";
contentClass += " d2h-code-side-emptyplaceholder";
type += " d2h-emptyplaceholder";
prefix = "&nbsp;";
lineWithoutPrefix = "&nbsp;";
} else if (!prefix) {
const lineWithPrefix = printerUtils.separatePrefix(isCombined, content);
prefix = lineWithPrefix.prefix;
lineWithoutPrefix = lineWithPrefix.line;
}
if (prefix === " ") {
prefix = "&nbsp;";
}
return hoganUtils.render(genericTemplatesPath, "line", {
type: type,
lineClass: lineClass,
contentClass: contentClass,
prefix: prefix,
content: lineWithoutPrefix,
lineNumber: number
});
};
SideBySidePrinter.prototype.generateEmptyDiff = function() {
const fileHtml = {};
fileHtml.right = "";
fileHtml.left = hoganUtils.render(genericTemplatesPath, "empty-diff", {
contentClass: "d2h-code-side-line",
diffParser: diffParser
});
return fileHtml;
};
module.exports.SideBySidePrinter = SideBySidePrinter;
})();

View file

@ -0,0 +1,352 @@
import * as utils from "./utils";
import HoganJsUtils from "./hoganjs-utils";
import * as Rematch from "./rematch";
import * as renderUtils from "./render-utils";
export interface SideBySideRendererConfig extends renderUtils.RenderConfig {
renderNothingWhenEmpty?: boolean;
matchingMaxComparisons?: number;
maxLineSizeInBlockForComparison?: number;
}
export const defaultSideBySideRendererConfig = {
...renderUtils.defaultRenderConfig,
renderNothingWhenEmpty: false,
matchingMaxComparisons: 2500,
maxLineSizeInBlockForComparison: 200
};
type FileHtml = {
right: string;
left: string;
};
const genericTemplatesPath = "generic";
const baseTemplatesPath = "side-by-side";
const iconsBaseTemplatesPath = "icon";
const tagsBaseTemplatesPath = "tag";
export default class SideBySideRenderer {
private readonly hoganUtils: HoganJsUtils;
private readonly config: typeof defaultSideBySideRendererConfig;
constructor(hoganUtils: HoganJsUtils, config: SideBySideRendererConfig) {
this.hoganUtils = hoganUtils;
this.config = { ...defaultSideBySideRendererConfig, ...config };
}
render(diffFiles: renderUtils.DiffFile[]): string | undefined {
const content = diffFiles
.map(file => {
let diffs;
if (file.blocks.length) {
diffs = this.generateSideBySideFileHtml(file);
} else {
diffs = this.generateEmptyDiff();
}
return this.makeDiffHtml(file, diffs);
})
.join("\n");
return this.hoganUtils.render(genericTemplatesPath, "wrapper", { content: content });
}
// TODO: Make this private after improving tests
generateEmptyDiff(): FileHtml {
return {
right: "",
left:
this.hoganUtils.render(genericTemplatesPath, "empty-diff", {
contentClass: "d2h-code-side-line",
CSSLineClass: renderUtils.CSSLineClass
}) || ""
};
}
// TODO: Make this private after improving tests
makeDiffHtml(file: renderUtils.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");
const fileTagTemplate = this.hoganUtils.template(tagsBaseTemplatesPath, renderUtils.getFileIcon(file));
return fileDiffTemplate.render({
file: file,
fileHtmlId: renderUtils.getHtmlId(file),
diffs: diffs,
filePath: filePathTemplate.render(
{
fileDiffName: renderUtils.filenameDiff(file)
},
{
fileIcon: fileIconTemplate,
fileTag: fileTagTemplate
}
)
});
}
// TODO: Make this private after improving tests
makeSideHtml(blockHeader: string): string {
return this.hoganUtils.render(genericTemplatesPath, "column-line-number", {
CSSLineClass: renderUtils.CSSLineClass,
blockHeader: utils.escapeForHtml(blockHeader),
lineClass: "d2h-code-side-linenumber",
contentClass: "d2h-code-side-line"
});
}
// 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));
const matcher = Rematch.newMatcherFn(distance);
const fileHtml = {
right: "",
left: ""
};
file.blocks.forEach(block => {
fileHtml.left += this.makeSideHtml(block.header);
fileHtml.right += this.makeSideHtml("");
let oldLines: renderUtils.DiffLine[] = [];
let newLines: renderUtils.DiffLine[] = [];
const processChangeBlock = (): void => {
let matches;
let insertType: renderUtils.CSSLineClass;
let deleteType: renderUtils.CSSLineClass;
const comparisons = oldLines.length * newLines.length;
const maxLineSizeInBlock = Math.max.apply(null, oldLines.concat(newLines).map(elem => elem.content.length));
const doMatching =
comparisons < this.config.matchingMaxComparisons &&
maxLineSizeInBlock < this.config.maxLineSizeInBlockForComparison &&
(this.config.matching === "lines" || this.config.matching === "words");
if (doMatching) {
matches = matcher(oldLines, newLines);
insertType = renderUtils.CSSLineClass.INSERT_CHANGES;
deleteType = renderUtils.CSSLineClass.DELETE_CHANGES;
} else {
matches = [[oldLines, newLines]];
insertType = renderUtils.CSSLineClass.INSERTS;
deleteType = renderUtils.CSSLineClass.DELETES;
}
matches.forEach(match => {
oldLines = match[0];
newLines = match[1];
const common = Math.min(oldLines.length, newLines.length);
const max = Math.max(oldLines.length, newLines.length);
for (let j = 0; j < common; j++) {
const oldLine = oldLines[j];
const newLine = newLines[j];
const diff = renderUtils.diffHighlight(oldLine.content, newLine.content, file.isCombined, this.config);
fileHtml.left += this.generateSingleLineHtml(
file.isCombined,
deleteType,
diff.oldLine.content,
oldLine.oldNumber,
diff.oldLine.prefix
);
fileHtml.right += this.generateSingleLineHtml(
file.isCombined,
insertType,
diff.newLine.content,
newLine.newNumber,
diff.newLine.prefix
);
}
if (max > common) {
const oldSlice = oldLines.slice(common);
const newSlice = newLines.slice(common);
const tmpHtml = this.processLines(file.isCombined, oldSlice, newSlice);
fileHtml.left += tmpHtml.left;
fileHtml.right += tmpHtml.right;
}
});
oldLines = [];
newLines = [];
};
for (let i = 0; i < block.lines.length; i++) {
const diffLine = block.lines[i];
const { prefix, 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))
) {
processChangeBlock();
}
if (diffLine.type === renderUtils.LineType.CONTEXT) {
fileHtml.left += this.generateSingleLineHtml(
file.isCombined,
renderUtils.toCSSClass(diffLine.type),
escapedLine,
diffLine.oldNumber,
prefix
);
fileHtml.right += this.generateSingleLineHtml(
file.isCombined,
renderUtils.toCSSClass(diffLine.type),
escapedLine,
diffLine.newNumber,
prefix
);
} else if (diffLine.type === renderUtils.LineType.INSERT && !oldLines.length) {
fileHtml.left += this.generateSingleLineHtml(file.isCombined, renderUtils.CSSLineClass.CONTEXT, "");
fileHtml.right += this.generateSingleLineHtml(
file.isCombined,
renderUtils.toCSSClass(diffLine.type),
escapedLine,
diffLine.newNumber,
prefix
);
} else if (diffLine.type === renderUtils.LineType.DELETE) {
oldLines.push(diffLine);
} else if (diffLine.type === renderUtils.LineType.INSERT && Boolean(oldLines.length)) {
newLines.push(diffLine);
} else {
console.error("unknown state in html side-by-side generator");
processChangeBlock();
}
}
processChangeBlock();
});
return fileHtml;
}
// TODO: Make this private after improving tests
processLines(isCombined: boolean, oldLines: renderUtils.DiffLine[], newLines: renderUtils.DiffLine[]): FileHtml {
const fileHtml = {
right: "",
left: ""
};
const maxLinesNumber = Math.max(oldLines.length, newLines.length);
for (let i = 0; i < maxLinesNumber; i++) {
const oldLine = oldLines[i];
const newLine = newLines[i];
let oldContent;
let newContent;
let oldPrefix;
let newPrefix;
if (oldLine) {
const { prefix, line } = renderUtils.deconstructLine(oldLine.content, isCombined);
oldContent = utils.escapeForHtml(line);
oldPrefix = prefix;
} else {
oldContent = "";
oldPrefix = "";
}
if (newLine) {
const { prefix, line } = renderUtils.deconstructLine(newLine.content, isCombined);
newContent = utils.escapeForHtml(line);
newPrefix = prefix;
} else {
newContent = "";
oldPrefix = "";
}
if (oldLine && newLine) {
fileHtml.left += this.generateSingleLineHtml(
isCombined,
renderUtils.toCSSClass(oldLine.type),
oldContent,
oldLine.oldNumber,
oldPrefix
);
fileHtml.right += this.generateSingleLineHtml(
isCombined,
renderUtils.toCSSClass(newLine.type),
newContent,
newLine.newNumber,
newPrefix
);
} else if (oldLine) {
fileHtml.left += this.generateSingleLineHtml(
isCombined,
renderUtils.toCSSClass(oldLine.type),
oldContent,
oldLine.oldNumber,
oldPrefix
);
fileHtml.right += this.generateSingleLineHtml(isCombined, renderUtils.CSSLineClass.CONTEXT, "");
} else if (newLine) {
fileHtml.left += this.generateSingleLineHtml(isCombined, renderUtils.CSSLineClass.CONTEXT, "");
fileHtml.right += this.generateSingleLineHtml(
isCombined,
renderUtils.toCSSClass(newLine.type),
newContent,
newLine.newNumber,
newPrefix
);
} else {
console.error("How did it get here?");
}
}
return fileHtml;
}
// TODO: Make this private after improving tests
generateSingleLineHtml(
isCombined: boolean,
type: renderUtils.CSSLineClass,
content: string,
number?: number,
possiblePrefix?: string
): string {
let lineWithoutPrefix = content;
let prefix = possiblePrefix;
let lineClass = "d2h-code-side-linenumber";
let contentClass = "d2h-code-side-line";
let preparedType: string = type;
if (!number && !content) {
lineClass += " d2h-code-side-emptyplaceholder";
contentClass += " d2h-code-side-emptyplaceholder";
preparedType += " d2h-emptyplaceholder";
prefix = "&nbsp;";
lineWithoutPrefix = "&nbsp;";
} else if (!prefix) {
const lineWithPrefix = renderUtils.deconstructLine(content, isCombined);
prefix = lineWithPrefix.prefix;
lineWithoutPrefix = lineWithPrefix.line;
}
if (prefix === " ") {
prefix = "&nbsp;";
}
return this.hoganUtils.render(genericTemplatesPath, "line", {
type: preparedType,
lineClass: lineClass,
contentClass: contentClass,
prefix: prefix,
content: lineWithoutPrefix,
lineNumber: number
});
}
}

View file

@ -1,6 +1,6 @@
<tr> <tr>
<td class="{{lineClass}} {{diffParser.LINE_TYPE.INFO}}"></td> <td class="{{lineClass}} {{CSSLineClass.INFO}}"></td>
<td class="{{diffParser.LINE_TYPE.INFO}}"> <td class="{{CSSLineClass.INFO}}">
<div class="{{contentClass}} {{diffParser.LINE_TYPE.INFO}}">{{{blockHeader}}}</div> <div class="{{contentClass}} {{CSSLineClass.INFO}}">{{{blockHeader}}}</div>
</td> </td>
</tr> </tr>

View file

@ -1,6 +1,6 @@
<tr> <tr>
<td class="{{diffParser.LINE_TYPE.INFO}}"> <td class="{{CSSLineClass.INFO}}">
<div class="{{contentClass}} {{diffParser.LINE_TYPE.INFO}}"> <div class="{{contentClass}} {{CSSLineClass.INFO}}">
File without changes File without changes
</div> </div>
</td> </td>

View file

@ -1,223 +0,0 @@
/*
*
* Diff to HTML (diff2html-ui.js)
* Author: rtfpessoa
*
* Depends on: [ jQuery ]
* Optional dependencies on: [ highlight.js ]
*
*/
/* global $, hljs, Diff2Html */
(function() {
const highlightJS = require("./highlight.js-internals.js").HighlightJS;
let diffJson = null;
const defaultTarget = "body";
let currentSelectionColumnId = -1;
function Diff2HtmlUI(config) {
const cfg = config || {};
if (cfg.diff) {
diffJson = Diff2Html.getJsonFromDiff(cfg.diff);
} else if (cfg.json) {
diffJson = cfg.json;
}
this._initSelection();
}
Diff2HtmlUI.prototype.draw = function(targetId, config) {
const cfg = config || {};
cfg.inputFormat = "json";
const $target = this._getTarget(targetId);
$target.html(Diff2Html.getPrettyHtml(diffJson, cfg));
if (cfg.synchronisedScroll) {
this.synchronisedScroll($target, cfg);
}
};
Diff2HtmlUI.prototype.synchronisedScroll = function(targetId) {
const $target = this._getTarget(targetId);
$target.find(".d2h-file-side-diff").scroll(function() {
const $this = $(this);
$this
.closest(".d2h-file-wrapper")
.find(".d2h-file-side-diff")
.scrollLeft($this.scrollLeft());
});
};
Diff2HtmlUI.prototype.fileListCloseable = function(targetId, startVisible) {
const $target = this._getTarget(targetId);
const hashTag = this._getHashTag();
const $showBtn = $target.find(".d2h-show");
const $hideBtn = $target.find(".d2h-hide");
const $fileList = $target.find(".d2h-file-list");
if (hashTag === "files-summary-show") show();
else if (hashTag === "files-summary-hide") hide();
else if (startVisible) show();
else hide();
$showBtn.click(show);
$hideBtn.click(hide);
function show() {
$showBtn.hide();
$hideBtn.show();
$fileList.show();
}
function hide() {
$hideBtn.hide();
$showBtn.show();
$fileList.hide();
}
};
Diff2HtmlUI.prototype.highlightCode = function(targetId) {
const that = this;
const $target = that._getTarget(targetId);
// collect all the diff files and execute the highlight on their lines
const $files = $target.find(".d2h-file-wrapper");
$files.map(function(_i, file) {
let oldLinesState;
let newLinesState;
const $file = $(file);
const language = $file.data("lang");
// collect all the code lines and execute the highlight on them
const $codeLines = $file.find(".d2h-code-line-ctn");
$codeLines.map(function(_j, line) {
const $line = $(line);
const text = line.textContent;
const lineParent = line.parentNode;
let lineState;
if (lineParent.className.indexOf("d2h-del") !== -1) {
lineState = oldLinesState;
} else {
lineState = newLinesState;
}
const result = hljs.getLanguage(language)
? hljs.highlight(language, text, true, lineState)
: hljs.highlightAuto(text);
if (lineParent.className.indexOf("d2h-del") !== -1) {
oldLinesState = result.top;
} else if (lineParent.className.indexOf("d2h-ins") !== -1) {
newLinesState = result.top;
} else {
oldLinesState = result.top;
newLinesState = result.top;
}
const originalStream = highlightJS.nodeStream(line);
if (originalStream.length) {
const resultNode = document.createElementNS("http://www.w3.org/1999/xhtml", "div");
resultNode.innerHTML = result.value;
result.value = highlightJS.mergeStreams(originalStream, highlightJS.nodeStream(resultNode), text);
}
$line.addClass("hljs");
$line.addClass(result.language);
$line.html(result.value);
});
});
};
Diff2HtmlUI.prototype._getTarget = function(targetId) {
let $target;
if (typeof targetId === "object" && targetId instanceof jQuery) {
$target = targetId;
} else if (typeof targetId === "string") {
$target = $(targetId);
} else {
console.error("Wrong target provided! Falling back to default value 'body'.");
console.log("Please provide a jQuery object or a valid DOM query string.");
$target = $(defaultTarget);
}
return $target;
};
Diff2HtmlUI.prototype._getHashTag = function() {
const docUrl = document.URL;
const hashTagIndex = docUrl.indexOf("#");
let hashTag = null;
if (hashTagIndex !== -1) {
hashTag = docUrl.substr(hashTagIndex + 1);
}
return hashTag;
};
Diff2HtmlUI.prototype._distinct = function(collection) {
return collection.filter(function(v, i) {
return collection.indexOf(v) === i;
});
};
Diff2HtmlUI.prototype._initSelection = function() {
const body = $("body");
const that = this;
body.on("mousedown", ".d2h-diff-table", function(event) {
const target = $(event.target);
const table = target.closest(".d2h-diff-table");
if (target.closest(".d2h-code-line,.d2h-code-side-line").length) {
table.removeClass("selecting-left");
table.addClass("selecting-right");
currentSelectionColumnId = 1;
} else if (target.closest(".d2h-code-linenumber,.d2h-code-side-linenumber").length) {
table.removeClass("selecting-right");
table.addClass("selecting-left");
currentSelectionColumnId = 0;
}
});
body.on("copy", ".d2h-diff-table", function(event) {
const clipboardData = event.originalEvent.clipboardData;
const text = that._getSelectedText();
clipboardData.setData("text", text);
event.preventDefault();
});
};
Diff2HtmlUI.prototype._getSelectedText = function() {
const sel = window.getSelection();
const range = sel.getRangeAt(0);
const doc = range.cloneContents();
const nodes = doc.querySelectorAll("tr");
let text = "";
const idx = currentSelectionColumnId;
if (nodes.length === 0) {
text = doc.textContent;
} else {
[].forEach.call(nodes, function(tr, i) {
const td = tr.cells[tr.cells.length === 1 ? 0 : idx];
text += (i ? "\n" : "") + td.textContent.replace(/(?:\r\n|\r|\n)/g, "");
});
}
return text;
};
module.exports.Diff2HtmlUI = Diff2HtmlUI;
// Expose diff2html in the browser
global.Diff2HtmlUI = Diff2HtmlUI;
})();

220
src/ui/js/diff2html-ui.ts Normal file
View file

@ -0,0 +1,220 @@
import HighlightJS from "highlight.js";
import * as HighlightJSInternals from "./highlight.js-internals";
import { html, Diff2HtmlConfig, defaultDiff2HtmlConfig } from "../../diff2html";
import { DiffFile } from "../../render-utils";
interface Diff2HtmlUIConfig extends Diff2HtmlConfig {
synchronisedScroll?: boolean;
}
const defaultDiff2HtmlUIConfig = {
...defaultDiff2HtmlConfig,
synchronisedScroll: true
};
export default class Diff2HtmlUI {
readonly config: typeof defaultDiff2HtmlUIConfig;
readonly diffHtml: string;
targetElement: HTMLElement;
currentSelectionColumnId = -1;
constructor(diffInput: string | DiffFile[], target: HTMLElement, config: Diff2HtmlUIConfig = {}) {
this.config = { ...defaultDiff2HtmlUIConfig, ...config };
this.diffHtml = html(diffInput, this.config);
this.targetElement = target;
}
draw(): void {
this.targetElement.innerHTML = this.diffHtml;
this.initSelection();
if (this.config.synchronisedScroll) this.synchronisedScroll();
}
synchronisedScroll(): void {
this.targetElement.querySelectorAll(".d2h-file-wrapper").forEach(wrapper => {
const [left, right] = [].slice.call(wrapper.querySelectorAll(".d2h-file-side-diff")) as HTMLElement[];
if (left === undefined || right === undefined) return;
const onScroll = (event: Event): void => {
if (event === null || event.target === null) return;
if (event.target === left) {
right.scrollTop = left.scrollTop;
} else {
left.scrollTop = right.scrollTop;
}
};
left.addEventListener("scroll", onScroll);
right.addEventListener("scroll", onScroll);
});
}
fileListCloseable(startVisible: boolean): void {
const hashTag = this.getHashTag();
const showBtn = this.targetElement.querySelector(".d2h-show") as HTMLElement;
const hideBtn = this.targetElement.querySelector(".d2h-hide") as HTMLElement;
const fileList = this.targetElement.querySelector(".d2h-file-list") as HTMLElement;
if (showBtn === null || hideBtn === null || fileList === null) return;
function show(): void {
showBtn.style.display = "";
hideBtn.style.display = "";
fileList.style.display = "";
}
function hide(): void {
showBtn.style.display = "none";
hideBtn.style.display = "none";
fileList.style.display = "none";
}
showBtn.addEventListener("click", () => show());
hideBtn.addEventListener("click", () => hide());
if (hashTag === "files-summary-show") show();
else if (hashTag === "files-summary-hide") hide();
else if (startVisible) show();
else hide();
}
highlightCode(): void {
// Collect all the diff files and execute the highlight on their lines
const files = this.targetElement.querySelectorAll(".d2h-file-wrapper");
files.forEach(file => {
let oldLinesState: HighlightJS.ICompiledMode;
let newLinesState: HighlightJS.ICompiledMode;
// Collect all the code lines and execute the highlight on them
const codeLines = file.querySelectorAll(".d2h-code-line-ctn");
codeLines.forEach(line => {
const text = line.textContent;
const lineParent = line.parentNode as HTMLElement;
if (lineParent === null || text === null) return;
const lineState = lineParent.className.indexOf("d2h-del") !== -1 ? oldLinesState : newLinesState;
const language = file.getAttribute("data-lang");
const result =
language && HighlightJS.getLanguage(language)
? HighlightJS.highlight(language, text, true, lineState)
: HighlightJS.highlightAuto(text);
if (this.instanceOfIHighlightResult(result)) {
if (lineParent.className.indexOf("d2h-del") !== -1) {
oldLinesState = result.top;
} else if (lineParent.className.indexOf("d2h-ins") !== -1) {
newLinesState = result.top;
} else {
oldLinesState = result.top;
newLinesState = result.top;
}
}
const originalStream = HighlightJSInternals.nodeStream(line);
if (originalStream.length) {
const resultNode = document.createElementNS("http://www.w3.org/1999/xhtml", "div");
resultNode.innerHTML = result.value;
result.value = HighlightJSInternals.mergeStreams(
originalStream,
HighlightJSInternals.nodeStream(resultNode),
text
);
}
line.classList.add("hljs");
line.classList.add("result.language");
line.innerHTML = result.value;
});
});
}
private instanceOfIHighlightResult(
object: HighlightJS.IHighlightResult | HighlightJS.IAutoHighlightResult
): object is HighlightJS.IHighlightResult {
return "top" in object;
}
private getHashTag(): string | null {
const docUrl = document.URL;
const hashTagIndex = docUrl.indexOf("#");
let hashTag = null;
if (hashTagIndex !== -1) {
hashTag = docUrl.substr(hashTagIndex + 1);
}
return hashTag;
}
private initSelection(): void {
const body = document.getElementsByTagName("body")[0];
const diffTable = body.getElementsByClassName("d2h-diff-table")[0];
diffTable.addEventListener("mousedown", event => {
if (event === null || event.target === null) return;
const mouseEvent = event as MouseEvent;
const target = mouseEvent.target as HTMLElement;
const table = target.closest(".d2h-diff-table");
if (table !== null) {
if (target.closest(".d2h-code-line,.d2h-code-side-line") !== null) {
table.classList.remove("selecting-left");
table.classList.add("selecting-right");
this.currentSelectionColumnId = 1;
} else if (target.closest(".d2h-code-linenumber,.d2h-code-side-linenumber") !== null) {
table.classList.remove("selecting-right");
table.classList.add("selecting-left");
this.currentSelectionColumnId = 0;
}
}
});
diffTable.addEventListener("copy", event => {
const clipboardEvent = event as ClipboardEvent;
const clipboardData = clipboardEvent.clipboardData;
const text = this.getSelectedText();
if (clipboardData === null || text === undefined) return;
clipboardData.setData("text", text);
event.preventDefault();
});
}
private getSelectedText(): string | undefined {
const sel = window.getSelection();
if (sel === null) return;
const range = sel.getRangeAt(0);
const doc = range.cloneContents();
const nodes = doc.querySelectorAll("tr");
const idx = this.currentSelectionColumnId;
let text = "";
if (nodes.length === 0) {
text = doc.textContent || "";
} else {
nodes.forEach((tr, i) => {
const td = tr.cells[tr.cells.length === 1 ? 0 : idx];
if (td === null || td.textContent === null) return;
text += (i ? "\n" : "") + td.textContent.replace(/(?:\r\n|\r|\n)/g, "");
});
}
return text;
}
}
// TODO: Avoid disabling types
// eslint-disable-next-line
// @ts-ignore
global.Diff2HtmlUI = Diff2HtmlUI;

View file

@ -1,142 +0,0 @@
/*
*
* highlight.js
* Author: isagalaev
*
*/
(function() {
function HighlightJS() {}
/*
* Copied from Highlight.js Private API
* Will be removed when this part of the API is exposed
*/
/* Utility vars */
const ArrayProto = [];
/* Utility functions */
function escape(value) {
return value
.replace(/&/gm, "&amp;")
.replace(/</gm, "&lt;")
.replace(/>/gm, "&gt;");
}
function tag(node) {
return node.nodeName.toLowerCase();
}
/* Stream merging */
HighlightJS.prototype.nodeStream = function(node) {
const result = [];
(function _nodeStream(node, offset) {
for (let child = node.firstChild; child; child = child.nextSibling) {
if (child.nodeType === 3) {
offset += child.nodeValue.length;
} else if (child.nodeType === 1) {
result.push({
event: "start",
offset: offset,
node: child
});
offset = _nodeStream(child, offset);
// Prevent void elements from having an end tag that would actually
// double them in the output. There are more void elements in HTML
// but we list only those realistically expected in code display.
if (!tag(child).match(/br|hr|img|input/)) {
result.push({
event: "stop",
offset: offset,
node: child
});
}
}
}
return offset;
})(node, 0);
return result;
};
HighlightJS.prototype.mergeStreams = function(original, highlighted, value) {
let processed = 0;
let result = "";
const nodeStack = [];
function selectStream() {
if (!original.length || !highlighted.length) {
return original.length ? original : highlighted;
}
if (original[0].offset !== highlighted[0].offset) {
return original[0].offset < highlighted[0].offset ? original : highlighted;
}
/*
To avoid starting the stream just before it should stop the order is
ensured that original always starts first and closes last:
if (event1 == 'start' && event2 == 'start')
return original;
if (event1 == 'start' && event2 == 'stop')
return highlighted;
if (event1 == 'stop' && event2 == 'start')
return original;
if (event1 == 'stop' && event2 == 'stop')
return highlighted;
... which is collapsed to:
*/
return highlighted[0].event === "start" ? original : highlighted;
}
function open(node) {
function attr_str(a) {
return " " + a.nodeName + '="' + escape(a.value) + '"';
}
result += "<" + tag(node) + ArrayProto.map.call(node.attributes, attr_str).join("") + ">";
}
function close(node) {
result += "</" + tag(node) + ">";
}
function render(event) {
(event.event === "start" ? open : close)(event.node);
}
while (original.length || highlighted.length) {
let stream = selectStream();
result += escape(value.substring(processed, stream[0].offset));
processed = stream[0].offset;
if (stream === original) {
/*
On any opening or closing tag of the original markup we first close
the entire highlighted node stack, then render the original tag along
with all the following original tags at the same offset and then
reopen all the tags on the highlighted stack.
*/
nodeStack.reverse().forEach(close);
do {
render(stream.splice(0, 1)[0]);
stream = selectStream();
} while (stream === original && stream.length && stream[0].offset === processed);
nodeStack.reverse().forEach(open);
} else {
if (stream[0].event === "start") {
nodeStack.push(stream[0].node);
} else {
nodeStack.pop();
}
render(stream.splice(0, 1)[0]);
}
}
return result + escape(value.substr(processed));
};
/* **** Highlight.js Private API **** */
module.exports.HighlightJS = new HighlightJS();
})();

View file

@ -0,0 +1,134 @@
/*
* Copied from Highlight.js Private API
* Will be removed when this part of the API is exposed
*/
/* Utility functions */
function escape(value: string): string {
return value
.replace(/&/gm, "&amp;")
.replace(/</gm, "&lt;")
.replace(/>/gm, "&gt;");
}
function tag(node: Node): string {
return node.nodeName.toLowerCase();
}
/* Stream merging */
type NodeEvent = {
event: "start" | "stop";
offset: number;
node: Node;
};
export function nodeStream(node: Node): NodeEvent[] {
const result: NodeEvent[] = [];
const nodeStream = (node: Node, offset: number): number => {
for (let child = node.firstChild; child; child = child.nextSibling) {
if (child.nodeType === 3 && child.nodeValue !== null) {
offset += child.nodeValue.length;
} else if (child.nodeType === 1) {
result.push({
event: "start",
offset: offset,
node: child
});
offset = nodeStream(child, offset);
// Prevent void elements from having an end tag that would actually
// double them in the output. There are more void elements in HTML
// but we list only those realistically expected in code display.
if (!tag(child).match(/br|hr|img|input/)) {
result.push({
event: "stop",
offset: offset,
node: child
});
}
}
}
return offset;
};
nodeStream(node, 0);
return result;
}
export function mergeStreams(original: NodeEvent[], highlighted: NodeEvent[], value: string): string {
let processed = 0;
let result = "";
const nodeStack = [];
function selectStream(): NodeEvent[] {
if (!original.length || !highlighted.length) {
return original.length ? original : highlighted;
}
if (original[0].offset !== highlighted[0].offset) {
return original[0].offset < highlighted[0].offset ? original : highlighted;
}
/*
To avoid starting the stream just before it should stop the order is
ensured that original always starts first and closes last:
if (event1 == 'start' && event2 == 'start')
return original;
if (event1 == 'start' && event2 == 'stop')
return highlighted;
if (event1 == 'stop' && event2 == 'start')
return original;
if (event1 == 'stop' && event2 == 'stop')
return highlighted;
... which is collapsed to:
*/
return highlighted[0].event === "start" ? original : highlighted;
}
function open(node: Node): void {
const htmlNode = node as HTMLElement;
result += `<${tag(node)} ${[].map
.call(htmlNode.attributes, (attr: Attr) => `${attr.nodeName}="${escape(attr.value)}"`)
.join(" ")}>`;
}
function close(node: Node): void {
result += "</" + tag(node) + ">";
}
function render(event: NodeEvent): void {
(event.event === "start" ? open : close)(event.node);
}
while (original.length || highlighted.length) {
let stream = selectStream();
result += escape(value.substring(processed, stream[0].offset));
processed = stream[0].offset;
if (stream === original) {
/*
On any opening or closing tag of the original markup we first close
the entire highlighted node stack, then render the original tag along
with all the following original tags at the same offset and then
reopen all the tags on the highlighted stack.
*/
nodeStack.reverse().forEach(close);
do {
render(stream.splice(0, 1)[0]);
stream = selectStream();
} while (stream === original && stream.length && stream[0].offset === processed);
nodeStack.reverse().forEach(open);
} else {
if (stream[0].event === "start") {
nodeStack.push(stream[0].node);
} else {
nodeStack.pop();
}
render(stream.splice(0, 1)[0]);
}
}
return result + escape(value.substr(processed));
}
/* **** Highlight.js Private API **** */

View file

@ -1,48 +0,0 @@
/*
*
* Utils (utils.js)
* Author: rtfpessoa
*
*/
(function() {
const merge = require("merge");
function Utils() {}
Utils.prototype.escape = function(str) {
return str
.slice(0)
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#x27;")
.replace(/\//g, "&#x2F;");
};
Utils.prototype.startsWith = function(str, start) {
if (typeof start === "object") {
let result = false;
start.forEach(function(s) {
if (str.indexOf(s) === 0) {
result = true;
}
});
return result;
}
return str && str.indexOf(start) === 0;
};
Utils.prototype.valueOrEmpty = function(value) {
return value || "";
};
Utils.prototype.safeConfig = function(cfg, defaultConfig) {
return merge.recursive(true, defaultConfig, cfg);
};
module.exports.Utils = new Utils();
})();

68
src/utils.ts Normal file
View file

@ -0,0 +1,68 @@
const specials = [
// Order matters for these
"-",
"[",
"]",
// Order doesn't matter for any of these
"/",
"{",
"}",
"(",
")",
"*",
"+",
"?",
".",
"\\",
"^",
"$",
"|"
];
// All characters will be escaped with '\'
// even though only some strictly require it when inside of []
const regex = RegExp("[" + specials.join("\\") + "]", "g");
/**
* Escapes all required characters for safe usage inside a RegExp
*/
export function escapeForRegExp(str: string): string {
return str.replace(regex, "\\$&");
}
/**
* Escapes all required characters for safe HTML rendering
*/
export function escapeForHtml(str: string): string {
return str
.slice(0)
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#x27;")
.replace(/\//g, "&#x2F;");
}
/**
* Converts all '\' in @path to unix style '/'
*/
export function unifyPath(path: string): string {
return path ? path.replace("\\", "/") : path;
}
/**
* Create unique number identifier for @text
*/
export function hashCode(text: string): number {
let i, chr, len;
let hash = 0;
for (i = 0, len = text.length; i < len; i++) {
chr = text.charCodeAt(i);
hash = (hash << 5) - hash + chr;
hash |= 0; // Convert to 32bit integer
}
return hash;
}

View file

@ -4,6 +4,7 @@
"target": "es5", "target": "es5",
"module": "commonjs", "module": "commonjs",
"moduleResolution": "node", "moduleResolution": "node",
"lib": ["es5", "es6", "es7", "esnext", "dom", "dom.iterable"],
// TODO: Change to true after migration to TS is complete // TODO: Change to true after migration to TS is complete
"allowJs": true, "allowJs": true,
"declaration": false, "declaration": false,
@ -21,8 +22,9 @@
"noUnusedLocals": true, "noUnusedLocals": true,
"noImplicitReturns": true, "noImplicitReturns": true,
"noFallthroughCasesInSwitch": true, "noFallthroughCasesInSwitch": true,
"forceConsistentCasingInFileNames": true "forceConsistentCasingInFileNames": true,
"esModuleInterop": true
}, },
"include": ["./src/**/*"], "include": ["./src/**/*", "./typings/**/*"],
"exclude": ["node_modules", "./src/__tests__/*"] "exclude": ["node_modules", "./src/__tests__/*"]
} }

View file

@ -6,5 +6,5 @@
"declarationMap": false, "declarationMap": false,
"sourceMap": false "sourceMap": false
}, },
"include": ["./scripts/**/*"] "include": ["./scripts/**/*", "./typings/**/*"]
} }

93
typings/hoganjs.d.ts vendored Normal file
View file

@ -0,0 +1,93 @@
// 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;
}

3
typings/merge.d.ts vendored Normal file
View file

@ -0,0 +1,3 @@
declare module "merge" {
export function recursive(clone: boolean, ...items: object[]): object;
}

View file

@ -1,8 +1,4 @@
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.13.1/highlight.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.13.1/languages/scala.min.js"></script>
<!-- diff2html --> <!-- diff2html -->
<script type="text/javascript" src="assets/diff2html.min.js"></script>
<script type="text/javascript" src="assets/diff2html-ui.min.js"></script> <script type="text/javascript" src="assets/diff2html-ui.min.js"></script>
<!-- --> <!-- -->

View file

@ -1,4 +1,5 @@
/* global Diff2HtmlUI */ /* global Diff2HtmlUI */
/* eslint-disable @typescript-eslint/explicit-function-return-type */
/* /*
* Example URLs: * Example URLs:
@ -13,40 +14,8 @@
* https://bitbucket.org/atlassian/amps/pull-requests/236 * https://bitbucket.org/atlassian/amps/pull-requests/236
*/ */
$(document).ready(function() {
// Improves browser compatibility
require("whatwg-fetch");
const searchParam = "diff"; const searchParam = "diff";
const $container = $(".container");
const $url = $("#url");
const $outputFormat = $("#diff-url-options-output-format");
const $showFiles = $("#diff-url-options-show-files");
const $matching = $("#diff-url-options-matching");
const $wordsThreshold = $("#diff-url-options-match-words-threshold");
const $matchingMaxComparisons = $("#diff-url-options-matching-max-comparisons");
if (window.location.search) {
const url = getUrlFromSearch(window.location.search);
$url.val(url);
smartDraw(url);
}
bind();
$outputFormat
.add($showFiles)
.add($matching)
.add($wordsThreshold)
.add($matchingMaxComparisons)
.change(function(e) {
console.log("");
console.log(e);
console.log("");
smartDraw(null, true);
});
function getUrlFromSearch(search) { function getUrlFromSearch(search) {
try { try {
return search return search
@ -59,33 +28,20 @@ $(document).ready(function() {
} }
function getParamsFromSearch(search) { function getParamsFromSearch(search) {
const map = {}; const map = new Map();
try { try {
search search
.split("?")[1] .split("?")[1]
.split("&") .split("&")
.map(function(e) { .forEach(e => {
const values = e.split("="); const values = e.split("=");
map[values[0]] = values[1]; map.set(values[0], values[1]);
}); });
} catch (_ignore) {} } catch (_ignore) {}
return map; return map;
} }
function bind() {
$("#url-btn").click(function(e) {
e.preventDefault();
const url = $url.val();
smartDraw(url);
});
$url.on("paste", function(e) {
const url = e.originalEvent.clipboardData.getData("Text");
smartDraw(url);
});
}
function prepareUrl(url) { function prepareUrl(url) {
let fetchUrl; let fetchUrl;
const headers = new Headers(); const headers = new Headers();
@ -143,78 +99,6 @@ $(document).ready(function() {
}; };
} }
function smartDraw(urlOpt, forced) {
const url = urlOpt || $url.val();
const req = prepareUrl(url);
draw(req, forced);
}
function draw(req, forced) {
if (!validateUrl(req.url)) {
console.error("Invalid url provided!");
return;
}
if (validateUrl(req.originalUrl)) updateUrl(req.originalUrl);
const outputFormat = $outputFormat.val();
const showFiles = $showFiles.is(":checked");
const matching = $matching.val();
const wordsThreshold = $wordsThreshold.val();
const matchingMaxComparisons = $matchingMaxComparisons.val();
fetch(req.url, {
method: "GET",
headers: req.headers,
mode: "cors",
cache: "default"
})
.then(function(res) {
return res.text();
})
.then(function(data) {
const container = "#url-diff-container";
const diff2htmlUi = new Diff2HtmlUI({ diff: data });
if (outputFormat === "side-by-side") {
$container.css({ width: "100%" });
} else {
$container.css({ width: "" });
}
const params = getParamsFromSearch(window.location.search);
delete params[searchParam];
if (forced) {
params.outputFormat = outputFormat;
params.showFiles = showFiles;
params.matching = matching;
params.wordsThreshold = wordsThreshold;
params.matchingMaxComparisons = matchingMaxComparisons;
} else {
params.outputFormat = params.outputFormat || outputFormat;
params.showFiles = String(params.showFiles) !== "false" || (params.showFiles === null && showFiles);
params.matching = params.matching || matching;
params.wordsThreshold = params.wordsThreshold || wordsThreshold;
params.matchingMaxComparisons = params.matchingMaxComparisons || matchingMaxComparisons;
$outputFormat.val(params.outputFormat);
$showFiles.prop("checked", params.showFiles);
$matching.val(params.matching);
$wordsThreshold.val(params.wordsThreshold);
$matchingMaxComparisons.val(params.matchingMaxComparisons);
}
params.synchronisedScroll = params.synchronisedScroll || true;
diff2htmlUi.draw(container, params);
diff2htmlUi.fileListCloseable(container, params.fileListCloseable || false);
if (params.highlight === undefined || params.highlight) {
diff2htmlUi.highlightCode(container);
}
});
}
function validateUrl(url) { function validateUrl(url) {
return /^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})).?)(?::\d{2,5})?(?:[/?#]\S*)?$/i.test( return /^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})).?)(?::\d{2,5})?(?:[/?#]\S*)?$/i.test(
url url
@ -236,4 +120,123 @@ $(document).ready(function() {
window.location = "demo.html?" + paramString; window.location = "demo.html?" + paramString;
} }
function draw(req, forced, elements) {
if (!validateUrl(req.url)) {
console.error("Invalid url provided!");
return;
}
if (validateUrl(req.originalUrl)) updateUrl(req.originalUrl);
const outputFormat = elements.outputFormat.val();
const showFiles = elements.showFiles.is(":checked");
const matching = elements.matching.val();
const wordsThreshold = elements.wordsThreshold.val();
const matchingMaxComparisons = elements.matchingMaxComparisons.val();
fetch(req.url, {
method: "GET",
headers: req.headers,
mode: "cors",
cache: "default"
})
.then(function(res) {
return res.text();
})
.then(function(data) {
const params = getParamsFromSearch(window.location.search);
delete params[searchParam];
if (forced) {
params.outputFormat = outputFormat;
params.showFiles = showFiles;
params.matching = matching;
params.wordsThreshold = wordsThreshold;
params.matchingMaxComparisons = matchingMaxComparisons;
} else {
params.outputFormat = params.outputFormat || outputFormat;
params.showFiles = String(params.showFiles) !== "false" || (params.showFiles === null && showFiles);
params.matching = params.matching || matching;
params.wordsThreshold = params.wordsThreshold || wordsThreshold;
params.matchingMaxComparisons = params.matchingMaxComparisons || matchingMaxComparisons;
elements.outputFormat.value = params.outputFormat;
elements.showFiles.setAttribute("checked", params.showFiles);
elements.matching.value = params.matching;
elements.wordsThreshold.value = params.wordsThreshold;
elements.matchingMaxComparisons.value = params.matchingMaxComparisons;
}
params.synchronisedScroll = params.synchronisedScroll || true;
const diff2htmlUi = new Diff2HtmlUI(data, elements.root);
if (outputFormat === "side-by-side") {
elements.container.css({ width: "100%" });
} else {
elements.container.css({ width: "" });
}
diff2htmlUi.draw();
diff2htmlUi.fileListCloseable(params.fileListCloseable || false);
if (params.highlight === undefined || params.highlight) {
diff2htmlUi.highlightCode();
}
return undefined;
})
.catch(() => {});
}
function smartDraw(urlOpt, urlElem, forced) {
const url = urlOpt || urlElem.val();
const req = prepareUrl(url);
draw(req, forced);
}
function bind(urlElem) {
$("#url-btn").click(e => {
e.preventDefault();
const url = urlElem.val();
smartDraw(url, urlElem);
}); });
urlElem.on("paste", e => {
const url = e.originalEvent.clipboardData.getData("Text");
smartDraw(url, urlElem);
});
}
document.addEventListener("DOMContentLoaded", function() {
// Improves browser compatibility
require("whatwg-fetch");
const elements = {
root: document.getElementById("url-diff-container"),
container: document.getElementsByClassName("container"),
url: document.getElementById("url"),
outputFormat: document.getElementById("diff-url-options-output-format"),
showFiles: document.getElementById("diff-url-options-show-files"),
matching: document.getElementById("diff-url-options-matching"),
wordsThreshold: document.getElementById("diff-url-options-match-words-threshold"),
matchingMaxComparisons: document.getElementById("diff-url-options-matching-max-comparisons")
};
if (window.location.search) {
const url = getUrlFromSearch(window.location.search);
elements.url.val(url);
smartDraw(url, elements.url);
}
bind();
elements.outputFormat
.add(elements.showFiles)
.add(elements.matching)
.add(elements.wordsThreshold)
.add(elements.matchingMaxComparisons)
.change(() => smartDraw(null, elements.url, true));
});
/* eslint-enable @typescript-eslint/explicit-function-return-type */

227
yarn.lock
View file

@ -10,17 +10,17 @@
"@babel/highlight" "^7.0.0" "@babel/highlight" "^7.0.0"
"@babel/core@^7.1.0": "@babel/core@^7.1.0":
version "7.6.2" version "7.6.4"
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.6.2.tgz#069a776e8d5e9eefff76236bc8845566bd31dd91" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.6.4.tgz#6ebd9fe00925f6c3e177bb726a188b5f578088ff"
integrity sha512-l8zto/fuoZIbncm+01p8zPSDZu/VuuJhAfA7d/AbzM09WR7iVhavvfNDYCNpo1VvLk6E6xgAoP9P+/EMJHuRkQ== integrity sha512-Rm0HGw101GY8FTzpWSyRbki/jzq+/PkNQJ+nSulrdY6gFGOsNseCqD6KHRYe2E+EdzuBdr2pxCp6s4Uk6eJ+XQ==
dependencies: dependencies:
"@babel/code-frame" "^7.5.5" "@babel/code-frame" "^7.5.5"
"@babel/generator" "^7.6.2" "@babel/generator" "^7.6.4"
"@babel/helpers" "^7.6.2" "@babel/helpers" "^7.6.2"
"@babel/parser" "^7.6.2" "@babel/parser" "^7.6.4"
"@babel/template" "^7.6.0" "@babel/template" "^7.6.0"
"@babel/traverse" "^7.6.2" "@babel/traverse" "^7.6.3"
"@babel/types" "^7.6.0" "@babel/types" "^7.6.3"
convert-source-map "^1.1.0" convert-source-map "^1.1.0"
debug "^4.1.0" debug "^4.1.0"
json5 "^2.1.0" json5 "^2.1.0"
@ -29,12 +29,12 @@
semver "^5.4.1" semver "^5.4.1"
source-map "^0.5.0" source-map "^0.5.0"
"@babel/generator@^7.4.0", "@babel/generator@^7.6.2": "@babel/generator@^7.4.0", "@babel/generator@^7.6.3", "@babel/generator@^7.6.4":
version "7.6.2" version "7.6.4"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.6.2.tgz#dac8a3c2df118334c2a29ff3446da1636a8f8c03" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.6.4.tgz#a4f8437287bf9671b07f483b76e3bb731bc97671"
integrity sha512-j8iHaIW4gGPnViaIHI7e9t/Hl8qLjERI6DcV9kEpAIDJsAOrcnXqRS7t+QbhL76pwbtqP+QCQLL0z1CyVmtjjQ== integrity sha512-jsBuXkFoZxk0yWLyGI9llT9oiQ2FeTASmRFE32U+aaDTfoE92t78eroO7PTpU/OrYq38hlcDM6vbfLDaOLy+7w==
dependencies: dependencies:
"@babel/types" "^7.6.0" "@babel/types" "^7.6.3"
jsesc "^2.5.1" jsesc "^2.5.1"
lodash "^4.17.13" lodash "^4.17.13"
source-map "^0.5.0" source-map "^0.5.0"
@ -85,10 +85,10 @@
esutils "^2.0.2" esutils "^2.0.2"
js-tokens "^4.0.0" js-tokens "^4.0.0"
"@babel/parser@^7.1.0", "@babel/parser@^7.4.3", "@babel/parser@^7.6.0", "@babel/parser@^7.6.2": "@babel/parser@^7.1.0", "@babel/parser@^7.4.3", "@babel/parser@^7.6.0", "@babel/parser@^7.6.3", "@babel/parser@^7.6.4":
version "7.6.2" version "7.6.4"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.6.2.tgz#205e9c95e16ba3b8b96090677a67c9d6075b70a1" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.6.4.tgz#cb9b36a7482110282d5cb6dd424ec9262b473d81"
integrity sha512-mdFqWrSPCmikBoaBYMuBulzTIKuXVPtEISFbRRVNwMWpCms/hmE2kRq0bblUHaNRKrjRlmVbx1sDHmjmRgD2Xg== integrity sha512-D8RHPW5qd0Vbyo3qb+YjO5nvUVRTXFLQ/FsDxJU2Nqz4uB5EnUN0ZQSEYpvTIbRuttig1XbHWU5oMeQwQSAA+A==
"@babel/plugin-syntax-object-rest-spread@^7.0.0": "@babel/plugin-syntax-object-rest-spread@^7.0.0":
version "7.2.0" version "7.2.0"
@ -106,25 +106,25 @@
"@babel/parser" "^7.6.0" "@babel/parser" "^7.6.0"
"@babel/types" "^7.6.0" "@babel/types" "^7.6.0"
"@babel/traverse@^7.1.0", "@babel/traverse@^7.4.3", "@babel/traverse@^7.6.2": "@babel/traverse@^7.1.0", "@babel/traverse@^7.4.3", "@babel/traverse@^7.6.2", "@babel/traverse@^7.6.3":
version "7.6.2" version "7.6.3"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.6.2.tgz#b0e2bfd401d339ce0e6c05690206d1e11502ce2c" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.6.3.tgz#66d7dba146b086703c0fb10dd588b7364cec47f9"
integrity sha512-8fRE76xNwNttVEF2TwxJDGBLWthUkHWSldmfuBzVRmEDWOtu4XdINTgN7TDWzuLg4bbeIMLvfMFD9we5YcWkRQ== integrity sha512-unn7P4LGsijIxaAJo/wpoU11zN+2IaClkQAxcJWBNCMS6cmVh802IyLHNkAjQ0iYnRS3nnxk5O3fuXW28IMxTw==
dependencies: dependencies:
"@babel/code-frame" "^7.5.5" "@babel/code-frame" "^7.5.5"
"@babel/generator" "^7.6.2" "@babel/generator" "^7.6.3"
"@babel/helper-function-name" "^7.1.0" "@babel/helper-function-name" "^7.1.0"
"@babel/helper-split-export-declaration" "^7.4.4" "@babel/helper-split-export-declaration" "^7.4.4"
"@babel/parser" "^7.6.2" "@babel/parser" "^7.6.3"
"@babel/types" "^7.6.0" "@babel/types" "^7.6.3"
debug "^4.1.0" debug "^4.1.0"
globals "^11.1.0" globals "^11.1.0"
lodash "^4.17.13" lodash "^4.17.13"
"@babel/types@^7.0.0", "@babel/types@^7.3.0", "@babel/types@^7.4.0", "@babel/types@^7.4.4", "@babel/types@^7.6.0": "@babel/types@^7.0.0", "@babel/types@^7.3.0", "@babel/types@^7.4.0", "@babel/types@^7.4.4", "@babel/types@^7.6.0", "@babel/types@^7.6.3":
version "7.6.1" version "7.6.3"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.6.1.tgz#53abf3308add3ac2a2884d539151c57c4b3ac648" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.6.3.tgz#3f07d96f854f98e2fbd45c64b0cb942d11e8ba09"
integrity sha512-X7gdiuaCmA0uRjCmRtYJNAVCc/q+5xSgsfKJHqMN4iNLILX39677fJE1O40arPMh0TTtS9ItH67yre6c7k6t0g== integrity sha512-CqbcpTxMcpuQTMhjI37ZHVgjBkysg5icREQIEZ0eG1yCNwg3oy+5AaLiOKmjsCj6nqOsa6Hf0ObjRVwokb7srA==
dependencies: dependencies:
esutils "^2.0.2" esutils "^2.0.2"
lodash "^4.17.13" lodash "^4.17.13"
@ -332,6 +332,11 @@
dependencies: dependencies:
"@babel/types" "^7.3.0" "@babel/types" "^7.3.0"
"@types/diff@4.0.2":
version "4.0.2"
resolved "https://registry.yarnpkg.com/@types/diff/-/diff-4.0.2.tgz#2e9bb89f9acc3ab0108f0f3dc4dbdcf2fff8a99c"
integrity sha512-mIenTfsIe586/yzsyfql69KRnA75S8SVXQbTLpDejRrjH0QSJcpu3AUOi/Vjnt9IOsXKxPhJfGpQUNMueIU1fQ==
"@types/eslint-visitor-keys@^1.0.0": "@types/eslint-visitor-keys@^1.0.0":
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d" resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d"
@ -351,10 +356,10 @@
"@types/minimatch" "*" "@types/minimatch" "*"
"@types/node" "*" "@types/node" "*"
"@types/hogan.js@^3.0.0": "@types/highlight.js@9.12.3":
version "3.0.0" version "9.12.3"
resolved "https://registry.yarnpkg.com/@types/hogan.js/-/hogan.js-3.0.0.tgz#bf26560f39a38224ab6d0491b06f72c8fbe0953d" resolved "https://registry.yarnpkg.com/@types/highlight.js/-/highlight.js-9.12.3.tgz#b672cfaac25cbbc634a0fd92c515f66faa18dbca"
integrity sha512-djkvb/AN43c3lIGCojNQ1FBS9VqqKhcTns5RQnHw4xBT/csy0jAssAsOiJ8NfaaioZaeKYE7XkVRxE5NeSZcaA== integrity sha512-pGF/zvYOACZ/gLGWdQH8zSwteQS1epp68yRcVLJMgUck/MjEn/FBYmPub9pXT8C1e4a8YZfHo1CKyV8q1vKUnQ==
"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0": "@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0":
version "2.0.1" version "2.0.1"
@ -398,19 +403,24 @@
resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA== integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==
"@types/mkdirp@^0.5.2": "@types/mkdirp@0.5.2":
version "0.5.2" version "0.5.2"
resolved "https://registry.yarnpkg.com/@types/mkdirp/-/mkdirp-0.5.2.tgz#503aacfe5cc2703d5484326b1b27efa67a339c1f" resolved "https://registry.yarnpkg.com/@types/mkdirp/-/mkdirp-0.5.2.tgz#503aacfe5cc2703d5484326b1b27efa67a339c1f"
integrity sha512-U5icWpv7YnZYGsN4/cmh3WD2onMY0aJIiTE6+51TwJCttdHvtCYmkBNOobHlXwrJRL0nkH9jH4kD+1FAdMN4Tg== integrity sha512-U5icWpv7YnZYGsN4/cmh3WD2onMY0aJIiTE6+51TwJCttdHvtCYmkBNOobHlXwrJRL0nkH9jH4kD+1FAdMN4Tg==
dependencies: dependencies:
"@types/node" "*" "@types/node" "*"
"@types/node@*", "@types/node@^12.7.2": "@types/node@*":
version "12.7.11" version "12.7.12"
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.7.11.tgz#be879b52031cfb5d295b047f5462d8ef1a716446" resolved "https://registry.yarnpkg.com/@types/node/-/node-12.7.12.tgz#7c6c571cc2f3f3ac4a59a5f2bd48f5bdbc8653cc"
integrity sha512-Otxmr2rrZLKRYIybtdG/sgeO+tHY20GxeDjcGmUnmmlCWyEnv2a2x1ZXBo3BTec4OiTXMQCiazB8NMBf0iRlFw== integrity sha512-KPYGmfD0/b1eXurQ59fXD1GBzhSQfz6/lKBxkaHX9dKTzjXbK68Zt7yGUxUsCS1jeTy/8aL+d9JEr+S54mpkWQ==
"@types/nopt@^3.0.29": "@types/node@12.7.2":
version "12.7.2"
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.7.2.tgz#c4e63af5e8823ce9cc3f0b34f7b998c2171f0c44"
integrity sha512-dyYO+f6ihZEtNPDcWNR1fkoTDf3zAK3lAABDze3mz6POyIercH0lEUawUFXlG8xaQZmm1yEBON/4TsYv/laDYg==
"@types/nopt@3.0.29":
version "3.0.29" version "3.0.29"
resolved "https://registry.yarnpkg.com/@types/nopt/-/nopt-3.0.29.tgz#f19df3db4c97ee1459a2740028320a71d70964ce" resolved "https://registry.yarnpkg.com/@types/nopt/-/nopt-3.0.29.tgz#f19df3db4c97ee1459a2740028320a71d70964ce"
integrity sha1-8Z3z20yX7hRZonQAKDIKcdcJZM4= integrity sha1-8Z3z20yX7hRZonQAKDIKcdcJZM4=
@ -726,18 +736,18 @@ atob@^2.1.1:
resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"
integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==
autoprefixer@^9.6.0: autoprefixer@9.6.0:
version "9.6.4" version "9.6.0"
resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.6.4.tgz#e6453be47af316b2923eaeaed87860f52ad4b7eb" resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.6.0.tgz#0111c6bde2ad20c6f17995a33fad7cf6854b4c87"
integrity sha512-Koz2cJU9dKOxG8P1f8uVaBntOv9lP4yz9ffWvWaicv9gHBPhpQB22nGijwd8gqW9CNT+UdkbQOQNLVI8jN1ZfQ== integrity sha512-kuip9YilBqhirhHEGHaBTZKXL//xxGnzvsD0FtBQa6z+A69qZD6s/BAX9VzDF1i9VKDquTJDQaPLSEhOnL6FvQ==
dependencies: dependencies:
browserslist "^4.7.0" browserslist "^4.6.1"
caniuse-lite "^1.0.30000998" caniuse-lite "^1.0.30000971"
chalk "^2.4.2" chalk "^2.4.2"
normalize-range "^0.1.2" normalize-range "^0.1.2"
num2fraction "^1.2.2" num2fraction "^1.2.2"
postcss "^7.0.18" postcss "^7.0.16"
postcss-value-parser "^4.0.2" postcss-value-parser "^3.3.1"
aws-sign2@~0.7.0: aws-sign2@~0.7.0:
version "0.7.0" version "0.7.0"
@ -949,7 +959,7 @@ browserify-zlib@~0.2.0:
dependencies: dependencies:
pako "~1.0.5" pako "~1.0.5"
browserify@^16.5.0: browserify@16.5.0:
version "16.5.0" version "16.5.0"
resolved "https://registry.yarnpkg.com/browserify/-/browserify-16.5.0.tgz#a1c2bc0431bec11fd29151941582e3f645ede881" resolved "https://registry.yarnpkg.com/browserify/-/browserify-16.5.0.tgz#a1c2bc0431bec11fd29151941582e3f645ede881"
integrity sha512-6bfI3cl76YLAnCZ75AGu/XPOsqUhRyc0F/olGIJeCxtfxF2HvPKEcmjU9M8oAPxl4uBY1U7Nry33Q6koV3f2iw== integrity sha512-6bfI3cl76YLAnCZ75AGu/XPOsqUhRyc0F/olGIJeCxtfxF2HvPKEcmjU9M8oAPxl4uBY1U7Nry33Q6koV3f2iw==
@ -1003,7 +1013,7 @@ browserify@^16.5.0:
vm-browserify "^1.0.0" vm-browserify "^1.0.0"
xtend "^4.0.0" xtend "^4.0.0"
browserslist@^4.7.0: browserslist@^4.6.1:
version "4.7.0" version "4.7.0"
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.7.0.tgz#9ee89225ffc07db03409f2fee524dc8227458a17" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.7.0.tgz#9ee89225ffc07db03409f2fee524dc8227458a17"
integrity sha512-9rGNDtnj+HaahxiVV38Gn8n8Lr8REKsel68v1sPFfIGEK6uSXTY3h9acgiT1dZVtOOUtifo/Dn8daDQ5dUgVsA== integrity sha512-9rGNDtnj+HaahxiVV38Gn8n8Lr8REKsel68v1sPFfIGEK6uSXTY3h9acgiT1dZVtOOUtifo/Dn8daDQ5dUgVsA==
@ -1108,7 +1118,7 @@ camelcase@^5.0.0, camelcase@^5.3.1:
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320"
integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
caniuse-lite@^1.0.30000989, caniuse-lite@^1.0.30000998: caniuse-lite@^1.0.30000971, caniuse-lite@^1.0.30000989:
version "1.0.30000999" version "1.0.30000999"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000999.tgz#427253a69ad7bea4aa8d8345687b8eec51ca0e43" resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000999.tgz#427253a69ad7bea4aa8d8345687b8eec51ca0e43"
integrity sha512-1CUyKyecPeksKwXZvYw0tEoaMCo/RwBlXmEtN5vVnabvO0KPd9RQLcaAuR9/1F+KDMv6esmOFWlsXuzDk+8rxg== integrity sha512-1CUyKyecPeksKwXZvYw0tEoaMCo/RwBlXmEtN5vVnabvO0KPd9RQLcaAuR9/1F+KDMv6esmOFWlsXuzDk+8rxg==
@ -1186,7 +1196,7 @@ class-utils@^0.3.5:
isobject "^3.0.0" isobject "^3.0.0"
static-extend "^0.1.1" static-extend "^0.1.1"
clean-css-cli@^4.3.0: clean-css-cli@4.3.0:
version "4.3.0" version "4.3.0"
resolved "https://registry.yarnpkg.com/clean-css-cli/-/clean-css-cli-4.3.0.tgz#8502aa86d1879e5b111af51b3c2abb799e0684ce" resolved "https://registry.yarnpkg.com/clean-css-cli/-/clean-css-cli-4.3.0.tgz#8502aa86d1879e5b111af51b3c2abb799e0684ce"
integrity sha512-8GHZfr+mG3zB/Lgqrr27qHBFsPSn0fyEI3f2rIZpxPxUbn2J6A8xyyeBRVTW8duDuXigN0s80vsXiXJOEFIO5Q== integrity sha512-8GHZfr+mG3zB/Lgqrr27qHBFsPSn0fyEI3f2rIZpxPxUbn2J6A8xyyeBRVTW8duDuXigN0s80vsXiXJOEFIO5Q==
@ -1237,7 +1247,7 @@ co@^4.6.0:
resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
integrity sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ= integrity sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=
codacy-coverage@^3.4.0: codacy-coverage@3.4.0:
version "3.4.0" version "3.4.0"
resolved "https://registry.yarnpkg.com/codacy-coverage/-/codacy-coverage-3.4.0.tgz#196af70844c4e4179718f7a7f9d96b921b4b3a67" resolved "https://registry.yarnpkg.com/codacy-coverage/-/codacy-coverage-3.4.0.tgz#196af70844c4e4179718f7a7f9d96b921b4b3a67"
integrity sha512-A0ats3/gZtOw76muu++HZ6QrInztWjjLefkLJmmBpjPfyn6nNwNLoApmGmj3F3dfgl2+o6u5GwPnUBkKdfKXTQ== integrity sha512-A0ats3/gZtOw76muu++HZ6QrInztWjjLefkLJmmBpjPfyn6nNwNLoApmGmj3F3dfgl2+o6u5GwPnUBkKdfKXTQ==
@ -1299,10 +1309,15 @@ commander@2.15.1:
resolved "https://registry.yarnpkg.com/commander/-/commander-2.15.1.tgz#df46e867d0fc2aec66a34662b406a9ccafff5b0f" resolved "https://registry.yarnpkg.com/commander/-/commander-2.15.1.tgz#df46e867d0fc2aec66a34662b406a9ccafff5b0f"
integrity sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag== integrity sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==
commander@2.x, commander@^2.20.0, commander@^2.x, commander@~2.20.0: commander@2.20.0:
version "2.20.1" version "2.20.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.1.tgz#3863ce3ca92d0831dcf2a102f5fb4b5926afd0f9" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.0.tgz#d58bb2b5c1ee8f87b0d340027e9e94e222c5a422"
integrity sha512-cCuLsMhJeWQ/ZpsFTbE765kvVfoeSddc4nU3up4fV+fDBcfUXnbITJ+JzhkdjzOqhURjZgujxaioam4RM9yGUg== integrity sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==
commander@2.x, commander@^2.20.0, commander@^2.x:
version "2.20.3"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
component-emitter@^1.2.1: component-emitter@^1.2.1:
version "1.3.0" version "1.3.0"
@ -1619,7 +1634,7 @@ diff@3.5.0:
resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12"
integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA== integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==
diff@^4.0.1: diff@4.0.1:
version "4.0.1" version "4.0.1"
resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.1.tgz#0c667cb467ebbb5cea7f14f135cc2dba7780a8ff" resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.1.tgz#0c667cb467ebbb5cea7f14f135cc2dba7780a8ff"
integrity sha512-s2+XdvhPCOF01LRQBC8hf4vhbVmI2CGS5aZnxLJlT5FtdhPCDFq80q++zK2KlrVorVDdL5BOGZ/VfLrVtYNF+Q== integrity sha512-s2+XdvhPCOF01LRQBC8hf4vhbVmI2CGS5aZnxLJlT5FtdhPCDFq80q++zK2KlrVorVDdL5BOGZ/VfLrVtYNF+Q==
@ -1683,9 +1698,9 @@ ecc-jsbn@~0.1.1:
safer-buffer "^2.1.0" safer-buffer "^2.1.0"
electron-to-chromium@^1.3.247: electron-to-chromium@^1.3.247:
version "1.3.275" version "1.3.281"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.275.tgz#19a38436e34216f51820fa2f4326d5ce141fa36f" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.281.tgz#2dbeb9f0bdffddb1662f9ca00d26c49d31dc0f7e"
integrity sha512-/YWtW/VapMnuYA1lNOaa1F4GhR1LBf+CUTp60lzDPEEh0XOzyOAyULyYZVF9vziZ3qSbTqCwmKwsyRXp66STbw== integrity sha512-oxXKngPjTWRmXFy4vV9FeAkPl7wU4xMejfOY+HXjGrj4T0z9l96loWWVDLJEtbT/aPKOWKrSz6xoYxd+YJ/gJA==
elliptic@^6.0.0: elliptic@^6.0.0:
version "6.5.1" version "6.5.1"
@ -1845,12 +1860,12 @@ eslint-plugin-prettier@3.1.0:
dependencies: dependencies:
prettier-linter-helpers "^1.0.0" prettier-linter-helpers "^1.0.0"
eslint-plugin-promise@^4.2.1: eslint-plugin-promise@4.2.1:
version "4.2.1" version "4.2.1"
resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-4.2.1.tgz#845fd8b2260ad8f82564c1222fce44ad71d9418a" resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-4.2.1.tgz#845fd8b2260ad8f82564c1222fce44ad71d9418a"
integrity sha512-VoM09vT7bfA7D+upt+FjeBO5eHIJQBUWki1aPvB+vbNiHS3+oGIJGIeyBtKQTME6UPXXy3vV07OL1tHd3ANuDw== integrity sha512-VoM09vT7bfA7D+upt+FjeBO5eHIJQBUWki1aPvB+vbNiHS3+oGIJGIeyBtKQTME6UPXXy3vV07OL1tHd3ANuDw==
eslint-plugin-standard@^4.0.1: eslint-plugin-standard@4.0.1:
version "4.0.1" version "4.0.1"
resolved "https://registry.yarnpkg.com/eslint-plugin-standard/-/eslint-plugin-standard-4.0.1.tgz#ff0519f7ffaff114f76d1bd7c3996eef0f6e20b4" resolved "https://registry.yarnpkg.com/eslint-plugin-standard/-/eslint-plugin-standard-4.0.1.tgz#ff0519f7ffaff114f76d1bd7c3996eef0f6e20b4"
integrity sha512-v/KBnfyaOMPmZc/dmc6ozOdWqekGp7bBGq4jLAecEfPGmfKiWS4sA8sC0LqiV9w5qmXAtXVn4M3p1jSyhY85SQ== integrity sha512-v/KBnfyaOMPmZc/dmc6ozOdWqekGp7bBGq4jLAecEfPGmfKiWS4sA8sC0LqiV9w5qmXAtXVn4M3p1jSyhY85SQ==
@ -2105,7 +2120,7 @@ fast-glob@^2.2.6:
merge2 "^1.2.3" merge2 "^1.2.3"
micromatch "^3.1.10" micromatch "^3.1.10"
fast-html-parser@^1.0.1: fast-html-parser@1.0.1:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/fast-html-parser/-/fast-html-parser-1.0.1.tgz#4ecc9683b8bb79afe11a50807b7853e79256cea2" resolved "https://registry.yarnpkg.com/fast-html-parser/-/fast-html-parser-1.0.1.tgz#4ecc9683b8bb79afe11a50807b7853e79256cea2"
integrity sha1-TsyWg7i7ea/hGlCAe3hT55JWzqI= integrity sha1-TsyWg7i7ea/hGlCAe3hT55JWzqI=
@ -2379,9 +2394,9 @@ growly@^1.3.0:
integrity sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE= integrity sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=
handlebars@^4.1.2: handlebars@^4.1.2:
version "4.4.2" version "4.4.3"
resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.4.2.tgz#8810a9821a9d6d52cb2f57d326d6ce7c3dfe741d" resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.4.3.tgz#180bae52c1d0e9ec0c15d7e82a4362d662762f6e"
integrity sha512-cIv17+GhL8pHHnRJzGu2wwcthL5sb8uDKBHvZ2Dtu5s1YNt0ljbzKbamnc+gr69y7bzwQiBdr5+hOpRd5pnOdg== integrity sha512-B0W4A2U1ww3q7VVthTKfh+epHx+q4mCt6iK+zEAzbMBpWQAwxCeKxEGpj/1oQTpzPXDNSOG7hmG14TsISH50yw==
dependencies: dependencies:
neo-async "^2.6.0" neo-async "^2.6.0"
optimist "^0.6.1" optimist "^0.6.1"
@ -2476,6 +2491,11 @@ he@1.1.1:
resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd" resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd"
integrity sha1-k0EP0hsAlzUVH4howvJx80J+I/0= integrity sha1-k0EP0hsAlzUVH4howvJx80J+I/0=
highlight.js@9.15.10:
version "9.15.10"
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.15.10.tgz#7b18ed75c90348c045eef9ed08ca1319a2219ad2"
integrity sha512-RoV7OkQm0T3os3Dd2VHLNMoaoDVx77Wygln3n9l5YV172XonWG6rgQD3XnF/BuFFZw9A0TJgmMSO8FEWQgvcXw==
hmac-drbg@^1.0.0: hmac-drbg@^1.0.0:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1"
@ -2495,7 +2515,7 @@ hoek@6.x.x:
resolved "https://registry.yarnpkg.com/hoek/-/hoek-6.1.3.tgz#73b7d33952e01fe27a38b0457294b79dd8da242c" resolved "https://registry.yarnpkg.com/hoek/-/hoek-6.1.3.tgz#73b7d33952e01fe27a38b0457294b79dd8da242c"
integrity sha512-YXXAAhmF9zpQbC7LEcREFtXfGq5K1fmd+4PHkBq8NUqmzW3G+Dq10bI/i0KucLRwss3YYFQ0fSfoxBZYiGUqtQ== integrity sha512-YXXAAhmF9zpQbC7LEcREFtXfGq5K1fmd+4PHkBq8NUqmzW3G+Dq10bI/i0KucLRwss3YYFQ0fSfoxBZYiGUqtQ==
hogan.js@^3.0.2: hogan.js@3.0.2:
version "3.0.2" version "3.0.2"
resolved "https://registry.yarnpkg.com/hogan.js/-/hogan.js-3.0.2.tgz#4cd9e1abd4294146e7679e41d7898732b02c7bfd" resolved "https://registry.yarnpkg.com/hogan.js/-/hogan.js-3.0.2.tgz#4cd9e1abd4294146e7679e41d7898732b02c7bfd"
integrity sha1-TNnhq9QpQUbnZ55B14mHMrAse/0= integrity sha1-TNnhq9QpQUbnZ55B14mHMrAse/0=
@ -2504,9 +2524,9 @@ hogan.js@^3.0.2:
nopt "1.0.10" nopt "1.0.10"
hosted-git-info@^2.1.4: hosted-git-info@^2.1.4:
version "2.8.4" version "2.8.5"
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.4.tgz#44119abaf4bc64692a16ace34700fed9c03e2546" resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.5.tgz#759cfcf2c4d156ade59b0b2dfabddc42a6b9c70c"
integrity sha512-pzXIvANXEFrc5oFFXRMkbLPQ2rXRoDERwDLyrcUxGhaZhgP54BBSl9Oheh7Vv0T090cszWBxPjkQQ5Sq1PbBRQ== integrity sha512-kssjab8CvdXfcXMXVcvsXum4Hwdq9XGtRD3TteMEvEbq0LXyiNQr6AprqKqfeaDXze7SxWvRxdpwE6ku7ikLkg==
html-encoding-sniffer@^1.0.2: html-encoding-sniffer@^1.0.2:
version "1.0.2" version "1.0.2"
@ -2547,9 +2567,9 @@ ieee754@^1.1.4:
integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg== integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==
ignore-walk@^3.0.1: ignore-walk@^3.0.1:
version "3.0.2" version "3.0.3"
resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.2.tgz#99d83a246c196ea5c93ef9315ad7b0819c35069b" resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.3.tgz#017e2447184bfeade7c238e4aefdd1e8f95b1e37"
integrity sha512-EXyErtpHbn75ZTsOADsfx6J/FPo6/5cjev46PXrcTpd8z3BoRkXgYu9/JVqrI7tusjmwCZutGeRJeU0Wo1e4Cw== integrity sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==
dependencies: dependencies:
minimatch "^3.0.4" minimatch "^3.0.4"
@ -3548,6 +3568,11 @@ locate-path@^3.0.0:
p-locate "^3.0.0" p-locate "^3.0.0"
path-exists "^3.0.0" path-exists "^3.0.0"
lodash.memoize@4.x:
version "4.1.2"
resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=
lodash.memoize@~3.0.3: lodash.memoize@~3.0.3:
version "3.0.4" version "3.0.4"
resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-3.0.4.tgz#2dcbd2c287cbc0a55cc42328bd0c736150d53e3f" resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-3.0.4.tgz#2dcbd2c287cbc0a55cc42328bd0c736150d53e3f"
@ -3654,11 +3679,6 @@ merge2@^1.2.3:
resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.3.0.tgz#5b366ee83b2f1582c48f87e47cf1a9352103ca81" resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.3.0.tgz#5b366ee83b2f1582c48f87e47cf1a9352103ca81"
integrity sha512-2j4DAdlBOkiSZIsaXk4mTE3sRS02yBHAtfy127xRV3bQUFqXkjHCHLW6Scv7DwNRbIWNHH8zpnz9zMaKXIdvYw== integrity sha512-2j4DAdlBOkiSZIsaXk4mTE3sRS02yBHAtfy127xRV3bQUFqXkjHCHLW6Scv7DwNRbIWNHH8zpnz9zMaKXIdvYw==
merge@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/merge/-/merge-1.2.1.tgz#38bebf80c3220a8a487b6fcfb3941bb11720c145"
integrity sha512-VjFo4P5Whtj4vsLzsYBu5ayHhoHJ0UqNm7ibvShmbmoz7tGi0vXaoJbGdB+GmDMLUdg8DpQXEIeVDAe8MaABvQ==
micromatch@^3.1.10, micromatch@^3.1.4: micromatch@^3.1.10, micromatch@^3.1.4:
version "3.1.10" version "3.1.10"
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23"
@ -3912,9 +3932,9 @@ node-pre-gyp@^0.12.0:
tar "^4" tar "^4"
node-releases@^1.1.29: node-releases@^1.1.29:
version "1.1.34" version "1.1.35"
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.34.tgz#ced4655ee1ba9c3a2c5dcbac385e19434155fd40" resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.35.tgz#32a74a3cd497aa77f23d509f483475fd160e4c48"
integrity sha512-fNn12JTEfniTuCqo0r9jXgl44+KxRH/huV7zM/KAGOKxDKrHr6EbT7SSs4B+DNxyBE2mks28AD+Jw6PkfY5uwA== integrity sha512-JGcM/wndCN/2elJlU0IGdVEJQQnJwsLbgPCFd2pY7V0mxf17bZ0Gb/lgOtL29ZQhvEX5shnVhxQyZz3ex94N8w==
dependencies: dependencies:
semver "^6.3.0" semver "^6.3.0"
@ -3925,7 +3945,7 @@ nopt@1.0.10:
dependencies: dependencies:
abbrev "1" abbrev "1"
nopt@^4.0.1: nopt@4.0.1, nopt@^4.0.1:
version "4.0.1" version "4.0.1"
resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d" resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d"
integrity sha1-0NRoWv1UFRk8jHUFYC0NF81kR00= integrity sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=
@ -3966,9 +3986,9 @@ npm-bundled@^1.0.1:
integrity sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g== integrity sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g==
npm-packlist@^1.1.6: npm-packlist@^1.1.6:
version "1.4.4" version "1.4.6"
resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.4.4.tgz#866224233850ac534b63d1a6e76050092b5d2f44" resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.4.6.tgz#53ba3ed11f8523079f1457376dd379ee4ea42ff4"
integrity sha512-zTLo8UcVYtDU3gdeaFu2Xu0n0EvelfHDGuqtNIn5RO7yQj4H1TqNdBc/yZjxnWA0PVB8D3Woyp0i5B43JwQ6Vw== integrity sha512-u65uQdb+qwtGvEJh/DgQgW1Xg7sqeNbmxYyrvlNznaVTjV3E5P6F/EFjM+BVHXl7JJlsdG8A64M0XI8FI/IOlg==
dependencies: dependencies:
ignore-walk "^3.0.1" ignore-walk "^3.0.1"
npm-bundled "^1.0.1" npm-bundled "^1.0.1"
@ -4364,7 +4384,7 @@ posix-character-classes@^0.1.0:
resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab"
integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs= integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=
postcss-cli@^6.1.3: postcss-cli@6.1.3:
version "6.1.3" version "6.1.3"
resolved "https://registry.yarnpkg.com/postcss-cli/-/postcss-cli-6.1.3.tgz#a9eec3e9cde4aaa90170546baf706f8af6f8ecec" resolved "https://registry.yarnpkg.com/postcss-cli/-/postcss-cli-6.1.3.tgz#a9eec3e9cde4aaa90170546baf706f8af6f8ecec"
integrity sha512-eieqJU+OR1OFc/lQqMsDmROTJpoMZFvoAQ+82utBQ8/8qGMTfH9bBSPsTdsagYA8uvNzxHw2I2cNSSJkLAGhvw== integrity sha512-eieqJU+OR1OFc/lQqMsDmROTJpoMZFvoAQ+82utBQ8/8qGMTfH9bBSPsTdsagYA8uvNzxHw2I2cNSSJkLAGhvw==
@ -4400,12 +4420,12 @@ postcss-reporter@^6.0.0:
log-symbols "^2.2.0" log-symbols "^2.2.0"
postcss "^7.0.7" postcss "^7.0.7"
postcss-value-parser@^4.0.2: postcss-value-parser@^3.3.1:
version "4.0.2" version "3.3.1"
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.0.2.tgz#482282c09a42706d1fc9a069b73f44ec08391dc9" resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz#9ff822547e2893213cf1c30efa51ac5fd1ba8281"
integrity sha512-LmeoohTpp/K4UiyQCwuGWlONxXamGzCMtFxLq4W1nZVGIQLYvMCJx3yAF9qyyuFpflABI9yVdtJAqbihOsCsJQ== integrity sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==
postcss@^7.0.0, postcss@^7.0.18, postcss@^7.0.7: postcss@^7.0.0, postcss@^7.0.16, postcss@^7.0.7:
version "7.0.18" version "7.0.18"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.18.tgz#4b9cda95ae6c069c67a4d933029eddd4838ac233" resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.18.tgz#4b9cda95ae6c069c67a4d933029eddd4838ac233"
integrity sha512-/7g1QXXgegpF+9GJj4iN7ChGF40sYuGYJ8WZu8DZWnmhQ/G36hfdk3q9LBJmoK+lZ+yzZ5KYpOoxq7LF1BxE8g== integrity sha512-/7g1QXXgegpF+9GJj4iN7ChGF40sYuGYJ8WZu8DZWnmhQ/G36hfdk3q9LBJmoK+lZ+yzZ5KYpOoxq7LF1BxE8g==
@ -5299,7 +5319,7 @@ tar@^4:
safe-buffer "^5.1.2" safe-buffer "^5.1.2"
yallist "^3.0.3" yallist "^3.0.3"
terser@^4.3.8: terser@4.3.8:
version "4.3.8" version "4.3.8"
resolved "https://registry.yarnpkg.com/terser/-/terser-4.3.8.tgz#707f05f3f4c1c70c840e626addfdb1c158a17136" resolved "https://registry.yarnpkg.com/terser/-/terser-4.3.8.tgz#707f05f3f4c1c70c840e626addfdb1c158a17136"
integrity sha512-otmIRlRVmLChAWsnSFNO0Bfk6YySuBp6G9qrHiJwlLDd4mxe2ta4sjI7TzIR+W1nBMjilzrMcPOz9pSusgx3hQ== integrity sha512-otmIRlRVmLChAWsnSFNO0Bfk6YySuBp6G9qrHiJwlLDd4mxe2ta4sjI7TzIR+W1nBMjilzrMcPOz9pSusgx3hQ==
@ -5420,15 +5440,16 @@ tr46@^1.0.1:
dependencies: dependencies:
punycode "^2.1.0" punycode "^2.1.0"
ts-jest@24.0.2: ts-jest@24.1.0:
version "24.0.2" version "24.1.0"
resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-24.0.2.tgz#8dde6cece97c31c03e80e474c749753ffd27194d" resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-24.1.0.tgz#2eaa813271a2987b7e6c3fefbda196301c131734"
integrity sha512-h6ZCZiA1EQgjczxq+uGLXQlNgeg02WWJBbeT8j6nyIBRQdglqbvzDoHahTEIiS6Eor6x8mK6PfZ7brQ9Q6tzHw== integrity sha512-HEGfrIEAZKfu1pkaxB9au17b1d9b56YZSqz5eCVE8mX68+5reOvlM93xGOzzCREIov9mdH7JBG+s0UyNAqr0tQ==
dependencies: dependencies:
bs-logger "0.x" bs-logger "0.x"
buffer-from "1.x" buffer-from "1.x"
fast-json-stable-stringify "2.x" fast-json-stable-stringify "2.x"
json5 "2.x" json5 "2.x"
lodash.memoize "4.x"
make-error "1.x" make-error "1.x"
mkdirp "0.x" mkdirp "0.x"
resolve "1.x" resolve "1.x"
@ -5476,17 +5497,17 @@ typedarray@^0.0.6:
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
typescript@^3.6.3: typescript@3.6.4:
version "3.6.3" version "3.6.4"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.6.3.tgz#fea942fabb20f7e1ca7164ff626f1a9f3f70b4da" resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.6.4.tgz#b18752bb3792bc1a0281335f7f6ebf1bbfc5b91d"
integrity sha512-N7bceJL1CtRQ2RiG0AQME13ksR7DiuQh/QehubYcghzv20tnh+MQnQIuJddTmsbqYj+dztchykemz0zFzlvdQw== integrity sha512-unoCll1+l+YK4i4F8f22TaNVPRHcD9PA3yCuZ8g5e0qGqlVlJ/8FSateOLLSagn+Yg5+ZwuPkL8LFUc0Jcvksg==
uglify-js@^3.1.4: uglify-js@^3.1.4:
version "3.6.0" version "3.6.1"
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.6.0.tgz#704681345c53a8b2079fb6cec294b05ead242ff5" resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.6.1.tgz#ae7688c50e1bdcf2f70a0e162410003cf9798311"
integrity sha512-W+jrUHJr3DXKhrsS7NUVxn3zqMOFn0hL/Ei6v0anCIMoKC93TjcflTagwIHLW7SfMFfiQuktQyFVCFHGUE0+yg== integrity sha512-+dSJLJpXBb6oMHP+Yvw8hUgElz4gLTh82XuX68QiJVTXaE5ibl6buzhNkQdYhBlIhozWOC9ge16wyRmjG4TwVQ==
dependencies: dependencies:
commander "~2.20.0" commander "2.20.0"
source-map "~0.6.1" source-map "~0.6.1"
umd@^3.0.0: umd@^3.0.0:
@ -5643,7 +5664,7 @@ whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.3:
dependencies: dependencies:
iconv-lite "0.4.24" iconv-lite "0.4.24"
whatwg-fetch@^3.0.0: whatwg-fetch@3.0.0:
version "3.0.0" version "3.0.0"
resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz#fc804e458cc460009b1a2b966bc8817d2578aefb" resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz#fc804e458cc460009b1a2b966bc8817d2578aefb"
integrity sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q== integrity sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q==