refactor: Unify escaping

This commit is contained in:
Rodrigo Fernandes 2019-11-26 09:44:09 +00:00
parent f8f5c10c57
commit 0f08c85938
No known key found for this signature in database
GPG key ID: 67157D2E3D4258B4
7 changed files with 188 additions and 200 deletions

View file

@ -299,7 +299,7 @@ describe("Diff2Html", () => {
' $a="<table><tr><td>\n' + ' $a="<table><tr><td>\n' +
" $a=\"<table><tr><td>- 1.1.9: Fix around ubuntu's inability to cache promises. [#877](https://github.com/FredrikNoren/ungit/pull/878)\n" + " $a=\"<table><tr><td>- 1.1.9: Fix around ubuntu's inability to cache promises. [#877](https://github.com/FredrikNoren/ungit/pull/878)\n" +
" - 1.1.8:\n" + " - 1.1.8:\n" +
"@@ -11,7 +10,7 @@ $a=\"<table><tr><td>- 1.1.9: Fix around ubuntu's inability to cache promises. [#8\n" + "@@ -11,7 +10,7 @@ $a=&quot;&lt;table&gt;&lt;tr&gt;&lt;td&gt;- 1.1.9: Fix around ubuntu&#x27;s inability to cache promises. [#8\n" +
" - 1.1.7:\n" + " - 1.1.7:\n" +
" - Fix diff flickering issue and optimization [#865](https://github.com/FredrikNoren/ungit/pull/865)\n" + " - Fix diff flickering issue and optimization [#865](https://github.com/FredrikNoren/ungit/pull/865)\n" +
" - Fix credential dialog issue [#864](https://github.com/FredrikNoren/ungit/pull/864)\n" + " - Fix credential dialog issue [#864](https://github.com/FredrikNoren/ungit/pull/864)\n" +

View file

@ -1,17 +1,50 @@
import * as renderUtils from "../render-utils"; import { escapeForHtml, getHtmlId, filenameDiff, diffHighlight } from "../render-utils";
import { DiffStyleType, LineMatchingType } from "../types"; import { DiffStyleType, LineMatchingType } from "../types";
describe("Utils", () => { describe("Utils", () => {
describe("escapeForHtml", () => {
it("should escape & with &amp;", () => {
const result = escapeForHtml("&");
expect(result).toEqual("&amp;");
});
it("should escape < with &lt;", () => {
const result = escapeForHtml("<");
expect(result).toEqual("&lt;");
});
it("should escape > with &gt;", () => {
const result = escapeForHtml(">");
expect(result).toEqual("&gt;");
});
it('should escape " with &quot;', () => {
const result = escapeForHtml('"');
expect(result).toEqual("&quot;");
});
it("should escape ' with &#x27;", () => {
const result = escapeForHtml("'");
expect(result).toEqual("&#x27;");
});
it("should escape / with &#x2F;", () => {
const result = escapeForHtml("/");
expect(result).toEqual("&#x2F;");
});
it("should escape a string containing HTML code", () => {
const result = escapeForHtml(`<a href="/search?q=diff2html">Search 'Diff2Html'</a>`);
expect(result).toEqual(
"&lt;a href=&quot;&#x2F;search?q=diff2html&quot;&gt;Search &#x27;Diff2Html&#x27;&lt;&#x2F;a&gt;"
);
});
});
describe("getHtmlId", () => { describe("getHtmlId", () => {
it("should generate file unique id", () => { it("should generate file unique id", () => {
const result = renderUtils.getHtmlId({ const result = 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", () => { it("should generate file unique id for empty hashes", () => {
const result = renderUtils.getHtmlId({ const result = getHtmlId({
oldName: "sample.js", oldName: "sample.js",
newName: "sample.js" newName: "sample.js"
}); });
@ -21,49 +54,49 @@ describe("Utils", () => {
describe("getDiffName", () => { describe("getDiffName", () => {
it("should generate the file name for a changed file", () => { it("should generate the file name for a changed file", () => {
const result = renderUtils.filenameDiff({ const result = 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", () => { it("should generate the file name for a changed file and full rename", () => {
const result = renderUtils.filenameDiff({ const result = 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", () => { it("should generate the file name for a changed file and prefix rename", () => {
const result = renderUtils.filenameDiff({ const result = 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", () => { it("should generate the file name for a changed file and suffix rename", () => {
const result = renderUtils.filenameDiff({ const result = 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", () => { it("should generate the file name for a changed file and middle rename", () => {
const result = renderUtils.filenameDiff({ const result = 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", () => { it("should generate the file name for a deleted file", () => {
const result = renderUtils.filenameDiff({ const result = 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", () => { it("should generate the file name for a new file", () => {
const result = renderUtils.filenameDiff({ const result = filenameDiff({
oldName: "/dev/null", oldName: "/dev/null",
newName: "src/my/file.js" newName: "src/my/file.js"
}); });
@ -73,7 +106,7 @@ describe("Utils", () => {
describe("diffHighlight", () => { describe("diffHighlight", () => {
it("should highlight two lines", () => { it("should highlight two lines", () => {
const result = renderUtils.diffHighlight("-var myVar = 2;", "+var myVariable = 3;", false, { const result = diffHighlight("-var myVar = 2;", "+var myVariable = 3;", false, {
matching: LineMatchingType.WORDS matching: LineMatchingType.WORDS
}); });
@ -89,7 +122,7 @@ describe("Utils", () => {
}); });
}); });
it("should highlight two lines char by char", () => { it("should highlight two lines char by char", () => {
const result = renderUtils.diffHighlight("-var myVar = 2;", "+var myVariable = 3;", false, { const result = diffHighlight("-var myVar = 2;", "+var myVariable = 3;", false, {
diffStyle: DiffStyleType.CHAR diffStyle: DiffStyleType.CHAR
}); });
@ -105,7 +138,7 @@ describe("Utils", () => {
}).toEqual(result); }).toEqual(result);
}); });
it("should highlight combined diff lines", () => { it("should highlight combined diff lines", () => {
const result = renderUtils.diffHighlight(" -var myVar = 2;", " +var myVariable = 3;", true, { const result = diffHighlight(" -var myVar = 2;", " +var myVariable = 3;", true, {
diffStyle: DiffStyleType.WORD, diffStyle: DiffStyleType.WORD,
matching: LineMatchingType.WORDS, matching: LineMatchingType.WORDS,
matchWordsThreshold: 1.0 matchWordsThreshold: 1.0

View file

@ -1,4 +1,4 @@
import { escapeForRegExp, escapeForHtml, unifyPath, hashCode } from "../utils"; import { escapeForRegExp, unifyPath, hashCode } from "../utils";
describe("Utils", () => { describe("Utils", () => {
describe("escapeForRegExp", () => { describe("escapeForRegExp", () => {
@ -12,53 +12,20 @@ describe("Utils", () => {
}); });
}); });
describe("escapeForHtml", () => { describe("unifyPath", () => {
it("should escape & with &amp;", () => { it("should unify windows style path", () => {
const result = escapeForHtml("&"); const result = unifyPath("\\Users\\Downloads\\diff.html");
expect(result).toEqual("&amp;"); expect(result).toEqual("/Users/Downloads/diff.html");
});
it("should escape < with &lt;", () => {
const result = escapeForHtml("<");
expect(result).toEqual("&lt;");
});
it("should escape > with &gt;", () => {
const result = escapeForHtml(">");
expect(result).toEqual("&gt;");
});
it('should escape " with &quot;', () => {
const result = escapeForHtml('"');
expect(result).toEqual("&quot;");
});
it("should escape ' with &#x27;", () => {
const result = escapeForHtml("'");
expect(result).toEqual("&#x27;");
});
it("should escape / with &#x2F;", () => {
const result = escapeForHtml("/");
expect(result).toEqual("&#x2F;");
});
it("should escape a string containing HTML code", () => {
const result = escapeForHtml(`<a href="/search?q=diff2html">Search 'Diff2Html'</a>`);
expect(result).toEqual(
"&lt;a href=&quot;&#x2F;search?q=diff2html&quot;&gt;Search &#x27;Diff2Html&#x27;&lt;&#x2F;a&gt;"
);
}); });
});
describe("unifyPath", () => { describe("hashCode", () => {
it("should unify windows style path", () => { it("should create consistent hash for a text piece", () => {
const result = unifyPath("\\Users\\Downloads\\diff.html"); const string = "/home/diff2html/diff.html";
expect(result).toEqual("/Users/Downloads/diff.html"); expect(hashCode(string)).toEqual(hashCode(string));
});
}); });
it("should create different hash for different text pieces", () => {
describe("hashCode", () => { expect(hashCode("/home/diff2html/diff1.html")).not.toEqual(hashCode("/home/diff2html/diff2.html"));
it("should create consistent hash for a text piece", () => {
const string = "/home/diff2html/diff.html";
expect(hashCode(string)).toEqual(hashCode(string));
});
it("should create different hash for different text pieces", () => {
expect(hashCode("/home/diff2html/diff1.html")).not.toEqual(hashCode("/home/diff2html/diff2.html"));
});
}); });
}); });
}); });

View file

@ -1,8 +1,7 @@
import * as utils from "./utils";
import HoganJsUtils from "./hoganjs-utils"; import HoganJsUtils from "./hoganjs-utils";
import * as Rematch from "./rematch"; import * as Rematch from "./rematch";
import * as renderUtils from "./render-utils"; import * as renderUtils from "./render-utils";
import { DiffFile, DiffBlock, DiffLine, LineType } from "./types"; import { DiffFile, DiffLine, LineType } from "./types";
export interface LineByLineRendererConfig extends renderUtils.RenderConfig { export interface LineByLineRendererConfig extends renderUtils.RenderConfig {
renderNothingWhenEmpty?: boolean; renderNothingWhenEmpty?: boolean;
@ -72,53 +71,6 @@ export default class LineByLineRenderer {
}); });
} }
// TODO: Make this private after improving tests
makeColumnLineNumberHtml(block: 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.content;
}
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 // TODO: Make this private after improving tests
generateEmptyDiff(): string { generateEmptyDiff(): string {
return this.hoganUtils.render(genericTemplatesPath, "empty-diff", { return this.hoganUtils.render(genericTemplatesPath, "empty-diff", {
@ -127,47 +79,20 @@ export default class LineByLineRenderer {
}); });
} }
// TODO: Make this private after improving tests
processLines(isCombined: boolean, oldLines: DiffLine[], newLines: 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 // TODO: Make this private after improving tests
generateFileHtml(file: DiffFile): string { generateFileHtml(file: DiffFile): string {
const distance = Rematch.newDistanceFn( const matcher = Rematch.newMatcherFn(
(e: DiffLine) => renderUtils.deconstructLine(e.content, file.isCombined).content Rematch.newDistanceFn((e: DiffLine) => renderUtils.deconstructLine(e.content, file.isCombined).content)
); );
const matcher = Rematch.newMatcherFn(distance);
return file.blocks return file.blocks
.map(block => { .map(block => {
let lines = this.makeColumnLineNumberHtml(block); let lines = this.hoganUtils.render(genericTemplatesPath, "column-line-number", {
CSSLineClass: renderUtils.CSSLineClass,
blockHeader: block.header,
lineClass: "d2h-code-linenumber",
contentClass: "d2h-code-line"
});
let oldLines: DiffLine[] = []; let oldLines: DiffLine[] = [];
let newLines: DiffLine[] = []; let newLines: DiffLine[] = [];
@ -242,8 +167,7 @@ export default class LineByLineRenderer {
for (let i = 0; i < block.lines.length; i++) { for (let i = 0; i < block.lines.length; i++) {
const diffLine = block.lines[i]; const diffLine = block.lines[i];
const { prefix, content: line } = renderUtils.deconstructLine(diffLine.content, file.isCombined); const { prefix, content } = renderUtils.deconstructLine(diffLine.content, file.isCombined);
const escapedLine = utils.escapeForHtml(line);
if ( if (
diffLine.type !== LineType.INSERT && diffLine.type !== LineType.INSERT &&
@ -256,7 +180,7 @@ export default class LineByLineRenderer {
lines += this.makeLineHtml( lines += this.makeLineHtml(
file.isCombined, file.isCombined,
renderUtils.toCSSClass(diffLine.type), renderUtils.toCSSClass(diffLine.type),
escapedLine, content,
diffLine.oldNumber, diffLine.oldNumber,
diffLine.newNumber, diffLine.newNumber,
prefix prefix
@ -265,7 +189,7 @@ export default class LineByLineRenderer {
lines += this.makeLineHtml( lines += this.makeLineHtml(
file.isCombined, file.isCombined,
renderUtils.toCSSClass(diffLine.type), renderUtils.toCSSClass(diffLine.type),
escapedLine, content,
diffLine.oldNumber, diffLine.oldNumber,
diffLine.newNumber, diffLine.newNumber,
prefix prefix
@ -286,4 +210,70 @@ export default class LineByLineRenderer {
}) })
.join("\n"); .join("\n");
} }
// 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.content;
}
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
processLines(isCombined: boolean, oldLines: DiffLine[], newLines: DiffLine[]): string {
let lines = "";
for (let i = 0; i < oldLines.length; i++) {
const oldLine = oldLines[i];
lines += this.makeLineHtml(
isCombined,
renderUtils.toCSSClass(oldLine.type),
oldLine.content,
oldLine.oldNumber,
oldLine.newNumber
);
}
for (let j = 0; j < newLines.length; j++) {
const newLine = newLines[j];
lines += this.makeLineHtml(
isCombined,
renderUtils.toCSSClass(newLine.type),
newLine.content,
newLine.oldNumber,
newLine.newNumber
);
}
return lines;
}
} }

View file

@ -1,6 +1,6 @@
import * as jsDiff from "diff"; import * as jsDiff from "diff";
import { unifyPath, escapeForHtml, hashCode } from "./utils"; import { unifyPath, hashCode } from "./utils";
import * as rematch from "./rematch"; import * as rematch from "./rematch";
import { LineMatchingType, DiffStyleType, LineType, DiffLineParts, DiffFile, DiffFileName } from "./types"; import { LineMatchingType, DiffStyleType, LineType, DiffLineParts, DiffFile, DiffFileName } from "./types";
@ -75,6 +75,21 @@ function prefixLength(isCombined: boolean): number {
return isCombined ? 2 : 1; return isCombined ? 2 : 1;
} }
/**
* Escapes all required characters for safe HTML rendering
*/
// TODO: Test this method inside deconstructLine since it should not be used anywhere else
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;");
}
/** /**
* Deconstructs diff @line by separating the content from the prefix type * Deconstructs diff @line by separating the content from the prefix type
*/ */
@ -82,7 +97,7 @@ export function deconstructLine(line: string, isCombined: boolean): DiffLinePart
const indexToSplit = prefixLength(isCombined); const indexToSplit = prefixLength(isCombined);
return { return {
prefix: line.substring(0, indexToSplit), prefix: line.substring(0, indexToSplit),
content: line.substring(indexToSplit) content: escapeForHtml(line.substring(indexToSplit))
}; };
} }
@ -209,11 +224,11 @@ export function diffHighlight(
return { return {
oldLine: { oldLine: {
prefix: line1.prefix, prefix: line1.prefix,
content: escapeForHtml(line1.content) content: line1.content
}, },
newLine: { newLine: {
prefix: line2.prefix, prefix: line2.prefix,
content: escapeForHtml(line2.content) content: line2.content
} }
}; };
} }
@ -248,11 +263,10 @@ export function diffHighlight(
const highlightedLine = diff.reduce((highlightedLine, part) => { const highlightedLine = diff.reduce((highlightedLine, part) => {
const elemType = part.added ? "ins" : part.removed ? "del" : null; const elemType = part.added ? "ins" : part.removed ? "del" : null;
const addClass = changedWords.indexOf(part) > -1 ? ' class="d2h-change"' : ""; const addClass = changedWords.indexOf(part) > -1 ? ' class="d2h-change"' : "";
const escapedValue = escapeForHtml(part.value);
return elemType !== null return elemType !== null
? `${highlightedLine}<${elemType}${addClass}>${escapedValue}</${elemType}>` ? `${highlightedLine}<${elemType}${addClass}>${part.value}</${elemType}>`
: `${highlightedLine}${escapedValue}`; : `${highlightedLine}${part.value}`;
}, ""); }, "");
return { return {

View file

@ -1,4 +1,3 @@
import * as utils from "./utils";
import HoganJsUtils from "./hoganjs-utils"; import HoganJsUtils from "./hoganjs-utils";
import * as Rematch from "./rematch"; import * as Rematch from "./rematch";
import * as renderUtils from "./render-utils"; import * as renderUtils from "./render-utils";
@ -47,20 +46,10 @@ export default class SideBySideRenderer {
return this.hoganUtils.render(genericTemplatesPath, "wrapper", { content: diffsHtml }); return this.hoganUtils.render(genericTemplatesPath, "wrapper", { content: diffsHtml });
} }
// 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 // TODO: Make this private after improving tests
makeFileDiffHtml(file: DiffFile, diffs: FileHtml): string { makeFileDiffHtml(file: DiffFile, diffs: FileHtml): string {
if (this.config.renderNothingWhenEmpty && Array.isArray(file.blocks) && file.blocks.length === 0) return "";
const fileDiffTemplate = this.hoganUtils.template(baseTemplatesPath, "file-diff"); const fileDiffTemplate = this.hoganUtils.template(baseTemplatesPath, "file-diff");
const filePathTemplate = this.hoganUtils.template(genericTemplatesPath, "file-path"); const filePathTemplate = this.hoganUtils.template(genericTemplatesPath, "file-path");
const fileIconTemplate = this.hoganUtils.template(iconsBaseTemplatesPath, "file"); const fileIconTemplate = this.hoganUtils.template(iconsBaseTemplatesPath, "file");
@ -83,21 +72,21 @@ export default class SideBySideRenderer {
} }
// TODO: Make this private after improving tests // TODO: Make this private after improving tests
makeSideHtml(blockHeader: string): string { generateEmptyDiff(): FileHtml {
return this.hoganUtils.render(genericTemplatesPath, "column-line-number", { return {
CSSLineClass: renderUtils.CSSLineClass, right: "",
blockHeader: utils.escapeForHtml(blockHeader), left: this.hoganUtils.render(genericTemplatesPath, "empty-diff", {
lineClass: "d2h-code-side-linenumber", contentClass: "d2h-code-side-line",
contentClass: "d2h-code-side-line" CSSLineClass: renderUtils.CSSLineClass
}); })
};
} }
// TODO: Make this private after improving tests // TODO: Make this private after improving tests
generateSideBySideFileHtml(file: DiffFile): FileHtml { generateSideBySideFileHtml(file: DiffFile): FileHtml {
const distance = Rematch.newDistanceFn( const matcher = Rematch.newMatcherFn(
(e: DiffLine) => renderUtils.deconstructLine(e.content, file.isCombined).content Rematch.newDistanceFn((e: DiffLine) => renderUtils.deconstructLine(e.content, file.isCombined).content)
); );
const matcher = Rematch.newMatcherFn(distance);
const fileHtml = { const fileHtml = {
right: "", right: "",
@ -183,8 +172,7 @@ export default class SideBySideRenderer {
for (let i = 0; i < block.lines.length; i++) { for (let i = 0; i < block.lines.length; i++) {
const diffLine = block.lines[i]; const diffLine = block.lines[i];
const { prefix, content: line } = renderUtils.deconstructLine(diffLine.content, file.isCombined); const { prefix, content } = renderUtils.deconstructLine(diffLine.content, file.isCombined);
const escapedLine = utils.escapeForHtml(line);
if ( if (
diffLine.type !== LineType.INSERT && diffLine.type !== LineType.INSERT &&
@ -197,14 +185,14 @@ export default class SideBySideRenderer {
fileHtml.left += this.generateSingleLineHtml( fileHtml.left += this.generateSingleLineHtml(
file.isCombined, file.isCombined,
renderUtils.toCSSClass(diffLine.type), renderUtils.toCSSClass(diffLine.type),
escapedLine, content,
diffLine.oldNumber, diffLine.oldNumber,
prefix prefix
); );
fileHtml.right += this.generateSingleLineHtml( fileHtml.right += this.generateSingleLineHtml(
file.isCombined, file.isCombined,
renderUtils.toCSSClass(diffLine.type), renderUtils.toCSSClass(diffLine.type),
escapedLine, content,
diffLine.newNumber, diffLine.newNumber,
prefix prefix
); );
@ -213,7 +201,7 @@ export default class SideBySideRenderer {
fileHtml.right += this.generateSingleLineHtml( fileHtml.right += this.generateSingleLineHtml(
file.isCombined, file.isCombined,
renderUtils.toCSSClass(diffLine.type), renderUtils.toCSSClass(diffLine.type),
escapedLine, content,
diffLine.newNumber, diffLine.newNumber,
prefix prefix
); );
@ -233,6 +221,16 @@ export default class SideBySideRenderer {
return fileHtml; return fileHtml;
} }
// TODO: Make this private after improving tests
makeSideHtml(blockHeader: string): string {
return this.hoganUtils.render(genericTemplatesPath, "column-line-number", {
CSSLineClass: renderUtils.CSSLineClass,
blockHeader: blockHeader,
lineClass: "d2h-code-side-linenumber",
contentClass: "d2h-code-side-line"
});
}
// TODO: Make this private after improving tests // TODO: Make this private after improving tests
processLines(isCombined: boolean, oldLines: DiffLine[], newLines: DiffLine[]): FileHtml { processLines(isCombined: boolean, oldLines: DiffLine[], newLines: DiffLine[]): FileHtml {
const fileHtml = { const fileHtml = {
@ -251,8 +249,8 @@ export default class SideBySideRenderer {
let newPrefix; let newPrefix;
if (oldLine) { if (oldLine) {
const { prefix, content: line } = renderUtils.deconstructLine(oldLine.content, isCombined); const { prefix, content } = renderUtils.deconstructLine(oldLine.content, isCombined);
oldContent = utils.escapeForHtml(line); oldContent = content;
oldPrefix = prefix; oldPrefix = prefix;
} else { } else {
oldContent = ""; oldContent = "";
@ -260,8 +258,8 @@ export default class SideBySideRenderer {
} }
if (newLine) { if (newLine) {
const { prefix, content: line } = renderUtils.deconstructLine(newLine.content, isCombined); const { prefix, content } = renderUtils.deconstructLine(newLine.content, isCombined);
newContent = utils.escapeForHtml(line); newContent = content;
newPrefix = prefix; newPrefix = prefix;
} else { } else {
newContent = ""; newContent = "";

View file

@ -30,20 +30,6 @@ export function escapeForRegExp(str: string): string {
return str.replace(regex, "\\$&"); 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 '/' * Converts all '\' in @path to unix style '/'
*/ */