From cc1b73336ab42e8065503711f5bcf9b64a38d63b Mon Sep 17 00:00:00 2001 From: Brian Gough Date: Tue, 21 Sep 2021 09:56:44 +0100 Subject: [PATCH] Merge pull request #4944 from overleaf/bg-use-file-line-errors use file line errors in log output GitOrigin-RevId: 6732b19552fe15431a70fbefbc572253c389c64e --- .../src/js/latex-log-parser.js | 21 +- libraries/latex-log-parser/tests/tests.js | 686 +++++++++++------- .../clsi/test/unit/js/LatexRunnerTests.js | 6 +- .../app/src/Features/Compile/ClsiManager.js | 6 + .../src/Features/Compile/CompileController.js | 2 + .../js/ide/pdf/controllers/PdfController.js | 3 + .../js/vendor/libs/latex-log-parser.js | 490 +++++++------ .../frontend/stylesheets/app/editor/pdf.less | 1 + .../test/unit/src/Compile/ClsiManagerTests.js | 2 + .../src/Compile/CompileControllerTests.js | 3 + 10 files changed, 756 insertions(+), 464 deletions(-) diff --git a/libraries/latex-log-parser/src/js/latex-log-parser.js b/libraries/latex-log-parser/src/js/latex-log-parser.js index 20c55144b9..7ec2f024b6 100644 --- a/libraries/latex-log-parser/src/js/latex-log-parser.js +++ b/libraries/latex-log-parser/src/js/latex-log-parser.js @@ -102,6 +102,9 @@ define(function () { content: '', raw: this.currentLine + '\n', } + } else if (this.currentLineIsFileLineError()) { + this.state = state.ERROR + this.parseFileLineError() } else if (this.currentLineIsRunawayArgument()) { this.parseRunawayArgumentError() } else if (this.currentLineIsWarning()) { @@ -128,7 +131,7 @@ define(function () { .join('\n') this.currentError.raw += this.currentError.content const lineNo = this.currentError.raw.match(/l\.([0-9]+)/) - if (lineNo) { + if (lineNo && this.currentError.line === null) { this.currentError.line = parseInt(lineNo[1], 10) } this.data.push(this.currentError) @@ -142,6 +145,10 @@ define(function () { return this.currentLine[0] === '!' } + this.currentLineIsFileLineError = function () { + return /^\/.*:\d+: .*/.test(this.currentLine) + } + this.currentLineIsRunawayArgument = function () { return this.currentLine.match(/^Runaway argument/) } @@ -158,6 +165,18 @@ define(function () { return !!this.currentLine.match(HBOX_WARNING_REGEX) } + this.parseFileLineError = function () { + const result = this.currentLine.match(/^(\/.*):(\d+): (.*)/) + this.currentError = { + line: result[2], + file: result[1], + level: 'error', + message: result[3], + content: '', + raw: this.currentLine + '\n', + } + } + this.parseRunawayArgumentError = function () { this.currentError = { line: null, diff --git a/libraries/latex-log-parser/tests/tests.js b/libraries/latex-log-parser/tests/tests.js index d3dbb2bcbd..f998163d5c 100644 --- a/libraries/latex-log-parser/tests/tests.js +++ b/libraries/latex-log-parser/tests/tests.js @@ -1,302 +1,490 @@ define([ - "../src/js/latex-log-parser", - "../src/js/bib-log-parser", - "text!logs/errors.log", - "text!logs/warnings.log", - "text!logs/bad-boxes.log", - "text!logs/biber-warnings.log", - "text!logs/natbib-warnings.log", - "text!logs/geometry-warnings.log", - "text!logs/caption-warnings.log", - "text!logs/runaway-arguments.log", - "text!logs/biber.blg", - "text!logs/bibtex.blg" -], -function(LatexParser, BibLogParser, errorLog, warningLog, badBoxesLog, - biberWarningsLog, natbibWarningsLog, geometryWarningsLog, captionWarningsLog, runawayArgumentsLog, biberBlg, bibtexBlg) { + '../src/js/latex-log-parser', + '../src/js/bib-log-parser', + 'text!logs/errors.log', + 'text!logs/warnings.log', + 'text!logs/bad-boxes.log', + 'text!logs/biber-warnings.log', + 'text!logs/natbib-warnings.log', + 'text!logs/geometry-warnings.log', + 'text!logs/caption-warnings.log', + 'text!logs/runaway-arguments.log', + 'text!logs/file-line-error.log', + 'text!logs/biber.blg', + 'text!logs/bibtex.blg', +], function ( + LatexParser, + BibLogParser, + errorLog, + warningLog, + badBoxesLog, + biberWarningsLog, + natbibWarningsLog, + geometryWarningsLog, + captionWarningsLog, + runawayArgumentsLog, + fileLineErrorLog, + biberBlg, + bibtexBlg +) { + function prettyFileList(files, depth) { + depth = depth || ' ' + for (var i = 0; i < files.length; i++) { + console.log(depth + files[i].path) + prettyFileList(files[i].files, depth + ' ') + } + } - function prettyFileList(files, depth) { - depth = depth || " "; - for (var i = 0; i < files.length; i++) { - console.log(depth + files[i].path); - prettyFileList(files[i].files, depth + " "); - } - } + module('Errors') - module("Errors"); + test('Error parsing', function () { + var errors = LatexParser.parse(errorLog, { + ignoreDuplicates: true, + }).errors - test("Error parsing", function() { - var errors = LatexParser.parse(errorLog, { - ignoreDuplicates : true - }).errors; + var expectedErrors = [ + [29, 'Undefined control sequence.'] + '', + [ + 30, + 'LaTeX Error: \\begin{equation} on input line 28 ended by \\end{equaion}.', + ] + '', + [30, 'Missing $ inserted.'] + '', + [30, 'Display math should end with $$.'] + '', + [46, 'Extra }, or forgotten \\right.'] + '', + [46, 'Missing \\right. inserted.'] + '', + [46, 'Missing } inserted.'] + '', + ] - var expectedErrors = [ - [29, "Undefined control sequence."] + "", - [30, "LaTeX Error: \\begin{equation} on input line 28 ended by \\end{equaion}."] + "", - [30, "Missing $ inserted."] + "", - [30, "Display math should end with $$."] + "", - [46, "Extra }, or forgotten \\right."] + "", - [46, "Missing \\right. inserted."] + "", - [46, "Missing } inserted."] + "" - ]; + expect(expectedErrors.length) + for (var i = 0; i < errors.length; i++) { + if ( + expectedErrors.indexOf([errors[i].line, errors[i].message] + '') > -1 + ) { + ok(true, 'Found error: ' + errors[i].message) + } + } + }) - expect(expectedErrors.length); - for (var i = 0; i < errors.length; i++) { - if (expectedErrors.indexOf([errors[i].line, errors[i].message] + "") > -1) { - ok(true, "Found error: " + errors[i].message); - } - } - }); + module('Bad boxes') - module("Bad boxes"); + test('Badbox parsing', function () { + var errors = LatexParser.parse(badBoxesLog).typesetting - test("Badbox parsing", function() { - var errors = LatexParser.parse(badBoxesLog).typesetting; + var expectedErrors = [ + [9, 'Overfull \\hbox (29.11179pt too wide) in paragraph at lines 9--10'] + + '', + [11, 'Underfull \\hbox (badness 10000) in paragraph at lines 11--13'] + + '', + [27, 'Overfull \\vbox (12.00034pt too high) detected at line 27'] + '', + [46, 'Underfull \\vbox (badness 10000) detected at line 46'] + '', + [54, 'Underfull \\hbox (badness 10000) in paragraph at lines 54--55'] + + '', + [58, 'Underfull \\hbox (badness 10000) in paragraph at lines 58--60'] + + '', + ] - var expectedErrors = [ - [9, "Overfull \\hbox (29.11179pt too wide) in paragraph at lines 9--10"] + "", - [11, "Underfull \\hbox (badness 10000) in paragraph at lines 11--13"] + "", - [27, "Overfull \\vbox (12.00034pt too high) detected at line 27"] + "", - [46, "Underfull \\vbox (badness 10000) detected at line 46"] + "", - [54, "Underfull \\hbox (badness 10000) in paragraph at lines 54--55"] + "", - [58, "Underfull \\hbox (badness 10000) in paragraph at lines 58--60"] + "" - ]; + expect(expectedErrors.length) + for (var i = 0; i < errors.length; i++) { + if ( + expectedErrors.indexOf([errors[i].line, errors[i].message] + '') > -1 + ) { + ok(true, 'Found error: ' + errors[i].message) + } + } + }) - expect(expectedErrors.length); - for (var i = 0; i < errors.length; i++) { - if (expectedErrors.indexOf([errors[i].line, errors[i].message] + "") > -1) { - ok(true, "Found error: " + errors[i].message); - } - } - }); + module('Warnings') - module("Warnings"); + test('Warning parsing', function () { + var errors = LatexParser.parse(warningLog).warnings - test("Warning parsing", function() { - var errors = LatexParser.parse(warningLog).warnings; + var expectedErrors = [ + [ + 7, + "Citation `Lambert:2010iw' on page 1 undefined on input line 7.", + 'compiles/d1585ce575dea4cab55f784a22a88652/sections/introduction.tex', + ] + '', + [ + 7, + "Citation `Lambert:2010iw' on page 1 undefined on input line 7.", + 'compiles/d1585ce575dea4cab55f784a22a88652/sections/introduction.tex', + ] + '', + [ + 72, + "Citation `Manton:2004tk' on page 3 undefined on input line 72.", + 'compiles/d1585ce575dea4cab55f784a22a88652/sections/instantons.tex', + ] + '', + [ + 108, + "Citation `Atiyah1978' on page 4 undefined on input line 108.", + 'compiles/d1585ce575dea4cab55f784a22a88652/sections/instantons.tex', + ] + '', + [ + 176, + "Citation `Dorey:1996hu' on page 5 undefined on input line 176.", + 'compiles/d1585ce575dea4cab55f784a22a88652/sections/instantons.tex', + ] + '', + [ + 3, + "Citation `Manton1982' on page 8 undefined on input line 3.", + 'compiles/d1585ce575dea4cab55f784a22a88652/sections/moduli_space_approximation.tex', + ] + '', + [ + 21, + "Citation `Weinberg:2006rq' on page 9 undefined on input line 21.", + 'compiles/d1585ce575dea4cab55f784a22a88652/sections/moduli_space_approximation.tex', + ] + '', + [ + 192, + "Citation `Bak:1999sv' on page 12 undefined on input line 192.", + 'compiles/d1585ce575dea4cab55f784a22a88652/sections/moduli_space_approximation.tex', + ] + '', + [ + 9, + "Citation `Peeters:2001np' on page 13 undefined on input line 9.", + 'compiles/d1585ce575dea4cab55f784a22a88652/sections/dynamics_of_single_instanton.tex', + ] + '', + [ + 27, + "Citation `Osborn:1981yf' on page 15 undefined on input line 27.", + 'compiles/d1585ce575dea4cab55f784a22a88652/sections/dynamics_of_two_instantons.tex', + ] + '', + [ + 27, + "Citation `Peeters:2001np' on page 15 undefined on input line 27.", + 'compiles/d1585ce575dea4cab55f784a22a88652/sections/dynamics_of_two_instantons.tex', + ] + '', + [ + 20, + "Citation `Osborn:1981yf' on page 22 undefined on input line 20.", + 'compiles/d1585ce575dea4cab55f784a22a88652/sections/appendices.tex', + ] + '', + [ + 103, + "Citation `Osborn:1981yf' on page 23 undefined on input line 103.", + 'compiles/d1585ce575dea4cab55f784a22a88652/sections/appendices.tex', + ] + '', + [ + 103, + "Citation `Peeters:2001np' on page 23 undefined on input line 103.", + 'compiles/d1585ce575dea4cab55f784a22a88652/sections/appendices.tex', + ] + '', + [ + 352, + "Citation `Peeters:2001np' on page 27 undefined on input line 352.", + 'compiles/d1585ce575dea4cab55f784a22a88652/sections/appendices.tex', + ] + '', + ] - var expectedErrors = [ - [7, "Citation `Lambert:2010iw' on page 1 undefined on input line 7.", "compiles/d1585ce575dea4cab55f784a22a88652/sections/introduction.tex"] + "", - [7, "Citation `Lambert:2010iw' on page 1 undefined on input line 7.", "compiles/d1585ce575dea4cab55f784a22a88652/sections/introduction.tex"] + "", - [72, "Citation `Manton:2004tk' on page 3 undefined on input line 72.", "compiles/d1585ce575dea4cab55f784a22a88652/sections/instantons.tex"] + "", - [108, "Citation `Atiyah1978' on page 4 undefined on input line 108.", "compiles/d1585ce575dea4cab55f784a22a88652/sections/instantons.tex"] + "", - [176, "Citation `Dorey:1996hu' on page 5 undefined on input line 176.", "compiles/d1585ce575dea4cab55f784a22a88652/sections/instantons.tex"] + "", - [3, "Citation `Manton1982' on page 8 undefined on input line 3.", "compiles/d1585ce575dea4cab55f784a22a88652/sections/moduli_space_approximation.tex"] + "", - [21, "Citation `Weinberg:2006rq' on page 9 undefined on input line 21.", "compiles/d1585ce575dea4cab55f784a22a88652/sections/moduli_space_approximation.tex"] + "", - [192, "Citation `Bak:1999sv' on page 12 undefined on input line 192.", "compiles/d1585ce575dea4cab55f784a22a88652/sections/moduli_space_approximation.tex"] + "", - [9, "Citation `Peeters:2001np' on page 13 undefined on input line 9.", "compiles/d1585ce575dea4cab55f784a22a88652/sections/dynamics_of_single_instanton.tex"] + "", - [27, "Citation `Osborn:1981yf' on page 15 undefined on input line 27.", "compiles/d1585ce575dea4cab55f784a22a88652/sections/dynamics_of_two_instantons.tex"] + "", - [27, "Citation `Peeters:2001np' on page 15 undefined on input line 27.", "compiles/d1585ce575dea4cab55f784a22a88652/sections/dynamics_of_two_instantons.tex"] + "", - [20, "Citation `Osborn:1981yf' on page 22 undefined on input line 20.", "compiles/d1585ce575dea4cab55f784a22a88652/sections/appendices.tex"] + "", - [103, "Citation `Osborn:1981yf' on page 23 undefined on input line 103.", "compiles/d1585ce575dea4cab55f784a22a88652/sections/appendices.tex"] + "", - [103, "Citation `Peeters:2001np' on page 23 undefined on input line 103.", "compiles/d1585ce575dea4cab55f784a22a88652/sections/appendices.tex"] + "", - [352, "Citation `Peeters:2001np' on page 27 undefined on input line 352.", "compiles/d1585ce575dea4cab55f784a22a88652/sections/appendices.tex"] + "" - ]; + expect(expectedErrors.length) + for (var i = 0; i < errors.length; i++) { + if ( + expectedErrors.indexOf( + [errors[i].line, errors[i].message, errors[i].file] + '' + ) > -1 + ) { + ok(true, 'Found error: ' + errors[i].message) + } + } + }) - expect(expectedErrors.length); - for (var i = 0; i < errors.length; i++) { - if (expectedErrors.indexOf([errors[i].line, errors[i].message, errors[i].file] + "") > -1) { - ok(true, "Found error: " + errors[i].message); - } - } - }); + module('Biber Warnings') - module("Biber Warnings"); + test('Biber Warning parsing', function () { + var errors = LatexParser.parse(biberWarningsLog).warnings - test("Biber Warning parsing", function() { - var errors = LatexParser.parse(biberWarningsLog).warnings; + var expectedErrors = [ + [ + null, + 'Package biblatex Warning: No "backend" specified, using Biber backend. To use BibTeX, load biblatex with the "backend=bibtex" option.', + '/usr/local/texlive/2013/texmf-dist/tex/latex/biblatex/biblatex.sty', + ] + '', + [ + null, + 'Package biblatex Warning: The following entry could not be found in the database: Missing3 Please verify the spelling and rerun LaTeX afterwards.', + '/compile/output.bbl', + ] + '', + [ + null, + 'Package biblatex Warning: The following entry could not be found in the database: Missing2 Please verify the spelling and rerun LaTeX afterwards.', + '/compile/output.bbl', + ] + '', + [ + null, + 'Package biblatex Warning: The following entry could not be found in the database: Missing1 Please verify the spelling and rerun LaTeX afterwards.', + '/compile/output.bbl', + ] + '', + ] - var expectedErrors = [ - [null, 'Package biblatex Warning: No "backend" specified, using Biber backend. To use BibTeX, load biblatex with the "backend=bibtex" option.', "/usr/local/texlive/2013/texmf-dist/tex/latex/biblatex/biblatex.sty"] + "", - [null, "Package biblatex Warning: The following entry could not be found in the database: Missing3 Please verify the spelling and rerun LaTeX afterwards.", "/compile/output.bbl"] + "", - [null, "Package biblatex Warning: The following entry could not be found in the database: Missing2 Please verify the spelling and rerun LaTeX afterwards.", "/compile/output.bbl"] + "", - [null, "Package biblatex Warning: The following entry could not be found in the database: Missing1 Please verify the spelling and rerun LaTeX afterwards.", "/compile/output.bbl"] + "" - ]; + expect(expectedErrors.length) + for (var i = 0; i < errors.length; i++) { + if ( + expectedErrors.indexOf( + [errors[i].line, errors[i].message, errors[i].file] + '' + ) > -1 + ) { + ok(true, 'Found error: ' + errors[i].message) + } else { + ok(false, 'Unexpected error found: ' + errors[i].message) + } + } + }) - expect(expectedErrors.length); - for (var i = 0; i < errors.length; i++) { - if (expectedErrors.indexOf([errors[i].line, errors[i].message, errors[i].file] + "") > -1) { - ok(true, "Found error: " + errors[i].message); - } else { - ok(false, "Unexpected error found: " + errors[i].message); - } - } - }); + module('Natbib Warnings') - module("Natbib Warnings"); + test('Natbib Warning parsing', function () { + var errors = LatexParser.parse(natbibWarningsLog).warnings - test("Natbib Warning parsing", function() { - var errors = LatexParser.parse(natbibWarningsLog).warnings; + var expectedErrors = [ + [ + 6, + "Package natbib Warning: Citation `blah' on page 1 undefined on input line 6.", + '/compile/main.tex', + ] + '', + [ + null, + 'Package natbib Warning: There were undefined citations.', + '/compile/main.tex', + ] + '', + ] - var expectedErrors = [ - [6, "Package natbib Warning: Citation `blah' on page 1 undefined on input line 6.", "/compile/main.tex"] + "", - [null, "Package natbib Warning: There were undefined citations.", "/compile/main.tex"] + "" - ]; + expect(expectedErrors.length) + for (var i = 0; i < errors.length; i++) { + if ( + expectedErrors.indexOf( + [errors[i].line, errors[i].message, errors[i].file] + '' + ) > -1 + ) { + ok(true, 'Found error: ' + errors[i].message) + } else { + ok(false, 'Unexpected error found: ' + errors[i].message) + } + } + }) - expect(expectedErrors.length); - for (var i = 0; i < errors.length; i++) { - if (expectedErrors.indexOf([errors[i].line, errors[i].message, errors[i].file] + "") > -1) { - ok(true, "Found error: " + errors[i].message); - } else { - ok(false, "Unexpected error found: " + errors[i].message); - } - } - }); + module('Geometry Warnings') - module("Geometry Warnings"); + test('Geometry Warning parsing', function () { + var errors = LatexParser.parse(geometryWarningsLog).warnings - test("Geometry Warning parsing", function() { - var errors = LatexParser.parse(geometryWarningsLog).warnings; + var expectedErrors = [ + [ + null, + "Package geometry Warning: Over-specification in `h'-direction. `width' (597.50787pt) is ignored.", + '/compile/main.tex', + ] + '', + [ + null, + "Package geometry Warning: Over-specification in `v'-direction. `height' (845.04684pt) is ignored.", + '/compile/main.tex', + ] + '', + ] - var expectedErrors = [ - [null, "Package geometry Warning: Over-specification in `h'-direction. `width' (597.50787pt) is ignored.", "/compile/main.tex"] + "", - [null, "Package geometry Warning: Over-specification in `v'-direction. `height' (845.04684pt) is ignored.", "/compile/main.tex"] + "" - ]; + expect(expectedErrors.length) + for (var i = 0; i < errors.length; i++) { + if ( + expectedErrors.indexOf( + [errors[i].line, errors[i].message, errors[i].file] + '' + ) > -1 + ) { + ok(true, 'Found error: ' + errors[i].message) + } else { + ok(false, 'Unexpected error found: ' + errors[i].message) + } + } + }) - expect(expectedErrors.length); - for (var i = 0; i < errors.length; i++) { - if (expectedErrors.indexOf([errors[i].line, errors[i].message, errors[i].file] + "") > -1) { - ok(true, "Found error: " + errors[i].message); - } else { - ok(false, "Unexpected error found: " + errors[i].message); - } - } - }); + module('Caption Warnings') - module("Caption Warnings"); + test('Caption Warning parsing', function () { + var errors = LatexParser.parse(captionWarningsLog).warnings - test("Caption Warning parsing", function() { - var errors = LatexParser.parse(captionWarningsLog).warnings; + var expectedErrors = [ + [ + null, + 'Package caption Warning: Unsupported document class (or package) detected, usage of the caption package is not recommended. See the caption package documentation for explanation.', + '/usr/local/texlive/2014/texmf-dist/tex/latex/caption/caption.sty', + ] + '', + [ + 46, + "Package caption Warning: The option `hypcap=true' will be ignored for this particular \\caption on input line 46. See the caption package documentation for explanation.", + '/compile/main.tex', + ] + '', + ] - var expectedErrors = [ - [null, "Package caption Warning: Unsupported document class (or package) detected, usage of the caption package is not recommended. See the caption package documentation for explanation.", "/usr/local/texlive/2014/texmf-dist/tex/latex/caption/caption.sty"] + "", - [46, "Package caption Warning: The option `hypcap=true' will be ignored for this particular \\caption on input line 46. See the caption package documentation for explanation.", "/compile/main.tex"] + "" - ]; + expect(expectedErrors.length) + for (var i = 0; i < errors.length; i++) { + if ( + expectedErrors.indexOf( + [errors[i].line, errors[i].message, errors[i].file] + '' + ) > -1 + ) { + ok(true, 'Found error: ' + errors[i].message) + } else { + ok(false, 'Unexpected error found: ' + errors[i].message) + } + } + }) - expect(expectedErrors.length); - for (var i = 0; i < errors.length; i++) { - if (expectedErrors.indexOf([errors[i].line, errors[i].message, errors[i].file] + "") > -1) { - ok(true, "Found error: " + errors[i].message); - } else { - ok(false, "Unexpected error found: " + errors[i].message); - } - } - }); + module('Runaway Arguments') - module("Runaway Arguments"); + test('Runaway Arguments parsing', function () { + var errors = LatexParser.parse(runawayArgumentsLog).errors - test("Runaway Arguments parsing", function() { - var errors = LatexParser.parse(runawayArgumentsLog).errors; + var expectedErrors = [ + [null, 'Runaway argument?', '/compile/runaway_argument.tex'] + '', + [null, 'Emergency stop.', '/compile/runaway_argument.tex'] + '', + ] - var expectedErrors = [ - [null, "Runaway argument?", "/compile/runaway_argument.tex"] + "", - [null, "Emergency stop.", "/compile/runaway_argument.tex"] + "" - ]; + expect(expectedErrors.length) + for (var i = 0; i < errors.length; i++) { + if ( + expectedErrors.indexOf( + [errors[i].line, errors[i].message, errors[i].file] + '' + ) > -1 + ) { + ok(true, 'Found error: ' + errors[i].message) + } else { + ok(false, 'Unexpected error found: ' + errors[i].message) + } + } + }) - expect(expectedErrors.length); - for (var i = 0; i < errors.length; i++) { - if (expectedErrors.indexOf([errors[i].line, errors[i].message, errors[i].file] + "") > -1) { - ok(true, "Found error: " + errors[i].message); - } else { - ok(false, "Unexpected error found: " + errors[i].message); - } - } - }); + module('File Line Errors') - module("General"); + test('File line error parsing', function () { + var errors = LatexParser.parse(fileLineErrorLog).errors - test("Ignore Duplicates", function() { - var errors = LatexParser.parse(errorLog).errors; - equal(errors.length, 10, "Duplicates included"); + var expectedErrors = [ + [ + 1, + 'Undefined control sequence.', + '/compile/a folder with spaces/a subfolder with spaces/a subsubfolder with spaces/another file with spaces.tex', + ] + '', + ] - errors = LatexParser.parse(errorLog, {ignoreDuplicates : true}).errors; - equal(errors.length, 7, "Duplicates ignored"); - }); + expect(expectedErrors.length) + for (var i = 0; i < errors.length; i++) { + if ( + expectedErrors.indexOf( + [errors[i].line, errors[i].message, errors[i].file] + '' + ) > -1 + ) { + ok(true, 'Found error: ' + errors[i].message) + } else { + ok(false, 'Unexpected error found: ' + errors[i].message) + } + } + }) - test("File paths", function() { - var errors = LatexParser.parse(errorLog).errors; + module('General') - for (var i = 0; i < errors.length; i++) { - equal(errors[i].file, "compiles/dff0c37d892f346e58fc14975a16bf69/sections/appendices.tex", "File path correct"); - } + test('Ignore Duplicates', function () { + var errors = LatexParser.parse(errorLog).errors + equal(errors.length, 10, 'Duplicates included') - errors = LatexParser.parse(badBoxesLog).all; - for (var i = 0; i < errors.length; i++) { - equal(errors[i].file, "compiles/b6cf470376785e64ad84c57e3296c912/logs/bad-boxes.tex", "File path correct"); - } - }); + errors = LatexParser.parse(errorLog, { ignoreDuplicates: true }).errors + equal(errors.length, 7, 'Duplicates ignored') + }) + test('File paths', function () { + var errors = LatexParser.parse(errorLog).errors - // biber-log-parser - module("BibLogParser"); + for (var i = 0; i < errors.length; i++) { + equal( + errors[i].file, + 'compiles/dff0c37d892f346e58fc14975a16bf69/sections/appendices.tex', + 'File path correct' + ) + } - test("Typical biber .blg file", function() { - var result = BibLogParser.parse(biberBlg, {}); - equal(typeof result, "object"); - equal(result.all.length, 14); - equal(result.errors.length, 1); - equal(result.warnings.length, 2); + errors = LatexParser.parse(badBoxesLog).all + for (var i = 0; i < errors.length; i++) { + equal( + errors[i].file, + 'compiles/b6cf470376785e64ad84c57e3296c912/logs/bad-boxes.tex', + 'File path correct' + ) + } + }) - var error = result.errors[0]; - equal(error.level, "error"); - equal(error.line, "8"); - equal(error.file, "bibliography.bib"); - equal(error.message, 'syntax error: at end of input, expected end of entry ("}" or ")") (skipping to next "@")'); - }); + // biber-log-parser + module('BibLogParser') - test("Not a .blg file", function() { - try { - var result = BibLogParser.parse(captionWarningsLog); - } catch(e) { - ok(true, "Should throw an error"); - } - }); + test('Typical biber .blg file', function () { + var result = BibLogParser.parse(biberBlg, {}) + equal(typeof result, 'object') + equal(result.all.length, 14) + equal(result.errors.length, 1) + equal(result.warnings.length, 2) - test("Empty string", function() { - try { - var result = BibLogParser.parse(''); - } catch(e) { - ok(true, "Should throw an error"); - } - }); + var error = result.errors[0] + equal(error.level, 'error') + equal(error.line, '8') + equal(error.file, 'bibliography.bib') + equal( + error.message, + 'syntax error: at end of input, expected end of entry ("}" or ")") (skipping to next "@")' + ) + }) - test("Not a string", function() { - try { - var result = BibLogParser.parse({a: 1}); - } catch(e) { - ok(true, "Should throw an error"); - } - }); + test('Not a .blg file', function () { + try { + var result = BibLogParser.parse(captionWarningsLog) + } catch (e) { + ok(true, 'Should throw an error') + } + }) - test("typical bibtex .blg file", function() { - var result = BibLogParser.parse(bibtexBlg, {}); - equal(typeof result, "object"); - equal(result.all.length, 13); + test('Empty string', function () { + try { + var result = BibLogParser.parse('') + } catch (e) { + ok(true, 'Should throw an error') + } + }) - equal(result.warnings.length, 6); - var firstWarning = result.warnings[0]; - equal(firstWarning.file, "references.bib"); - equal(firstWarning.line, "152"); - equal(firstWarning.level, "warning"); - equal(firstWarning.message, 'string name "something" is undefined'); + test('Not a string', function () { + try { + var result = BibLogParser.parse({ a: 1 }) + } catch (e) { + ok(true, 'Should throw an error') + } + }) - var thirdWarning = result.warnings[2]; - equal(thirdWarning.message, "can't use both author and editor fields in Binney87"); + test('typical bibtex .blg file', function () { + var result = BibLogParser.parse(bibtexBlg, {}) + equal(typeof result, 'object') + equal(result.all.length, 13) - equal(result.errors.length, 7); - var firstError = result.errors[0]; - equal(firstError.file, 'references.bib'); - equal(firstError.line, '196'); - equal(firstError.level, 'error'); - equal(firstError.message.indexOf("I was expecting a `,' or a `}'"), 0); - equal(firstError.message.indexOf("(Error may have been on previous line)") > 0, true); - var crossReferenceError = result.errors[5]; - equal(crossReferenceError.level, 'error'); - equal(crossReferenceError.message.indexOf('A bad cross reference'), 0); - var styleError = result.errors[6]; - equal(styleError.level, 'error'); - equal(styleError.message.indexOf("I couldn't open style file aa.bst"), 0); + equal(result.warnings.length, 6) + var firstWarning = result.warnings[0] + equal(firstWarning.file, 'references.bib') + equal(firstWarning.line, '152') + equal(firstWarning.level, 'warning') + equal(firstWarning.message, 'string name "something" is undefined') - }); + var thirdWarning = result.warnings[2] + equal( + thirdWarning.message, + "can't use both author and editor fields in Binney87" + ) -}); + equal(result.errors.length, 7) + var firstError = result.errors[0] + equal(firstError.file, 'references.bib') + equal(firstError.line, '196') + equal(firstError.level, 'error') + equal(firstError.message.indexOf("I was expecting a `,' or a `}'"), 0) + equal( + firstError.message.indexOf('(Error may have been on previous line)') > 0, + true + ) + var crossReferenceError = result.errors[5] + equal(crossReferenceError.level, 'error') + equal(crossReferenceError.message.indexOf('A bad cross reference'), 0) + var styleError = result.errors[6] + equal(styleError.level, 'error') + equal(styleError.message.indexOf("I couldn't open style file aa.bst"), 0) + }) +}) diff --git a/services/clsi/test/unit/js/LatexRunnerTests.js b/services/clsi/test/unit/js/LatexRunnerTests.js index 16f40bd7d8..9a32d190dc 100644 --- a/services/clsi/test/unit/js/LatexRunnerTests.js +++ b/services/clsi/test/unit/js/LatexRunnerTests.js @@ -175,7 +175,7 @@ describe('LatexRunner', function () { compiler: this.compiler, image: this.image, timeout: (this.timeout = 42000), - flags: ['-file-line-error', '-halt-on-error'], + flags: ['-shell-restricted', '-halt-on-error'], }, this.callback ) @@ -184,10 +184,10 @@ describe('LatexRunner', function () { return it('should include the flags in the command', function () { const command = this.CommandRunner.run.args[0][1] const flags = command.filter( - arg => arg === '-file-line-error' || arg === '-halt-on-error' + arg => arg === '-shell-restricted' || arg === '-halt-on-error' ) flags.length.should.equal(2) - flags[0].should.equal('-file-line-error') + flags[0].should.equal('-shell-restricted') return flags[1].should.equal('-halt-on-error') }) }) diff --git a/services/web/app/src/Features/Compile/ClsiManager.js b/services/web/app/src/Features/Compile/ClsiManager.js index fb4fdce487..0998250e3f 100644 --- a/services/web/app/src/Features/Compile/ClsiManager.js +++ b/services/web/app/src/Features/Compile/ClsiManager.js @@ -787,6 +787,7 @@ const ClsiManager = { _finaliseRequest(projectId, options, project, docs, files, callback) { const resources = [] + let flags let rootResourcePath = null let rootResourcePathOverride = null let hasMainFile = false @@ -847,6 +848,10 @@ const ClsiManager = { }) } + if (options.fileLineErrors) { + flags = ['-file-line-error'] + } + callback(null, { compile: { options: { @@ -860,6 +865,7 @@ const ClsiManager = { compileGroup: options.compileGroup, enablePdfCaching: (Settings.enablePdfCaching && options.enablePdfCaching) || false, + flags: flags, }, rootResourcePath, resources, diff --git a/services/web/app/src/Features/Compile/CompileController.js b/services/web/app/src/Features/Compile/CompileController.js index bd06436a08..d5b7807530 100644 --- a/services/web/app/src/Features/Compile/CompileController.js +++ b/services/web/app/src/Features/Compile/CompileController.js @@ -44,10 +44,12 @@ module.exports = CompileController = { const project_id = req.params.Project_id const isAutoCompile = !!req.query.auto_compile const enablePdfCaching = !!req.query.enable_pdf_caching + const fileLineErrors = !!req.query.file_line_errors const user_id = SessionManager.getLoggedInUserId(req.session) const options = { isAutoCompile, enablePdfCaching, + fileLineErrors, } if (req.body.rootDoc_id) { diff --git a/services/web/frontend/js/ide/pdf/controllers/PdfController.js b/services/web/frontend/js/ide/pdf/controllers/PdfController.js index 09cb84ba56..7087fdf195 100644 --- a/services/web/frontend/js/ide/pdf/controllers/PdfController.js +++ b/services/web/frontend/js/ide/pdf/controllers/PdfController.js @@ -283,6 +283,9 @@ App.controller( if (getMeta('ol-enablePdfCaching')) { params.enable_pdf_caching = true } + if (window.location.search.includes('file_line_errors=true')) { + params.file_line_errors = 'true' + } // if the previous run was a check, clear the error logs if ($scope.check) { $scope.pdf.logEntries = {} diff --git a/services/web/frontend/js/vendor/libs/latex-log-parser.js b/services/web/frontend/js/vendor/libs/latex-log-parser.js index cad979eb98..7ec2f024b6 100644 --- a/services/web/frontend/js/vendor/libs/latex-log-parser.js +++ b/services/web/frontend/js/vendor/libs/latex-log-parser.js @@ -1,298 +1,366 @@ -// Generated by CoffeeScript 1.10.0 -define(function() { - var HBOX_WARNING_REGEX, LATEX_WARNING_REGEX, LINES_REGEX, LOG_WRAP_LIMIT, LatexParser, LogText, PACKAGE_REGEX, PACKAGE_WARNING_REGEX, state; - LOG_WRAP_LIMIT = 79; - LATEX_WARNING_REGEX = /^LaTeX Warning: (.*)$/; - HBOX_WARNING_REGEX = /^(Over|Under)full \\(v|h)box/; - PACKAGE_WARNING_REGEX = /^(Package \b.+\b Warning:.*)$/; - LINES_REGEX = /lines? ([0-9]+)/; - PACKAGE_REGEX = /^Package (\b.+\b) Warning/; - LogText = function(text) { - var i, wrappedLines; - this.text = text.replace(/(\r\n)|\r/g, '\n'); - wrappedLines = this.text.split('\n'); - this.lines = [wrappedLines[0]]; - i = 1; +define(function () { + // Define some constants + const LOG_WRAP_LIMIT = 79 + const LATEX_WARNING_REGEX = /^LaTeX Warning: (.*)$/ + const HBOX_WARNING_REGEX = /^(Over|Under)full \\(v|h)box/ + const PACKAGE_WARNING_REGEX = /^(Package \b.+\b Warning:.*)$/ + // This is used to parse the line number from common latex warnings + const LINES_REGEX = /lines? ([0-9]+)/ + // This is used to parse the package name from the package warnings + const PACKAGE_REGEX = /^Package (\b.+\b) Warning/ + + const LogText = function (text) { + this.text = text.replace(/(\r\n)|\r/g, '\n') + // Join any lines which look like they have wrapped. + const wrappedLines = this.text.split('\n') + this.lines = [wrappedLines[0]] + let i = 1 while (i < wrappedLines.length) { - if (wrappedLines[i - 1].length === LOG_WRAP_LIMIT && wrappedLines[i - 1].slice(-3) !== '...') { - this.lines[this.lines.length - 1] += wrappedLines[i]; + // If the previous line is as long as the wrap limit then + // append this line to it. + // Some lines end with ... when LaTeX knows it's hit the limit + // These shouldn't be wrapped. + if ( + wrappedLines[i - 1].length === LOG_WRAP_LIMIT && + wrappedLines[i - 1].slice(-3) !== '...' + ) { + this.lines[this.lines.length - 1] += wrappedLines[i] } else { - this.lines.push(wrappedLines[i]); + this.lines.push(wrappedLines[i]) } - i++; + i++ } - this.row = 0; - }; - (function() { - this.nextLine = function() { - this.row++; + this.row = 0 + } + + ;(function () { + this.nextLine = function () { + this.row++ if (this.row >= this.lines.length) { - return false; + return false } else { - return this.lines[this.row]; + return this.lines[this.row] } - }; - this.rewindLine = function() { - this.row--; - }; - this.linesUpToNextWhitespaceLine = function() { - return this.linesUpToNextMatchingLine(/^ *$/); - }; - this.linesUpToNextMatchingLine = function(match) { - var lines, nextLine; - lines = []; - nextLine = this.nextLine(); + } + + this.rewindLine = function () { + this.row-- + } + + this.linesUpToNextWhitespaceLine = function () { + return this.linesUpToNextMatchingLine(/^ *$/) + } + + this.linesUpToNextMatchingLine = function (match) { + const lines = [] + let nextLine = this.nextLine() if (nextLine !== false) { - lines.push(nextLine); + lines.push(nextLine) } - while (nextLine !== false && !nextLine.match(match) && nextLine !== false) { - nextLine = this.nextLine(); + while ( + nextLine !== false && + !nextLine.match(match) && + nextLine !== false + ) { + nextLine = this.nextLine() if (nextLine !== false) { - lines.push(nextLine); + lines.push(nextLine) } } - return lines; - }; - }).call(LogText.prototype); - state = { + return lines + } + }.call(LogText.prototype)) + + const state = { NORMAL: 0, - ERROR: 1 - }; - LatexParser = function(text, options) { - this.log = new LogText(text); - this.state = state.NORMAL; - options = options || {}; - this.fileBaseNames = options.fileBaseNames || [/compiles/, /\/usr\/local/]; - this.ignoreDuplicates = options.ignoreDuplicates; - this.data = []; - this.fileStack = []; - this.currentFileList = this.rootFileList = []; - this.openParens = 0; - }; - (function() { - this.parse = function() { - var lineNo; + ERROR: 1, + } + + const LatexParser = function (text, options) { + this.log = new LogText(text) + this.state = state.NORMAL + options = options || {} + this.fileBaseNames = options.fileBaseNames || [/compiles/, /\/usr\/local/] + this.ignoreDuplicates = options.ignoreDuplicates + this.data = [] + this.fileStack = [] + this.currentFileList = this.rootFileList = [] + this.openParens = 0 + } + + ;(function () { + this.parse = function () { while ((this.currentLine = this.log.nextLine()) !== false) { if (this.state === state.NORMAL) { if (this.currentLineIsError()) { - this.state = state.ERROR; + this.state = state.ERROR this.currentError = { line: null, file: this.currentFilePath, level: 'error', message: this.currentLine.slice(2), content: '', - raw: this.currentLine + '\n' - }; + raw: this.currentLine + '\n', + } + } else if (this.currentLineIsFileLineError()) { + this.state = state.ERROR + this.parseFileLineError() } else if (this.currentLineIsRunawayArgument()) { - this.parseRunawayArgumentError(); + this.parseRunawayArgumentError() } else if (this.currentLineIsWarning()) { - this.parseSingleWarningLine(LATEX_WARNING_REGEX); + this.parseSingleWarningLine(LATEX_WARNING_REGEX) } else if (this.currentLineIsHboxWarning()) { - this.parseHboxLine(); + this.parseHboxLine() } else if (this.currentLineIsPackageWarning()) { - this.parseMultipleWarningLine(); + this.parseMultipleWarningLine() } else { - this.parseParensForFilenames(); + this.parseParensForFilenames() } } if (this.state === state.ERROR) { - this.currentError.content += this.log.linesUpToNextMatchingLine(/^l\.[0-9]+/).join('\n'); - this.currentError.content += '\n'; - this.currentError.content += this.log.linesUpToNextWhitespaceLine().join('\n'); - this.currentError.content += '\n'; - this.currentError.content += this.log.linesUpToNextWhitespaceLine().join('\n'); - this.currentError.raw += this.currentError.content; - lineNo = this.currentError.raw.match(/l\.([0-9]+)/); - if (lineNo) { - this.currentError.line = parseInt(lineNo[1], 10); + this.currentError.content += this.log + .linesUpToNextMatchingLine(/^l\.[0-9]+/) + .join('\n') + this.currentError.content += '\n' + this.currentError.content += this.log + .linesUpToNextWhitespaceLine() + .join('\n') + this.currentError.content += '\n' + this.currentError.content += this.log + .linesUpToNextWhitespaceLine() + .join('\n') + this.currentError.raw += this.currentError.content + const lineNo = this.currentError.raw.match(/l\.([0-9]+)/) + if (lineNo && this.currentError.line === null) { + this.currentError.line = parseInt(lineNo[1], 10) } - this.data.push(this.currentError); - this.state = state.NORMAL; + this.data.push(this.currentError) + this.state = state.NORMAL } } - return this.postProcess(this.data); - }; - this.currentLineIsError = function() { - return this.currentLine[0] === '!'; - }; - this.currentLineIsRunawayArgument = function() { - return this.currentLine.match(/^Runaway argument/); - }; - this.currentLineIsWarning = function() { - return !!this.currentLine.match(LATEX_WARNING_REGEX); - }; - this.currentLineIsPackageWarning = function() { - return !!this.currentLine.match(PACKAGE_WARNING_REGEX); - }; - this.currentLineIsHboxWarning = function() { - return !!this.currentLine.match(HBOX_WARNING_REGEX); - }; - this.parseRunawayArgumentError = function() { - var lineNo; + return this.postProcess(this.data) + } + + this.currentLineIsError = function () { + return this.currentLine[0] === '!' + } + + this.currentLineIsFileLineError = function () { + return /^\/.*:\d+: .*/.test(this.currentLine) + } + + this.currentLineIsRunawayArgument = function () { + return this.currentLine.match(/^Runaway argument/) + } + + this.currentLineIsWarning = function () { + return !!this.currentLine.match(LATEX_WARNING_REGEX) + } + + this.currentLineIsPackageWarning = function () { + return !!this.currentLine.match(PACKAGE_WARNING_REGEX) + } + + this.currentLineIsHboxWarning = function () { + return !!this.currentLine.match(HBOX_WARNING_REGEX) + } + + this.parseFileLineError = function () { + const result = this.currentLine.match(/^(\/.*):(\d+): (.*)/) + this.currentError = { + line: result[2], + file: result[1], + level: 'error', + message: result[3], + content: '', + raw: this.currentLine + '\n', + } + } + + this.parseRunawayArgumentError = function () { this.currentError = { line: null, file: this.currentFilePath, level: 'error', message: this.currentLine, content: '', - raw: this.currentLine + '\n' - }; - this.currentError.content += this.log.linesUpToNextWhitespaceLine().join('\n'); - this.currentError.content += '\n'; - this.currentError.content += this.log.linesUpToNextWhitespaceLine().join('\n'); - this.currentError.raw += this.currentError.content; - lineNo = this.currentError.raw.match(/l\.([0-9]+)/); + raw: this.currentLine + '\n', + } + this.currentError.content += this.log + .linesUpToNextWhitespaceLine() + .join('\n') + this.currentError.content += '\n' + this.currentError.content += this.log + .linesUpToNextWhitespaceLine() + .join('\n') + this.currentError.raw += this.currentError.content + const lineNo = this.currentError.raw.match(/l\.([0-9]+)/) if (lineNo) { - this.currentError.line = parseInt(lineNo[1], 10); + this.currentError.line = parseInt(lineNo[1], 10) } - return this.data.push(this.currentError); - }; - this.parseSingleWarningLine = function(prefix_regex) { - var line, lineMatch, warning, warningMatch; - warningMatch = this.currentLine.match(prefix_regex); + return this.data.push(this.currentError) + } + + this.parseSingleWarningLine = function (prefix_regex) { + const warningMatch = this.currentLine.match(prefix_regex) if (!warningMatch) { - return; + return } - warning = warningMatch[1]; - lineMatch = warning.match(LINES_REGEX); - line = lineMatch ? parseInt(lineMatch[1], 10) : null; + const warning = warningMatch[1] + const lineMatch = warning.match(LINES_REGEX) + const line = lineMatch ? parseInt(lineMatch[1], 10) : null this.data.push({ - line: line, + line, file: this.currentFilePath, level: 'warning', message: warning, - raw: warning - }); - }; - this.parseMultipleWarningLine = function() { - var line, lineMatch, packageMatch, packageName, prefixRegex, raw_message, warningMatch, warning_lines; - warningMatch = this.currentLine.match(PACKAGE_WARNING_REGEX); + raw: warning, + }) + } + + this.parseMultipleWarningLine = function () { + // Some package warnings are multiple lines, let's parse the first line + let warningMatch = this.currentLine.match(PACKAGE_WARNING_REGEX) if (!warningMatch) { - return; + return } - warning_lines = [warningMatch[1]]; - lineMatch = this.currentLine.match(LINES_REGEX); - line = lineMatch ? parseInt(lineMatch[1], 10) : null; - packageMatch = this.currentLine.match(PACKAGE_REGEX); - packageName = packageMatch[1]; - prefixRegex = new RegExp('(?:\\(' + packageName + '\\))*[\\s]*(.*)', 'i'); + // Something strange happened, return early + const warning_lines = [warningMatch[1]] + let lineMatch = this.currentLine.match(LINES_REGEX) + let line = lineMatch ? parseInt(lineMatch[1], 10) : null + const packageMatch = this.currentLine.match(PACKAGE_REGEX) + const packageName = packageMatch[1] + // Regex to get rid of the unnecesary (packagename) prefix in most multi-line warnings + const prefixRegex = new RegExp( + '(?:\\(' + packageName + '\\))*[\\s]*(.*)', + 'i' + ) + // After every warning message there's a blank line, let's use it while (!!(this.currentLine = this.log.nextLine())) { - lineMatch = this.currentLine.match(LINES_REGEX); - line = lineMatch ? parseInt(lineMatch[1], 10) : line; - warningMatch = this.currentLine.match(prefixRegex); - warning_lines.push(warningMatch[1]); + lineMatch = this.currentLine.match(LINES_REGEX) + line = lineMatch ? parseInt(lineMatch[1], 10) : line + warningMatch = this.currentLine.match(prefixRegex) + warning_lines.push(warningMatch[1]) } - raw_message = warning_lines.join(' '); + const raw_message = warning_lines.join(' ') this.data.push({ - line: line, + line, file: this.currentFilePath, level: 'warning', message: raw_message, - raw: raw_message - }); - }; - this.parseHboxLine = function() { - var line, lineMatch; - lineMatch = this.currentLine.match(LINES_REGEX); - line = lineMatch ? parseInt(lineMatch[1], 10) : null; + raw: raw_message, + }) + } + + this.parseHboxLine = function () { + const lineMatch = this.currentLine.match(LINES_REGEX) + const line = lineMatch ? parseInt(lineMatch[1], 10) : null this.data.push({ - line: line, + line, file: this.currentFilePath, level: 'typesetting', message: this.currentLine, - raw: this.currentLine - }); - }; - this.parseParensForFilenames = function() { - var filePath, newFile, pos, previousFile, token; - pos = this.currentLine.search(/\(|\)/); + raw: this.currentLine, + }) + } + + // Check if we're entering or leaving a new file in this line + + this.parseParensForFilenames = function () { + const pos = this.currentLine.search(/\(|\)/) if (pos !== -1) { - token = this.currentLine[pos]; - this.currentLine = this.currentLine.slice(pos + 1); + const token = this.currentLine[pos] + this.currentLine = this.currentLine.slice(pos + 1) if (token === '(') { - filePath = this.consumeFilePath(); + const filePath = this.consumeFilePath() if (filePath) { - this.currentFilePath = filePath; - newFile = { + this.currentFilePath = filePath + const newFile = { path: filePath, - files: [] - }; - this.fileStack.push(newFile); - this.currentFileList.push(newFile); - this.currentFileList = newFile.files; + files: [], + } + this.fileStack.push(newFile) + this.currentFileList.push(newFile) + this.currentFileList = newFile.files } else { - this.openParens++; + this.openParens++ } } else if (token === ')') { if (this.openParens > 0) { - this.openParens--; + this.openParens-- } else { if (this.fileStack.length > 1) { - this.fileStack.pop(); - previousFile = this.fileStack[this.fileStack.length - 1]; - this.currentFilePath = previousFile.path; - this.currentFileList = previousFile.files; + this.fileStack.pop() + const previousFile = this.fileStack[this.fileStack.length - 1] + this.currentFilePath = previousFile.path + this.currentFileList = previousFile.files } } } - this.parseParensForFilenames(); + // else { + // Something has gone wrong but all we can do now is ignore it :( + // } + // Process the rest of the line + this.parseParensForFilenames() } - }; - this.consumeFilePath = function() { - var endOfFilePath, path; + } + + this.consumeFilePath = function () { + // Our heuristic for detecting file names are rather crude + // A file may not contain a space, or ) in it + // To be a file path it must have at least one / if (!this.currentLine.match(/^\/?([^ \)]+\/)+/)) { - return false; + return false } - endOfFilePath = this.currentLine.search(RegExp(' |\\)')); - path = void 0; + const endOfFilePath = this.currentLine.search(RegExp(' |\\)')) + let path = undefined if (endOfFilePath === -1) { - path = this.currentLine; - this.currentLine = ''; + path = this.currentLine + this.currentLine = '' } else { - path = this.currentLine.slice(0, endOfFilePath); - this.currentLine = this.currentLine.slice(endOfFilePath); + path = this.currentLine.slice(0, endOfFilePath) + this.currentLine = this.currentLine.slice(endOfFilePath) } - return path; - }; - return this.postProcess = function(data) { - var all, errors, hashEntry, hashes, i, typesetting, warnings; - all = []; - errors = []; - warnings = []; - typesetting = []; - hashes = []; - hashEntry = function(entry) { - return entry.raw; - }; - i = 0; + return path + } + + this.postProcess = function (data) { + const all = [] + const errors = [] + const warnings = [] + const typesetting = [] + const hashes = [] + + const hashEntry = entry => entry.raw + + let i = 0 while (i < data.length) { if (this.ignoreDuplicates && hashes.indexOf(hashEntry(data[i])) > -1) { - i++; - continue; + i++ + continue } if (data[i].level === 'error') { - errors.push(data[i]); + errors.push(data[i]) } else if (data[i].level === 'typesetting') { - typesetting.push(data[i]); + typesetting.push(data[i]) } else if (data[i].level === 'warning') { - warnings.push(data[i]); + warnings.push(data[i]) } - all.push(data[i]); - hashes.push(hashEntry(data[i])); - i++; + all.push(data[i]) + hashes.push(hashEntry(data[i])) + i++ } return { - errors: errors, - warnings: warnings, - typesetting: typesetting, - all: all, - files: this.rootFileList - }; - }; - }).call(LatexParser.prototype); - LatexParser.parse = function(text, options) { - return new LatexParser(text, options).parse(); - }; - return LatexParser; -}); + errors, + warnings, + typesetting, + all, + files: this.rootFileList, + } + } + }.call(LatexParser.prototype)) + + LatexParser.parse = (text, options) => new LatexParser(text, options).parse() + + return LatexParser +}) diff --git a/services/web/frontend/stylesheets/app/editor/pdf.less b/services/web/frontend/stylesheets/app/editor/pdf.less index ae045f7360..f0840a63b2 100644 --- a/services/web/frontend/stylesheets/app/editor/pdf.less +++ b/services/web/frontend/stylesheets/app/editor/pdf.less @@ -246,6 +246,7 @@ } pre { font-size: 12px; + white-space: pre-wrap; } .dropdown { position: relative; diff --git a/services/web/test/unit/src/Compile/ClsiManagerTests.js b/services/web/test/unit/src/Compile/ClsiManagerTests.js index b4389ab59e..28d98fd933 100644 --- a/services/web/test/unit/src/Compile/ClsiManagerTests.js +++ b/services/web/test/unit/src/Compile/ClsiManagerTests.js @@ -620,6 +620,7 @@ describe('ClsiManager', function () { syncState: undefined, compileGroup: 'standard', enablePdfCaching: false, + flags: undefined, }, // "01234567890abcdef" rootResourcePath: 'main.tex', resources: [ @@ -716,6 +717,7 @@ describe('ClsiManager', function () { syncState: '01234567890abcdef', compileGroup: 'priority', enablePdfCaching: false, + flags: undefined, }, rootResourcePath: 'main.tex', resources: [ diff --git a/services/web/test/unit/src/Compile/CompileControllerTests.js b/services/web/test/unit/src/Compile/CompileControllerTests.js index 7a4d921b73..15cbcc3b71 100644 --- a/services/web/test/unit/src/Compile/CompileControllerTests.js +++ b/services/web/test/unit/src/Compile/CompileControllerTests.js @@ -107,6 +107,7 @@ describe('CompileController', function () { .calledWith(this.project_id, this.user_id, { isAutoCompile: false, enablePdfCaching: false, + fileLineErrors: false, }) .should.equal(true) }) @@ -137,6 +138,7 @@ describe('CompileController', function () { .calledWith(this.project_id, this.user_id, { isAutoCompile: true, enablePdfCaching: false, + fileLineErrors: false, }) .should.equal(true) }) @@ -154,6 +156,7 @@ describe('CompileController', function () { isAutoCompile: false, enablePdfCaching: false, draft: true, + fileLineErrors: false, }) .should.equal(true) })