diff --git a/README.md b/README.md index 9fe1c5e..f9c119d 100644 --- a/README.md +++ b/README.md @@ -149,6 +149,10 @@ The HTML output accepts a Javascript object with configuration. Possible options - `outputFormat`: the format of the output data: `'line-by-line'` or `'side-by-side'`, default is `'line-by-line'` - `drawFileList`: show a file list before the diff: `true` or `false`, default is `true` - `diffStyle`: show differences level in each line: `word` or `char`, default is `word` +- `diffMaxChanges`: number of changed lines after which a file diff is deemed as too big and not displayed, default is + `undefined` +- `diffTooBigMessage`: function allowing to customize the message in case of file diff too big (if `diffMaxChanges` is + set) - `matching`: matching level: `'lines'` for matching lines, `'words'` for matching lines and words or `'none'`, default is `none` - `matchWordsThreshold`: similarity threshold for word matching, default is `0.25` diff --git a/src/__tests__/diff-parser-tests.ts b/src/__tests__/diff-parser-tests.ts index 5768d8c..a3121f3 100644 --- a/src/__tests__/diff-parser-tests.ts +++ b/src/__tests__/diff-parser-tests.ts @@ -1975,5 +1975,231 @@ describe('DiffParser', () => { ] `); }); + + it('should work when `diffMaxChanges` is set and excedeed', () => { + const diff = + 'diff --git a/src/core/init.js b/src/core/init.js\n' + + 'index e49196a..50f310c 100644\n' + + '--- a/src/core/init.js\n' + + '+++ b/src/core/init.js\n' + + '@@ -101,7 +101,7 @@ var rootjQuery,\n' + + ' // HANDLE: $(function)\n' + + ' // Shortcut for document ready\n' + + ' } else if ( jQuery.isFunction( selector ) ) {\n' + + '- return typeof rootjQuery.ready !== "undefined" ?\n' + + '+ return rootjQuery.ready !== undefined ?\n' + + ' rootjQuery.ready( selector ) :\n' + + ' // Execute immediately if ready is not present\n' + + ' selector( jQuery );\n' + + 'diff --git a/src/event.js b/src/event.js\n' + + 'index 7336f4d..6183f70 100644\n' + + '--- a/src/event.js\n' + + '+++ b/src/event.js\n' + + '@@ -1,6 +1,5 @@\n' + + ' define([\n' + + ' "./core",\n' + + '- "./var/strundefined",\n' + + ' "./var/rnotwhite",\n' + + ' "./var/hasOwn",\n' + + ' "./var/slice",\n'; + const result = parse(diff, { diffMaxChanges: 1 }); + expect(result).toMatchInlineSnapshot(` + Array [ + Object { + "addedLines": 0, + "blocks": Array [ + Object { + "header": "Diff too big to be displayed", + "lines": Array [], + "newStartLine": 0, + "oldStartLine": 0, + "oldStartLine2": null, + }, + ], + "checksumAfter": "50f310c", + "checksumBefore": "e49196a", + "deletedLines": 0, + "isCombined": false, + "isGitDiff": true, + "isTooBig": true, + "language": "js", + "mode": "100644", + "newName": "src/core/init.js", + "oldName": "src/core/init.js", + }, + Object { + "addedLines": 0, + "blocks": Array [ + Object { + "header": "@@ -1,6 +1,5 @@", + "lines": Array [ + Object { + "content": " define([", + "newNumber": 1, + "oldNumber": 1, + "type": "context", + }, + Object { + "content": " \\"./core\\",", + "newNumber": 2, + "oldNumber": 2, + "type": "context", + }, + Object { + "content": "- \\"./var/strundefined\\",", + "newNumber": undefined, + "oldNumber": 3, + "type": "delete", + }, + Object { + "content": " \\"./var/rnotwhite\\",", + "newNumber": 3, + "oldNumber": 4, + "type": "context", + }, + Object { + "content": " \\"./var/hasOwn\\",", + "newNumber": 4, + "oldNumber": 5, + "type": "context", + }, + Object { + "content": " \\"./var/slice\\",", + "newNumber": 5, + "oldNumber": 6, + "type": "context", + }, + ], + "newStartLine": 1, + "oldStartLine": 1, + "oldStartLine2": null, + }, + ], + "checksumAfter": "6183f70", + "checksumBefore": "7336f4d", + "deletedLines": 1, + "isCombined": false, + "isGitDiff": true, + "language": "js", + "mode": "100644", + "newName": "src/event.js", + "oldName": "src/event.js", + }, + ] + `); + }); + + it('should work when `diffMaxChanges` is set and excedeed, and `diffTooBigMessage` is set', () => { + const diff = + 'diff --git a/src/core/init.js b/src/core/init.js\n' + + 'index e49196a..50f310c 100644\n' + + '--- a/src/core/init.js\n' + + '+++ b/src/core/init.js\n' + + '@@ -101,7 +101,7 @@ var rootjQuery,\n' + + ' // HANDLE: $(function)\n' + + ' // Shortcut for document ready\n' + + ' } else if ( jQuery.isFunction( selector ) ) {\n' + + '- return typeof rootjQuery.ready !== "undefined" ?\n' + + '+ return rootjQuery.ready !== undefined ?\n' + + ' rootjQuery.ready( selector ) :\n' + + ' // Execute immediately if ready is not present\n' + + ' selector( jQuery );\n' + + 'diff --git a/src/event.js b/src/event.js\n' + + 'index 7336f4d..6183f70 100644\n' + + '--- a/src/event.js\n' + + '+++ b/src/event.js\n' + + '@@ -1,6 +1,5 @@\n' + + ' define([\n' + + ' "./core",\n' + + '- "./var/strundefined",\n' + + ' "./var/rnotwhite",\n' + + ' "./var/hasOwn",\n' + + ' "./var/slice",\n'; + const result = parse(diff, { diffMaxChanges: 1, diffTooBigMessage: (i: number) => `Custom ${i}` }); + expect(result).toMatchInlineSnapshot(` + Array [ + Object { + "addedLines": 0, + "blocks": Array [ + Object { + "header": "Custom 0", + "lines": Array [], + "newStartLine": 0, + "oldStartLine": 0, + "oldStartLine2": null, + }, + ], + "checksumAfter": "50f310c", + "checksumBefore": "e49196a", + "deletedLines": 0, + "isCombined": false, + "isGitDiff": true, + "isTooBig": true, + "language": "js", + "mode": "100644", + "newName": "src/core/init.js", + "oldName": "src/core/init.js", + }, + Object { + "addedLines": 0, + "blocks": Array [ + Object { + "header": "@@ -1,6 +1,5 @@", + "lines": Array [ + Object { + "content": " define([", + "newNumber": 1, + "oldNumber": 1, + "type": "context", + }, + Object { + "content": " \\"./core\\",", + "newNumber": 2, + "oldNumber": 2, + "type": "context", + }, + Object { + "content": "- \\"./var/strundefined\\",", + "newNumber": undefined, + "oldNumber": 3, + "type": "delete", + }, + Object { + "content": " \\"./var/rnotwhite\\",", + "newNumber": 3, + "oldNumber": 4, + "type": "context", + }, + Object { + "content": " \\"./var/hasOwn\\",", + "newNumber": 4, + "oldNumber": 5, + "type": "context", + }, + Object { + "content": " \\"./var/slice\\",", + "newNumber": 5, + "oldNumber": 6, + "type": "context", + }, + ], + "newStartLine": 1, + "oldStartLine": 1, + "oldStartLine2": null, + }, + ], + "checksumAfter": "6183f70", + "checksumBefore": "7336f4d", + "deletedLines": 1, + "isCombined": false, + "isGitDiff": true, + "language": "js", + "mode": "100644", + "newName": "src/event.js", + "oldName": "src/event.js", + }, + ] + `); + }); }); }); diff --git a/src/__tests__/line-by-line-tests.ts b/src/__tests__/line-by-line-tests.ts index ea7ad89..75f9aa7 100644 --- a/src/__tests__/line-by-line-tests.ts +++ b/src/__tests__/line-by-line-tests.ts @@ -505,6 +505,65 @@ describe('LineByLineRenderer', () => { " `); }); + + it('should work for too big file diff', () => { + const exampleJson = [ + { + blocks: [ + { + header: 'Custom link to render', + lines: [], + newStartLine: 0, + oldStartLine: 0, + oldStartLine2: undefined, + }, + ], + deletedLines: 0, + addedLines: 0, + oldName: 'sample', + language: 'js', + newName: 'sample', + isCombined: false, + isGitDiff: false, + isTooBig: true, + }, + ]; + + const hoganUtils = new HoganJsUtils({}); + const lineByLineRenderer = new LineByLineRenderer(hoganUtils); + const html = lineByLineRenderer.render(exampleJson); + expect(html).toMatchInlineSnapshot(` + "
+
+
+ + + + sample + CHANGED + +
+
+
+ + + + + + + +
+ +
+
+
+
+
" + `); + }); }); describe('_generateFileHtml', () => { diff --git a/src/__tests__/side-by-side-printer-tests.ts b/src/__tests__/side-by-side-printer-tests.ts index 6863a1a..2bbc59c 100644 --- a/src/__tests__/side-by-side-printer-tests.ts +++ b/src/__tests__/side-by-side-printer-tests.ts @@ -406,6 +406,81 @@ describe('SideBySideRenderer', () => { " `); }); + + it('should work for too big file diff', () => { + const exampleJson = [ + { + blocks: [ + { + header: 'Custom link to render', + lines: [], + newStartLine: 0, + oldStartLine: 0, + oldStartLine2: undefined, + }, + ], + deletedLines: 0, + addedLines: 0, + oldName: 'sample', + language: 'js', + newName: 'sample', + isCombined: false, + isGitDiff: false, + isTooBig: true, + }, + ]; + + const hoganUtils = new HoganJsUtils({}); + const sideBySideRenderer = new SideBySideRenderer(hoganUtils); + const html = sideBySideRenderer.render(exampleJson); + expect(html).toMatchInlineSnapshot(` + "
+
+
+ + + + sample + CHANGED + +
+
+
+
+ + + + + + + +
+ +
+
+
+
+
+ + + + + + + +
+
+
+
+
+
+
+
" + `); + }); }); describe('processLines', () => { diff --git a/src/diff-parser.ts b/src/diff-parser.ts index 1c816a0..084acb1 100644 --- a/src/diff-parser.ts +++ b/src/diff-parser.ts @@ -4,6 +4,8 @@ import { escapeForRegExp } from './utils'; export interface DiffParserConfig { srcPrefix?: string; dstPrefix?: string; + diffMaxChanges?: number; + diffTooBigMessage?: (fileIndex: number) => string; } function getExtension(filename: string, language: string): string { @@ -299,6 +301,30 @@ export function parse(diffInput: string, config: DiffParserConfig = {}): DiffFil startFile(); } + // Ignore remaining diff for current file if marked as too big + if (currentFile?.isTooBig) { + return; + } + + if ( + currentFile && + typeof config.diffMaxChanges === 'number' && + currentFile.addedLines + currentFile.deletedLines > config.diffMaxChanges + ) { + currentFile.isTooBig = true; + currentFile.addedLines = 0; + currentFile.deletedLines = 0; + currentFile.blocks = []; + currentBlock = null; + + const message = + typeof config.diffTooBigMessage === 'function' + ? config.diffTooBigMessage(files.length) + : 'Diff too big to be displayed'; + startBlock(message); + return; + } + /* * We need to make sure that we have the three lines of the header. * This avoids cases like the ones described in: diff --git a/src/line-by-line-renderer.ts b/src/line-by-line-renderer.ts index 969782a..4258095 100644 --- a/src/line-by-line-renderer.ts +++ b/src/line-by-line-renderer.ts @@ -95,7 +95,7 @@ export default class LineByLineRenderer { .map(block => { let lines = this.hoganUtils.render(genericTemplatesPath, 'block-header', { CSSLineClass: renderUtils.CSSLineClass, - blockHeader: renderUtils.escapeForHtml(block.header), + blockHeader: file.isTooBig ? block.header : renderUtils.escapeForHtml(block.header), lineClass: 'd2h-code-linenumber', contentClass: 'd2h-code-line', }); diff --git a/src/side-by-side-renderer.ts b/src/side-by-side-renderer.ts index e38f7d0..9a0fb6b 100644 --- a/src/side-by-side-renderer.ts +++ b/src/side-by-side-renderer.ts @@ -97,7 +97,7 @@ export default class SideBySideRenderer { return file.blocks .map(block => { const fileHtml = { - left: this.makeHeaderHtml(block.header), + left: this.makeHeaderHtml(block.header, file), right: this.makeHeaderHtml(''), }; @@ -203,10 +203,10 @@ export default class SideBySideRenderer { return doMatching ? matcher(oldLines, newLines) : [[oldLines, newLines]]; } - makeHeaderHtml(blockHeader: string): string { + makeHeaderHtml(blockHeader: string, file?: DiffFile): string { return this.hoganUtils.render(genericTemplatesPath, 'block-header', { CSSLineClass: renderUtils.CSSLineClass, - blockHeader: renderUtils.escapeForHtml(blockHeader), + blockHeader: file?.isTooBig ? blockHeader : renderUtils.escapeForHtml(blockHeader), lineClass: 'd2h-code-side-linenumber', contentClass: 'd2h-code-side-line', }); diff --git a/src/types.ts b/src/types.ts index d573ac9..ea28b49 100644 --- a/src/types.ts +++ b/src/types.ts @@ -62,6 +62,7 @@ export interface DiffFile extends DiffFileName { isCopy?: boolean; isRename?: boolean; isBinary?: boolean; + isTooBig?: boolean; unchangedPercentage?: number; changedPercentage?: number; checksumBefore?: string | string[]; diff --git a/website/templates/pages/demo/demo.ts b/website/templates/pages/demo/demo.ts index 42eccae..62ba9e5 100644 --- a/website/templates/pages/demo/demo.ts +++ b/website/templates/pages/demo/demo.ts @@ -21,6 +21,7 @@ import './demo.css'; type URLParams = { diff?: string; + diffTooBigMessage?: string; [key: string]: string | boolean | number | undefined; }; @@ -116,9 +117,9 @@ function prepareRequest(url: string): Request { } function getConfiguration(urlParams: URLParams): Diff2HtmlUIConfig { - // Removing `diff` form `urlParams` to avoid being inserted + // Removing `diff` and `diffTooBigMessage` form `urlParams` to avoid being inserted // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { diff, ...urlParamsRest } = urlParams; + const { diff, diffTooBigMessage, ...urlParamsRest } = urlParams; const config: URLParams = { ...defaultDiff2HtmlUIConfig, ...urlParamsRest,