ソースを参照

Merge pull request #28893 from overleaf/mj-linter-brace-check

[web] Allow braces in documentclass options

GitOrigin-RevId: 9675d3fc760a3b7d402c5a9df57a0cf183a1e648
Mathias Jakobsen 1 年間 前
親
コミット
f9d0f7e3ee

+ 18 - 2
services/web/frontend/js/features/source-editor/languages/latex/linter/latex-linter.worker.js

@@ -338,11 +338,20 @@ const readOptionalArgumentWithUnderscores = function (TokeniseResult, k) {
   }
   }
 
 
   let label = ''
   let label = ''
+  let groupDepth = 0
   let j, tok
   let j, tok
   for (j = k + 1; (tok = Tokens[j]); j++) {
   for (j = k + 1; (tok = Tokens[j]); j++) {
     if (tok[1] === '{') {
     if (tok[1] === '{') {
-      // unclosed label
-      break
+      if (label.length === 0) {
+        // We haven't seen a [ yet, so there is no optional argument
+        break
+      }
+      groupDepth++
+    } else if (tok[1] === '}') {
+      groupDepth--
+      if (groupDepth < 0) {
+        break
+      }
     } else if (tok[1] === 'Text') {
     } else if (tok[1] === 'Text') {
       const str = text.substring(tok[2], tok[3])
       const str = text.substring(tok[2], tok[3])
       label = label + str
       label = label + str
@@ -357,6 +366,13 @@ const readOptionalArgumentWithUnderscores = function (TokeniseResult, k) {
       break // breaking due to unrecognised token
       break // breaking due to unrecognised token
     }
     }
   }
   }
+  if (groupDepth !== 0) {
+    const missing = groupDepth > 0 ? '{' : '}'
+    // mismatched braces
+    const e = new Error(`Unmatched ${missing} in label`)
+    e.pos = j + 1
+    return e
+  }
 
 
   if (label.length === 0) {
   if (label.length === 0) {
     return null
     return null

+ 21 - 2
services/web/test/frontend/features/source-editor/languages/latex/latex-linter.test.ts

@@ -473,9 +473,8 @@ describe('LatexLinter', function () {
 
 
   it('should reject an unclosed hyperref label', function () {
   it('should reject an unclosed hyperref label', function () {
     const { errors } = Parse('\\hyperref[foo_bar{foo bar}')
     const { errors } = Parse('\\hyperref[foo_bar{foo bar}')
-    assert.equal(errors.length, 2)
+    assert.equal(errors.length, 1)
     assert.equal(errors[0].text, 'invalid hyperref label')
     assert.equal(errors[0].text, 'invalid hyperref label')
-    assert.equal(errors[1].text, 'unexpected close group }')
   })
   })
 
 
   it('should accept a hyperref command without an optional argument', function () {
   it('should accept a hyperref command without an optional argument', function () {
@@ -511,6 +510,26 @@ describe('LatexLinter', function () {
     assert.equal(errors.length, 0)
     assert.equal(errors.length, 0)
   })
   })
 
 
+  it('should accept a documentclass with braces in options', function () {
+    const { errors } = Parse(
+      '\\documentclass[a4paper,margin={1in,0.5in}]{article}'
+    )
+    assert.equal(errors.length, 0)
+  })
+
+  it('should reject documentclass with unbalanced braces in options', function () {
+    const { errors } = Parse('\\documentclass[foo={bar]{article}')
+    assert.equal(errors.length, 2)
+    assert.equal(errors[0].text, 'invalid documentclass option')
+    assert.equal(errors[1].text, 'unexpected close group }')
+  })
+
+  it('should reject documentclass with out of order braces in options', function () {
+    const { errors } = Parse('\\documentclass[foo=}bar{]{article}')
+    assert.equal(errors.length, 1)
+    assert.equal(errors[0].text, 'invalid documentclass option')
+  })
+
   // %novalidate
   // %novalidate
   // %begin novalidate
   // %begin novalidate
   // %end novalidate
   // %end novalidate