From f0eba8e742d6fec09b587d63d62135a349a4209e Mon Sep 17 00:00:00 2001 From: Mathias Jakobsen Date: Fri, 7 Jun 2024 12:05:49 +0100 Subject: [PATCH] Merge pull request #18676 from overleaf/mj-web-linter-document-class [web] Allow underscore in optional argument in \documentclass GitOrigin-RevId: c6f8716548411ade03814c8123df76fa8aa70ecc --- .../languages/latex/latex-language.ts | 3 +- .../latex/linter/latex-linter.worker.js | 30 +++++++++++++++++-- .../source-editor/lezer-latex/latex.grammar | 1 + .../languages/latex/latex-linter.test.ts | 17 +++++++++++ 4 files changed, 47 insertions(+), 4 deletions(-) diff --git a/services/web/frontend/js/features/source-editor/languages/latex/latex-language.ts b/services/web/frontend/js/features/source-editor/languages/latex/latex-language.ts index 509a604c93..e669913c87 100644 --- a/services/web/frontend/js/features/source-editor/languages/latex/latex-language.ts +++ b/services/web/frontend/js/features/source-editor/languages/latex/latex-language.ts @@ -152,8 +152,7 @@ export const LaTeXLanguage = LRLanguage.define({ 'HrefCommand/ShortTextArgument/ShortArg/...': t.link, 'HrefCommand/UrlArgument/...': t.monospace, 'CtrlSeq Csname': t.tagName, - 'DocumentClass/OptionalArgument/ShortOptionalArg/Normal': - t.attributeValue, + 'DocumentClass/OptionalArgument/ShortOptionalArg/...': t.attributeValue, 'DocumentClass/ShortTextArgument/ShortArg/Normal': t.typeName, 'ListEnvironment/BeginEnv/OptionalArgument/...': t.monospace, Number: t.number, 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 19b9532297..6ae97d474b 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,7 +322,7 @@ const read1filename = function (TokeniseResult, k) { } } -const readOptionalLabel = function (TokeniseResult, k) { +const readOptionalArgumentWithUnderscores = function (TokeniseResult, k) { // read a label my_label:text.. const Tokens = TokeniseResult.tokens const text = TokeniseResult.text @@ -348,6 +348,7 @@ const readOptionalLabel = function (TokeniseResult, k) { label = label + str if (str.match(/\]/)) { // breaking due to ] + j++ break } } else if (tok[1] === '_') { @@ -800,6 +801,31 @@ const InterpretTokens = function (TokeniseResult, ErrorReporter) { ) { // Environments.push({command: "end", name: "user-defined-equation", token: token}); seenUserDefinedEndEquation = true + } else if (seq === 'documentclass') { + // try to read any optional params [LABEL].... allowing for + // underscores, advance if found + let newPos = readOptionalArgumentWithUnderscores(TokeniseResult, i) + if (newPos instanceof Error) { + TokenErrorFromTo( + Tokens[i + 1], + Tokens[Math.min(newPos.pos, len - 1)], + 'invalid documentclass option' + ) + i = newPos.pos + } else if (newPos == null) { + /* do nothing */ + } else { + i = newPos + } + // Read parameter {....}, ignore if missing + newPos = readDefinition(TokeniseResult, i) + if (newPos === null) { + // NOTE: We could choose to throw an error here, as the argument is + // required. However, that would show errors as you are typing. So + // maybe it's better to be lenient. + } else { + i = newPos + } } else if ( seq === 'newcommand' || seq === 'renewcommand' || @@ -1038,7 +1064,7 @@ const InterpretTokens = function (TokeniseResult, ErrorReporter) { } else if (seq === 'hyperref') { // try to read any optional params [LABEL].... allowing for // underscores, advance if found - let newPos = readOptionalLabel(TokeniseResult, i) + let newPos = readOptionalArgumentWithUnderscores(TokeniseResult, i) if (newPos instanceof Error) { TokenErrorFromTo( Tokens[i + 1], diff --git a/services/web/frontend/js/features/source-editor/lezer-latex/latex.grammar b/services/web/frontend/js/features/source-editor/lezer-latex/latex.grammar index 01a5aa8f1f..a05ec70fe7 100644 --- a/services/web/frontend/js/features/source-editor/lezer-latex/latex.grammar +++ b/services/web/frontend/js/features/source-editor/lezer-latex/latex.grammar @@ -484,6 +484,7 @@ ShortOptionalArg { ( textBase | NonEmptyGroup | "#" // macro character + | "_" // underscore is used in some parameter names )* } 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 cbfce9e145..f5c374077f 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 @@ -494,6 +494,23 @@ describe('LatexLinter', function () { assert.equal(errors[0].text, 'unclosed group {') }) + it('should accept documentclass with no options', function () { + const { errors } = Parse('\\documentclass{article}') + assert.equal(errors.length, 0) + }) + + it('should accept documentclass with options', function () { + const { errors } = Parse('\\documentclass[a4paper]{article}') + assert.equal(errors.length, 0) + }) + + it('should accept documentclass with underscore in options', function () { + const { errors } = Parse( + '\\documentclass[my_custom_document_class_option]{my-custom-class}' + ) + assert.equal(errors.length, 0) + }) + // %novalidate // %begin novalidate // %end novalidate