mirror of
https://github.com/anomalyco/opencode.git
synced 2026-11-08 01:07:06 +03:00
200 lines
4.5 KiB
TypeScript
200 lines
4.5 KiB
TypeScript
// Seeded Markdown generator and mutator for differential fuzzing against commonmark.js (core) and
|
|
// micromark + micromark-extension-gfm (GFM). Biased toward nesting, lists, emphasis, fences, and tables.
|
|
import * as commonmark from "commonmark"
|
|
import { tests as specTests } from "commonmark-spec"
|
|
import { micromark } from "micromark"
|
|
import { gfm, gfmHtml } from "micromark-extension-gfm"
|
|
import { createMarkdownStream, renderBlocksToHtml } from "../src/index"
|
|
|
|
export function rng(seed: number) {
|
|
let s = seed >>> 0 || 1
|
|
|
|
return () => {
|
|
s ^= s << 13
|
|
s >>>= 0
|
|
s ^= s >>> 17
|
|
s ^= s << 5
|
|
s >>>= 0
|
|
|
|
return s / 0x100000000
|
|
}
|
|
}
|
|
|
|
const PREFIXES = ["", "", "", "- ", "* ", "+ ", "1. ", "2) ", "10. ", "> ", " ", " ", " ", "\t", "- ", "1. "]
|
|
const LEADERS = [
|
|
"# ",
|
|
"## ",
|
|
"###### ",
|
|
"```",
|
|
"~~~",
|
|
"````js",
|
|
"```py x",
|
|
"---",
|
|
"***",
|
|
"___",
|
|
"===",
|
|
"- - -",
|
|
"<div>",
|
|
"</div>",
|
|
"<!-- c -->",
|
|
"<pre>",
|
|
"</pre>",
|
|
"[x]: /url",
|
|
'[y]: <a b> "t"',
|
|
"[Foo bar]:\n/u",
|
|
" code",
|
|
"",
|
|
]
|
|
const TABLE = ["| a | b |", "|---|---|", "| - | :-: |", "a | b", "--|--", "| x |", "|:--|", "| `c|d` | e\\|f |"]
|
|
const INLINE = [
|
|
"foo",
|
|
"bar",
|
|
"baz",
|
|
"*",
|
|
"**",
|
|
"***",
|
|
"_",
|
|
"__",
|
|
"`",
|
|
"``",
|
|
"[",
|
|
"]",
|
|
"(",
|
|
")",
|
|
"",
|
|
'[a](<b c> "t")',
|
|
"[x]",
|
|
"[x][]",
|
|
"[y][x]",
|
|
"",
|
|
" ",
|
|
" ",
|
|
"\t",
|
|
" \n",
|
|
"\\\n",
|
|
"*a*",
|
|
"**b**",
|
|
"_c_",
|
|
"__d__",
|
|
"`e`",
|
|
"a*b",
|
|
"a_b",
|
|
"*(",
|
|
")*",
|
|
'"',
|
|
"'",
|
|
"ä",
|
|
"😀",
|
|
"[ ]",
|
|
"[x]",
|
|
"---",
|
|
"1.",
|
|
"#",
|
|
"|",
|
|
]
|
|
|
|
function inlineText(r: () => number, max: number): string {
|
|
let out = ""
|
|
const n = 1 + Math.floor(r() * max)
|
|
|
|
for (let i = 0; i < n; i++) {
|
|
out += INLINE[Math.floor(r() * INLINE.length)]!
|
|
|
|
if (r() < 0.5) out += " "
|
|
}
|
|
|
|
return out
|
|
}
|
|
|
|
export function generate(r: () => number, gfmMode: boolean): string {
|
|
const lines: string[] = []
|
|
const count = 1 + Math.floor(r() * 8)
|
|
|
|
for (let i = 0; i < count; i++) {
|
|
let line = ""
|
|
const depth = Math.floor(r() * r() * 4)
|
|
|
|
for (let d = 0; d < depth; d++) line += PREFIXES[Math.floor(r() * PREFIXES.length)]!
|
|
const roll = r()
|
|
|
|
if (gfmMode && roll < 0.15) line += TABLE[Math.floor(r() * TABLE.length)]!
|
|
else if (roll < 0.4) line += LEADERS[Math.floor(r() * LEADERS.length)]! + (r() < 0.5 ? inlineText(r, 3) : "")
|
|
else if (roll < 0.5) line += ""
|
|
else line += inlineText(r, 6)
|
|
lines.push(line)
|
|
}
|
|
|
|
return lines.join("\n") + (r() < 0.7 ? "\n" : "")
|
|
}
|
|
|
|
const SPECIALS = "*_`[]()!<>&\\~|#-+=:.> \n\t1"
|
|
const specExamples: string[] = specTests.map((e) => e.markdown.replace(/→/g, "\t"))
|
|
|
|
export function mutate(r: () => number): string {
|
|
let s = specExamples[Math.floor(r() * specExamples.length)]!
|
|
const edits = 1 + Math.floor(r() * 4)
|
|
|
|
for (let e = 0; e < edits; e++) {
|
|
const at = Math.floor(r() * (s.length + 1))
|
|
const roll = r()
|
|
|
|
if (roll < 0.4) s = s.slice(0, at) + SPECIALS[Math.floor(r() * SPECIALS.length)] + s.slice(at)
|
|
else if (roll < 0.7) s = s.slice(0, at) + s.slice(at + 1)
|
|
else {
|
|
const other = specExamples[Math.floor(r() * specExamples.length)]!
|
|
const from = Math.floor(r() * other.length)
|
|
s = s.slice(0, at) + other.slice(from, from + 1 + Math.floor(r() * 12)) + s.slice(at)
|
|
}
|
|
}
|
|
|
|
return s
|
|
}
|
|
|
|
const cmParser = new commonmark.Parser()
|
|
const cmRenderer = new commonmark.HtmlRenderer()
|
|
|
|
export function referenceCore(md: string): string {
|
|
return cmRenderer.render(cmParser.parse(md))
|
|
}
|
|
|
|
export function referenceGfm(md: string): string {
|
|
return micromark(md, { allowDangerousHtml: true, extensions: [gfm()], htmlExtensions: [gfmHtml()] })
|
|
}
|
|
|
|
export function patchmark(md: string, gfmMode: boolean): string {
|
|
const opts = { gfm: gfmMode, math: false }
|
|
|
|
return renderBlocksToHtml(createMarkdownStream(opts).update(md, { streaming: false }).blocks, opts)
|
|
}
|
|
|
|
/** Normalize insignificant HTML differences between renderers (whitespace between tags, void-tag syntax, attribute order) */
|
|
export function normalizeHtml(html: string): string {
|
|
return html
|
|
.replace(/<input ([^>]*?)\s*\/?>/g, (_m, attrs: string) => `<input ${attrs.trim().split(/\s+/).sort().join(" ")}>`)
|
|
.replace(/(<input [^>]+>)\n/g, "$1 ")
|
|
.replace(/<(br|hr|img[^>]*?)\s*\/?>/g, "<$1>")
|
|
.replace(/\s+<\/li>/g, "</li>")
|
|
.replace(/>\s+</g, "><")
|
|
.replace(/\s+$/g, "")
|
|
.replace(/^\s+/g, "")
|
|
}
|