tempest-react-sdk 0.77.0 → 0.78.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/lib/alias/collect.mjs +20 -3
- package/bin/lib/alias/index.mjs +12 -1
- package/bin/lib/alias/index.test.mjs +24 -0
- package/bin/lib/css/analyze.test.mjs +21 -1
- package/bin/lib/css/collect.mjs +14 -0
- package/bin/lib/css/extract.mjs +63 -5
- package/bin/lib/css/extract.test.mjs +110 -1
- package/bin/lib/css/fix.e2e.test.mjs +50 -0
- package/bin/lib/css/index.mjs +6 -1
- package/bin/lib/fs/contain.mjs +50 -0
- package/bin/lib/icons/generate.mjs +28 -3
- package/bin/lib/icons/generate.test.mjs +57 -1
- package/bin/lib/openapi/escape.mjs +20 -6
- package/bin/lib/openapi/generate.encode.test.mjs +120 -0
- package/bin/lib/openapi/generate.hostile.test.mjs +38 -9
- package/bin/lib/openapi/generate.mjs +41 -6
- package/bin/lib/openapi/generate.test.mjs +7 -2
- package/bin/lib/openapi/schema-to-zod.mjs +5 -3
- package/bin/tempest-gen.test.mjs +188 -0
- package/bin/tempest.mjs +120 -44
- package/dist/auth/create-tempest-auth.cjs +1 -1
- package/dist/auth/create-tempest-auth.cjs.map +1 -1
- package/dist/auth/create-tempest-auth.js +186 -113
- package/dist/auth/create-tempest-auth.js.map +1 -1
- package/dist/br/pix.cjs +1 -1
- package/dist/br/pix.cjs.map +1 -1
- package/dist/br/pix.js +30 -27
- package/dist/br/pix.js.map +1 -1
- package/dist/br.d.ts +11 -4
- package/dist/components/CodeBlock/CodeBlock.cjs +1 -1
- package/dist/components/CodeBlock/CodeBlock.cjs.map +1 -1
- package/dist/components/CodeBlock/CodeBlock.js +1 -1
- package/dist/components/CodeBlock/CodeBlock.js.map +1 -1
- package/dist/components/CodeBlock/tokenize.cjs +1 -1
- package/dist/components/CodeBlock/tokenize.cjs.map +1 -1
- package/dist/components/CodeBlock/tokenize.js +54 -25
- package/dist/components/CodeBlock/tokenize.js.map +1 -1
- package/dist/components/Markdown/markdown-parse.cjs +6 -6
- package/dist/components/Markdown/markdown-parse.cjs.map +1 -1
- package/dist/components/Markdown/markdown-parse.js +91 -74
- package/dist/components/Markdown/markdown-parse.js.map +1 -1
- package/dist/http/resumable-upload.cjs +1 -1
- package/dist/http/resumable-upload.cjs.map +1 -1
- package/dist/http/resumable-upload.js +141 -105
- package/dist/http/resumable-upload.js.map +1 -1
- package/dist/http/retry.cjs +1 -1
- package/dist/http/retry.cjs.map +1 -1
- package/dist/http/retry.js +5 -6
- package/dist/http/retry.js.map +1 -1
- package/dist/sw/background-sync.cjs +1 -1
- package/dist/sw/background-sync.cjs.map +1 -1
- package/dist/sw/background-sync.js +32 -31
- package/dist/sw/background-sync.js.map +1 -1
- package/dist/sw/credential-headers.cjs.map +1 -1
- package/dist/sw/credential-headers.js.map +1 -1
- package/dist/sw.d.ts +46 -8
- package/dist/telemetry/app-error-reporter.cjs +1 -1
- package/dist/telemetry/app-error-reporter.cjs.map +1 -1
- package/dist/telemetry/app-error-reporter.js +1 -1
- package/dist/telemetry/app-error-reporter.js.map +1 -1
- package/dist/tempest-react-sdk.cjs +1 -1
- package/dist/tempest-react-sdk.d.ts +214 -12
- package/dist/tempest-react-sdk.js +119 -119
- package/dist/utils/csv.cjs +3 -3
- package/dist/utils/csv.cjs.map +1 -1
- package/dist/utils/csv.js +11 -10
- package/dist/utils/csv.js.map +1 -1
- package/dist/{telemetry/app-error-owner.cjs → utils/owner-fingerprint.cjs} +2 -2
- package/dist/utils/owner-fingerprint.cjs.map +1 -0
- package/dist/{telemetry/app-error-owner.js → utils/owner-fingerprint.js} +3 -3
- package/dist/utils/owner-fingerprint.js.map +1 -0
- package/dist/vite/tempest-csp.cjs +2 -2
- package/dist/vite/tempest-csp.cjs.map +1 -1
- package/dist/vite/tempest-csp.js +69 -58
- package/dist/vite/tempest-csp.js.map +1 -1
- package/dist/vite.d.ts +5 -3
- package/package.json +1 -1
- package/dist/telemetry/app-error-owner.cjs.map +0 -1
- package/dist/telemetry/app-error-owner.js.map +0 -1
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
var e={ts:`typescript`,typescript:`typescript`,js:`javascript`,javascript:`javascript`,mjs:`javascript`,cjs:`javascript`,tsx:`tsx`,jsx:`jsx`,json:`json`,jsonc:`json`,css:`css`,scss:`css`,html:`html`,xml:`html`,svg:`html`,sh:`bash`,bash:`bash`,shell:`bash`,zsh:`bash`,console:`bash`,py:`python`,python:`python`,sql:`sql`};function t(t){return t?e[t.toLowerCase()]??`plain`:`plain`}var n=`as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|keyof|let|new|of|private|protected|public|readonly|return|satisfies|set|static|super|switch|this|throw|try|type|typeof|var|void|while|with|yield`,r=`true|false|null|undefined|NaN|Infinity`,i={typescript:a(!1),javascript:a(!1),tsx:a(!0),jsx:a(!0),json:[{kind:`property`,pattern:/"(?:[^"
|
|
1
|
+
var e={ts:`typescript`,typescript:`typescript`,js:`javascript`,javascript:`javascript`,mjs:`javascript`,cjs:`javascript`,tsx:`tsx`,jsx:`jsx`,json:`json`,jsonc:`json`,css:`css`,scss:`css`,html:`html`,xml:`html`,svg:`html`,sh:`bash`,bash:`bash`,shell:`bash`,zsh:`bash`,console:`bash`,py:`python`,python:`python`,sql:`sql`};function t(t){return t?e[t.toLowerCase()]??`plain`:`plain`}var n=`as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|keyof|let|new|of|private|protected|public|readonly|return|satisfies|set|static|super|switch|this|throw|try|type|typeof|var|void|while|with|yield`,r=`true|false|null|undefined|NaN|Infinity`,i={typescript:a(!1),javascript:a(!1),tsx:a(!0),jsx:a(!0),json:[{kind:`property`,pattern:/"(?:[^"\\\n]|\\[\s\S])*"(?=\s*:)/y},{kind:`string`,pattern:/"(?:[^"\\\n]|\\[\s\S])*"?/y},{kind:`number`,pattern:/-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?/y},{kind:`literal`,pattern:/\b(?:true|false|null)\b/y},{kind:`punctuation`,pattern:/[{}[\],:]/y}],css:[{kind:`comment`,pattern:/\/\*[\s\S]*?(?:\*\/|$)/y},{kind:`string`,pattern:/"(?:[^"\\\n]|\\[\s\S])*"?|'(?:[^'\\\n]|\\[\s\S])*'?/y},{kind:`keyword`,pattern:/@[a-zA-Z-]+/y},{kind:`property`,pattern:/(?<![.#[\](),>+~:])[\w-]+(?=\s*:(?![\w/:]))/y},{kind:`function`,pattern:/[\w-]+(?=\()/y},{kind:`number`,pattern:/#[0-9a-fA-F]{3,8}\b/y},{kind:`number`,pattern:/(?<![\w-])-?\d+(?:\.\d+)?(?:px|rem|em|%|s|ms|vh|vw|fr|deg)?/y},{kind:`punctuation`,pattern:/[{}();:,>+~]/y},{kind:`plain`,pattern:/[\w-]+/y}],html:[{kind:`comment`,pattern:/<!--[\s\S]*?(?:-->|$)/y},{kind:`string`,pattern:/"(?:[^"\\]|\\[\s\S])*"?|'(?:[^'\\]|\\[\s\S])*'?/y},{kind:`tag`,pattern:/<\/?[a-zA-Z][\w:-]*/y},{kind:`attribute`,pattern:/[a-zA-Z][\w:-]*(?==)/y},{kind:`punctuation`,pattern:/\/?>|=/y},{kind:`plain`,pattern:/[a-zA-Z][\w:-]*/y}],bash:[{kind:`comment`,pattern:/#[^\n]*/y},{kind:`string`,pattern:/"(?:[^"\\]|\\[\s\S])*"?|'[^']*'?/y},{kind:`keyword`,pattern:/\b(?:if|then|else|elif|fi|for|while|do|done|case|esac|function|return|export|local|source|set|cd|echo)\b/y},{kind:`property`,pattern:/\$\{?[A-Za-z_][\w]*\}?/y},{kind:`attribute`,pattern:/(?<=\s)--?[a-zA-Z][\w-]*/y},{kind:`punctuation`,pattern:/[|&;()<>]/y}],python:[{kind:`comment`,pattern:/#[^\n]*/y},{kind:`string`,pattern:/"""[\s\S]*?(?:"""|$)|'''[\s\S]*?(?:'''|$)|"(?:[^"\\\n]|\\[\s\S])*"?|'(?:[^'\\\n]|\\[\s\S])*'?/y},{kind:`keyword`,pattern:/\b(?:and|as|assert|async|await|break|class|continue|def|del|elif|else|except|finally|for|from|global|if|import|in|is|lambda|nonlocal|not|or|pass|raise|return|try|while|with|yield)\b/y},{kind:`literal`,pattern:/\b(?:True|False|None|self)\b/y},{kind:`number`,pattern:/\b\d+(?:\.\d+)?\b/y},{kind:`function`,pattern:/[A-Za-z_]\w*(?=\()/y},{kind:`punctuation`,pattern:/[{}[\]().,:;=+\-*/<>!]/y},{kind:`plain`,pattern:/[A-Za-z_]\w*/y}],sql:[{kind:`comment`,pattern:/--[^\n]*|\/\*[\s\S]*?(?:\*\/|$)/y},{kind:`string`,pattern:/'(?:[^']|'')*'?/y},{kind:`keyword`,pattern:/\b(?:select|from|where|insert|into|values|update|set|delete|create|table|alter|drop|index|join|inner|left|right|outer|on|group|by|order|having|limit|offset|as|and|or|not|in|is|null|distinct|union|all|with|returning|primary|key|foreign|references|default)\b/iy},{kind:`number`,pattern:/\b\d+(?:\.\d+)?\b/y},{kind:`punctuation`,pattern:/[(),;*=<>]/y}],plain:[]};function a(e){let t=[{kind:`comment`,pattern:/\/\/[^\n]*|\/\*[\s\S]*?(?:\*\/|$)/y},{kind:`string`,pattern:/`(?:[^`\\]|\\[\s\S])*`?|"(?:[^"\\\n]|\\[\s\S])*"?|(?<![\w$])'(?:[^'\\\n]|\\[\s\S])*'?/y},{kind:`keyword`,pattern:RegExp(`\\b(?:${n})(?![\\w$])`,`y`)},{kind:`literal`,pattern:RegExp(`\\b(?:${r})(?![\\w$])`,`y`)},{kind:`number`,pattern:/\b(?:0[xX][\da-fA-F_]+|\d[\d_]*(?:\.[\d_]+)?(?:[eE][+-]?\d+)?)(?![\w$])/y},{kind:`function`,pattern:/(?<![\w$])[A-Za-z_$][\w$]*(?=\s*\()/y},{kind:`punctuation`,pattern:/[{}[\]().,;:?!=<>+\-*/%&|^~]/y},{kind:`plain`,pattern:/(?:[A-Za-z_][\w$]*|\$+)/y}];return e&&(t.splice(1,0,{kind:`string`,pattern:/(?:(?<==)|(?<==\s))"[^"]*"?/y},{kind:`string`,pattern:/(?:(?<==)|(?<==\s))'[^']*'?/y}),t.splice(4,0,{kind:`tag`,pattern:/<\/?[A-Za-z][\w.]*|\/>/y})),t}function o(e,n){let r=i[t(n)];if(r.length===0)return e===``?[]:[{kind:`plain`,value:e}];let a=[],o=0,s=0,c=e=>{let t=a[a.length-1];t&&t.kind===`plain`&&e.kind===`plain`?t.value+=e.value:a.push(e)},l=t=>{t>o&&c({kind:`plain`,value:e.slice(o,t)})};for(;s<e.length;){let t=null;for(let n of r){n.pattern.lastIndex=s;let r=n.pattern.exec(e);if(r&&r[0].length>0){t={kind:n.kind,value:r[0]};break}}t?(l(s),c(t),s+=t.value.length,o=s):s++}return l(e.length),a}function s(e,t){let n=[[]];for(let r of o(e,t))r.value.split(`
|
|
2
2
|
`).forEach((e,t)=>{t>0&&n.push([]),e!==``&&n[n.length-1].push({kind:r.kind,value:e})});return n}exports.resolveLanguage=t,exports.tokenize=o,exports.tokenizeLines=s;
|
|
3
3
|
//# sourceMappingURL=tokenize.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tokenize.cjs","names":[],"sources":["../../../src/components/CodeBlock/tokenize.ts"],"sourcesContent":["/** What a token is painted as. */\nexport type TokenKind =\n | \"plain\"\n | \"comment\"\n | \"string\"\n | \"number\"\n | \"keyword\"\n | \"literal\"\n | \"function\"\n | \"punctuation\"\n | \"tag\"\n | \"attribute\"\n | \"property\";\n\nexport interface Token {\n kind: TokenKind;\n value: string;\n}\n\n/** Languages the tokenizer knows. Anything else renders as plain text. */\nexport type CodeLanguage =\n | \"typescript\"\n | \"javascript\"\n | \"tsx\"\n | \"jsx\"\n | \"json\"\n | \"css\"\n | \"html\"\n | \"bash\"\n | \"python\"\n | \"sql\"\n | \"plain\";\n\n/** Aliases people actually type, mapped onto the grammars above. */\nconst ALIASES: Record<string, CodeLanguage> = {\n ts: \"typescript\",\n typescript: \"typescript\",\n js: \"javascript\",\n javascript: \"javascript\",\n mjs: \"javascript\",\n cjs: \"javascript\",\n tsx: \"tsx\",\n jsx: \"jsx\",\n json: \"json\",\n jsonc: \"json\",\n css: \"css\",\n scss: \"css\",\n html: \"html\",\n xml: \"html\",\n svg: \"html\",\n sh: \"bash\",\n bash: \"bash\",\n shell: \"bash\",\n zsh: \"bash\",\n console: \"bash\",\n py: \"python\",\n python: \"python\",\n sql: \"sql\",\n};\n\n/**\n * Normalise a language name onto a known grammar.\n *\n * @param language - Whatever the caller passed, e.g. `\"ts\"` or `\"Shell\"`.\n * @returns The grammar to use; `\"plain\"` when there is no match.\n */\nexport function resolveLanguage(language: string | undefined): CodeLanguage {\n if (!language) return \"plain\";\n return ALIASES[language.toLowerCase()] ?? \"plain\";\n}\n\n/** One grammar rule: a sticky pattern and the kind it produces. */\ninterface Rule {\n kind: TokenKind;\n pattern: RegExp;\n}\n\nconst JS_KEYWORDS =\n \"as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|keyof|let|new|of|private|protected|public|readonly|return|satisfies|set|static|super|switch|this|throw|try|type|typeof|var|void|while|with|yield\";\n\nconst JS_LITERALS = \"true|false|null|undefined|NaN|Infinity\";\n\n/**\n * Rules per grammar, tried in order at each position.\n *\n * Order carries meaning: comments and strings come first so a keyword inside a\n * string stays a string. Every pattern is sticky (`y`) and anchored by\n * `lastIndex`, so a rule can only match where the scanner currently stands.\n */\nconst GRAMMARS: Record<CodeLanguage, Rule[]> = {\n typescript: jsRules(false),\n javascript: jsRules(false),\n tsx: jsRules(true),\n jsx: jsRules(true),\n json: [\n { kind: \"property\", pattern: /\"(?:[^\"\\\\]|\\\\.)*\"(?=\\s*:)/y },\n { kind: \"string\", pattern: /\"(?:[^\"\\\\]|\\\\.)*\"/y },\n { kind: \"number\", pattern: /-?\\d+(?:\\.\\d+)?(?:[eE][+-]?\\d+)?/y },\n { kind: \"literal\", pattern: /\\b(?:true|false|null)\\b/y },\n { kind: \"punctuation\", pattern: /[{}[\\],:]/y },\n ],\n css: [\n { kind: \"comment\", pattern: /\\/\\*[\\s\\S]*?\\*\\//y },\n { kind: \"string\", pattern: /\"(?:[^\"\\\\]|\\\\.)*\"|'(?:[^'\\\\]|\\\\.)*'/y },\n { kind: \"keyword\", pattern: /@[a-zA-Z-]+/y },\n { kind: \"property\", pattern: /--?[a-zA-Z][\\w-]*(?=\\s*:)/y },\n { kind: \"function\", pattern: /[a-zA-Z-]+(?=\\()/y },\n { kind: \"number\", pattern: /-?\\d+(?:\\.\\d+)?(?:px|rem|em|%|s|ms|vh|vw|fr|deg)?/y },\n { kind: \"punctuation\", pattern: /[{}();:,>+~]/y },\n ],\n html: [\n { kind: \"comment\", pattern: /<!--[\\s\\S]*?-->/y },\n { kind: \"string\", pattern: /\"(?:[^\"\\\\]|\\\\.)*\"|'(?:[^'\\\\]|\\\\.)*'/y },\n { kind: \"tag\", pattern: /<\\/?[a-zA-Z][\\w:-]*/y },\n { kind: \"attribute\", pattern: /[a-zA-Z][\\w:-]*(?==)/y },\n { kind: \"punctuation\", pattern: /\\/?>|=/y },\n ],\n bash: [\n { kind: \"comment\", pattern: /#[^\\n]*/y },\n { kind: \"string\", pattern: /\"(?:[^\"\\\\]|\\\\.)*\"|'[^']*'/y },\n {\n kind: \"keyword\",\n pattern:\n /\\b(?:if|then|else|elif|fi|for|while|do|done|case|esac|function|return|export|local|source|set|cd|echo)\\b/y,\n },\n { kind: \"property\", pattern: /\\$\\{?[A-Za-z_][\\w]*\\}?/y },\n { kind: \"attribute\", pattern: /(?<=\\s)--?[a-zA-Z][\\w-]*/y },\n { kind: \"punctuation\", pattern: /[|&;()<>]/y },\n ],\n python: [\n { kind: \"comment\", pattern: /#[^\\n]*/y },\n {\n kind: \"string\",\n pattern: /\"\"\"[\\s\\S]*?\"\"\"|'''[\\s\\S]*?'''|\"(?:[^\"\\\\]|\\\\.)*\"|'(?:[^'\\\\]|\\\\.)*'/y,\n },\n {\n kind: \"keyword\",\n pattern:\n /\\b(?:and|as|assert|async|await|break|class|continue|def|del|elif|else|except|finally|for|from|global|if|import|in|is|lambda|nonlocal|not|or|pass|raise|return|try|while|with|yield)\\b/y,\n },\n { kind: \"literal\", pattern: /\\b(?:True|False|None|self)\\b/y },\n { kind: \"number\", pattern: /\\b\\d+(?:\\.\\d+)?\\b/y },\n { kind: \"function\", pattern: /[A-Za-z_]\\w*(?=\\()/y },\n { kind: \"punctuation\", pattern: /[{}[\\]().,:;=+\\-*/<>!]/y },\n ],\n sql: [\n { kind: \"comment\", pattern: /--[^\\n]*|\\/\\*[\\s\\S]*?\\*\\//y },\n { kind: \"string\", pattern: /'(?:[^']|'')*'/y },\n {\n kind: \"keyword\",\n // Case-insensitive: SQL is written both ways and both should paint.\n pattern:\n /\\b(?:select|from|where|insert|into|values|update|set|delete|create|table|alter|drop|index|join|inner|left|right|outer|on|group|by|order|having|limit|offset|as|and|or|not|in|is|null|distinct|union|all|with|returning|primary|key|foreign|references|default)\\b/iy,\n },\n { kind: \"number\", pattern: /\\b\\d+(?:\\.\\d+)?\\b/y },\n { kind: \"punctuation\", pattern: /[(),;*=<>]/y },\n ],\n plain: [],\n};\n\n/** The shared JavaScript-family rules, with JSX tags added for tsx/jsx. */\nfunction jsRules(withJsx: boolean): Rule[] {\n const rules: Rule[] = [\n { kind: \"comment\", pattern: /\\/\\/[^\\n]*|\\/\\*[\\s\\S]*?\\*\\//y },\n {\n kind: \"string\",\n pattern: /`(?:[^`\\\\]|\\\\.)*`|\"(?:[^\"\\\\]|\\\\.)*\"|'(?:[^'\\\\]|\\\\.)*'/y,\n },\n { kind: \"keyword\", pattern: new RegExp(`\\\\b(?:${JS_KEYWORDS})\\\\b`, \"y\") },\n { kind: \"literal\", pattern: new RegExp(`\\\\b(?:${JS_LITERALS})\\\\b`, \"y\") },\n {\n kind: \"number\",\n pattern: /\\b(?:0[xX][\\da-fA-F_]+|\\d[\\d_]*(?:\\.[\\d_]+)?(?:[eE][+-]?\\d+)?)\\b/y,\n },\n { kind: \"function\", pattern: /[A-Za-z_$][\\w$]*(?=\\s*\\()/y },\n { kind: \"punctuation\", pattern: /[{}[\\]().,;:?!=<>+\\-*/%&|^~]/y },\n ];\n if (withJsx) {\n // Before the keyword rule, so `<Button` reads as a tag and not as `<`.\n rules.splice(2, 0, { kind: \"tag\", pattern: /<\\/?[A-Za-z][\\w.]*|\\/>/y });\n }\n return rules;\n}\n\n/**\n * Split source into coloured tokens.\n *\n * This is a **scanner, not a parser**: it recognises comments, strings, numbers,\n * keywords and punctuation by pattern, and knows nothing about scope, types or\n * grammar. That is a deliberate ceiling. A real parser per language is a\n * dependency the size of the rest of the SDK, and the payoff — being right about\n * the corner cases in a documentation snippet — is small. Where it is unsure it\n * emits `plain`, which renders as ordinary text rather than as something wrong.\n *\n * Unknown languages produce a single `plain` token, so an unhighlighted block is\n * a normal outcome and never an error.\n *\n * @param code - The source.\n * @param language - Grammar name or alias.\n * @returns Tokens covering the input exactly, in order.\n *\n * @example\n * tokenize(\"const x = 1;\", \"ts\");\n * // [{kind: \"keyword\", value: \"const\"}, {kind: \"plain\", value: \" x \"}, …]\n */\nexport function tokenize(code: string, language: string | undefined): Token[] {\n const rules = GRAMMARS[resolveLanguage(language)];\n if (rules.length === 0) return code === \"\" ? [] : [{ kind: \"plain\", value: code }];\n\n const tokens: Token[] = [];\n let plainFrom = 0;\n let at = 0;\n\n const flushPlain = (until: number) => {\n if (until > plainFrom) tokens.push({ kind: \"plain\", value: code.slice(plainFrom, until) });\n };\n\n while (at < code.length) {\n let matched: Token | null = null;\n for (const rule of rules) {\n rule.pattern.lastIndex = at;\n const found = rule.pattern.exec(code);\n if (found && found[0].length > 0) {\n matched = { kind: rule.kind, value: found[0] };\n break;\n }\n }\n if (matched) {\n flushPlain(at);\n tokens.push(matched);\n at += matched.value.length;\n plainFrom = at;\n } else {\n at++;\n }\n }\n flushPlain(code.length);\n return tokens;\n}\n\n/**\n * The same tokens, split at newlines so each line can be rendered on its own.\n *\n * Line numbers and highlighted lines both need a per-line structure, and a token\n * is free to span a line break — a block comment usually does. Splitting here\n * keeps that out of the component.\n *\n * @param code - The source.\n * @param language - Grammar name or alias.\n * @returns One token array per line. Always at least one line.\n */\nexport function tokenizeLines(code: string, language: string | undefined): Token[][] {\n const lines: Token[][] = [[]];\n for (const token of tokenize(code, language)) {\n const pieces = token.value.split(\"\\n\");\n pieces.forEach((piece, index) => {\n if (index > 0) lines.push([]);\n if (piece !== \"\") lines[lines.length - 1].push({ kind: token.kind, value: piece });\n });\n }\n return lines;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"tokenize.cjs","names":[],"sources":["../../../src/components/CodeBlock/tokenize.ts"],"sourcesContent":["/** What a token is painted as. */\nexport type TokenKind =\n | \"plain\"\n | \"comment\"\n | \"string\"\n | \"number\"\n | \"keyword\"\n | \"literal\"\n | \"function\"\n | \"punctuation\"\n | \"tag\"\n | \"attribute\"\n | \"property\";\n\nexport interface Token {\n kind: TokenKind;\n value: string;\n}\n\n/** Languages the tokenizer knows. Anything else renders as plain text. */\nexport type CodeLanguage =\n | \"typescript\"\n | \"javascript\"\n | \"tsx\"\n | \"jsx\"\n | \"json\"\n | \"css\"\n | \"html\"\n | \"bash\"\n | \"python\"\n | \"sql\"\n | \"plain\";\n\n/** Aliases people actually type, mapped onto the grammars above. */\nconst ALIASES: Record<string, CodeLanguage> = {\n ts: \"typescript\",\n typescript: \"typescript\",\n js: \"javascript\",\n javascript: \"javascript\",\n mjs: \"javascript\",\n cjs: \"javascript\",\n tsx: \"tsx\",\n jsx: \"jsx\",\n json: \"json\",\n jsonc: \"json\",\n css: \"css\",\n scss: \"css\",\n html: \"html\",\n xml: \"html\",\n svg: \"html\",\n sh: \"bash\",\n bash: \"bash\",\n shell: \"bash\",\n zsh: \"bash\",\n console: \"bash\",\n py: \"python\",\n python: \"python\",\n sql: \"sql\",\n};\n\n/**\n * Normalise a language name onto a known grammar.\n *\n * @param language - Whatever the caller passed, e.g. `\"ts\"` or `\"Shell\"`.\n * @returns The grammar to use; `\"plain\"` when there is no match.\n */\nexport function resolveLanguage(language: string | undefined): CodeLanguage {\n if (!language) return \"plain\";\n return ALIASES[language.toLowerCase()] ?? \"plain\";\n}\n\n/** One grammar rule: a sticky pattern and the kind it produces. */\ninterface Rule {\n kind: TokenKind;\n pattern: RegExp;\n}\n\nconst JS_KEYWORDS =\n \"as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|keyof|let|new|of|private|protected|public|readonly|return|satisfies|set|static|super|switch|this|throw|try|type|typeof|var|void|while|with|yield\";\n\nconst JS_LITERALS = \"true|false|null|undefined|NaN|Infinity\";\n\n/**\n * Rules per grammar, tried in order at each position.\n *\n * Order carries meaning: comments and strings come first so a keyword inside a\n * string stays a string. Every pattern is sticky (`y`) and anchored by\n * `lastIndex`, so a rule can only match where the scanner currently stands.\n *\n * Every string and comment pattern makes its closing delimiter **optional** —\n * `\"...\"?`, `/*...*?` / `<!--...--?` — so an unterminated opener is consumed as a\n * single token instead of failing and being rescanned from the next opener.\n * Without that, a `\"` followed by many escaped quotes was re-read to the end of\n * the input from each opening quote, quadratic in the input (#477).\n *\n * A well-formed string that closes on a later line stays one token where the\n * language allows the newline: bash, SQL, HTML and the JavaScript template\n * literal carry a raw `\\n`, and every grammar lets an escape cross the line feed\n * (`\\\\[\\s\\S]`), so `\"a\\` + LF + `b\"` is one string. A quote string that cannot\n * hold a raw newline — the JavaScript and JSON `\"`/`'`, CSS, the Python\n * `\"`/`'` — still ends at the line when it is unterminated, which is the only\n * case the optional close cuts at the line. A JSX attribute value is the\n * exception in the JavaScript family: `=\"…\"` has its own rule, ahead of the\n * plain JS strings, so it may hold a raw line feed — `<input placeholder=\"a` +\n * LF + `b\">` is one string where the plain JS rule would have ended it at the\n * line. The rule matches at the `=` and looks behind for it, so `=` keeps its\n * paint; `(?<==\\s)` covers the spaced `= \"…\"` form too.\n *\n * Each grammar also ends with a `plain` rule that consumes a run of identifier\n * characters in one step. The rules that need a lookahead — the JavaScript\n * `function`, the HTML `attribute`, the CSS `property` and `function`, the\n * Python `function` — match a whole run and then fail if the character after it\n * is not the expected one; with only `at++` to fall back on, the scanner retried\n * that run from every character in it. A code fence holding a long run of word\n * characters and no punctuation (a base64 blob, a minified token) took 6 s at\n * 80 KB and 25 s at 160 KB; the fallback consumes the run once, so the scan\n * stays linear (#477 sweep).\n *\n * The `plain` rule matches an identifier run while the scanner's fallback\n * collects whatever no rule matched, so one plain stretch arrives in pieces;\n * `tokenize` fuses adjacent `plain` tokens back into one, which is the array\n * shape — and the one span per run — that predates the fallback (#477).\n *\n * The JavaScript fallback does **not** start at `$` — it takes a `$` run and an\n * identifier run separately — so a leading `$type`, `$default` and `$this` keep\n * the `$` plain and the name a keyword. Inside an identifier the `$` is an\n * ordinary character, so `foo$this`, `a$true` and `x$1` are one `plain` token\n * instead of painting `this`, `true` and `1` out of them; the `'` string rule\n * carries the same boundary in a lookbehind, so an apostrophe in prose\n * (`Don't`) does not open a string that runs to the end of the input.\n *\n * The same boundary closes the JavaScript keyword, literal and number rules:\n * they end at `(?![\\w$])` rather than `\\b`, because `\\b` holds before a `$`, so\n * `if$foo` would paint `if` and leave the scanner standing on the `$` in the\n * middle of the identifier. The `function` rule only starts where no identifier\n * character precedes it (`(?<![\\w$])`). Without that lookbehind a rule that\n * stopped inside a run — the leading-`$` split above stops at every `$1` of\n * `$1$1$1…` — handed the rest of the run to `function`, which read it to the end\n * and failed for want of a `(`, once per `$`: 80 KB of `$1` took 2.8 s and\n * 120 KB of `if$` 4.2 s (#498). With both boundaries every rule that can start\n * inside a run fails at once or consumes a bounded piece of it, so each run is\n * read a constant number of times and `if$foo(`, `this$x(` and `true$y(` paint\n * as the one called identifier they are.\n *\n * The CSS `property` and `function` rules match a whole word where it stands —\n * `font-size` and `translate3d` paint as one token rather than splitting the\n * tail — and a `#rgb`/`#rrggbb` colour paints as a number. A word before a\n * colon only counts as a property when the colon starts a value boundary\n * (whitespace, `;`, `}`): after a `.`/`#`/`(`/`,`/`>`/`+`/`~`/`:` it is a\n * selector, and when the colon is followed by an identifier character it could\n * be a pseudo-class (`li:hover`) or a declaration with no space (`color:red`) —\n * neither can be told apart without a parser, so both stay `plain`, which is\n * what `origin/main` painted (`#477`).\n */\nconst GRAMMARS: Record<CodeLanguage, Rule[]> = {\n typescript: jsRules(false),\n javascript: jsRules(false),\n tsx: jsRules(true),\n jsx: jsRules(true),\n json: [\n { kind: \"property\", pattern: /\"(?:[^\"\\\\\\n]|\\\\[\\s\\S])*\"(?=\\s*:)/y },\n { kind: \"string\", pattern: /\"(?:[^\"\\\\\\n]|\\\\[\\s\\S])*\"?/y },\n { kind: \"number\", pattern: /-?\\d+(?:\\.\\d+)?(?:[eE][+-]?\\d+)?/y },\n { kind: \"literal\", pattern: /\\b(?:true|false|null)\\b/y },\n { kind: \"punctuation\", pattern: /[{}[\\],:]/y },\n ],\n css: [\n { kind: \"comment\", pattern: /\\/\\*[\\s\\S]*?(?:\\*\\/|$)/y },\n { kind: \"string\", pattern: /\"(?:[^\"\\\\\\n]|\\\\[\\s\\S])*\"?|'(?:[^'\\\\\\n]|\\\\[\\s\\S])*'?/y },\n { kind: \"keyword\", pattern: /@[a-zA-Z-]+/y },\n { kind: \"property\", pattern: /(?<![.#[\\](),>+~:])[\\w-]+(?=\\s*:(?![\\w/:]))/y },\n { kind: \"function\", pattern: /[\\w-]+(?=\\()/y },\n { kind: \"number\", pattern: /#[0-9a-fA-F]{3,8}\\b/y },\n { kind: \"number\", pattern: /(?<![\\w-])-?\\d+(?:\\.\\d+)?(?:px|rem|em|%|s|ms|vh|vw|fr|deg)?/y },\n { kind: \"punctuation\", pattern: /[{}();:,>+~]/y },\n { kind: \"plain\", pattern: /[\\w-]+/y },\n ],\n html: [\n { kind: \"comment\", pattern: /<!--[\\s\\S]*?(?:-->|$)/y },\n { kind: \"string\", pattern: /\"(?:[^\"\\\\]|\\\\[\\s\\S])*\"?|'(?:[^'\\\\]|\\\\[\\s\\S])*'?/y },\n { kind: \"tag\", pattern: /<\\/?[a-zA-Z][\\w:-]*/y },\n { kind: \"attribute\", pattern: /[a-zA-Z][\\w:-]*(?==)/y },\n { kind: \"punctuation\", pattern: /\\/?>|=/y },\n { kind: \"plain\", pattern: /[a-zA-Z][\\w:-]*/y },\n ],\n bash: [\n { kind: \"comment\", pattern: /#[^\\n]*/y },\n { kind: \"string\", pattern: /\"(?:[^\"\\\\]|\\\\[\\s\\S])*\"?|'[^']*'?/y },\n {\n kind: \"keyword\",\n pattern:\n /\\b(?:if|then|else|elif|fi|for|while|do|done|case|esac|function|return|export|local|source|set|cd|echo)\\b/y,\n },\n { kind: \"property\", pattern: /\\$\\{?[A-Za-z_][\\w]*\\}?/y },\n { kind: \"attribute\", pattern: /(?<=\\s)--?[a-zA-Z][\\w-]*/y },\n { kind: \"punctuation\", pattern: /[|&;()<>]/y },\n ],\n python: [\n { kind: \"comment\", pattern: /#[^\\n]*/y },\n {\n kind: \"string\",\n pattern:\n /\"\"\"[\\s\\S]*?(?:\"\"\"|$)|'''[\\s\\S]*?(?:'''|$)|\"(?:[^\"\\\\\\n]|\\\\[\\s\\S])*\"?|'(?:[^'\\\\\\n]|\\\\[\\s\\S])*'?/y,\n },\n {\n kind: \"keyword\",\n pattern:\n /\\b(?:and|as|assert|async|await|break|class|continue|def|del|elif|else|except|finally|for|from|global|if|import|in|is|lambda|nonlocal|not|or|pass|raise|return|try|while|with|yield)\\b/y,\n },\n { kind: \"literal\", pattern: /\\b(?:True|False|None|self)\\b/y },\n { kind: \"number\", pattern: /\\b\\d+(?:\\.\\d+)?\\b/y },\n { kind: \"function\", pattern: /[A-Za-z_]\\w*(?=\\()/y },\n { kind: \"punctuation\", pattern: /[{}[\\]().,:;=+\\-*/<>!]/y },\n { kind: \"plain\", pattern: /[A-Za-z_]\\w*/y },\n ],\n sql: [\n { kind: \"comment\", pattern: /--[^\\n]*|\\/\\*[\\s\\S]*?(?:\\*\\/|$)/y },\n { kind: \"string\", pattern: /'(?:[^']|'')*'?/y },\n {\n kind: \"keyword\",\n // Case-insensitive: SQL is written both ways and both should paint.\n pattern:\n /\\b(?:select|from|where|insert|into|values|update|set|delete|create|table|alter|drop|index|join|inner|left|right|outer|on|group|by|order|having|limit|offset|as|and|or|not|in|is|null|distinct|union|all|with|returning|primary|key|foreign|references|default)\\b/iy,\n },\n { kind: \"number\", pattern: /\\b\\d+(?:\\.\\d+)?\\b/y },\n { kind: \"punctuation\", pattern: /[(),;*=<>]/y },\n ],\n plain: [],\n};\n\n/** The shared JavaScript-family rules, with JSX tags added for tsx/jsx. */\nfunction jsRules(withJsx: boolean): Rule[] {\n const rules: Rule[] = [\n { kind: \"comment\", pattern: /\\/\\/[^\\n]*|\\/\\*[\\s\\S]*?(?:\\*\\/|$)/y },\n {\n kind: \"string\",\n pattern:\n /`(?:[^`\\\\]|\\\\[\\s\\S])*`?|\"(?:[^\"\\\\\\n]|\\\\[\\s\\S])*\"?|(?<![\\w$])'(?:[^'\\\\\\n]|\\\\[\\s\\S])*'?/y,\n },\n { kind: \"keyword\", pattern: new RegExp(`\\\\b(?:${JS_KEYWORDS})(?![\\\\w$])`, \"y\") },\n { kind: \"literal\", pattern: new RegExp(`\\\\b(?:${JS_LITERALS})(?![\\\\w$])`, \"y\") },\n {\n kind: \"number\",\n pattern: /\\b(?:0[xX][\\da-fA-F_]+|\\d[\\d_]*(?:\\.[\\d_]+)?(?:[eE][+-]?\\d+)?)(?![\\w$])/y,\n },\n { kind: \"function\", pattern: /(?<![\\w$])[A-Za-z_$][\\w$]*(?=\\s*\\()/y },\n { kind: \"punctuation\", pattern: /[{}[\\]().,;:?!=<>+\\-*/%&|^~]/y },\n { kind: \"plain\", pattern: /(?:[A-Za-z_][\\w$]*|\\$+)/y },\n ];\n if (withJsx) {\n rules.splice(\n 1,\n 0,\n { kind: \"string\", pattern: /(?:(?<==)|(?<==\\s))\"[^\"]*\"?/y },\n { kind: \"string\", pattern: /(?:(?<==)|(?<==\\s))'[^']*'?/y },\n );\n rules.splice(4, 0, { kind: \"tag\", pattern: /<\\/?[A-Za-z][\\w.]*|\\/>/y });\n }\n return rules;\n}\n\n/**\n * Split source into coloured tokens.\n *\n * This is a **scanner, not a parser**: it recognises comments, strings, numbers,\n * keywords and punctuation by pattern, and knows nothing about scope, types or\n * grammar. That is a deliberate ceiling. A real parser per language is a\n * dependency the size of the rest of the SDK, and the payoff — being right about\n * the corner cases in a documentation snippet — is small. Where it is unsure it\n * emits `plain`, which renders as ordinary text rather than as something wrong.\n *\n * Unknown languages produce a single `plain` token, so an unhighlighted block is\n * a normal outcome and never an error.\n *\n * The scan is linear in the source: an unterminated string or comment becomes a\n * single token that runs to its close, or to the end of the input — or, for a\n * quote that cannot hold a newline and never closes, to the end of the line — so\n * the openers inside it are never rescanned (#477).\n *\n * @param code - The source.\n * @param language - Grammar name or alias.\n * @returns Tokens covering the input exactly, in order.\n *\n * @example\n * tokenize(\"const x = 1;\", \"ts\");\n * // [{kind: \"keyword\", value: \"const\"}, {kind: \"plain\", value: \" \"}, …]\n */\nexport function tokenize(code: string, language: string | undefined): Token[] {\n const rules = GRAMMARS[resolveLanguage(language)];\n if (rules.length === 0) return code === \"\" ? [] : [{ kind: \"plain\", value: code }];\n\n const tokens: Token[] = [];\n let plainFrom = 0;\n let at = 0;\n\n /**\n * Append a token, fusing it with the previous one when both are `plain`.\n *\n * The `plain` rule matches an identifier run while the scanner's fallback\n * collects the characters no rule matched, so one plain stretch of source\n * arrives in pieces. Fusing them keeps the array shape `origin/main`\n * produced, where a plain run was a single token and each got one span in\n * the DOM rather than one per identifier (#477).\n */\n const push = (token: Token) => {\n const last = tokens[tokens.length - 1];\n if (last && last.kind === \"plain\" && token.kind === \"plain\") {\n last.value += token.value;\n } else {\n tokens.push(token);\n }\n };\n\n const flushPlain = (until: number) => {\n if (until > plainFrom) push({ kind: \"plain\", value: code.slice(plainFrom, until) });\n };\n\n while (at < code.length) {\n let matched: Token | null = null;\n for (const rule of rules) {\n rule.pattern.lastIndex = at;\n const found = rule.pattern.exec(code);\n if (found && found[0].length > 0) {\n matched = { kind: rule.kind, value: found[0] };\n break;\n }\n }\n if (matched) {\n flushPlain(at);\n push(matched);\n at += matched.value.length;\n plainFrom = at;\n } else {\n at++;\n }\n }\n flushPlain(code.length);\n return tokens;\n}\n\n/**\n * The same tokens, split at newlines so each line can be rendered on its own.\n *\n * Line numbers and highlighted lines both need a per-line structure, and a token\n * is free to span a line break — a block comment usually does. Splitting here\n * keeps that out of the component.\n *\n * @param code - The source.\n * @param language - Grammar name or alias.\n * @returns One token array per line. Always at least one line.\n */\nexport function tokenizeLines(code: string, language: string | undefined): Token[][] {\n const lines: Token[][] = [[]];\n for (const token of tokenize(code, language)) {\n const pieces = token.value.split(\"\\n\");\n pieces.forEach((piece, index) => {\n if (index > 0) lines.push([]);\n if (piece !== \"\") lines[lines.length - 1].push({ kind: token.kind, value: piece });\n });\n }\n return lines;\n}\n"],"mappings":"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"}
|
|
@@ -35,11 +35,11 @@ var n = "as|async|await|break|case|catch|class|const|continue|debugger|default|d
|
|
|
35
35
|
json: [
|
|
36
36
|
{
|
|
37
37
|
kind: "property",
|
|
38
|
-
pattern: /"(?:[^"
|
|
38
|
+
pattern: /"(?:[^"\\\n]|\\[\s\S])*"(?=\s*:)/y
|
|
39
39
|
},
|
|
40
40
|
{
|
|
41
41
|
kind: "string",
|
|
42
|
-
pattern: /"(?:[^"
|
|
42
|
+
pattern: /"(?:[^"\\\n]|\\[\s\S])*"?/y
|
|
43
43
|
},
|
|
44
44
|
{
|
|
45
45
|
kind: "number",
|
|
@@ -57,11 +57,11 @@ var n = "as|async|await|break|case|catch|class|const|continue|debugger|default|d
|
|
|
57
57
|
css: [
|
|
58
58
|
{
|
|
59
59
|
kind: "comment",
|
|
60
|
-
pattern: /\/\*[\s\S]
|
|
60
|
+
pattern: /\/\*[\s\S]*?(?:\*\/|$)/y
|
|
61
61
|
},
|
|
62
62
|
{
|
|
63
63
|
kind: "string",
|
|
64
|
-
pattern: /"(?:[^"
|
|
64
|
+
pattern: /"(?:[^"\\\n]|\\[\s\S])*"?|'(?:[^'\\\n]|\\[\s\S])*'?/y
|
|
65
65
|
},
|
|
66
66
|
{
|
|
67
67
|
kind: "keyword",
|
|
@@ -69,29 +69,37 @@ var n = "as|async|await|break|case|catch|class|const|continue|debugger|default|d
|
|
|
69
69
|
},
|
|
70
70
|
{
|
|
71
71
|
kind: "property",
|
|
72
|
-
pattern:
|
|
72
|
+
pattern: /(?<![.#[\](),>+~:])[\w-]+(?=\s*:(?![\w/:]))/y
|
|
73
73
|
},
|
|
74
74
|
{
|
|
75
75
|
kind: "function",
|
|
76
|
-
pattern: /[
|
|
76
|
+
pattern: /[\w-]+(?=\()/y
|
|
77
77
|
},
|
|
78
78
|
{
|
|
79
79
|
kind: "number",
|
|
80
|
-
pattern:
|
|
80
|
+
pattern: /#[0-9a-fA-F]{3,8}\b/y
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
kind: "number",
|
|
84
|
+
pattern: /(?<![\w-])-?\d+(?:\.\d+)?(?:px|rem|em|%|s|ms|vh|vw|fr|deg)?/y
|
|
81
85
|
},
|
|
82
86
|
{
|
|
83
87
|
kind: "punctuation",
|
|
84
88
|
pattern: /[{}();:,>+~]/y
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
kind: "plain",
|
|
92
|
+
pattern: /[\w-]+/y
|
|
85
93
|
}
|
|
86
94
|
],
|
|
87
95
|
html: [
|
|
88
96
|
{
|
|
89
97
|
kind: "comment",
|
|
90
|
-
pattern: /<!--[\s\S]
|
|
98
|
+
pattern: /<!--[\s\S]*?(?:-->|$)/y
|
|
91
99
|
},
|
|
92
100
|
{
|
|
93
101
|
kind: "string",
|
|
94
|
-
pattern: /"(?:[^"\\]
|
|
102
|
+
pattern: /"(?:[^"\\]|\\[\s\S])*"?|'(?:[^'\\]|\\[\s\S])*'?/y
|
|
95
103
|
},
|
|
96
104
|
{
|
|
97
105
|
kind: "tag",
|
|
@@ -104,6 +112,10 @@ var n = "as|async|await|break|case|catch|class|const|continue|debugger|default|d
|
|
|
104
112
|
{
|
|
105
113
|
kind: "punctuation",
|
|
106
114
|
pattern: /\/?>|=/y
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
kind: "plain",
|
|
118
|
+
pattern: /[a-zA-Z][\w:-]*/y
|
|
107
119
|
}
|
|
108
120
|
],
|
|
109
121
|
bash: [
|
|
@@ -113,7 +125,7 @@ var n = "as|async|await|break|case|catch|class|const|continue|debugger|default|d
|
|
|
113
125
|
},
|
|
114
126
|
{
|
|
115
127
|
kind: "string",
|
|
116
|
-
pattern: /"(?:[^"\\]
|
|
128
|
+
pattern: /"(?:[^"\\]|\\[\s\S])*"?|'[^']*'?/y
|
|
117
129
|
},
|
|
118
130
|
{
|
|
119
131
|
kind: "keyword",
|
|
@@ -139,7 +151,7 @@ var n = "as|async|await|break|case|catch|class|const|continue|debugger|default|d
|
|
|
139
151
|
},
|
|
140
152
|
{
|
|
141
153
|
kind: "string",
|
|
142
|
-
pattern: /"""[\s\S]*?"""|'''[\s\S]*?'''|"(?:[^"
|
|
154
|
+
pattern: /"""[\s\S]*?(?:"""|$)|'''[\s\S]*?(?:'''|$)|"(?:[^"\\\n]|\\[\s\S])*"?|'(?:[^'\\\n]|\\[\s\S])*'?/y
|
|
143
155
|
},
|
|
144
156
|
{
|
|
145
157
|
kind: "keyword",
|
|
@@ -160,16 +172,20 @@ var n = "as|async|await|break|case|catch|class|const|continue|debugger|default|d
|
|
|
160
172
|
{
|
|
161
173
|
kind: "punctuation",
|
|
162
174
|
pattern: /[{}[\]().,:;=+\-*/<>!]/y
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
kind: "plain",
|
|
178
|
+
pattern: /[A-Za-z_]\w*/y
|
|
163
179
|
}
|
|
164
180
|
],
|
|
165
181
|
sql: [
|
|
166
182
|
{
|
|
167
183
|
kind: "comment",
|
|
168
|
-
pattern: /--[^\n]*|\/\*[\s\S]
|
|
184
|
+
pattern: /--[^\n]*|\/\*[\s\S]*?(?:\*\/|$)/y
|
|
169
185
|
},
|
|
170
186
|
{
|
|
171
187
|
kind: "string",
|
|
172
|
-
pattern: /'(?:[^']|'')*'
|
|
188
|
+
pattern: /'(?:[^']|'')*'?/y
|
|
173
189
|
},
|
|
174
190
|
{
|
|
175
191
|
kind: "keyword",
|
|
@@ -190,37 +206,47 @@ function a(e) {
|
|
|
190
206
|
let t = [
|
|
191
207
|
{
|
|
192
208
|
kind: "comment",
|
|
193
|
-
pattern: /\/\/[^\n]*|\/\*[\s\S]
|
|
209
|
+
pattern: /\/\/[^\n]*|\/\*[\s\S]*?(?:\*\/|$)/y
|
|
194
210
|
},
|
|
195
211
|
{
|
|
196
212
|
kind: "string",
|
|
197
|
-
pattern: /`(?:[^`\\]
|
|
213
|
+
pattern: /`(?:[^`\\]|\\[\s\S])*`?|"(?:[^"\\\n]|\\[\s\S])*"?|(?<![\w$])'(?:[^'\\\n]|\\[\s\S])*'?/y
|
|
198
214
|
},
|
|
199
215
|
{
|
|
200
216
|
kind: "keyword",
|
|
201
|
-
pattern: RegExp(`\\b(?:${n})\\
|
|
217
|
+
pattern: RegExp(`\\b(?:${n})(?![\\w$])`, "y")
|
|
202
218
|
},
|
|
203
219
|
{
|
|
204
220
|
kind: "literal",
|
|
205
|
-
pattern: RegExp(`\\b(?:${r})\\
|
|
221
|
+
pattern: RegExp(`\\b(?:${r})(?![\\w$])`, "y")
|
|
206
222
|
},
|
|
207
223
|
{
|
|
208
224
|
kind: "number",
|
|
209
|
-
pattern: /\b(?:0[xX][\da-fA-F_]+|\d[\d_]*(?:\.[\d_]+)?(?:[eE][+-]?\d+)?)\
|
|
225
|
+
pattern: /\b(?:0[xX][\da-fA-F_]+|\d[\d_]*(?:\.[\d_]+)?(?:[eE][+-]?\d+)?)(?![\w$])/y
|
|
210
226
|
},
|
|
211
227
|
{
|
|
212
228
|
kind: "function",
|
|
213
|
-
pattern: /[A-Za-z_$][\w$]*(?=\s*\()/y
|
|
229
|
+
pattern: /(?<![\w$])[A-Za-z_$][\w$]*(?=\s*\()/y
|
|
214
230
|
},
|
|
215
231
|
{
|
|
216
232
|
kind: "punctuation",
|
|
217
233
|
pattern: /[{}[\]().,;:?!=<>+\-*/%&|^~]/y
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
kind: "plain",
|
|
237
|
+
pattern: /(?:[A-Za-z_][\w$]*|\$+)/y
|
|
218
238
|
}
|
|
219
239
|
];
|
|
220
|
-
return e && t.splice(
|
|
240
|
+
return e && (t.splice(1, 0, {
|
|
241
|
+
kind: "string",
|
|
242
|
+
pattern: /(?:(?<==)|(?<==\s))"[^"]*"?/y
|
|
243
|
+
}, {
|
|
244
|
+
kind: "string",
|
|
245
|
+
pattern: /(?:(?<==)|(?<==\s))'[^']*'?/y
|
|
246
|
+
}), t.splice(4, 0, {
|
|
221
247
|
kind: "tag",
|
|
222
248
|
pattern: /<\/?[A-Za-z][\w.]*|\/>/y
|
|
223
|
-
}), t;
|
|
249
|
+
})), t;
|
|
224
250
|
}
|
|
225
251
|
function o(e, n) {
|
|
226
252
|
let r = i[t(n)];
|
|
@@ -228,8 +254,11 @@ function o(e, n) {
|
|
|
228
254
|
kind: "plain",
|
|
229
255
|
value: e
|
|
230
256
|
}];
|
|
231
|
-
let a = [], o = 0, s = 0, c = (
|
|
232
|
-
t
|
|
257
|
+
let a = [], o = 0, s = 0, c = (e) => {
|
|
258
|
+
let t = a[a.length - 1];
|
|
259
|
+
t && t.kind === "plain" && e.kind === "plain" ? t.value += e.value : a.push(e);
|
|
260
|
+
}, l = (t) => {
|
|
261
|
+
t > o && c({
|
|
233
262
|
kind: "plain",
|
|
234
263
|
value: e.slice(o, t)
|
|
235
264
|
});
|
|
@@ -247,9 +276,9 @@ function o(e, n) {
|
|
|
247
276
|
break;
|
|
248
277
|
}
|
|
249
278
|
}
|
|
250
|
-
t ? (
|
|
279
|
+
t ? (l(s), c(t), s += t.value.length, o = s) : s++;
|
|
251
280
|
}
|
|
252
|
-
return
|
|
281
|
+
return l(e.length), a;
|
|
253
282
|
}
|
|
254
283
|
function s(e, t) {
|
|
255
284
|
let n = [[]];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tokenize.js","names":[],"sources":["../../../src/components/CodeBlock/tokenize.ts"],"sourcesContent":["/** What a token is painted as. */\nexport type TokenKind =\n | \"plain\"\n | \"comment\"\n | \"string\"\n | \"number\"\n | \"keyword\"\n | \"literal\"\n | \"function\"\n | \"punctuation\"\n | \"tag\"\n | \"attribute\"\n | \"property\";\n\nexport interface Token {\n kind: TokenKind;\n value: string;\n}\n\n/** Languages the tokenizer knows. Anything else renders as plain text. */\nexport type CodeLanguage =\n | \"typescript\"\n | \"javascript\"\n | \"tsx\"\n | \"jsx\"\n | \"json\"\n | \"css\"\n | \"html\"\n | \"bash\"\n | \"python\"\n | \"sql\"\n | \"plain\";\n\n/** Aliases people actually type, mapped onto the grammars above. */\nconst ALIASES: Record<string, CodeLanguage> = {\n ts: \"typescript\",\n typescript: \"typescript\",\n js: \"javascript\",\n javascript: \"javascript\",\n mjs: \"javascript\",\n cjs: \"javascript\",\n tsx: \"tsx\",\n jsx: \"jsx\",\n json: \"json\",\n jsonc: \"json\",\n css: \"css\",\n scss: \"css\",\n html: \"html\",\n xml: \"html\",\n svg: \"html\",\n sh: \"bash\",\n bash: \"bash\",\n shell: \"bash\",\n zsh: \"bash\",\n console: \"bash\",\n py: \"python\",\n python: \"python\",\n sql: \"sql\",\n};\n\n/**\n * Normalise a language name onto a known grammar.\n *\n * @param language - Whatever the caller passed, e.g. `\"ts\"` or `\"Shell\"`.\n * @returns The grammar to use; `\"plain\"` when there is no match.\n */\nexport function resolveLanguage(language: string | undefined): CodeLanguage {\n if (!language) return \"plain\";\n return ALIASES[language.toLowerCase()] ?? \"plain\";\n}\n\n/** One grammar rule: a sticky pattern and the kind it produces. */\ninterface Rule {\n kind: TokenKind;\n pattern: RegExp;\n}\n\nconst JS_KEYWORDS =\n \"as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|keyof|let|new|of|private|protected|public|readonly|return|satisfies|set|static|super|switch|this|throw|try|type|typeof|var|void|while|with|yield\";\n\nconst JS_LITERALS = \"true|false|null|undefined|NaN|Infinity\";\n\n/**\n * Rules per grammar, tried in order at each position.\n *\n * Order carries meaning: comments and strings come first so a keyword inside a\n * string stays a string. Every pattern is sticky (`y`) and anchored by\n * `lastIndex`, so a rule can only match where the scanner currently stands.\n */\nconst GRAMMARS: Record<CodeLanguage, Rule[]> = {\n typescript: jsRules(false),\n javascript: jsRules(false),\n tsx: jsRules(true),\n jsx: jsRules(true),\n json: [\n { kind: \"property\", pattern: /\"(?:[^\"\\\\]|\\\\.)*\"(?=\\s*:)/y },\n { kind: \"string\", pattern: /\"(?:[^\"\\\\]|\\\\.)*\"/y },\n { kind: \"number\", pattern: /-?\\d+(?:\\.\\d+)?(?:[eE][+-]?\\d+)?/y },\n { kind: \"literal\", pattern: /\\b(?:true|false|null)\\b/y },\n { kind: \"punctuation\", pattern: /[{}[\\],:]/y },\n ],\n css: [\n { kind: \"comment\", pattern: /\\/\\*[\\s\\S]*?\\*\\//y },\n { kind: \"string\", pattern: /\"(?:[^\"\\\\]|\\\\.)*\"|'(?:[^'\\\\]|\\\\.)*'/y },\n { kind: \"keyword\", pattern: /@[a-zA-Z-]+/y },\n { kind: \"property\", pattern: /--?[a-zA-Z][\\w-]*(?=\\s*:)/y },\n { kind: \"function\", pattern: /[a-zA-Z-]+(?=\\()/y },\n { kind: \"number\", pattern: /-?\\d+(?:\\.\\d+)?(?:px|rem|em|%|s|ms|vh|vw|fr|deg)?/y },\n { kind: \"punctuation\", pattern: /[{}();:,>+~]/y },\n ],\n html: [\n { kind: \"comment\", pattern: /<!--[\\s\\S]*?-->/y },\n { kind: \"string\", pattern: /\"(?:[^\"\\\\]|\\\\.)*\"|'(?:[^'\\\\]|\\\\.)*'/y },\n { kind: \"tag\", pattern: /<\\/?[a-zA-Z][\\w:-]*/y },\n { kind: \"attribute\", pattern: /[a-zA-Z][\\w:-]*(?==)/y },\n { kind: \"punctuation\", pattern: /\\/?>|=/y },\n ],\n bash: [\n { kind: \"comment\", pattern: /#[^\\n]*/y },\n { kind: \"string\", pattern: /\"(?:[^\"\\\\]|\\\\.)*\"|'[^']*'/y },\n {\n kind: \"keyword\",\n pattern:\n /\\b(?:if|then|else|elif|fi|for|while|do|done|case|esac|function|return|export|local|source|set|cd|echo)\\b/y,\n },\n { kind: \"property\", pattern: /\\$\\{?[A-Za-z_][\\w]*\\}?/y },\n { kind: \"attribute\", pattern: /(?<=\\s)--?[a-zA-Z][\\w-]*/y },\n { kind: \"punctuation\", pattern: /[|&;()<>]/y },\n ],\n python: [\n { kind: \"comment\", pattern: /#[^\\n]*/y },\n {\n kind: \"string\",\n pattern: /\"\"\"[\\s\\S]*?\"\"\"|'''[\\s\\S]*?'''|\"(?:[^\"\\\\]|\\\\.)*\"|'(?:[^'\\\\]|\\\\.)*'/y,\n },\n {\n kind: \"keyword\",\n pattern:\n /\\b(?:and|as|assert|async|await|break|class|continue|def|del|elif|else|except|finally|for|from|global|if|import|in|is|lambda|nonlocal|not|or|pass|raise|return|try|while|with|yield)\\b/y,\n },\n { kind: \"literal\", pattern: /\\b(?:True|False|None|self)\\b/y },\n { kind: \"number\", pattern: /\\b\\d+(?:\\.\\d+)?\\b/y },\n { kind: \"function\", pattern: /[A-Za-z_]\\w*(?=\\()/y },\n { kind: \"punctuation\", pattern: /[{}[\\]().,:;=+\\-*/<>!]/y },\n ],\n sql: [\n { kind: \"comment\", pattern: /--[^\\n]*|\\/\\*[\\s\\S]*?\\*\\//y },\n { kind: \"string\", pattern: /'(?:[^']|'')*'/y },\n {\n kind: \"keyword\",\n // Case-insensitive: SQL is written both ways and both should paint.\n pattern:\n /\\b(?:select|from|where|insert|into|values|update|set|delete|create|table|alter|drop|index|join|inner|left|right|outer|on|group|by|order|having|limit|offset|as|and|or|not|in|is|null|distinct|union|all|with|returning|primary|key|foreign|references|default)\\b/iy,\n },\n { kind: \"number\", pattern: /\\b\\d+(?:\\.\\d+)?\\b/y },\n { kind: \"punctuation\", pattern: /[(),;*=<>]/y },\n ],\n plain: [],\n};\n\n/** The shared JavaScript-family rules, with JSX tags added for tsx/jsx. */\nfunction jsRules(withJsx: boolean): Rule[] {\n const rules: Rule[] = [\n { kind: \"comment\", pattern: /\\/\\/[^\\n]*|\\/\\*[\\s\\S]*?\\*\\//y },\n {\n kind: \"string\",\n pattern: /`(?:[^`\\\\]|\\\\.)*`|\"(?:[^\"\\\\]|\\\\.)*\"|'(?:[^'\\\\]|\\\\.)*'/y,\n },\n { kind: \"keyword\", pattern: new RegExp(`\\\\b(?:${JS_KEYWORDS})\\\\b`, \"y\") },\n { kind: \"literal\", pattern: new RegExp(`\\\\b(?:${JS_LITERALS})\\\\b`, \"y\") },\n {\n kind: \"number\",\n pattern: /\\b(?:0[xX][\\da-fA-F_]+|\\d[\\d_]*(?:\\.[\\d_]+)?(?:[eE][+-]?\\d+)?)\\b/y,\n },\n { kind: \"function\", pattern: /[A-Za-z_$][\\w$]*(?=\\s*\\()/y },\n { kind: \"punctuation\", pattern: /[{}[\\]().,;:?!=<>+\\-*/%&|^~]/y },\n ];\n if (withJsx) {\n // Before the keyword rule, so `<Button` reads as a tag and not as `<`.\n rules.splice(2, 0, { kind: \"tag\", pattern: /<\\/?[A-Za-z][\\w.]*|\\/>/y });\n }\n return rules;\n}\n\n/**\n * Split source into coloured tokens.\n *\n * This is a **scanner, not a parser**: it recognises comments, strings, numbers,\n * keywords and punctuation by pattern, and knows nothing about scope, types or\n * grammar. That is a deliberate ceiling. A real parser per language is a\n * dependency the size of the rest of the SDK, and the payoff — being right about\n * the corner cases in a documentation snippet — is small. Where it is unsure it\n * emits `plain`, which renders as ordinary text rather than as something wrong.\n *\n * Unknown languages produce a single `plain` token, so an unhighlighted block is\n * a normal outcome and never an error.\n *\n * @param code - The source.\n * @param language - Grammar name or alias.\n * @returns Tokens covering the input exactly, in order.\n *\n * @example\n * tokenize(\"const x = 1;\", \"ts\");\n * // [{kind: \"keyword\", value: \"const\"}, {kind: \"plain\", value: \" x \"}, …]\n */\nexport function tokenize(code: string, language: string | undefined): Token[] {\n const rules = GRAMMARS[resolveLanguage(language)];\n if (rules.length === 0) return code === \"\" ? [] : [{ kind: \"plain\", value: code }];\n\n const tokens: Token[] = [];\n let plainFrom = 0;\n let at = 0;\n\n const flushPlain = (until: number) => {\n if (until > plainFrom) tokens.push({ kind: \"plain\", value: code.slice(plainFrom, until) });\n };\n\n while (at < code.length) {\n let matched: Token | null = null;\n for (const rule of rules) {\n rule.pattern.lastIndex = at;\n const found = rule.pattern.exec(code);\n if (found && found[0].length > 0) {\n matched = { kind: rule.kind, value: found[0] };\n break;\n }\n }\n if (matched) {\n flushPlain(at);\n tokens.push(matched);\n at += matched.value.length;\n plainFrom = at;\n } else {\n at++;\n }\n }\n flushPlain(code.length);\n return tokens;\n}\n\n/**\n * The same tokens, split at newlines so each line can be rendered on its own.\n *\n * Line numbers and highlighted lines both need a per-line structure, and a token\n * is free to span a line break — a block comment usually does. Splitting here\n * keeps that out of the component.\n *\n * @param code - The source.\n * @param language - Grammar name or alias.\n * @returns One token array per line. Always at least one line.\n */\nexport function tokenizeLines(code: string, language: string | undefined): Token[][] {\n const lines: Token[][] = [[]];\n for (const token of tokenize(code, language)) {\n const pieces = token.value.split(\"\\n\");\n pieces.forEach((piece, index) => {\n if (index > 0) lines.push([]);\n if (piece !== \"\") lines[lines.length - 1].push({ kind: token.kind, value: piece });\n });\n }\n return lines;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"tokenize.js","names":[],"sources":["../../../src/components/CodeBlock/tokenize.ts"],"sourcesContent":["/** What a token is painted as. */\nexport type TokenKind =\n | \"plain\"\n | \"comment\"\n | \"string\"\n | \"number\"\n | \"keyword\"\n | \"literal\"\n | \"function\"\n | \"punctuation\"\n | \"tag\"\n | \"attribute\"\n | \"property\";\n\nexport interface Token {\n kind: TokenKind;\n value: string;\n}\n\n/** Languages the tokenizer knows. Anything else renders as plain text. */\nexport type CodeLanguage =\n | \"typescript\"\n | \"javascript\"\n | \"tsx\"\n | \"jsx\"\n | \"json\"\n | \"css\"\n | \"html\"\n | \"bash\"\n | \"python\"\n | \"sql\"\n | \"plain\";\n\n/** Aliases people actually type, mapped onto the grammars above. */\nconst ALIASES: Record<string, CodeLanguage> = {\n ts: \"typescript\",\n typescript: \"typescript\",\n js: \"javascript\",\n javascript: \"javascript\",\n mjs: \"javascript\",\n cjs: \"javascript\",\n tsx: \"tsx\",\n jsx: \"jsx\",\n json: \"json\",\n jsonc: \"json\",\n css: \"css\",\n scss: \"css\",\n html: \"html\",\n xml: \"html\",\n svg: \"html\",\n sh: \"bash\",\n bash: \"bash\",\n shell: \"bash\",\n zsh: \"bash\",\n console: \"bash\",\n py: \"python\",\n python: \"python\",\n sql: \"sql\",\n};\n\n/**\n * Normalise a language name onto a known grammar.\n *\n * @param language - Whatever the caller passed, e.g. `\"ts\"` or `\"Shell\"`.\n * @returns The grammar to use; `\"plain\"` when there is no match.\n */\nexport function resolveLanguage(language: string | undefined): CodeLanguage {\n if (!language) return \"plain\";\n return ALIASES[language.toLowerCase()] ?? \"plain\";\n}\n\n/** One grammar rule: a sticky pattern and the kind it produces. */\ninterface Rule {\n kind: TokenKind;\n pattern: RegExp;\n}\n\nconst JS_KEYWORDS =\n \"as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|keyof|let|new|of|private|protected|public|readonly|return|satisfies|set|static|super|switch|this|throw|try|type|typeof|var|void|while|with|yield\";\n\nconst JS_LITERALS = \"true|false|null|undefined|NaN|Infinity\";\n\n/**\n * Rules per grammar, tried in order at each position.\n *\n * Order carries meaning: comments and strings come first so a keyword inside a\n * string stays a string. Every pattern is sticky (`y`) and anchored by\n * `lastIndex`, so a rule can only match where the scanner currently stands.\n *\n * Every string and comment pattern makes its closing delimiter **optional** —\n * `\"...\"?`, `/*...*?` / `<!--...--?` — so an unterminated opener is consumed as a\n * single token instead of failing and being rescanned from the next opener.\n * Without that, a `\"` followed by many escaped quotes was re-read to the end of\n * the input from each opening quote, quadratic in the input (#477).\n *\n * A well-formed string that closes on a later line stays one token where the\n * language allows the newline: bash, SQL, HTML and the JavaScript template\n * literal carry a raw `\\n`, and every grammar lets an escape cross the line feed\n * (`\\\\[\\s\\S]`), so `\"a\\` + LF + `b\"` is one string. A quote string that cannot\n * hold a raw newline — the JavaScript and JSON `\"`/`'`, CSS, the Python\n * `\"`/`'` — still ends at the line when it is unterminated, which is the only\n * case the optional close cuts at the line. A JSX attribute value is the\n * exception in the JavaScript family: `=\"…\"` has its own rule, ahead of the\n * plain JS strings, so it may hold a raw line feed — `<input placeholder=\"a` +\n * LF + `b\">` is one string where the plain JS rule would have ended it at the\n * line. The rule matches at the `=` and looks behind for it, so `=` keeps its\n * paint; `(?<==\\s)` covers the spaced `= \"…\"` form too.\n *\n * Each grammar also ends with a `plain` rule that consumes a run of identifier\n * characters in one step. The rules that need a lookahead — the JavaScript\n * `function`, the HTML `attribute`, the CSS `property` and `function`, the\n * Python `function` — match a whole run and then fail if the character after it\n * is not the expected one; with only `at++` to fall back on, the scanner retried\n * that run from every character in it. A code fence holding a long run of word\n * characters and no punctuation (a base64 blob, a minified token) took 6 s at\n * 80 KB and 25 s at 160 KB; the fallback consumes the run once, so the scan\n * stays linear (#477 sweep).\n *\n * The `plain` rule matches an identifier run while the scanner's fallback\n * collects whatever no rule matched, so one plain stretch arrives in pieces;\n * `tokenize` fuses adjacent `plain` tokens back into one, which is the array\n * shape — and the one span per run — that predates the fallback (#477).\n *\n * The JavaScript fallback does **not** start at `$` — it takes a `$` run and an\n * identifier run separately — so a leading `$type`, `$default` and `$this` keep\n * the `$` plain and the name a keyword. Inside an identifier the `$` is an\n * ordinary character, so `foo$this`, `a$true` and `x$1` are one `plain` token\n * instead of painting `this`, `true` and `1` out of them; the `'` string rule\n * carries the same boundary in a lookbehind, so an apostrophe in prose\n * (`Don't`) does not open a string that runs to the end of the input.\n *\n * The same boundary closes the JavaScript keyword, literal and number rules:\n * they end at `(?![\\w$])` rather than `\\b`, because `\\b` holds before a `$`, so\n * `if$foo` would paint `if` and leave the scanner standing on the `$` in the\n * middle of the identifier. The `function` rule only starts where no identifier\n * character precedes it (`(?<![\\w$])`). Without that lookbehind a rule that\n * stopped inside a run — the leading-`$` split above stops at every `$1` of\n * `$1$1$1…` — handed the rest of the run to `function`, which read it to the end\n * and failed for want of a `(`, once per `$`: 80 KB of `$1` took 2.8 s and\n * 120 KB of `if$` 4.2 s (#498). With both boundaries every rule that can start\n * inside a run fails at once or consumes a bounded piece of it, so each run is\n * read a constant number of times and `if$foo(`, `this$x(` and `true$y(` paint\n * as the one called identifier they are.\n *\n * The CSS `property` and `function` rules match a whole word where it stands —\n * `font-size` and `translate3d` paint as one token rather than splitting the\n * tail — and a `#rgb`/`#rrggbb` colour paints as a number. A word before a\n * colon only counts as a property when the colon starts a value boundary\n * (whitespace, `;`, `}`): after a `.`/`#`/`(`/`,`/`>`/`+`/`~`/`:` it is a\n * selector, and when the colon is followed by an identifier character it could\n * be a pseudo-class (`li:hover`) or a declaration with no space (`color:red`) —\n * neither can be told apart without a parser, so both stay `plain`, which is\n * what `origin/main` painted (`#477`).\n */\nconst GRAMMARS: Record<CodeLanguage, Rule[]> = {\n typescript: jsRules(false),\n javascript: jsRules(false),\n tsx: jsRules(true),\n jsx: jsRules(true),\n json: [\n { kind: \"property\", pattern: /\"(?:[^\"\\\\\\n]|\\\\[\\s\\S])*\"(?=\\s*:)/y },\n { kind: \"string\", pattern: /\"(?:[^\"\\\\\\n]|\\\\[\\s\\S])*\"?/y },\n { kind: \"number\", pattern: /-?\\d+(?:\\.\\d+)?(?:[eE][+-]?\\d+)?/y },\n { kind: \"literal\", pattern: /\\b(?:true|false|null)\\b/y },\n { kind: \"punctuation\", pattern: /[{}[\\],:]/y },\n ],\n css: [\n { kind: \"comment\", pattern: /\\/\\*[\\s\\S]*?(?:\\*\\/|$)/y },\n { kind: \"string\", pattern: /\"(?:[^\"\\\\\\n]|\\\\[\\s\\S])*\"?|'(?:[^'\\\\\\n]|\\\\[\\s\\S])*'?/y },\n { kind: \"keyword\", pattern: /@[a-zA-Z-]+/y },\n { kind: \"property\", pattern: /(?<![.#[\\](),>+~:])[\\w-]+(?=\\s*:(?![\\w/:]))/y },\n { kind: \"function\", pattern: /[\\w-]+(?=\\()/y },\n { kind: \"number\", pattern: /#[0-9a-fA-F]{3,8}\\b/y },\n { kind: \"number\", pattern: /(?<![\\w-])-?\\d+(?:\\.\\d+)?(?:px|rem|em|%|s|ms|vh|vw|fr|deg)?/y },\n { kind: \"punctuation\", pattern: /[{}();:,>+~]/y },\n { kind: \"plain\", pattern: /[\\w-]+/y },\n ],\n html: [\n { kind: \"comment\", pattern: /<!--[\\s\\S]*?(?:-->|$)/y },\n { kind: \"string\", pattern: /\"(?:[^\"\\\\]|\\\\[\\s\\S])*\"?|'(?:[^'\\\\]|\\\\[\\s\\S])*'?/y },\n { kind: \"tag\", pattern: /<\\/?[a-zA-Z][\\w:-]*/y },\n { kind: \"attribute\", pattern: /[a-zA-Z][\\w:-]*(?==)/y },\n { kind: \"punctuation\", pattern: /\\/?>|=/y },\n { kind: \"plain\", pattern: /[a-zA-Z][\\w:-]*/y },\n ],\n bash: [\n { kind: \"comment\", pattern: /#[^\\n]*/y },\n { kind: \"string\", pattern: /\"(?:[^\"\\\\]|\\\\[\\s\\S])*\"?|'[^']*'?/y },\n {\n kind: \"keyword\",\n pattern:\n /\\b(?:if|then|else|elif|fi|for|while|do|done|case|esac|function|return|export|local|source|set|cd|echo)\\b/y,\n },\n { kind: \"property\", pattern: /\\$\\{?[A-Za-z_][\\w]*\\}?/y },\n { kind: \"attribute\", pattern: /(?<=\\s)--?[a-zA-Z][\\w-]*/y },\n { kind: \"punctuation\", pattern: /[|&;()<>]/y },\n ],\n python: [\n { kind: \"comment\", pattern: /#[^\\n]*/y },\n {\n kind: \"string\",\n pattern:\n /\"\"\"[\\s\\S]*?(?:\"\"\"|$)|'''[\\s\\S]*?(?:'''|$)|\"(?:[^\"\\\\\\n]|\\\\[\\s\\S])*\"?|'(?:[^'\\\\\\n]|\\\\[\\s\\S])*'?/y,\n },\n {\n kind: \"keyword\",\n pattern:\n /\\b(?:and|as|assert|async|await|break|class|continue|def|del|elif|else|except|finally|for|from|global|if|import|in|is|lambda|nonlocal|not|or|pass|raise|return|try|while|with|yield)\\b/y,\n },\n { kind: \"literal\", pattern: /\\b(?:True|False|None|self)\\b/y },\n { kind: \"number\", pattern: /\\b\\d+(?:\\.\\d+)?\\b/y },\n { kind: \"function\", pattern: /[A-Za-z_]\\w*(?=\\()/y },\n { kind: \"punctuation\", pattern: /[{}[\\]().,:;=+\\-*/<>!]/y },\n { kind: \"plain\", pattern: /[A-Za-z_]\\w*/y },\n ],\n sql: [\n { kind: \"comment\", pattern: /--[^\\n]*|\\/\\*[\\s\\S]*?(?:\\*\\/|$)/y },\n { kind: \"string\", pattern: /'(?:[^']|'')*'?/y },\n {\n kind: \"keyword\",\n // Case-insensitive: SQL is written both ways and both should paint.\n pattern:\n /\\b(?:select|from|where|insert|into|values|update|set|delete|create|table|alter|drop|index|join|inner|left|right|outer|on|group|by|order|having|limit|offset|as|and|or|not|in|is|null|distinct|union|all|with|returning|primary|key|foreign|references|default)\\b/iy,\n },\n { kind: \"number\", pattern: /\\b\\d+(?:\\.\\d+)?\\b/y },\n { kind: \"punctuation\", pattern: /[(),;*=<>]/y },\n ],\n plain: [],\n};\n\n/** The shared JavaScript-family rules, with JSX tags added for tsx/jsx. */\nfunction jsRules(withJsx: boolean): Rule[] {\n const rules: Rule[] = [\n { kind: \"comment\", pattern: /\\/\\/[^\\n]*|\\/\\*[\\s\\S]*?(?:\\*\\/|$)/y },\n {\n kind: \"string\",\n pattern:\n /`(?:[^`\\\\]|\\\\[\\s\\S])*`?|\"(?:[^\"\\\\\\n]|\\\\[\\s\\S])*\"?|(?<![\\w$])'(?:[^'\\\\\\n]|\\\\[\\s\\S])*'?/y,\n },\n { kind: \"keyword\", pattern: new RegExp(`\\\\b(?:${JS_KEYWORDS})(?![\\\\w$])`, \"y\") },\n { kind: \"literal\", pattern: new RegExp(`\\\\b(?:${JS_LITERALS})(?![\\\\w$])`, \"y\") },\n {\n kind: \"number\",\n pattern: /\\b(?:0[xX][\\da-fA-F_]+|\\d[\\d_]*(?:\\.[\\d_]+)?(?:[eE][+-]?\\d+)?)(?![\\w$])/y,\n },\n { kind: \"function\", pattern: /(?<![\\w$])[A-Za-z_$][\\w$]*(?=\\s*\\()/y },\n { kind: \"punctuation\", pattern: /[{}[\\]().,;:?!=<>+\\-*/%&|^~]/y },\n { kind: \"plain\", pattern: /(?:[A-Za-z_][\\w$]*|\\$+)/y },\n ];\n if (withJsx) {\n rules.splice(\n 1,\n 0,\n { kind: \"string\", pattern: /(?:(?<==)|(?<==\\s))\"[^\"]*\"?/y },\n { kind: \"string\", pattern: /(?:(?<==)|(?<==\\s))'[^']*'?/y },\n );\n rules.splice(4, 0, { kind: \"tag\", pattern: /<\\/?[A-Za-z][\\w.]*|\\/>/y });\n }\n return rules;\n}\n\n/**\n * Split source into coloured tokens.\n *\n * This is a **scanner, not a parser**: it recognises comments, strings, numbers,\n * keywords and punctuation by pattern, and knows nothing about scope, types or\n * grammar. That is a deliberate ceiling. A real parser per language is a\n * dependency the size of the rest of the SDK, and the payoff — being right about\n * the corner cases in a documentation snippet — is small. Where it is unsure it\n * emits `plain`, which renders as ordinary text rather than as something wrong.\n *\n * Unknown languages produce a single `plain` token, so an unhighlighted block is\n * a normal outcome and never an error.\n *\n * The scan is linear in the source: an unterminated string or comment becomes a\n * single token that runs to its close, or to the end of the input — or, for a\n * quote that cannot hold a newline and never closes, to the end of the line — so\n * the openers inside it are never rescanned (#477).\n *\n * @param code - The source.\n * @param language - Grammar name or alias.\n * @returns Tokens covering the input exactly, in order.\n *\n * @example\n * tokenize(\"const x = 1;\", \"ts\");\n * // [{kind: \"keyword\", value: \"const\"}, {kind: \"plain\", value: \" \"}, …]\n */\nexport function tokenize(code: string, language: string | undefined): Token[] {\n const rules = GRAMMARS[resolveLanguage(language)];\n if (rules.length === 0) return code === \"\" ? [] : [{ kind: \"plain\", value: code }];\n\n const tokens: Token[] = [];\n let plainFrom = 0;\n let at = 0;\n\n /**\n * Append a token, fusing it with the previous one when both are `plain`.\n *\n * The `plain` rule matches an identifier run while the scanner's fallback\n * collects the characters no rule matched, so one plain stretch of source\n * arrives in pieces. Fusing them keeps the array shape `origin/main`\n * produced, where a plain run was a single token and each got one span in\n * the DOM rather than one per identifier (#477).\n */\n const push = (token: Token) => {\n const last = tokens[tokens.length - 1];\n if (last && last.kind === \"plain\" && token.kind === \"plain\") {\n last.value += token.value;\n } else {\n tokens.push(token);\n }\n };\n\n const flushPlain = (until: number) => {\n if (until > plainFrom) push({ kind: \"plain\", value: code.slice(plainFrom, until) });\n };\n\n while (at < code.length) {\n let matched: Token | null = null;\n for (const rule of rules) {\n rule.pattern.lastIndex = at;\n const found = rule.pattern.exec(code);\n if (found && found[0].length > 0) {\n matched = { kind: rule.kind, value: found[0] };\n break;\n }\n }\n if (matched) {\n flushPlain(at);\n push(matched);\n at += matched.value.length;\n plainFrom = at;\n } else {\n at++;\n }\n }\n flushPlain(code.length);\n return tokens;\n}\n\n/**\n * The same tokens, split at newlines so each line can be rendered on its own.\n *\n * Line numbers and highlighted lines both need a per-line structure, and a token\n * is free to span a line break — a block comment usually does. Splitting here\n * keeps that out of the component.\n *\n * @param code - The source.\n * @param language - Grammar name or alias.\n * @returns One token array per line. Always at least one line.\n */\nexport function tokenizeLines(code: string, language: string | undefined): Token[][] {\n const lines: Token[][] = [[]];\n for (const token of tokenize(code, language)) {\n const pieces = token.value.split(\"\\n\");\n pieces.forEach((piece, index) => {\n if (index > 0) lines.push([]);\n if (piece !== \"\") lines[lines.length - 1].push({ kind: token.kind, value: piece });\n });\n }\n return lines;\n}\n"],"mappings":"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"}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
const e=require("../../utils/safe-url.cjs");var t=/^\s{0,3}(`{3,}|~{3,})\s*([\w+-]*)$/,n=/^\s{0,3}(#{1,6})\s+/,r=/^\s{0,3}([-*_])\s*(?:\1\s*){2,}$/,i=/^\s{0,3}>\s?(.*)$/,a=/^(\
|
|
1
|
+
const e=require("../../utils/safe-url.cjs");var t=/^\s{0,3}(`{3,}|~{3,})\s*([\w+-]*)$/,n=/^\s{0,3}(#{1,6})\s+/,r=/^\s{0,3}([-*_])\s*(?:\1\s*){2,}$/,i=/^\s{0,3}>\s?(.*)$/,a=/^([^\S\n\r]*)([-*+])[^\S\n\r]+/,o=/^([^\S\n\r]*)(\d{1,9})[.)][^\S\n\r]+/;function s(e,t){let n=e.exec(t);if(!n)return null;let r=t.slice(n[0].length);return r.includes(`\u2028`)||r.includes(`\u2029`)?null:{indent:n[1],marker:n[2],content:r}}function c(e){return s(a,e)}function l(e){return s(o,e)}var u=/^\s{0,3}\|[\s|:-]*$|^\s{0,3}:?-+:?(\s*\|\s*:?-+:?)+\s*\|?$/,d=32;function f(e){let t=n.exec(e);if(!t)return null;let r=e.slice(t[0].length).trimEnd(),i=r.length;for(;i>0&&r[i-1]===`#`;)--i;return r=r.slice(0,i).trimEnd(),{level:t[1].length,text:r}}function p(e){return e.endsWith(` `)}function m(e){return h(e.replace(/\r\n?/g,`
|
|
2
2
|
`).split(`
|
|
3
|
-
`),0)}function
|
|
4
|
-
`);return t?[{type:`paragraph`,children:
|
|
5
|
-
`)});continue}let
|
|
6
|
-
`)}function
|
|
3
|
+
`),0)}function h(e,n){if(n>=d){let t=e.map(e=>e.trim()).filter(Boolean).join(`
|
|
4
|
+
`);return t?[{type:`paragraph`,children:S(t)}]:[]}let a=[],o=0;for(;o<e.length;){let s=e[o];if(s.trim()===``){o+=1;continue}let d=t.exec(s.trimEnd());if(d){let t=d[1][0],n=[];for(o+=1;o<e.length&&!RegExp(`^\\s{0,3}${t}{3,}\\s*$`).test(e[o]);)n.push(e[o]),o+=1;o+=1,a.push({type:`code`,language:d[2]||null,value:n.join(`
|
|
5
|
+
`)});continue}let m=f(s);if(m){a.push({type:`heading`,level:m.level,children:S(m.text)}),o+=1;continue}if(r.test(s)){a.push({type:`rule`}),o+=1;continue}if(i.test(s)){let t=[];for(;o<e.length;){let n=i.exec(e[o]);if(n){t.push(n[1]),o+=1;continue}if(e[o].trim()===``)break;t.push(e[o]),o+=1}a.push({type:`quote`,children:h(t,n+1)});continue}if(c(s)||l(s)){let[t,r]=_(e,o,n);a.push(t),o=r;continue}if(o+1<e.length&&s.includes(`|`)&&u.test(e[o+1].trimEnd())){let[t,n]=y(e,o);a.push(t),o=n;continue}let v=[],b=[];for(;o<e.length&&e[o].trim()!==``;){let n=e[o];if(f(n)||r.test(n)||t.test(n.trimEnd())||i.test(n)||c(n)||l(n))break;v.push(n.trim()),b.push(p(n)),o+=1}v.length>0&&a.push({type:`paragraph`,children:S(g(v,b))})}return a}function g(e,t){return e.map((n,r)=>t[r]&&r<e.length-1?`${n} `:n).join(`
|
|
6
|
+
`)}function _(e,t,n){let r=c(e[t]),i=l(e[t]),a=i!==null,o=(i??r)?.indent.length??0,s=a?Number(i?.marker??1):1,u=[],d=[],f=t,p=()=>{d.length!==0&&(u.push(h(d,n+1)),d=[])};for(;f<e.length;){let t=e[f];if(t.trim()===``){if(f+1<e.length&&e[f+1].trim()!==``){d.push(``),f+=1;continue}break}let n=c(t),r=l(t),i=n??r;if(i&&i.indent.length<=o){if(!(a?r:n))break;p(),d.push(i.content),f+=1;continue}if(i){d.push(t.slice(o)),f+=1;continue}if(t.search(/\S/)>o){d.push(t.trim()),f+=1;continue}break}return p(),[{type:`list`,ordered:a,start:s,items:u},f]}function v(e){let t=e.trim().replace(/^\|/,``).replace(/\|$/,``),n=[],r=``;for(let e=0;e<t.length;e+=1){if(t[e]===`\\`&&t[e+1]===`|`){r+=`|`,e+=1;continue}if(t[e]===`|`){n.push(r),r=``;continue}r+=t[e]}return n.push(r),n.map(e=>e.trim())}function y(e,t){let n=v(e[t]),r=v(e[t+1]).map(e=>{let t=e.startsWith(`:`),n=e.endsWith(`:`);return t&&n?`center`:n?`right`:t?`left`:null}),i=[],a=t+2;for(;a<e.length&&e[a].trim()!==``&&e[a].includes(`|`);)i.push(v(e[a]).map(S)),a+=1;return[{type:`table`,align:r,head:n.map(S),rows:i},a]}var b=[{guard:{kind:`link`,opener:`![`},first:`!`,pattern:/^!\[([^\]]*)\]\(((?:[^()\s]|\([^()\s]*\))+)(?:\s+"[^"]*")?\)/,build:t=>{let n=e.safeImageUrl(t[2]);return n?{type:`image`,src:n,alt:t[1]}:{type:`text`,value:t[1]}}},{guard:{kind:`link`,opener:`[`},first:`[`,pattern:/^\[([^\]]*)\]\(((?:[^()\s]|\([^()\s]*\))+)(?:\s+"[^"]*")?\)/,build:t=>{let n=e.safeLinkUrl(t[2]),r=S(t[1]);return n?{type:`link`,href:n,children:r}:{type:`text`,value:t[1]}}},{first:"`",pattern:/^`([^`]+)`/,build:e=>({type:`code`,value:e[1]})},{first:`*`,pattern:/^\*\*([\s\S]+?)\*\*/,build:e=>({type:`strong`,children:S(e[1])})},{first:`_`,pattern:/^__([\s\S]+?)__/,build:e=>({type:`strong`,children:S(e[1])})},{first:`~`,pattern:/^~~([\s\S]+?)~~/,build:e=>({type:`del`,children:S(e[1])})},{first:`*`,pattern:/^\*([^*\n]+)\*/,build:e=>({type:`em`,children:S(e[1])})},{first:`_`,pattern:/^_([^_\n]+)_/,build:e=>({type:`em`,children:S(e[1])})},{guard:{kind:`autolink`,opener:`<`},first:`<`,pattern:/^<((?:https?:\/\/|mailto:)[^>\s]+)>/,build:t=>{let n=e.safeLinkUrl(t[1]);return n?{type:`link`,href:n,children:[{type:`text`,value:t[1]}]}:{type:`text`,value:t[1]}}},{first:`\\`,pattern:/^\\\n/,build:()=>({type:`break`})}],x=new Set(b.map(e=>e.first));function S(e){let t=[],n=``,r=0,i=-1,a=-1,o=-1,s=/[>\s]/g,c=()=>{n&&t.push({type:`text`,value:n}),n=``},l=t=>{if(i<t){let n=e.indexOf(`]`,t);i=n===-1?e.length:n}return i},u=t=>(o<t&&(s.lastIndex=t,o=s.exec(e)?.index??e.length),o),d=t=>{if(!t)return!0;if(!e.startsWith(t.opener,r))return!1;if(t.kind===`autolink`)return e[u(r+1)]===`>`;let n=l(r+t.opener.length);return e[n+1]===`(`&&n!==a};for(;r<e.length;){if(e[r]===`\\`&&r+1<e.length&&e[r+1]!==`
|
|
7
7
|
`){n+=e[r+1],r+=2;continue}if(e[r]===` `){let i=r;for(;e[i]===` `;)i+=1;i-r>=2&&e[i]===`
|
|
8
|
-
`?(c(),t.push({type:`break`}),r=i+1):(n+=e.slice(r,i),r=i);continue}if(!
|
|
8
|
+
`?(c(),t.push({type:`break`}),r=i+1):(n+=e.slice(r,i),r=i);continue}if(!x.has(e[r])){n+=e[r],r+=1;continue}let i=e.slice(r),o=!1;for(let n of b){if(n.first!==e[r]||!d(n.guard))continue;let s=n.pattern.exec(i);if(!s){n.guard?.kind===`link`&&(a=l(r+n.guard.opener.length));continue}let u=n.build(s);if(u){c(),t.push(u),r+=s[0].length,o=!0;break}}o||(n+=e[r],r+=1)}return c(),t}exports.parseInline=S,exports.parseMarkdown=m;
|
|
9
9
|
//# sourceMappingURL=markdown-parse.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"markdown-parse.cjs","names":[],"sources":["../../../src/components/Markdown/markdown-parse.ts"],"sourcesContent":["/**\n * @tempest-limits file-lines, function-lines — a Markdown block parser is a state\n * machine over line kinds: paragraph, heading, fence, list, quote, table and\n * thematic break each need to see the lines around them to know where they end.\n * parseBlocks is that loop, and cutting it into per-kind functions would hand each\n * one the same lookahead cursor.\n */\n// A small Markdown parser for the subset an app actually renders from untrusted\n// text: comments, ticket bodies, release notes, a product description.\n//\n// It produces a node tree, never an HTML string, and the renderer turns that into\n// React elements. No `dangerouslySetInnerHTML` exists anywhere in this component —\n// which is the property that makes rendering somebody else's Markdown safe, and it\n// is structural rather than a promise about escaping.\n//\n// Raw HTML in the input is therefore not \"sanitized\", it is **text**: `<script>` in\n// a comment renders as the four characters somebody typed. That is the whole point,\n// and it is also the documented limit — this is not a CommonMark implementation and\n// does not try to be.\n\nimport { safeImageUrl, safeLinkUrl } from \"@/utils/safe-url\";\n\n/** Inline content. */\nexport type MarkdownInline =\n | { type: \"text\"; value: string }\n | { type: \"strong\"; children: MarkdownInline[] }\n | { type: \"em\"; children: MarkdownInline[] }\n | { type: \"del\"; children: MarkdownInline[] }\n | { type: \"code\"; value: string }\n | { type: \"link\"; href: string; children: MarkdownInline[] }\n | { type: \"image\"; src: string; alt: string }\n | { type: \"break\" };\n\n/** Column alignment of a table, from the delimiter row. */\nexport type MarkdownAlign = \"left\" | \"center\" | \"right\" | null;\n\n/** Block content. */\nexport type MarkdownBlock =\n | { type: \"heading\"; level: number; children: MarkdownInline[] }\n | { type: \"paragraph\"; children: MarkdownInline[] }\n | { type: \"code\"; language: string | null; value: string }\n | { type: \"quote\"; children: MarkdownBlock[] }\n | { type: \"list\"; ordered: boolean; start: number; items: MarkdownBlock[][] }\n | {\n type: \"table\";\n align: MarkdownAlign[];\n head: MarkdownInline[][];\n rows: MarkdownInline[][][];\n }\n | { type: \"rule\" };\n\n/**\n * A fence opener, matched against the line **after** `trimEnd()`.\n *\n * The trailing `\\s*` lives in the `trimEnd()` rather than in the pattern: with it,\n * `\\s*([\\w+-]*)\\s*$` is two whitespace quantifiers separated by one that may be\n * empty, and a fence followed by 20 000 spaces and a stray `!` took 0.36 s (#437).\n */\nconst FENCE = /^\\s{0,3}(`{3,}|~{3,})\\s*([\\w+-]*)$/;\n/**\n * The opening marker of an ATX heading. The text and the optional closing `#` run\n * are cut by {@link matchHeading} with string operations, not by this pattern.\n */\nconst HEADING_OPEN = /^\\s{0,3}(#{1,6})\\s+/;\nconst RULE = /^\\s{0,3}([-*_])\\s*(?:\\1\\s*){2,}$/;\nconst QUOTE = /^\\s{0,3}>\\s?(.*)$/;\nconst BULLET = /^(\\s*)([-*+])\\s+(.*)$/;\nconst ORDERED = /^(\\s*)(\\d{1,9})[.)]\\s+(.*)$/;\n/**\n * A table's delimiter row, matched against the line **after** `trimEnd()`.\n *\n * The `|` is required somewhere in the line, and that is what tells a one-column\n * table (`| --- |`) apart from a thematic break (`---`) — a single column is a\n * legitimate table, and the first version of this regex demanded two. The trailing\n * whitespace is trimmed before the test for the same reason as {@link FENCE}: a\n * `\\s*\\|?\\s*$` tail backtracked quadratically on `- |` plus 50 000 spaces (#437).\n */\nconst TABLE_DELIMITER = /^\\s{0,3}\\|[\\s|:-]*$|^\\s{0,3}:?-+:?(\\s*\\|\\s*:?-+:?)+\\s*\\|?$/;\n\n/**\n * How deep quotes and lists may nest before the rest is read as a paragraph.\n *\n * Each level is one recursive {@link parseBlocks} call and one nested element in\n * the render, so `\"- \".repeat(20000)` used to throw `RangeError: Maximum call stack\n * size exceeded` out of the component (#437). Real documents stop at a handful of\n * levels; past this cap the words are kept, only the nesting is dropped.\n */\nconst MAX_NESTING_DEPTH = 32;\n\n/**\n * Match an ATX heading in linear time.\n *\n * The regex this replaces, `/^\\s{0,3}(#{1,6})\\s+(.*?)\\s*#*\\s*$/`, let a lazy `.*?`\n * and two `\\s*` race for the same run of spaces: `\"# a\" + \" \".repeat(4000) + \"b\"`\n * took about 4.7 s, cubic in the run (#437). Only the opening marker is a regex\n * now. The text is the rest of the line minus the longest suffix shaped like\n * `\\s*#*\\s*` — exactly what the lazy group left behind, so `# título #`,\n * `# título#` and `# #` read as before.\n *\n * @param line - One source line.\n * @returns The level and raw text, or `null` when the line is not a heading.\n */\nfunction matchHeading(line: string): { level: number; text: string } | null {\n const open = HEADING_OPEN.exec(line);\n if (!open) return null;\n let text = line.slice(open[0].length).trimEnd();\n let end = text.length;\n while (end > 0 && text[end - 1] === \"#\") end -= 1;\n text = text.slice(0, end).trimEnd();\n return { level: open[1].length, text };\n}\n\n/**\n * Whether a paragraph line ends in a hard break: two or more trailing spaces.\n *\n * `/ {2,}$/` was quadratic on a line with a long inner run of spaces — it retried\n * the run from every space and failed at the next word each time (0.57 s for\n * 50 000 spaces, #437).\n */\nfunction endsWithHardBreak(line: string): boolean {\n return line.endsWith(\" \");\n}\n\n/**\n * Parse a Markdown document into blocks.\n *\n * @param source - Raw Markdown.\n * @returns Block nodes in document order.\n */\nexport function parseMarkdown(source: string): MarkdownBlock[] {\n const lines = source.replace(/\\r\\n?/g, \"\\n\").split(\"\\n\");\n return parseBlocks(lines, 0);\n}\n\n/**\n * Parse a run of lines into blocks.\n *\n * @param lines - The lines of this container.\n * @param depth - How many quotes and lists enclose them. At\n * {@link MAX_NESTING_DEPTH} the lines become one paragraph instead of more\n * containers, so a hostile document cannot exhaust the stack.\n * @returns The blocks.\n */\nfunction parseBlocks(lines: string[], depth: number): MarkdownBlock[] {\n if (depth >= MAX_NESTING_DEPTH) {\n const source = lines\n .map((line) => line.trim())\n .filter(Boolean)\n .join(\"\\n\");\n return source ? [{ type: \"paragraph\", children: parseInline(source) }] : [];\n }\n const blocks: MarkdownBlock[] = [];\n let i = 0;\n\n while (i < lines.length) {\n const line = lines[i];\n\n if (line.trim() === \"\") {\n i += 1;\n continue;\n }\n\n const fence = FENCE.exec(line.trimEnd());\n if (fence) {\n const marker = fence[1][0];\n const body: string[] = [];\n i += 1;\n while (i < lines.length && !new RegExp(`^\\\\s{0,3}${marker}{3,}\\\\s*$`).test(lines[i])) {\n body.push(lines[i]);\n i += 1;\n }\n // An unterminated fence still yields a code block: the alternative is to\n // render the rest of the document as prose full of backticks.\n i += 1;\n blocks.push({ type: \"code\", language: fence[2] || null, value: body.join(\"\\n\") });\n continue;\n }\n\n const heading = matchHeading(line);\n if (heading) {\n blocks.push({\n type: \"heading\",\n level: heading.level,\n children: parseInline(heading.text),\n });\n i += 1;\n continue;\n }\n\n if (RULE.test(line)) {\n blocks.push({ type: \"rule\" });\n i += 1;\n continue;\n }\n\n if (QUOTE.test(line)) {\n const inner: string[] = [];\n while (i < lines.length) {\n const match = QUOTE.exec(lines[i]);\n if (match) {\n inner.push(match[1]);\n i += 1;\n continue;\n }\n // A blank line ends the quote; a plain line continues it (lazy\n // continuation), which is how people actually write quotes.\n if (lines[i].trim() === \"\") break;\n inner.push(lines[i]);\n i += 1;\n }\n blocks.push({ type: \"quote\", children: parseBlocks(inner, depth + 1) });\n continue;\n }\n\n if (BULLET.test(line) || ORDERED.test(line)) {\n const [list, next] = parseList(lines, i, depth);\n blocks.push(list);\n i = next;\n continue;\n }\n\n if (\n i + 1 < lines.length &&\n line.includes(\"|\") &&\n TABLE_DELIMITER.test(lines[i + 1].trimEnd())\n ) {\n const [table, next] = parseTable(lines, i);\n blocks.push(table);\n i = next;\n continue;\n }\n\n const paragraph: string[] = [];\n const hardBreaks: boolean[] = [];\n while (i < lines.length && lines[i].trim() !== \"\") {\n const current = lines[i];\n if (\n matchHeading(current) ||\n RULE.test(current) ||\n FENCE.test(current.trimEnd()) ||\n QUOTE.test(current) ||\n BULLET.test(current) ||\n ORDERED.test(current)\n ) {\n break;\n }\n paragraph.push(current.trim());\n hardBreaks.push(endsWithHardBreak(current));\n i += 1;\n }\n if (paragraph.length > 0) {\n blocks.push({\n type: \"paragraph\",\n children: parseInline(joinParagraph(paragraph, hardBreaks)),\n });\n }\n }\n\n return blocks;\n}\n\n/**\n * Re-join the lines of a paragraph, keeping the two-space hard break.\n *\n * Every line is trimmed before it is parsed, because leading indentation is not\n * content and a trailing space is invisible noise in almost every line. The one\n * exception is the CommonMark hard break: two or more trailing spaces mean\n * \"break the line here\", so trimming first would delete the only mark that says\n * so and the ` \\n` inline rule could never match. The flag is recorded before\n * the trim and the marker put back here, on every line but the last — a break\n * after the final line of a paragraph would break into nothing.\n *\n * @param lines - The trimmed lines, in order.\n * @param hardBreaks - Whether each line ended in two or more spaces.\n * @returns The paragraph source the inline parser reads.\n */\nfunction joinParagraph(lines: readonly string[], hardBreaks: readonly boolean[]): string {\n return lines\n .map((line, index) => (hardBreaks[index] && index < lines.length - 1 ? `${line} ` : line))\n .join(\"\\n\");\n}\n\n/**\n * Parse a list, including nested lists and multi-line items.\n *\n * Nesting is decided by indentation relative to the **first** item's marker, so a\n * list indented inside a quote or another list still reads correctly.\n *\n * @param lines - The lines of the enclosing container.\n * @param start - Index of the first item's line.\n * @param depth - Nesting depth of the enclosing container.\n * @returns The list node and the index of the first line after it.\n */\nfunction parseList(lines: string[], start: number, depth: number): [MarkdownBlock, number] {\n const bulletStart = BULLET.exec(lines[start]);\n const orderedStart = ORDERED.exec(lines[start]);\n // The two markers are mutually exclusive — `-*+` versus digits — so whichever\n // matched decides the list kind.\n const ordered = orderedStart !== null;\n const first = orderedStart ?? bulletStart;\n const baseIndent = (first?.[1] ?? \"\").length;\n const startNumber = ordered ? Number(orderedStart?.[2] ?? 1) : 1;\n\n const items: MarkdownBlock[][] = [];\n let buffer: string[] = [];\n let i = start;\n\n const flush = (): void => {\n if (buffer.length === 0) return;\n items.push(parseBlocks(buffer, depth + 1));\n buffer = [];\n };\n\n while (i < lines.length) {\n const line = lines[i];\n if (line.trim() === \"\") {\n // A single blank line inside a list is a loose item, not the end. Two in\n // a row end it, which is what a blank-then-paragraph document means.\n if (i + 1 < lines.length && lines[i + 1].trim() !== \"\") {\n buffer.push(\"\");\n i += 1;\n continue;\n }\n break;\n }\n const bullet = BULLET.exec(line);\n const numbered = ORDERED.exec(line);\n const match = bullet ?? numbered;\n\n if (match && match[1].length <= baseIndent) {\n const sameKind = ordered ? Boolean(numbered) : Boolean(bullet);\n if (!sameKind) break;\n flush();\n buffer.push(match[3]);\n i += 1;\n continue;\n }\n if (match) {\n // Deeper marker: keep the indentation so the recursive call sees a list.\n buffer.push(line.slice(baseIndent));\n i += 1;\n continue;\n }\n if (line.search(/\\S/) > baseIndent) {\n buffer.push(line.trim());\n i += 1;\n continue;\n }\n break;\n }\n flush();\n\n return [{ type: \"list\", ordered, start: startNumber, items }, i];\n}\n\n/** Split a table row on unescaped pipes. */\nfunction splitRow(line: string): string[] {\n const trimmed = line.trim().replace(/^\\|/, \"\").replace(/\\|$/, \"\");\n const cells: string[] = [];\n let current = \"\";\n for (let i = 0; i < trimmed.length; i += 1) {\n if (trimmed[i] === \"\\\\\" && trimmed[i + 1] === \"|\") {\n current += \"|\";\n i += 1;\n continue;\n }\n if (trimmed[i] === \"|\") {\n cells.push(current);\n current = \"\";\n continue;\n }\n current += trimmed[i];\n }\n cells.push(current);\n return cells.map((cell) => cell.trim());\n}\n\n/**\n * Parse a GFM pipe table.\n *\n * @returns The table node and the index of the first line after it.\n */\nfunction parseTable(lines: string[], start: number): [MarkdownBlock, number] {\n const head = splitRow(lines[start]);\n const align: MarkdownAlign[] = splitRow(lines[start + 1]).map((cell) => {\n const left = cell.startsWith(\":\");\n const right = cell.endsWith(\":\");\n if (left && right) return \"center\";\n if (right) return \"right\";\n if (left) return \"left\";\n return null;\n });\n\n const rows: MarkdownInline[][][] = [];\n let i = start + 2;\n while (i < lines.length && lines[i].trim() !== \"\" && lines[i].includes(\"|\")) {\n rows.push(splitRow(lines[i]).map(parseInline));\n i += 1;\n }\n\n return [{ type: \"table\", align, head: head.map(parseInline), rows }, i];\n}\n\n/**\n * The precheck a rule needs before its pattern may run, keyed by the opener the\n * pattern starts with.\n *\n * `link` covers links and images, whose label ends at the first `]`; `autolink`\n * covers `<scheme:...>`, which ends at the first `>` or whitespace. Without the\n * precheck each pattern scanned to the end of the text from every opener, and\n * `\"[\".repeat(50000)` took 0.6 s (#437).\n */\ntype InlineGuard = { kind: \"link\"; opener: \"[\" | \"![\" } | { kind: \"autolink\"; opener: \"<\" };\n\n/** Inline delimiters, longest marker first so `**` wins over `*`. */\nconst INLINE_RULES: Array<{\n first: string;\n pattern: RegExp;\n guard?: InlineGuard;\n build: (match: RegExpExecArray) => MarkdownInline | null;\n}> = [\n {\n guard: { kind: \"link\", opener: \"![\" },\n first: \"!\",\n pattern: /^!\\[([^\\]]*)\\]\\(((?:[^()\\s]|\\([^()\\s]*\\))+)(?:\\s+\"[^\"]*\")?\\)/,\n build: (match) => {\n const src = safeImageUrl(match[2]);\n return src ? { type: \"image\", src, alt: match[1] } : { type: \"text\", value: match[1] };\n },\n },\n {\n guard: { kind: \"link\", opener: \"[\" },\n first: \"[\",\n pattern: /^\\[([^\\]]*)\\]\\(((?:[^()\\s]|\\([^()\\s]*\\))+)(?:\\s+\"[^\"]*\")?\\)/,\n build: (match) => {\n const href = safeLinkUrl(match[2]);\n const children = parseInline(match[1]);\n // A rejected URL keeps the label as plain text: dropping the text too\n // would silently delete words somebody wrote.\n return href ? { type: \"link\", href, children } : { type: \"text\", value: match[1] };\n },\n },\n { first: \"`\", pattern: /^`([^`]+)`/, build: (match) => ({ type: \"code\", value: match[1] }) },\n {\n first: \"*\",\n pattern: /^\\*\\*([\\s\\S]+?)\\*\\*/,\n build: (match) => ({ type: \"strong\", children: parseInline(match[1]) }),\n },\n {\n first: \"_\",\n pattern: /^__([\\s\\S]+?)__/,\n build: (match) => ({ type: \"strong\", children: parseInline(match[1]) }),\n },\n {\n first: \"~\",\n pattern: /^~~([\\s\\S]+?)~~/,\n build: (match) => ({ type: \"del\", children: parseInline(match[1]) }),\n },\n {\n first: \"*\",\n pattern: /^\\*([^*\\n]+)\\*/,\n build: (match) => ({ type: \"em\", children: parseInline(match[1]) }),\n },\n {\n first: \"_\",\n pattern: /^_([^_\\n]+)_/,\n build: (match) => ({ type: \"em\", children: parseInline(match[1]) }),\n },\n {\n guard: { kind: \"autolink\", opener: \"<\" },\n first: \"<\",\n pattern: /^<((?:https?:\\/\\/|mailto:)[^>\\s]+)>/,\n build: (match) => {\n const href = safeLinkUrl(match[1]);\n return href\n ? { type: \"link\", href, children: [{ type: \"text\", value: match[1] }] }\n : { type: \"text\", value: match[1] };\n },\n },\n { first: \"\\\\\", pattern: /^\\\\\\n/, build: () => ({ type: \"break\" }) },\n];\n\n/** Characters some inline rule can start with; any other character is text. */\nconst INLINE_FIRST = new Set(INLINE_RULES.map((rule) => rule.first));\n\n/**\n * Parse inline Markdown.\n *\n * A backslash escapes the next character, so `\\*not italic\\*` renders with the\n * asterisks. Anything that matches no rule is text — an unclosed `**` is two\n * asterisks, not a bold run to the end of the paragraph.\n *\n * The parse is linear in the text, which matters because the text is somebody\n * else's (#437):\n *\n * - A run of spaces is consumed whole: it is a hard break when two or more of them\n * end at a newline, and text otherwise. Matching `^ {2,}\\n` from every space\n * rescanned the run each time.\n * - A link or image label ends at the first `]` after the opener, so every opener\n * before the same `]` shares the same `](...)` tail. The position of that `]` is\n * cached, an opener whose `]` is not followed by `(` is skipped without running\n * the pattern, and once a tail has failed no later opener retries it.\n * - An autolink ends at the first `>` or whitespace, also cached; when that stop\n * is not `>` the pattern cannot match and is not run.\n * - A rule runs only at its own first character, so a run of `[` costs one set\n * lookup per character instead of every pattern in the table.\n *\n * @param source - One block's text.\n * @returns Inline nodes.\n */\nexport function parseInline(source: string): MarkdownInline[] {\n const nodes: MarkdownInline[] = [];\n let text = \"\";\n let i = 0;\n\n let bracketAt = -1;\n let deadTailAt = -1;\n let stopAt = -1;\n const stops = /[>\\s]/g;\n\n const flush = (): void => {\n if (text) nodes.push({ type: \"text\", value: text });\n text = \"\";\n };\n\n const closingBracket = (from: number): number => {\n if (bracketAt < from) {\n const found = source.indexOf(\"]\", from);\n bracketAt = found === -1 ? source.length : found;\n }\n return bracketAt;\n };\n\n const autolinkStop = (from: number): number => {\n if (stopAt < from) {\n stops.lastIndex = from;\n stopAt = stops.exec(source)?.index ?? source.length;\n }\n return stopAt;\n };\n\n const allowed = (guard: InlineGuard | undefined): boolean => {\n if (!guard) return true;\n if (!source.startsWith(guard.opener, i)) return false;\n if (guard.kind === \"autolink\") return source[autolinkStop(i + 1)] === \">\";\n const close = closingBracket(i + guard.opener.length);\n return source[close + 1] === \"(\" && close !== deadTailAt;\n };\n\n while (i < source.length) {\n if (source[i] === \"\\\\\" && i + 1 < source.length && source[i + 1] !== \"\\n\") {\n text += source[i + 1];\n i += 2;\n continue;\n }\n\n if (source[i] === \" \") {\n let end = i;\n while (source[end] === \" \") end += 1;\n if (end - i >= 2 && source[end] === \"\\n\") {\n flush();\n nodes.push({ type: \"break\" });\n i = end + 1;\n } else {\n text += source.slice(i, end);\n i = end;\n }\n continue;\n }\n\n if (!INLINE_FIRST.has(source[i])) {\n text += source[i];\n i += 1;\n continue;\n }\n\n const rest = source.slice(i);\n let matched = false;\n for (const rule of INLINE_RULES) {\n if (rule.first !== source[i] || !allowed(rule.guard)) continue;\n const match = rule.pattern.exec(rest);\n if (!match) {\n if (rule.guard?.kind === \"link\") {\n deadTailAt = closingBracket(i + rule.guard.opener.length);\n }\n continue;\n }\n const node = rule.build(match);\n if (!node) continue;\n flush();\n nodes.push(node);\n i += match[0].length;\n matched = true;\n break;\n }\n if (matched) continue;\n\n text += source[i];\n i += 1;\n }\n\n flush();\n return nodes;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"markdown-parse.cjs","names":[],"sources":["../../../src/components/Markdown/markdown-parse.ts"],"sourcesContent":["/**\n * @tempest-limits file-lines, function-lines — a Markdown block parser is a state\n * machine over line kinds: paragraph, heading, fence, list, quote, table and\n * thematic break each need to see the lines around them to know where they end.\n * parseBlocks is that loop, and cutting it into per-kind functions would hand each\n * one the same lookahead cursor.\n */\n// A small Markdown parser for the subset an app actually renders from untrusted\n// text: comments, ticket bodies, release notes, a product description.\n//\n// It produces a node tree, never an HTML string, and the renderer turns that into\n// React elements. No `dangerouslySetInnerHTML` exists anywhere in this component —\n// which is the property that makes rendering somebody else's Markdown safe, and it\n// is structural rather than a promise about escaping.\n//\n// Raw HTML in the input is therefore not \"sanitized\", it is **text**: `<script>` in\n// a comment renders as the four characters somebody typed. That is the whole point,\n// and it is also the documented limit — this is not a CommonMark implementation and\n// does not try to be.\n\nimport { safeImageUrl, safeLinkUrl } from \"@/utils/safe-url\";\n\n/** Inline content. */\nexport type MarkdownInline =\n | { type: \"text\"; value: string }\n | { type: \"strong\"; children: MarkdownInline[] }\n | { type: \"em\"; children: MarkdownInline[] }\n | { type: \"del\"; children: MarkdownInline[] }\n | { type: \"code\"; value: string }\n | { type: \"link\"; href: string; children: MarkdownInline[] }\n | { type: \"image\"; src: string; alt: string }\n | { type: \"break\" };\n\n/** Column alignment of a table, from the delimiter row. */\nexport type MarkdownAlign = \"left\" | \"center\" | \"right\" | null;\n\n/** Block content. */\nexport type MarkdownBlock =\n | { type: \"heading\"; level: number; children: MarkdownInline[] }\n | { type: \"paragraph\"; children: MarkdownInline[] }\n | { type: \"code\"; language: string | null; value: string }\n | { type: \"quote\"; children: MarkdownBlock[] }\n | { type: \"list\"; ordered: boolean; start: number; items: MarkdownBlock[][] }\n | {\n type: \"table\";\n align: MarkdownAlign[];\n head: MarkdownInline[][];\n rows: MarkdownInline[][][];\n }\n | { type: \"rule\" };\n\n/**\n * A fence opener, matched against the line **after** `trimEnd()`.\n *\n * The trailing `\\s*` lives in the `trimEnd()` rather than in the pattern: with it,\n * `\\s*([\\w+-]*)\\s*$` is two whitespace quantifiers separated by one that may be\n * empty, and a fence followed by 20 000 spaces and a stray `!` took 0.36 s (#437).\n */\nconst FENCE = /^\\s{0,3}(`{3,}|~{3,})\\s*([\\w+-]*)$/;\n/**\n * The opening marker of an ATX heading. The text and the optional closing `#` run\n * are cut by {@link matchHeading} with string operations, not by this pattern.\n */\nconst HEADING_OPEN = /^\\s{0,3}(#{1,6})\\s+/;\nconst RULE = /^\\s{0,3}([-*_])\\s*(?:\\1\\s*){2,}$/;\nconst QUOTE = /^\\s{0,3}>\\s?(.*)$/;\n/**\n * The opener of a list item: indentation, marker and the whitespace that\n * separates the marker from the content.\n *\n * The content is sliced off with a string operation rather than captured,\n * because it may not hold U+2028/U+2029. Those are line terminators the old\n * `(.*)$` could not cross, so a line with one after the marker was a paragraph\n * and has to stay one. Capturing the content with a regex that stops at them (a\n * `[^\\u2028\\u2029]*`) would put the separator and the content back to racing for\n * a run of spaces — quadratic, 3.6 s for a bullet padded with 80 000 spaces\n * (#478). Matching only the opener and slicing sidesteps that entirely.\n *\n * The separator is `[^\\S\\n\\r]`: every whitespace character except CR and LF,\n * which a line never contains. That keeps a separator a person pastes from Word\n * or Docs — NBSP, a form feed, a leading BOM — reading as a list, exactly as the\n * old `\\s+` did, instead of silently turning the line into a paragraph.\n */\nconst BULLET_OPENER = /^([^\\S\\n\\r]*)([-*+])[^\\S\\n\\r]+/;\nconst ORDERED_OPENER = /^([^\\S\\n\\r]*)(\\d{1,9})[.)][^\\S\\n\\r]+/;\n\n/** A list item's opener: how far it is indented, its marker and its content. */\ninterface ListOpener {\n indent: string;\n marker: string;\n content: string;\n}\n\n/**\n * Read a list item's opener, or `null` when the line is not one.\n *\n * A line whose content holds U+2028/U+2029 is not a list item: the old `(.*)$`\n * stopped at the separator, never reached `$`, and left the line a paragraph,\n * and {@link parseMarkdown} still splits only on `\\n` and `\\r`. Keeping that\n * here is what lets the opener stay a plain pattern with no `.*` to race.\n *\n * @param pattern - {@link BULLET_OPENER} or {@link ORDERED_OPENER}.\n * @param line - One source line.\n * @returns The opener, or `null` when the line is not a list item.\n */\nfunction matchListOpener(pattern: RegExp, line: string): ListOpener | null {\n const match = pattern.exec(line);\n if (!match) return null;\n const content = line.slice(match[0].length);\n if (content.includes(\"\\u2028\") || content.includes(\"\\u2029\")) return null;\n return { indent: match[1], marker: match[2], content };\n}\n\n/** {@link matchListOpener} for a bullet. */\nfunction matchBullet(line: string): ListOpener | null {\n return matchListOpener(BULLET_OPENER, line);\n}\n\n/** {@link matchListOpener} for an ordered marker. */\nfunction matchOrdered(line: string): ListOpener | null {\n return matchListOpener(ORDERED_OPENER, line);\n}\n/**\n * A table's delimiter row, matched against the line **after** `trimEnd()`.\n *\n * The `|` is required somewhere in the line, and that is what tells a one-column\n * table (`| --- |`) apart from a thematic break (`---`) — a single column is a\n * legitimate table, and the first version of this regex demanded two. The trailing\n * whitespace is trimmed before the test for the same reason as {@link FENCE}: a\n * `\\s*\\|?\\s*$` tail backtracked quadratically on `- |` plus 50 000 spaces (#437).\n */\nconst TABLE_DELIMITER = /^\\s{0,3}\\|[\\s|:-]*$|^\\s{0,3}:?-+:?(\\s*\\|\\s*:?-+:?)+\\s*\\|?$/;\n\n/**\n * How deep quotes and lists may nest before the rest is read as a paragraph.\n *\n * Each level is one recursive {@link parseBlocks} call and one nested element in\n * the render, so `\"- \".repeat(20000)` used to throw `RangeError: Maximum call stack\n * size exceeded` out of the component (#437). Real documents stop at a handful of\n * levels; past this cap the words are kept, only the nesting is dropped.\n */\nconst MAX_NESTING_DEPTH = 32;\n\n/**\n * Match an ATX heading in linear time.\n *\n * The regex this replaces, `/^\\s{0,3}(#{1,6})\\s+(.*?)\\s*#*\\s*$/`, let a lazy `.*?`\n * and two `\\s*` race for the same run of spaces: `\"# a\" + \" \".repeat(4000) + \"b\"`\n * took about 4.7 s, cubic in the run (#437). Only the opening marker is a regex\n * now. The text is the rest of the line minus the longest suffix shaped like\n * `\\s*#*\\s*` — exactly what the lazy group left behind, so `# título #`,\n * `# título#` and `# #` read as before.\n *\n * @param line - One source line.\n * @returns The level and raw text, or `null` when the line is not a heading.\n */\nfunction matchHeading(line: string): { level: number; text: string } | null {\n const open = HEADING_OPEN.exec(line);\n if (!open) return null;\n let text = line.slice(open[0].length).trimEnd();\n let end = text.length;\n while (end > 0 && text[end - 1] === \"#\") end -= 1;\n text = text.slice(0, end).trimEnd();\n return { level: open[1].length, text };\n}\n\n/**\n * Whether a paragraph line ends in a hard break: two or more trailing spaces.\n *\n * `/ {2,}$/` was quadratic on a line with a long inner run of spaces — it retried\n * the run from every space and failed at the next word each time (0.57 s for\n * 50 000 spaces, #437).\n */\nfunction endsWithHardBreak(line: string): boolean {\n return line.endsWith(\" \");\n}\n\n/**\n * Parse a Markdown document into blocks.\n *\n * @param source - Raw Markdown.\n * @returns Block nodes in document order.\n */\nexport function parseMarkdown(source: string): MarkdownBlock[] {\n const lines = source.replace(/\\r\\n?/g, \"\\n\").split(\"\\n\");\n return parseBlocks(lines, 0);\n}\n\n/**\n * Parse a run of lines into blocks.\n *\n * @param lines - The lines of this container.\n * @param depth - How many quotes and lists enclose them. At\n * {@link MAX_NESTING_DEPTH} the lines become one paragraph instead of more\n * containers, so a hostile document cannot exhaust the stack.\n * @returns The blocks.\n */\nfunction parseBlocks(lines: string[], depth: number): MarkdownBlock[] {\n if (depth >= MAX_NESTING_DEPTH) {\n const source = lines\n .map((line) => line.trim())\n .filter(Boolean)\n .join(\"\\n\");\n return source ? [{ type: \"paragraph\", children: parseInline(source) }] : [];\n }\n const blocks: MarkdownBlock[] = [];\n let i = 0;\n\n while (i < lines.length) {\n const line = lines[i];\n\n if (line.trim() === \"\") {\n i += 1;\n continue;\n }\n\n const fence = FENCE.exec(line.trimEnd());\n if (fence) {\n const marker = fence[1][0];\n const body: string[] = [];\n i += 1;\n while (i < lines.length && !new RegExp(`^\\\\s{0,3}${marker}{3,}\\\\s*$`).test(lines[i])) {\n body.push(lines[i]);\n i += 1;\n }\n // An unterminated fence still yields a code block: the alternative is to\n // render the rest of the document as prose full of backticks.\n i += 1;\n blocks.push({ type: \"code\", language: fence[2] || null, value: body.join(\"\\n\") });\n continue;\n }\n\n const heading = matchHeading(line);\n if (heading) {\n blocks.push({\n type: \"heading\",\n level: heading.level,\n children: parseInline(heading.text),\n });\n i += 1;\n continue;\n }\n\n if (RULE.test(line)) {\n blocks.push({ type: \"rule\" });\n i += 1;\n continue;\n }\n\n if (QUOTE.test(line)) {\n const inner: string[] = [];\n while (i < lines.length) {\n const match = QUOTE.exec(lines[i]);\n if (match) {\n inner.push(match[1]);\n i += 1;\n continue;\n }\n // A blank line ends the quote; a plain line continues it (lazy\n // continuation), which is how people actually write quotes.\n if (lines[i].trim() === \"\") break;\n inner.push(lines[i]);\n i += 1;\n }\n blocks.push({ type: \"quote\", children: parseBlocks(inner, depth + 1) });\n continue;\n }\n\n if (matchBullet(line) || matchOrdered(line)) {\n const [list, next] = parseList(lines, i, depth);\n blocks.push(list);\n i = next;\n continue;\n }\n\n if (\n i + 1 < lines.length &&\n line.includes(\"|\") &&\n TABLE_DELIMITER.test(lines[i + 1].trimEnd())\n ) {\n const [table, next] = parseTable(lines, i);\n blocks.push(table);\n i = next;\n continue;\n }\n\n const paragraph: string[] = [];\n const hardBreaks: boolean[] = [];\n while (i < lines.length && lines[i].trim() !== \"\") {\n const current = lines[i];\n if (\n matchHeading(current) ||\n RULE.test(current) ||\n FENCE.test(current.trimEnd()) ||\n QUOTE.test(current) ||\n matchBullet(current) ||\n matchOrdered(current)\n ) {\n break;\n }\n paragraph.push(current.trim());\n hardBreaks.push(endsWithHardBreak(current));\n i += 1;\n }\n if (paragraph.length > 0) {\n blocks.push({\n type: \"paragraph\",\n children: parseInline(joinParagraph(paragraph, hardBreaks)),\n });\n }\n }\n\n return blocks;\n}\n\n/**\n * Re-join the lines of a paragraph, keeping the two-space hard break.\n *\n * Every line is trimmed before it is parsed, because leading indentation is not\n * content and a trailing space is invisible noise in almost every line. The one\n * exception is the CommonMark hard break: two or more trailing spaces mean\n * \"break the line here\", so trimming first would delete the only mark that says\n * so and the ` \\n` inline rule could never match. The flag is recorded before\n * the trim and the marker put back here, on every line but the last — a break\n * after the final line of a paragraph would break into nothing.\n *\n * @param lines - The trimmed lines, in order.\n * @param hardBreaks - Whether each line ended in two or more spaces.\n * @returns The paragraph source the inline parser reads.\n */\nfunction joinParagraph(lines: readonly string[], hardBreaks: readonly boolean[]): string {\n return lines\n .map((line, index) => (hardBreaks[index] && index < lines.length - 1 ? `${line} ` : line))\n .join(\"\\n\");\n}\n\n/**\n * Parse a list, including nested lists and multi-line items.\n *\n * Nesting is decided by indentation relative to the **first** item's marker, so a\n * list indented inside a quote or another list still reads correctly.\n *\n * @param lines - The lines of the enclosing container.\n * @param start - Index of the first item's line.\n * @param depth - Nesting depth of the enclosing container.\n * @returns The list node and the index of the first line after it.\n */\nfunction parseList(lines: string[], start: number, depth: number): [MarkdownBlock, number] {\n const bulletStart = matchBullet(lines[start]);\n const orderedStart = matchOrdered(lines[start]);\n // The two markers are mutually exclusive — `-*+` versus digits — so whichever\n // matched decides the list kind.\n const ordered = orderedStart !== null;\n const first = orderedStart ?? bulletStart;\n const baseIndent = first?.indent.length ?? 0;\n const startNumber = ordered ? Number(orderedStart?.marker ?? 1) : 1;\n\n const items: MarkdownBlock[][] = [];\n let buffer: string[] = [];\n let i = start;\n\n const flush = (): void => {\n if (buffer.length === 0) return;\n items.push(parseBlocks(buffer, depth + 1));\n buffer = [];\n };\n\n while (i < lines.length) {\n const line = lines[i];\n if (line.trim() === \"\") {\n // A single blank line inside a list is a loose item, not the end. Two in\n // a row end it, which is what a blank-then-paragraph document means.\n if (i + 1 < lines.length && lines[i + 1].trim() !== \"\") {\n buffer.push(\"\");\n i += 1;\n continue;\n }\n break;\n }\n const bullet = matchBullet(line);\n const numbered = matchOrdered(line);\n const match = bullet ?? numbered;\n\n if (match && match.indent.length <= baseIndent) {\n const sameKind = ordered ? Boolean(numbered) : Boolean(bullet);\n if (!sameKind) break;\n flush();\n buffer.push(match.content);\n i += 1;\n continue;\n }\n if (match) {\n // Deeper marker: keep the indentation so the recursive call sees a list.\n buffer.push(line.slice(baseIndent));\n i += 1;\n continue;\n }\n if (line.search(/\\S/) > baseIndent) {\n buffer.push(line.trim());\n i += 1;\n continue;\n }\n break;\n }\n flush();\n\n return [{ type: \"list\", ordered, start: startNumber, items }, i];\n}\n\n/** Split a table row on unescaped pipes. */\nfunction splitRow(line: string): string[] {\n const trimmed = line.trim().replace(/^\\|/, \"\").replace(/\\|$/, \"\");\n const cells: string[] = [];\n let current = \"\";\n for (let i = 0; i < trimmed.length; i += 1) {\n if (trimmed[i] === \"\\\\\" && trimmed[i + 1] === \"|\") {\n current += \"|\";\n i += 1;\n continue;\n }\n if (trimmed[i] === \"|\") {\n cells.push(current);\n current = \"\";\n continue;\n }\n current += trimmed[i];\n }\n cells.push(current);\n return cells.map((cell) => cell.trim());\n}\n\n/**\n * Parse a GFM pipe table.\n *\n * @returns The table node and the index of the first line after it.\n */\nfunction parseTable(lines: string[], start: number): [MarkdownBlock, number] {\n const head = splitRow(lines[start]);\n const align: MarkdownAlign[] = splitRow(lines[start + 1]).map((cell) => {\n const left = cell.startsWith(\":\");\n const right = cell.endsWith(\":\");\n if (left && right) return \"center\";\n if (right) return \"right\";\n if (left) return \"left\";\n return null;\n });\n\n const rows: MarkdownInline[][][] = [];\n let i = start + 2;\n while (i < lines.length && lines[i].trim() !== \"\" && lines[i].includes(\"|\")) {\n rows.push(splitRow(lines[i]).map(parseInline));\n i += 1;\n }\n\n return [{ type: \"table\", align, head: head.map(parseInline), rows }, i];\n}\n\n/**\n * The precheck a rule needs before its pattern may run, keyed by the opener the\n * pattern starts with.\n *\n * `link` covers links and images, whose label ends at the first `]`; `autolink`\n * covers `<scheme:...>`, which ends at the first `>` or whitespace. Without the\n * precheck each pattern scanned to the end of the text from every opener, and\n * `\"[\".repeat(50000)` took 0.6 s (#437).\n */\ntype InlineGuard = { kind: \"link\"; opener: \"[\" | \"![\" } | { kind: \"autolink\"; opener: \"<\" };\n\n/** Inline delimiters, longest marker first so `**` wins over `*`. */\nconst INLINE_RULES: Array<{\n first: string;\n pattern: RegExp;\n guard?: InlineGuard;\n build: (match: RegExpExecArray) => MarkdownInline | null;\n}> = [\n {\n guard: { kind: \"link\", opener: \"![\" },\n first: \"!\",\n pattern: /^!\\[([^\\]]*)\\]\\(((?:[^()\\s]|\\([^()\\s]*\\))+)(?:\\s+\"[^\"]*\")?\\)/,\n build: (match) => {\n const src = safeImageUrl(match[2]);\n return src ? { type: \"image\", src, alt: match[1] } : { type: \"text\", value: match[1] };\n },\n },\n {\n guard: { kind: \"link\", opener: \"[\" },\n first: \"[\",\n pattern: /^\\[([^\\]]*)\\]\\(((?:[^()\\s]|\\([^()\\s]*\\))+)(?:\\s+\"[^\"]*\")?\\)/,\n build: (match) => {\n const href = safeLinkUrl(match[2]);\n const children = parseInline(match[1]);\n // A rejected URL keeps the label as plain text: dropping the text too\n // would silently delete words somebody wrote.\n return href ? { type: \"link\", href, children } : { type: \"text\", value: match[1] };\n },\n },\n { first: \"`\", pattern: /^`([^`]+)`/, build: (match) => ({ type: \"code\", value: match[1] }) },\n {\n first: \"*\",\n pattern: /^\\*\\*([\\s\\S]+?)\\*\\*/,\n build: (match) => ({ type: \"strong\", children: parseInline(match[1]) }),\n },\n {\n first: \"_\",\n pattern: /^__([\\s\\S]+?)__/,\n build: (match) => ({ type: \"strong\", children: parseInline(match[1]) }),\n },\n {\n first: \"~\",\n pattern: /^~~([\\s\\S]+?)~~/,\n build: (match) => ({ type: \"del\", children: parseInline(match[1]) }),\n },\n {\n first: \"*\",\n pattern: /^\\*([^*\\n]+)\\*/,\n build: (match) => ({ type: \"em\", children: parseInline(match[1]) }),\n },\n {\n first: \"_\",\n pattern: /^_([^_\\n]+)_/,\n build: (match) => ({ type: \"em\", children: parseInline(match[1]) }),\n },\n {\n guard: { kind: \"autolink\", opener: \"<\" },\n first: \"<\",\n pattern: /^<((?:https?:\\/\\/|mailto:)[^>\\s]+)>/,\n build: (match) => {\n const href = safeLinkUrl(match[1]);\n return href\n ? { type: \"link\", href, children: [{ type: \"text\", value: match[1] }] }\n : { type: \"text\", value: match[1] };\n },\n },\n { first: \"\\\\\", pattern: /^\\\\\\n/, build: () => ({ type: \"break\" }) },\n];\n\n/** Characters some inline rule can start with; any other character is text. */\nconst INLINE_FIRST = new Set(INLINE_RULES.map((rule) => rule.first));\n\n/**\n * Parse inline Markdown.\n *\n * A backslash escapes the next character, so `\\*not italic\\*` renders with the\n * asterisks. Anything that matches no rule is text — an unclosed `**` is two\n * asterisks, not a bold run to the end of the paragraph.\n *\n * The parse is linear in the text, which matters because the text is somebody\n * else's (#437):\n *\n * - A run of spaces is consumed whole: it is a hard break when two or more of them\n * end at a newline, and text otherwise. Matching `^ {2,}\\n` from every space\n * rescanned the run each time.\n * - A link or image label ends at the first `]` after the opener, so every opener\n * before the same `]` shares the same `](...)` tail. The position of that `]` is\n * cached, an opener whose `]` is not followed by `(` is skipped without running\n * the pattern, and once a tail has failed no later opener retries it.\n * - An autolink ends at the first `>` or whitespace, also cached; when that stop\n * is not `>` the pattern cannot match and is not run.\n * - A rule runs only at its own first character, so a run of `[` costs one set\n * lookup per character instead of every pattern in the table.\n *\n * @param source - One block's text.\n * @returns Inline nodes.\n */\nexport function parseInline(source: string): MarkdownInline[] {\n const nodes: MarkdownInline[] = [];\n let text = \"\";\n let i = 0;\n\n let bracketAt = -1;\n let deadTailAt = -1;\n let stopAt = -1;\n const stops = /[>\\s]/g;\n\n const flush = (): void => {\n if (text) nodes.push({ type: \"text\", value: text });\n text = \"\";\n };\n\n const closingBracket = (from: number): number => {\n if (bracketAt < from) {\n const found = source.indexOf(\"]\", from);\n bracketAt = found === -1 ? source.length : found;\n }\n return bracketAt;\n };\n\n const autolinkStop = (from: number): number => {\n if (stopAt < from) {\n stops.lastIndex = from;\n stopAt = stops.exec(source)?.index ?? source.length;\n }\n return stopAt;\n };\n\n const allowed = (guard: InlineGuard | undefined): boolean => {\n if (!guard) return true;\n if (!source.startsWith(guard.opener, i)) return false;\n if (guard.kind === \"autolink\") return source[autolinkStop(i + 1)] === \">\";\n const close = closingBracket(i + guard.opener.length);\n return source[close + 1] === \"(\" && close !== deadTailAt;\n };\n\n while (i < source.length) {\n if (source[i] === \"\\\\\" && i + 1 < source.length && source[i + 1] !== \"\\n\") {\n text += source[i + 1];\n i += 2;\n continue;\n }\n\n if (source[i] === \" \") {\n let end = i;\n while (source[end] === \" \") end += 1;\n if (end - i >= 2 && source[end] === \"\\n\") {\n flush();\n nodes.push({ type: \"break\" });\n i = end + 1;\n } else {\n text += source.slice(i, end);\n i = end;\n }\n continue;\n }\n\n if (!INLINE_FIRST.has(source[i])) {\n text += source[i];\n i += 1;\n continue;\n }\n\n const rest = source.slice(i);\n let matched = false;\n for (const rule of INLINE_RULES) {\n if (rule.first !== source[i] || !allowed(rule.guard)) continue;\n const match = rule.pattern.exec(rest);\n if (!match) {\n if (rule.guard?.kind === \"link\") {\n deadTailAt = closingBracket(i + rule.guard.opener.length);\n }\n continue;\n }\n const node = rule.build(match);\n if (!node) continue;\n flush();\n nodes.push(node);\n i += match[0].length;\n matched = true;\n break;\n }\n if (matched) continue;\n\n text += source[i];\n i += 1;\n }\n\n flush();\n return nodes;\n}\n"],"mappings":"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"}
|