From 2f7b48d50fb23baa5a7cee08b5c82f747d75077d Mon Sep 17 00:00:00 2001 From: Brian Gough Date: Wed, 20 Sep 2023 15:07:18 +0100 Subject: [PATCH] Merge pull request #14121 from overleaf/bg-best-allow-underscore-in-hyperref-labels allow underscore in hyperref labels GitOrigin-RevId: c4e0cfcdef92dec959dceba9a7ae55920812fdb0 --- .../latex/linter/latex-linter.worker.js | 70 +++++++++++++++++++ .../languages/latex/latex-linter.test.ts | 34 +++++++++ 2 files changed, 104 insertions(+) diff --git a/services/web/frontend/js/features/source-editor/languages/latex/linter/latex-linter.worker.js b/services/web/frontend/js/features/source-editor/languages/latex/linter/latex-linter.worker.js index 6f55d2a904..7f594a109f 100644 --- a/services/web/frontend/js/features/source-editor/languages/latex/linter/latex-linter.worker.js +++ b/services/web/frontend/js/features/source-editor/languages/latex/linter/latex-linter.worker.js @@ -322,6 +322,54 @@ const read1filename = function (TokeniseResult, k) { } } +const readOptionalLabel = function (TokeniseResult, k) { + // read a label my_label:text.. + const Tokens = TokeniseResult.tokens + const text = TokeniseResult.text + + const params = Tokens[k + 1] + + // Quick check for arguments like [label] + if (params && params[1] === 'Text') { + const paramNum = text.substring(params[2], params[3]) + if (paramNum.match(/^(\[[^\]]*\])*\s*$/)) { + return k + 1 // got it + } + } + + let label = '' + let j, tok + for (j = k + 1, tok; (tok = Tokens[j]); j++) { + if (tok[1] === '{') { + // unclosed label + break + } else if (tok[1] === 'Text') { + const str = text.substring(tok[2], tok[3]) + label = label + str + if (str.match(/\]/)) { + // breaking due to ] + break + } + } else if (tok[1] === '_') { + label = label + tok[1] + } else { + break // breaking due to unrecognised token + } + } + + if (label.length === 0) { + return null + } else if (label.length > 0 && /^\[[^\]]*\]\s*$/.test(label)) { + // make sure the label is of the form [label] + return j - 1 // advance past these tokens + } else { + // invalid label + const e = new Error('Invalid label') + e.pos = j + 1 + return e + } +} + const readOptionalParams = function (TokeniseResult, k) { // read an optional parameter [N] where N is a number, used // for \newcommand{\foo}[2]... meaning 2 parameters @@ -956,6 +1004,28 @@ const InterpretTokens = function (TokeniseResult, ErrorReporter) { i = newPos } nextGroupMathMode = false + } else if (seq === 'hyperref') { + // try to read any optional params [LABEL].... allowing for + // underscores, advance if found + let newPos = readOptionalLabel(TokeniseResult, i) + if (newPos instanceof Error) { + TokenErrorFromTo( + Tokens[i + 1], + Tokens[newPos.pos], + 'invalid hyperref label' + ) + i = newPos.pos + } else { + i = newPos + } + // try to read parameter {....}, advance if found + newPos = readDefinition(TokeniseResult, i) + if (newPos === null) { + /* do nothing */ + } else { + i = newPos + } + nextGroupMathMode = false } else if (seq === 'resizebox') { // try to read any optional params [BAR]...., advance if found let newPos = readOptionalGeneric(TokeniseResult, i) diff --git a/services/web/test/frontend/features/source-editor/languages/latex/latex-linter.test.ts b/services/web/test/frontend/features/source-editor/languages/latex/latex-linter.test.ts index c596217942..73567b9a26 100644 --- a/services/web/test/frontend/features/source-editor/languages/latex/latex-linter.test.ts +++ b/services/web/test/frontend/features/source-editor/languages/latex/latex-linter.test.ts @@ -410,6 +410,40 @@ describe('LatexLinter', function () { assert.equal(errors.length, 0) }) + it('should accept a plain hyperref command', function () { + const { errors } = Parse('\\hyperref{http://www.overleaf.com/}') + assert.equal(errors.length, 0) + }) + + it('should accept a hyperref command with underscores in the url ', function () { + const { errors } = Parse('\\hyperref{http://www.overleaf.com/my_page.html}') + assert.equal(errors.length, 0) + }) + + it('should accept a hyperref command with category, name and text arguments ', function () { + const { errors } = Parse( + '\\hyperref{http://www.overleaf.com/}{category}{name}{text}' + ) + assert.equal(errors.length, 0) + }) + + it('should accept an underscore in a hyperref label', function () { + const { errors } = Parse('\\hyperref[foo_bar]{foo bar}') + assert.equal(errors.length, 0) + }) + + it('should reject a $ in a hyperref label', function () { + const { errors } = Parse('\\hyperref[foo$bar]{foo bar}') + assert.equal(errors.length, 1) + }) + + it('should reject an unclosed hyperref label', function () { + const { errors } = Parse('\\hyperref[foo_bar{foo bar}') + assert.equal(errors.length, 2) + assert.equal(errors[0].text, 'invalid hyperref label') + assert.equal(errors[1].text, 'unexpected close group }') + }) + // %novalidate // %begin novalidate // %end novalidate