diff --git a/packages/mosaic/src/fleet/fleet-documentation.spec.ts b/packages/mosaic/src/fleet/fleet-documentation.spec.ts index 9d821ae..d936929 100644 --- a/packages/mosaic/src/fleet/fleet-documentation.spec.ts +++ b/packages/mosaic/src/fleet/fleet-documentation.spec.ts @@ -475,6 +475,18 @@ function codeSurfaceDiagnostics(path: string, source: string): SurfaceDiagnostic } continue; } + if (/^(?: {0,3}(?:(?:> ?)|(?:(?:[-+*]|[0-9]{1,9}[.)]) +)))+(`{3,}|~{3,})/.test(line)) { + diagnostics.push({ path, line: lineNumber, code: 'fence-context' }); + continue; + } + if (/^(?: {4,}|\t).*\S/.test(line)) { + diagnostics.push({ path, line: lineNumber, code: 'indented-code' }); + continue; + } + if (/`{2,}/.test(line) && !/^```[a-z-]+$/.test(line)) { + diagnostics.push({ path, line: lineNumber, code: 'inline-delimiter' }); + continue; + } const marker = line.match(/^(`{3,}|~{3,})(.*)$/); if (marker !== null) { block += 1; @@ -495,15 +507,7 @@ function codeSurfaceDiagnostics(path: string, source: string): SurfaceDiagnostic inFence = true; continue; } - if (/^(?: {1,3}| {0,3}(?:>|[-+*]|[0-9]{1,9}[.)]) )(`{3,}|~{3,})/.test(line)) { - diagnostics.push({ path, line: lineNumber, code: 'fence-context' }); - continue; - } - if (/^(?: {4}|\t)\S/.test(line)) { - diagnostics.push({ path, line: lineNumber, code: 'indented-code' }); - continue; - } - if (/<\/?(?:pre|code|script|style|xmp|listing)(?:\s|\/?>)/i.test(line)) { + if (/<\/?(?:pre|code|script|style|xmp|listing)(?=$|\s|[>/])/i.test(line)) { diagnostics.push({ path, line: lineNumber, code: 'raw-code-container' }); } for (const match of line.matchAll(/(? { } }); - it('rejects indented code and raw HTML code containers', (): void => { - expect(codeSurfaceDiagnostics('fixture.md', ' mosaic fleet verify\n')).toEqual([ - { path: 'fixture.md', line: 1, code: 'indented-code' }, - ]); - expect(codeSurfaceDiagnostics('fixture.md', '
mosaic fleet verify
\n')).toEqual([ - { path: 'fixture.md', line: 1, code: 'raw-code-container' }, - ]); + it('rejects inline delimiter runs instead of silently omitting them', (): void => { + for (const source of ['``literal``', '```literal```', 'text ``literal`` text']) { + expect(codeSurfaceDiagnostics('fixture.md', source)).toContainEqual({ + path: 'fixture.md', + line: 1, + code: 'inline-delimiter', + }); + } + }); + + it('rejects every nonblank line with four leading spaces or a leading tab', (): void => { + for (const source of [ + ' mosaic fleet verify', + ' mosaic fleet verify', + ' \tmosaic fleet verify', + '\t mosaic fleet verify', + ]) { + expect(codeSurfaceDiagnostics('fixture.md', source)).toEqual([ + { path: 'fixture.md', line: 1, code: 'indented-code' }, + ]); + } + }); + + it('rejects raw HTML code-container tag prefixes at every boundary', (): void => { + for (const source of [ + '', + '
',
+      'prose')).toEqual([]);
+  });
+
+  it('rejects nested blockquote and list fence contexts', (): void => {
+    for (const source of [
+      '>> ```fleet-command',
+      '> > ```fleet-command',
+      '> - ```fleet-command',
+      '- > ```fleet-command',
+    ]) {
+      expect(codeSurfaceDiagnostics('fixture.md', source)).toEqual([
+        { path: 'fixture.md', line: 1, code: 'fence-context' },
+      ]);
+    }
   });
 
   it('requires optional metavariables to use bracketed angle notation', (): void => {