@wordpress/block-serialization-default-parser
Advanced tools
+230
| "use strict"; | ||
| var __defProp = Object.defineProperty; | ||
| var __getOwnPropDesc = Object.getOwnPropertyDescriptor; | ||
| var __getOwnPropNames = Object.getOwnPropertyNames; | ||
| var __hasOwnProp = Object.prototype.hasOwnProperty; | ||
| var __export = (target, all) => { | ||
| for (var name in all) | ||
| __defProp(target, name, { get: all[name], enumerable: true }); | ||
| }; | ||
| var __copyProps = (to, from, except, desc) => { | ||
| if (from && typeof from === "object" || typeof from === "function") { | ||
| for (let key of __getOwnPropNames(from)) | ||
| if (!__hasOwnProp.call(to, key) && key !== except) | ||
| __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); | ||
| } | ||
| return to; | ||
| }; | ||
| var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); | ||
| // packages/block-serialization-default-parser/src/index.ts | ||
| var index_exports = {}; | ||
| __export(index_exports, { | ||
| parse: () => parse | ||
| }); | ||
| module.exports = __toCommonJS(index_exports); | ||
| var document; | ||
| var offset; | ||
| var output; | ||
| var stack; | ||
| var tokenizer = /<!--\s+(\/)?wp:([a-z][a-z0-9_-]*\/)?([a-z][a-z0-9_-]*)\s+({(?:(?=([^}]+|}+(?=})|(?!}\s+\/?-->)[^])*)\5|[^]*?)}\s+)?(\/)?-->/g; | ||
| function Block(blockName, attrs, innerBlocks, innerHTML, innerContent) { | ||
| return { | ||
| blockName, | ||
| attrs, | ||
| innerBlocks, | ||
| innerHTML, | ||
| innerContent | ||
| }; | ||
| } | ||
| function Freeform(innerHTML) { | ||
| return Block(null, {}, [], innerHTML, [innerHTML]); | ||
| } | ||
| function Frame(block, tokenStart, tokenLength, prevOffset, leadingHtmlStart) { | ||
| return { | ||
| block, | ||
| tokenStart, | ||
| tokenLength, | ||
| prevOffset: prevOffset || tokenStart + tokenLength, | ||
| leadingHtmlStart | ||
| }; | ||
| } | ||
| var parse = (doc) => { | ||
| document = doc; | ||
| offset = 0; | ||
| output = []; | ||
| stack = []; | ||
| tokenizer.lastIndex = 0; | ||
| do { | ||
| } while (proceed()); | ||
| return output; | ||
| }; | ||
| function proceed() { | ||
| const stackDepth = stack.length; | ||
| const next = nextToken(); | ||
| const [tokenType, blockName, attrs, startOffset, tokenLength] = next; | ||
| const leadingHtmlStart = startOffset > offset ? offset : null; | ||
| switch (tokenType) { | ||
| case "no-more-tokens": | ||
| if (0 === stackDepth) { | ||
| addFreeform(); | ||
| return false; | ||
| } | ||
| if (1 === stackDepth) { | ||
| addBlockFromStack(); | ||
| return false; | ||
| } | ||
| while (0 < stack.length) { | ||
| addBlockFromStack(); | ||
| } | ||
| return false; | ||
| case "void-block": | ||
| if (0 === stackDepth) { | ||
| if (null !== leadingHtmlStart) { | ||
| output.push( | ||
| Freeform( | ||
| document.substr( | ||
| leadingHtmlStart, | ||
| startOffset - leadingHtmlStart | ||
| ) | ||
| ) | ||
| ); | ||
| } | ||
| output.push(Block(blockName, attrs, [], "", [])); | ||
| offset = startOffset + tokenLength; | ||
| return true; | ||
| } | ||
| addInnerBlock( | ||
| Block(blockName, attrs, [], "", []), | ||
| startOffset, | ||
| tokenLength | ||
| ); | ||
| offset = startOffset + tokenLength; | ||
| return true; | ||
| case "block-opener": | ||
| stack.push( | ||
| Frame( | ||
| Block(blockName, attrs, [], "", []), | ||
| startOffset, | ||
| tokenLength, | ||
| startOffset + tokenLength, | ||
| leadingHtmlStart | ||
| ) | ||
| ); | ||
| offset = startOffset + tokenLength; | ||
| return true; | ||
| case "block-closer": | ||
| if (0 === stackDepth) { | ||
| addFreeform(); | ||
| return false; | ||
| } | ||
| if (1 === stackDepth) { | ||
| addBlockFromStack(startOffset); | ||
| offset = startOffset + tokenLength; | ||
| return true; | ||
| } | ||
| const stackTop = stack.pop(); | ||
| const html = document.substr( | ||
| stackTop.prevOffset, | ||
| startOffset - stackTop.prevOffset | ||
| ); | ||
| stackTop.block.innerHTML += html; | ||
| stackTop.block.innerContent.push(html); | ||
| stackTop.prevOffset = startOffset + tokenLength; | ||
| addInnerBlock( | ||
| stackTop.block, | ||
| stackTop.tokenStart, | ||
| stackTop.tokenLength, | ||
| startOffset + tokenLength | ||
| ); | ||
| offset = startOffset + tokenLength; | ||
| return true; | ||
| default: | ||
| addFreeform(); | ||
| return false; | ||
| } | ||
| } | ||
| function parseJSON(input) { | ||
| try { | ||
| return JSON.parse(input); | ||
| } catch (e) { | ||
| return null; | ||
| } | ||
| } | ||
| function nextToken() { | ||
| const matches = tokenizer.exec(document); | ||
| if (null === matches) { | ||
| return ["no-more-tokens", "", null, 0, 0]; | ||
| } | ||
| const startedAt = matches.index; | ||
| const [ | ||
| match, | ||
| closerMatch, | ||
| namespaceMatch, | ||
| nameMatch, | ||
| attrsMatch, | ||
| , | ||
| voidMatch | ||
| ] = matches; | ||
| const length = match.length; | ||
| const isCloser = !!closerMatch; | ||
| const isVoid = !!voidMatch; | ||
| const namespace = namespaceMatch || "core/"; | ||
| const name = namespace + nameMatch; | ||
| const hasAttrs = !!attrsMatch; | ||
| const attrs = hasAttrs ? parseJSON(attrsMatch) : {}; | ||
| if (isCloser && (isVoid || hasAttrs)) { | ||
| } | ||
| if (isVoid) { | ||
| return ["void-block", name, attrs, startedAt, length]; | ||
| } | ||
| if (isCloser) { | ||
| return ["block-closer", name, null, startedAt, length]; | ||
| } | ||
| return ["block-opener", name, attrs, startedAt, length]; | ||
| } | ||
| function addFreeform(rawLength) { | ||
| const length = rawLength ? rawLength : document.length - offset; | ||
| if (0 === length) { | ||
| return; | ||
| } | ||
| output.push(Freeform(document.substr(offset, length))); | ||
| } | ||
| function addInnerBlock(block, tokenStart, tokenLength, lastOffset) { | ||
| const parent = stack[stack.length - 1]; | ||
| parent.block.innerBlocks.push(block); | ||
| const html = document.substr( | ||
| parent.prevOffset, | ||
| tokenStart - parent.prevOffset | ||
| ); | ||
| if (html) { | ||
| parent.block.innerHTML += html; | ||
| parent.block.innerContent.push(html); | ||
| } | ||
| parent.block.innerContent.push(null); | ||
| parent.prevOffset = lastOffset ? lastOffset : tokenStart + tokenLength; | ||
| } | ||
| function addBlockFromStack(endOffset) { | ||
| const { block, leadingHtmlStart, prevOffset, tokenStart } = stack.pop(); | ||
| const html = endOffset ? document.substr(prevOffset, endOffset - prevOffset) : document.substr(prevOffset); | ||
| if (html) { | ||
| block.innerHTML += html; | ||
| block.innerContent.push(html); | ||
| } | ||
| if (null !== leadingHtmlStart) { | ||
| output.push( | ||
| Freeform( | ||
| document.substr( | ||
| leadingHtmlStart, | ||
| tokenStart - leadingHtmlStart | ||
| ) | ||
| ) | ||
| ); | ||
| } | ||
| output.push(block); | ||
| } | ||
| // Annotate the CommonJS export names for ESM import in node: | ||
| 0 && (module.exports = { | ||
| parse | ||
| }); | ||
| //# sourceMappingURL=index.cjs.map |
| { | ||
| "version": 3, | ||
| "sources": ["../src/index.ts"], | ||
| "sourcesContent": ["let document: string;\nlet offset: number;\nlet output: ParsedBlock[];\nlet stack: ParsedFrame[];\n\ntype Attributes = Record< string, any > | null;\n\ntype ParsedBlock = {\n\tblockName: string | null;\n\tattrs: Attributes;\n\tinnerBlocks: ParsedBlock[];\n\tinnerHTML: string;\n\tinnerContent: Array< string | null >;\n};\n\ntype ParsedFrame = {\n\tblock: ParsedBlock;\n\ttokenStart: number;\n\ttokenLength: number;\n\tprevOffset: number;\n\tleadingHtmlStart: number | null;\n};\n\ntype TokenType =\n\t| 'no-more-tokens'\n\t| 'void-block'\n\t| 'block-opener'\n\t| 'block-closer';\n\ntype Token = [ TokenType, string, Attributes, number, number ];\n\n/**\n * Matches block comment delimiters\n *\n * While most of this pattern is straightforward the attribute parsing\n * incorporates a tricks to make sure we don't choke on specific input\n *\n * - since JavaScript has no possessive quantifier or atomic grouping\n * we are emulating it with a trick\n *\n * we want a possessive quantifier or atomic group to prevent backtracking\n * on the `}`s should we fail to match the remainder of the pattern\n *\n * we can emulate this with a positive lookahead and back reference\n * (a++)*c === ((?=(a+))\\1)*c\n *\n * let's examine an example:\n * - /(a+)*c/.test('aaaaaaaaaaaaad') fails after over 49,000 steps\n * - /(a++)*c/.test('aaaaaaaaaaaaad') fails after 85 steps\n * - /(?>a+)*c/.test('aaaaaaaaaaaaad') fails after 126 steps\n *\n * this is because the possessive `++` and the atomic group `(?>)`\n * tell the engine that all those `a`s belong together as a single group\n * and so it won't split it up when stepping backwards to try and match\n *\n * if we use /((?=(a+))\\1)*c/ then we get the same behavior as the atomic group\n * or possessive and prevent the backtracking because the `a+` is matched but\n * not captured. thus, we find the long string of `a`s and remember it, then\n * reference it as a whole unit inside our pattern\n *\n * @see http://instanceof.me/post/52245507631/regex-emulate-atomic-grouping-with-lookahead\n * @see http://blog.stevenlevithan.com/archives/mimic-atomic-groups\n * @see https://javascript.info/regexp-infinite-backtracking-problem\n *\n * once browsers reliably support atomic grouping or possessive\n * quantifiers natively we should remove this trick and simplify\n *\n * @since 3.8.0\n * @since 4.6.1 added optimization to prevent backtracking on attribute parsing\n */\nconst tokenizer =\n\t/<!--\\s+(\\/)?wp:([a-z][a-z0-9_-]*\\/)?([a-z][a-z0-9_-]*)\\s+({(?:(?=([^}]+|}+(?=})|(?!}\\s+\\/?-->)[^])*)\\5|[^]*?)}\\s+)?(\\/)?-->/g;\n\n/**\n * Constructs a block object.\n *\n * @param blockName Either the abbreviated core types, e.g. \"paragraph\", or the fully-qualified\n * block type with namespace and type, e.g. \"core/paragraph\" or \"my-plugin/csv-table\".\n * @param attrs The attributes for the block, or null if there are no attributes.\n * @param innerBlocks An array of inner blocks.\n * @param innerHTML The inner HTML of the block.\n * @param innerContent An array of inner content strings.\n * @return The block object.\n */\nfunction Block(\n\tblockName: string | null,\n\tattrs: Attributes,\n\tinnerBlocks: ParsedBlock[],\n\tinnerHTML: string,\n\tinnerContent: string[]\n): ParsedBlock {\n\treturn {\n\t\tblockName,\n\t\tattrs,\n\t\tinnerBlocks,\n\t\tinnerHTML,\n\t\tinnerContent,\n\t};\n}\n\n/**\n * Constructs a freeform block object.\n *\n * @param innerHTML The inner HTML of the block.\n * @return The freeform block object.\n */\nfunction Freeform( innerHTML: string ): ParsedBlock {\n\treturn Block( null, {}, [], innerHTML, [ innerHTML ] );\n}\n\n/**\n * Constructs a frame object.\n *\n * @param block The block object.\n * @param tokenStart The start offset of the token in the document.\n * @param tokenLength The length of the token in the document.\n * @param prevOffset The offset of the previous token in the document.\n * @param leadingHtmlStart The start offset of leading HTML before the block.\n * @return The frame object.\n */\nfunction Frame(\n\tblock: ParsedBlock,\n\ttokenStart: number,\n\ttokenLength: number,\n\tprevOffset: number | null,\n\tleadingHtmlStart: number | null\n): ParsedFrame {\n\treturn {\n\t\tblock,\n\t\ttokenStart,\n\t\ttokenLength,\n\t\tprevOffset: prevOffset || tokenStart + tokenLength,\n\t\tleadingHtmlStart,\n\t};\n}\n\n/**\n * Parser function, that converts input HTML into a block based structure.\n *\n * @param doc The HTML document to parse.\n *\n * @example\n * Input post:\n * ```html\n * <!-- wp:columns {\"columns\":3} -->\n * <div class=\"wp-block-columns has-3-columns\"><!-- wp:column -->\n * <div class=\"wp-block-column\"><!-- wp:paragraph -->\n * <p>Left</p>\n * <!-- /wp:paragraph --></div>\n * <!-- /wp:column -->\n *\n * <!-- wp:column -->\n * <div class=\"wp-block-column\"><!-- wp:paragraph -->\n * <p><strong>Middle</strong></p>\n * <!-- /wp:paragraph --></div>\n * <!-- /wp:column -->\n *\n * <!-- wp:column -->\n * <div class=\"wp-block-column\"></div>\n * <!-- /wp:column --></div>\n * <!-- /wp:columns -->\n * ```\n *\n * Parsing code:\n * ```js\n * import { parse } from '@wordpress/block-serialization-default-parser';\n *\n * parse( post ) === [\n * {\n * blockName: \"core/columns\",\n * attrs: {\n * columns: 3\n * },\n * innerBlocks: [\n * {\n * blockName: \"core/column\",\n * attrs: null,\n * innerBlocks: [\n * {\n * blockName: \"core/paragraph\",\n * attrs: null,\n * innerBlocks: [],\n * innerHTML: \"\\n<p>Left</p>\\n\"\n * }\n * ],\n * innerHTML: '\\n<div class=\"wp-block-column\"></div>\\n'\n * },\n * {\n * blockName: \"core/column\",\n * attrs: null,\n * innerBlocks: [\n * {\n * blockName: \"core/paragraph\",\n * attrs: null,\n * innerBlocks: [],\n * innerHTML: \"\\n<p><strong>Middle</strong></p>\\n\"\n * }\n * ],\n * innerHTML: '\\n<div class=\"wp-block-column\"></div>\\n'\n * },\n * {\n * blockName: \"core/column\",\n * attrs: null,\n * innerBlocks: [],\n * innerHTML: '\\n<div class=\"wp-block-column\"></div>\\n'\n * }\n * ],\n * innerHTML: '\\n<div class=\"wp-block-columns has-3-columns\">\\n\\n\\n\\n</div>\\n'\n * }\n * ];\n * ```\n * @return A block-based representation of the input HTML.\n */\nexport const parse = ( doc: string ): ParsedBlock[] => {\n\tdocument = doc;\n\toffset = 0;\n\toutput = [];\n\tstack = [];\n\ttokenizer.lastIndex = 0;\n\n\tdo {\n\t\t// twiddle our thumbs\n\t} while ( proceed() );\n\n\treturn output;\n};\n\n/**\n * Parses the next token in the input document.\n *\n * @return Returns true when there is more tokens to parse.\n */\nfunction proceed(): boolean {\n\tconst stackDepth = stack.length;\n\tconst next = nextToken();\n\tconst [ tokenType, blockName, attrs, startOffset, tokenLength ] = next;\n\n\t// We may have some HTML soup before the next block.\n\tconst leadingHtmlStart = startOffset > offset ? offset : null;\n\n\tswitch ( tokenType ) {\n\t\tcase 'no-more-tokens':\n\t\t\t// If not in a block then flush output.\n\t\t\tif ( 0 === stackDepth ) {\n\t\t\t\taddFreeform();\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\t// Otherwise we have a problem\n\t\t\t// This is an error\n\t\t\t// we have options\n\t\t\t// - treat it all as freeform text\n\t\t\t// - assume an implicit closer (easiest when not nesting)\n\n\t\t\t// For the easy case we'll assume an implicit closer.\n\t\t\tif ( 1 === stackDepth ) {\n\t\t\t\taddBlockFromStack();\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\t// For the nested case where it's more difficult we'll\n\t\t\t// have to assume that multiple closers are missing\n\t\t\t// and so we'll collapse the whole stack piecewise.\n\t\t\twhile ( 0 < stack.length ) {\n\t\t\t\taddBlockFromStack();\n\t\t\t}\n\t\t\treturn false;\n\t\tcase 'void-block':\n\t\t\t// easy case is if we stumbled upon a void block\n\t\t\t// in the top-level of the document.\n\t\t\tif ( 0 === stackDepth ) {\n\t\t\t\tif ( null !== leadingHtmlStart ) {\n\t\t\t\t\toutput.push(\n\t\t\t\t\t\tFreeform(\n\t\t\t\t\t\t\tdocument.substr(\n\t\t\t\t\t\t\t\tleadingHtmlStart,\n\t\t\t\t\t\t\t\tstartOffset - leadingHtmlStart\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t)\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\toutput.push( Block( blockName, attrs, [], '', [] ) );\n\t\t\t\toffset = startOffset + tokenLength;\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\t// Otherwise we found an inner block.\n\t\t\taddInnerBlock(\n\t\t\t\tBlock( blockName, attrs, [], '', [] ),\n\t\t\t\tstartOffset,\n\t\t\t\ttokenLength\n\t\t\t);\n\t\t\toffset = startOffset + tokenLength;\n\t\t\treturn true;\n\n\t\tcase 'block-opener':\n\t\t\t// Track all newly-opened blocks on the stack.\n\t\t\tstack.push(\n\t\t\t\tFrame(\n\t\t\t\t\tBlock( blockName, attrs, [], '', [] ),\n\t\t\t\t\tstartOffset,\n\t\t\t\t\ttokenLength,\n\t\t\t\t\tstartOffset + tokenLength,\n\t\t\t\t\tleadingHtmlStart\n\t\t\t\t)\n\t\t\t);\n\t\t\toffset = startOffset + tokenLength;\n\t\t\treturn true;\n\n\t\tcase 'block-closer':\n\t\t\t// If we're missing an opener we're in trouble\n\t\t\t// This is an error.\n\t\t\tif ( 0 === stackDepth ) {\n\t\t\t\t// We have options\n\t\t\t\t// - assume an implicit opener\n\t\t\t\t// - assume _this_ is the opener\n\t\t\t\t// - give up and close out the document.\n\t\t\t\taddFreeform();\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\t// If we're not nesting then this is easy - close the block.\n\t\t\tif ( 1 === stackDepth ) {\n\t\t\t\taddBlockFromStack( startOffset );\n\t\t\t\toffset = startOffset + tokenLength;\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\t// Otherwise we're nested and we have to close out the current\n\t\t\t// block and add it as a innerBlock to the parent.\n\t\t\tconst stackTop = stack.pop() as ParsedFrame;\n\t\t\tconst html = document.substr(\n\t\t\t\tstackTop.prevOffset,\n\t\t\t\tstartOffset - stackTop.prevOffset\n\t\t\t);\n\t\t\tstackTop.block.innerHTML += html;\n\t\t\tstackTop.block.innerContent.push( html );\n\t\t\tstackTop.prevOffset = startOffset + tokenLength;\n\n\t\t\taddInnerBlock(\n\t\t\t\tstackTop.block,\n\t\t\t\tstackTop.tokenStart,\n\t\t\t\tstackTop.tokenLength,\n\t\t\t\tstartOffset + tokenLength\n\t\t\t);\n\t\t\toffset = startOffset + tokenLength;\n\t\t\treturn true;\n\n\t\tdefault:\n\t\t\t// This is an error.\n\t\t\taddFreeform();\n\t\t\treturn false;\n\t}\n}\n\n/**\n * Parse JSON if valid, otherwise return null\n *\n * Note that JSON coming from the block comment\n * delimiters is constrained to be an object\n * and cannot be things like `true` or `null`\n *\n * @param input JSON input string to parse\n * @return parsed JSON if valid or null if invalid\n */\nfunction parseJSON( input: string ): Object | null {\n\ttry {\n\t\treturn JSON.parse( input );\n\t} catch ( e ) {\n\t\treturn null;\n\t}\n}\n\n/**\n * Finds the next token in the document.\n *\n * @return The next matched token.\n */\nfunction nextToken(): Token {\n\t// Aye the magic\n\t// we're using a single RegExp to tokenize the block comment delimiters\n\t// we're also using a trick here because the only difference between a\n\t// block opener and a block closer is the leading `/` before `wp:` (and\n\t// a closer has no attributes). we can trap them both and process the\n\t// match back in JavaScript to see which one it was.\n\tconst matches = tokenizer.exec( document );\n\n\t// We have no more tokens.\n\tif ( null === matches ) {\n\t\treturn [ 'no-more-tokens', '', null, 0, 0 ];\n\t}\n\n\tconst startedAt = matches.index;\n\tconst [\n\t\tmatch,\n\t\tcloserMatch,\n\t\tnamespaceMatch,\n\t\tnameMatch,\n\t\tattrsMatch /* Internal/unused. */,\n\t\t,\n\t\tvoidMatch,\n\t] = matches;\n\n\tconst length = match.length;\n\tconst isCloser = !! closerMatch;\n\tconst isVoid = !! voidMatch;\n\tconst namespace = namespaceMatch || 'core/';\n\tconst name = namespace + nameMatch;\n\tconst hasAttrs = !! attrsMatch;\n\tconst attrs = hasAttrs ? parseJSON( attrsMatch ) : {};\n\n\t// This state isn't allowed\n\t// This is an error.\n\tif ( isCloser && ( isVoid || hasAttrs ) ) {\n\t\t// We can ignore them since they don't hurt anything\n\t\t// we may warn against this at some point or reject it.\n\t}\n\n\tif ( isVoid ) {\n\t\treturn [ 'void-block', name, attrs, startedAt, length ];\n\t}\n\n\tif ( isCloser ) {\n\t\treturn [ 'block-closer', name, null, startedAt, length ];\n\t}\n\n\treturn [ 'block-opener', name, attrs, startedAt, length ];\n}\n\n/**\n * Adds a freeform block to the output.\n *\n * @param rawLength Optional length of the raw HTML to include as freeform content.\n */\nfunction addFreeform( rawLength?: number ) {\n\tconst length = rawLength ? rawLength : document.length - offset;\n\n\tif ( 0 === length ) {\n\t\treturn;\n\t}\n\n\toutput.push( Freeform( document.substr( offset, length ) ) );\n}\n\n/**\n * Adds inner block to the parent block.\n *\n * @param block The inner block to be added to the parent.\n * @param tokenStart The start offset of the block token in the document.\n * @param tokenLength The total length of the block token.\n * @param lastOffset Optional offset marking the end of the current block,\n * used to update the parent's HTML content boundaries.\n */\nfunction addInnerBlock(\n\tblock: ParsedBlock,\n\ttokenStart: number,\n\ttokenLength: number,\n\tlastOffset?: number\n) {\n\tconst parent = stack[ stack.length - 1 ];\n\tparent.block.innerBlocks.push( block );\n\tconst html = document.substr(\n\t\tparent.prevOffset,\n\t\ttokenStart - parent.prevOffset\n\t);\n\n\tif ( html ) {\n\t\tparent.block.innerHTML += html;\n\t\tparent.block.innerContent.push( html );\n\t}\n\n\tparent.block.innerContent.push( null );\n\tparent.prevOffset = lastOffset ? lastOffset : tokenStart + tokenLength;\n}\n\n/**\n * Adds block from the stack to the output.\n *\n * @param endOffset Optional offset marking the end of the block's HTML content.\n */\nfunction addBlockFromStack( endOffset?: number ) {\n\tconst { block, leadingHtmlStart, prevOffset, tokenStart } =\n\t\tstack.pop() as ParsedFrame;\n\n\tconst html = endOffset\n\t\t? document.substr( prevOffset, endOffset - prevOffset )\n\t\t: document.substr( prevOffset );\n\n\tif ( html ) {\n\t\tblock.innerHTML += html;\n\t\tblock.innerContent.push( html );\n\t}\n\n\tif ( null !== leadingHtmlStart ) {\n\t\toutput.push(\n\t\t\tFreeform(\n\t\t\t\tdocument.substr(\n\t\t\t\t\tleadingHtmlStart,\n\t\t\t\t\ttokenStart - leadingHtmlStart\n\t\t\t\t)\n\t\t\t)\n\t\t);\n\t}\n\n\toutput.push( block );\n}\n"], | ||
| "mappings": "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", | ||
| "names": [] | ||
| } |
+12
-4
| { | ||
| "name": "@wordpress/block-serialization-default-parser", | ||
| "version": "5.36.1-next.6deb34194.0", | ||
| "version": "5.36.1-next.738bb1424.0", | ||
| "description": "Block serialization specification parser for WordPress posts.", | ||
@@ -26,3 +26,11 @@ "author": "The WordPress Contributors", | ||
| }, | ||
| "main": "build/index.js", | ||
| "files": [ | ||
| "src", | ||
| "build", | ||
| "build-module", | ||
| "build-types", | ||
| "*.md" | ||
| ], | ||
| "type": "module", | ||
| "main": "build/index.cjs", | ||
| "module": "build-module/index.js", | ||
@@ -33,3 +41,3 @@ "exports": { | ||
| "import": "./build-module/index.js", | ||
| "require": "./build/index.js" | ||
| "require": "./build/index.cjs" | ||
| }, | ||
@@ -45,3 +53,3 @@ "./package.json": "./package.json" | ||
| }, | ||
| "gitHead": "457096e9e9b3896d2d4fe54fc19d7fb571ee9a44" | ||
| "gitHead": "ab1b004c0d61c295aa34bc86ea07f979343983ce" | ||
| } |
-230
| "use strict"; | ||
| var __defProp = Object.defineProperty; | ||
| var __getOwnPropDesc = Object.getOwnPropertyDescriptor; | ||
| var __getOwnPropNames = Object.getOwnPropertyNames; | ||
| var __hasOwnProp = Object.prototype.hasOwnProperty; | ||
| var __export = (target, all) => { | ||
| for (var name in all) | ||
| __defProp(target, name, { get: all[name], enumerable: true }); | ||
| }; | ||
| var __copyProps = (to, from, except, desc) => { | ||
| if (from && typeof from === "object" || typeof from === "function") { | ||
| for (let key of __getOwnPropNames(from)) | ||
| if (!__hasOwnProp.call(to, key) && key !== except) | ||
| __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); | ||
| } | ||
| return to; | ||
| }; | ||
| var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); | ||
| // packages/block-serialization-default-parser/src/index.ts | ||
| var index_exports = {}; | ||
| __export(index_exports, { | ||
| parse: () => parse | ||
| }); | ||
| module.exports = __toCommonJS(index_exports); | ||
| var document; | ||
| var offset; | ||
| var output; | ||
| var stack; | ||
| var tokenizer = /<!--\s+(\/)?wp:([a-z][a-z0-9_-]*\/)?([a-z][a-z0-9_-]*)\s+({(?:(?=([^}]+|}+(?=})|(?!}\s+\/?-->)[^])*)\5|[^]*?)}\s+)?(\/)?-->/g; | ||
| function Block(blockName, attrs, innerBlocks, innerHTML, innerContent) { | ||
| return { | ||
| blockName, | ||
| attrs, | ||
| innerBlocks, | ||
| innerHTML, | ||
| innerContent | ||
| }; | ||
| } | ||
| function Freeform(innerHTML) { | ||
| return Block(null, {}, [], innerHTML, [innerHTML]); | ||
| } | ||
| function Frame(block, tokenStart, tokenLength, prevOffset, leadingHtmlStart) { | ||
| return { | ||
| block, | ||
| tokenStart, | ||
| tokenLength, | ||
| prevOffset: prevOffset || tokenStart + tokenLength, | ||
| leadingHtmlStart | ||
| }; | ||
| } | ||
| var parse = (doc) => { | ||
| document = doc; | ||
| offset = 0; | ||
| output = []; | ||
| stack = []; | ||
| tokenizer.lastIndex = 0; | ||
| do { | ||
| } while (proceed()); | ||
| return output; | ||
| }; | ||
| function proceed() { | ||
| const stackDepth = stack.length; | ||
| const next = nextToken(); | ||
| const [tokenType, blockName, attrs, startOffset, tokenLength] = next; | ||
| const leadingHtmlStart = startOffset > offset ? offset : null; | ||
| switch (tokenType) { | ||
| case "no-more-tokens": | ||
| if (0 === stackDepth) { | ||
| addFreeform(); | ||
| return false; | ||
| } | ||
| if (1 === stackDepth) { | ||
| addBlockFromStack(); | ||
| return false; | ||
| } | ||
| while (0 < stack.length) { | ||
| addBlockFromStack(); | ||
| } | ||
| return false; | ||
| case "void-block": | ||
| if (0 === stackDepth) { | ||
| if (null !== leadingHtmlStart) { | ||
| output.push( | ||
| Freeform( | ||
| document.substr( | ||
| leadingHtmlStart, | ||
| startOffset - leadingHtmlStart | ||
| ) | ||
| ) | ||
| ); | ||
| } | ||
| output.push(Block(blockName, attrs, [], "", [])); | ||
| offset = startOffset + tokenLength; | ||
| return true; | ||
| } | ||
| addInnerBlock( | ||
| Block(blockName, attrs, [], "", []), | ||
| startOffset, | ||
| tokenLength | ||
| ); | ||
| offset = startOffset + tokenLength; | ||
| return true; | ||
| case "block-opener": | ||
| stack.push( | ||
| Frame( | ||
| Block(blockName, attrs, [], "", []), | ||
| startOffset, | ||
| tokenLength, | ||
| startOffset + tokenLength, | ||
| leadingHtmlStart | ||
| ) | ||
| ); | ||
| offset = startOffset + tokenLength; | ||
| return true; | ||
| case "block-closer": | ||
| if (0 === stackDepth) { | ||
| addFreeform(); | ||
| return false; | ||
| } | ||
| if (1 === stackDepth) { | ||
| addBlockFromStack(startOffset); | ||
| offset = startOffset + tokenLength; | ||
| return true; | ||
| } | ||
| const stackTop = stack.pop(); | ||
| const html = document.substr( | ||
| stackTop.prevOffset, | ||
| startOffset - stackTop.prevOffset | ||
| ); | ||
| stackTop.block.innerHTML += html; | ||
| stackTop.block.innerContent.push(html); | ||
| stackTop.prevOffset = startOffset + tokenLength; | ||
| addInnerBlock( | ||
| stackTop.block, | ||
| stackTop.tokenStart, | ||
| stackTop.tokenLength, | ||
| startOffset + tokenLength | ||
| ); | ||
| offset = startOffset + tokenLength; | ||
| return true; | ||
| default: | ||
| addFreeform(); | ||
| return false; | ||
| } | ||
| } | ||
| function parseJSON(input) { | ||
| try { | ||
| return JSON.parse(input); | ||
| } catch (e) { | ||
| return null; | ||
| } | ||
| } | ||
| function nextToken() { | ||
| const matches = tokenizer.exec(document); | ||
| if (null === matches) { | ||
| return ["no-more-tokens", "", null, 0, 0]; | ||
| } | ||
| const startedAt = matches.index; | ||
| const [ | ||
| match, | ||
| closerMatch, | ||
| namespaceMatch, | ||
| nameMatch, | ||
| attrsMatch, | ||
| , | ||
| voidMatch | ||
| ] = matches; | ||
| const length = match.length; | ||
| const isCloser = !!closerMatch; | ||
| const isVoid = !!voidMatch; | ||
| const namespace = namespaceMatch || "core/"; | ||
| const name = namespace + nameMatch; | ||
| const hasAttrs = !!attrsMatch; | ||
| const attrs = hasAttrs ? parseJSON(attrsMatch) : {}; | ||
| if (isCloser && (isVoid || hasAttrs)) { | ||
| } | ||
| if (isVoid) { | ||
| return ["void-block", name, attrs, startedAt, length]; | ||
| } | ||
| if (isCloser) { | ||
| return ["block-closer", name, null, startedAt, length]; | ||
| } | ||
| return ["block-opener", name, attrs, startedAt, length]; | ||
| } | ||
| function addFreeform(rawLength) { | ||
| const length = rawLength ? rawLength : document.length - offset; | ||
| if (0 === length) { | ||
| return; | ||
| } | ||
| output.push(Freeform(document.substr(offset, length))); | ||
| } | ||
| function addInnerBlock(block, tokenStart, tokenLength, lastOffset) { | ||
| const parent = stack[stack.length - 1]; | ||
| parent.block.innerBlocks.push(block); | ||
| const html = document.substr( | ||
| parent.prevOffset, | ||
| tokenStart - parent.prevOffset | ||
| ); | ||
| if (html) { | ||
| parent.block.innerHTML += html; | ||
| parent.block.innerContent.push(html); | ||
| } | ||
| parent.block.innerContent.push(null); | ||
| parent.prevOffset = lastOffset ? lastOffset : tokenStart + tokenLength; | ||
| } | ||
| function addBlockFromStack(endOffset) { | ||
| const { block, leadingHtmlStart, prevOffset, tokenStart } = stack.pop(); | ||
| const html = endOffset ? document.substr(prevOffset, endOffset - prevOffset) : document.substr(prevOffset); | ||
| if (html) { | ||
| block.innerHTML += html; | ||
| block.innerContent.push(html); | ||
| } | ||
| if (null !== leadingHtmlStart) { | ||
| output.push( | ||
| Freeform( | ||
| document.substr( | ||
| leadingHtmlStart, | ||
| tokenStart - leadingHtmlStart | ||
| ) | ||
| ) | ||
| ); | ||
| } | ||
| output.push(block); | ||
| } | ||
| // Annotate the CommonJS export names for ESM import in node: | ||
| 0 && (module.exports = { | ||
| parse | ||
| }); | ||
| //# sourceMappingURL=index.js.map |
| { | ||
| "version": 3, | ||
| "sources": ["../src/index.ts"], | ||
| "sourcesContent": ["let document: string;\nlet offset: number;\nlet output: ParsedBlock[];\nlet stack: ParsedFrame[];\n\ntype Attributes = Record< string, any > | null;\n\ntype ParsedBlock = {\n\tblockName: string | null;\n\tattrs: Attributes;\n\tinnerBlocks: ParsedBlock[];\n\tinnerHTML: string;\n\tinnerContent: Array< string | null >;\n};\n\ntype ParsedFrame = {\n\tblock: ParsedBlock;\n\ttokenStart: number;\n\ttokenLength: number;\n\tprevOffset: number;\n\tleadingHtmlStart: number | null;\n};\n\ntype TokenType =\n\t| 'no-more-tokens'\n\t| 'void-block'\n\t| 'block-opener'\n\t| 'block-closer';\n\ntype Token = [ TokenType, string, Attributes, number, number ];\n\n/**\n * Matches block comment delimiters\n *\n * While most of this pattern is straightforward the attribute parsing\n * incorporates a tricks to make sure we don't choke on specific input\n *\n * - since JavaScript has no possessive quantifier or atomic grouping\n * we are emulating it with a trick\n *\n * we want a possessive quantifier or atomic group to prevent backtracking\n * on the `}`s should we fail to match the remainder of the pattern\n *\n * we can emulate this with a positive lookahead and back reference\n * (a++)*c === ((?=(a+))\\1)*c\n *\n * let's examine an example:\n * - /(a+)*c/.test('aaaaaaaaaaaaad') fails after over 49,000 steps\n * - /(a++)*c/.test('aaaaaaaaaaaaad') fails after 85 steps\n * - /(?>a+)*c/.test('aaaaaaaaaaaaad') fails after 126 steps\n *\n * this is because the possessive `++` and the atomic group `(?>)`\n * tell the engine that all those `a`s belong together as a single group\n * and so it won't split it up when stepping backwards to try and match\n *\n * if we use /((?=(a+))\\1)*c/ then we get the same behavior as the atomic group\n * or possessive and prevent the backtracking because the `a+` is matched but\n * not captured. thus, we find the long string of `a`s and remember it, then\n * reference it as a whole unit inside our pattern\n *\n * @see http://instanceof.me/post/52245507631/regex-emulate-atomic-grouping-with-lookahead\n * @see http://blog.stevenlevithan.com/archives/mimic-atomic-groups\n * @see https://javascript.info/regexp-infinite-backtracking-problem\n *\n * once browsers reliably support atomic grouping or possessive\n * quantifiers natively we should remove this trick and simplify\n *\n * @since 3.8.0\n * @since 4.6.1 added optimization to prevent backtracking on attribute parsing\n */\nconst tokenizer =\n\t/<!--\\s+(\\/)?wp:([a-z][a-z0-9_-]*\\/)?([a-z][a-z0-9_-]*)\\s+({(?:(?=([^}]+|}+(?=})|(?!}\\s+\\/?-->)[^])*)\\5|[^]*?)}\\s+)?(\\/)?-->/g;\n\n/**\n * Constructs a block object.\n *\n * @param blockName Either the abbreviated core types, e.g. \"paragraph\", or the fully-qualified\n * block type with namespace and type, e.g. \"core/paragraph\" or \"my-plugin/csv-table\".\n * @param attrs The attributes for the block, or null if there are no attributes.\n * @param innerBlocks An array of inner blocks.\n * @param innerHTML The inner HTML of the block.\n * @param innerContent An array of inner content strings.\n * @return The block object.\n */\nfunction Block(\n\tblockName: string | null,\n\tattrs: Attributes,\n\tinnerBlocks: ParsedBlock[],\n\tinnerHTML: string,\n\tinnerContent: string[]\n): ParsedBlock {\n\treturn {\n\t\tblockName,\n\t\tattrs,\n\t\tinnerBlocks,\n\t\tinnerHTML,\n\t\tinnerContent,\n\t};\n}\n\n/**\n * Constructs a freeform block object.\n *\n * @param innerHTML The inner HTML of the block.\n * @return The freeform block object.\n */\nfunction Freeform( innerHTML: string ): ParsedBlock {\n\treturn Block( null, {}, [], innerHTML, [ innerHTML ] );\n}\n\n/**\n * Constructs a frame object.\n *\n * @param block The block object.\n * @param tokenStart The start offset of the token in the document.\n * @param tokenLength The length of the token in the document.\n * @param prevOffset The offset of the previous token in the document.\n * @param leadingHtmlStart The start offset of leading HTML before the block.\n * @return The frame object.\n */\nfunction Frame(\n\tblock: ParsedBlock,\n\ttokenStart: number,\n\ttokenLength: number,\n\tprevOffset: number | null,\n\tleadingHtmlStart: number | null\n): ParsedFrame {\n\treturn {\n\t\tblock,\n\t\ttokenStart,\n\t\ttokenLength,\n\t\tprevOffset: prevOffset || tokenStart + tokenLength,\n\t\tleadingHtmlStart,\n\t};\n}\n\n/**\n * Parser function, that converts input HTML into a block based structure.\n *\n * @param doc The HTML document to parse.\n *\n * @example\n * Input post:\n * ```html\n * <!-- wp:columns {\"columns\":3} -->\n * <div class=\"wp-block-columns has-3-columns\"><!-- wp:column -->\n * <div class=\"wp-block-column\"><!-- wp:paragraph -->\n * <p>Left</p>\n * <!-- /wp:paragraph --></div>\n * <!-- /wp:column -->\n *\n * <!-- wp:column -->\n * <div class=\"wp-block-column\"><!-- wp:paragraph -->\n * <p><strong>Middle</strong></p>\n * <!-- /wp:paragraph --></div>\n * <!-- /wp:column -->\n *\n * <!-- wp:column -->\n * <div class=\"wp-block-column\"></div>\n * <!-- /wp:column --></div>\n * <!-- /wp:columns -->\n * ```\n *\n * Parsing code:\n * ```js\n * import { parse } from '@wordpress/block-serialization-default-parser';\n *\n * parse( post ) === [\n * {\n * blockName: \"core/columns\",\n * attrs: {\n * columns: 3\n * },\n * innerBlocks: [\n * {\n * blockName: \"core/column\",\n * attrs: null,\n * innerBlocks: [\n * {\n * blockName: \"core/paragraph\",\n * attrs: null,\n * innerBlocks: [],\n * innerHTML: \"\\n<p>Left</p>\\n\"\n * }\n * ],\n * innerHTML: '\\n<div class=\"wp-block-column\"></div>\\n'\n * },\n * {\n * blockName: \"core/column\",\n * attrs: null,\n * innerBlocks: [\n * {\n * blockName: \"core/paragraph\",\n * attrs: null,\n * innerBlocks: [],\n * innerHTML: \"\\n<p><strong>Middle</strong></p>\\n\"\n * }\n * ],\n * innerHTML: '\\n<div class=\"wp-block-column\"></div>\\n'\n * },\n * {\n * blockName: \"core/column\",\n * attrs: null,\n * innerBlocks: [],\n * innerHTML: '\\n<div class=\"wp-block-column\"></div>\\n'\n * }\n * ],\n * innerHTML: '\\n<div class=\"wp-block-columns has-3-columns\">\\n\\n\\n\\n</div>\\n'\n * }\n * ];\n * ```\n * @return A block-based representation of the input HTML.\n */\nexport const parse = ( doc: string ): ParsedBlock[] => {\n\tdocument = doc;\n\toffset = 0;\n\toutput = [];\n\tstack = [];\n\ttokenizer.lastIndex = 0;\n\n\tdo {\n\t\t// twiddle our thumbs\n\t} while ( proceed() );\n\n\treturn output;\n};\n\n/**\n * Parses the next token in the input document.\n *\n * @return Returns true when there is more tokens to parse.\n */\nfunction proceed(): boolean {\n\tconst stackDepth = stack.length;\n\tconst next = nextToken();\n\tconst [ tokenType, blockName, attrs, startOffset, tokenLength ] = next;\n\n\t// We may have some HTML soup before the next block.\n\tconst leadingHtmlStart = startOffset > offset ? offset : null;\n\n\tswitch ( tokenType ) {\n\t\tcase 'no-more-tokens':\n\t\t\t// If not in a block then flush output.\n\t\t\tif ( 0 === stackDepth ) {\n\t\t\t\taddFreeform();\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\t// Otherwise we have a problem\n\t\t\t// This is an error\n\t\t\t// we have options\n\t\t\t// - treat it all as freeform text\n\t\t\t// - assume an implicit closer (easiest when not nesting)\n\n\t\t\t// For the easy case we'll assume an implicit closer.\n\t\t\tif ( 1 === stackDepth ) {\n\t\t\t\taddBlockFromStack();\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\t// For the nested case where it's more difficult we'll\n\t\t\t// have to assume that multiple closers are missing\n\t\t\t// and so we'll collapse the whole stack piecewise.\n\t\t\twhile ( 0 < stack.length ) {\n\t\t\t\taddBlockFromStack();\n\t\t\t}\n\t\t\treturn false;\n\t\tcase 'void-block':\n\t\t\t// easy case is if we stumbled upon a void block\n\t\t\t// in the top-level of the document.\n\t\t\tif ( 0 === stackDepth ) {\n\t\t\t\tif ( null !== leadingHtmlStart ) {\n\t\t\t\t\toutput.push(\n\t\t\t\t\t\tFreeform(\n\t\t\t\t\t\t\tdocument.substr(\n\t\t\t\t\t\t\t\tleadingHtmlStart,\n\t\t\t\t\t\t\t\tstartOffset - leadingHtmlStart\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t)\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\toutput.push( Block( blockName, attrs, [], '', [] ) );\n\t\t\t\toffset = startOffset + tokenLength;\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\t// Otherwise we found an inner block.\n\t\t\taddInnerBlock(\n\t\t\t\tBlock( blockName, attrs, [], '', [] ),\n\t\t\t\tstartOffset,\n\t\t\t\ttokenLength\n\t\t\t);\n\t\t\toffset = startOffset + tokenLength;\n\t\t\treturn true;\n\n\t\tcase 'block-opener':\n\t\t\t// Track all newly-opened blocks on the stack.\n\t\t\tstack.push(\n\t\t\t\tFrame(\n\t\t\t\t\tBlock( blockName, attrs, [], '', [] ),\n\t\t\t\t\tstartOffset,\n\t\t\t\t\ttokenLength,\n\t\t\t\t\tstartOffset + tokenLength,\n\t\t\t\t\tleadingHtmlStart\n\t\t\t\t)\n\t\t\t);\n\t\t\toffset = startOffset + tokenLength;\n\t\t\treturn true;\n\n\t\tcase 'block-closer':\n\t\t\t// If we're missing an opener we're in trouble\n\t\t\t// This is an error.\n\t\t\tif ( 0 === stackDepth ) {\n\t\t\t\t// We have options\n\t\t\t\t// - assume an implicit opener\n\t\t\t\t// - assume _this_ is the opener\n\t\t\t\t// - give up and close out the document.\n\t\t\t\taddFreeform();\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\t// If we're not nesting then this is easy - close the block.\n\t\t\tif ( 1 === stackDepth ) {\n\t\t\t\taddBlockFromStack( startOffset );\n\t\t\t\toffset = startOffset + tokenLength;\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\t// Otherwise we're nested and we have to close out the current\n\t\t\t// block and add it as a innerBlock to the parent.\n\t\t\tconst stackTop = stack.pop() as ParsedFrame;\n\t\t\tconst html = document.substr(\n\t\t\t\tstackTop.prevOffset,\n\t\t\t\tstartOffset - stackTop.prevOffset\n\t\t\t);\n\t\t\tstackTop.block.innerHTML += html;\n\t\t\tstackTop.block.innerContent.push( html );\n\t\t\tstackTop.prevOffset = startOffset + tokenLength;\n\n\t\t\taddInnerBlock(\n\t\t\t\tstackTop.block,\n\t\t\t\tstackTop.tokenStart,\n\t\t\t\tstackTop.tokenLength,\n\t\t\t\tstartOffset + tokenLength\n\t\t\t);\n\t\t\toffset = startOffset + tokenLength;\n\t\t\treturn true;\n\n\t\tdefault:\n\t\t\t// This is an error.\n\t\t\taddFreeform();\n\t\t\treturn false;\n\t}\n}\n\n/**\n * Parse JSON if valid, otherwise return null\n *\n * Note that JSON coming from the block comment\n * delimiters is constrained to be an object\n * and cannot be things like `true` or `null`\n *\n * @param input JSON input string to parse\n * @return parsed JSON if valid or null if invalid\n */\nfunction parseJSON( input: string ): Object | null {\n\ttry {\n\t\treturn JSON.parse( input );\n\t} catch ( e ) {\n\t\treturn null;\n\t}\n}\n\n/**\n * Finds the next token in the document.\n *\n * @return The next matched token.\n */\nfunction nextToken(): Token {\n\t// Aye the magic\n\t// we're using a single RegExp to tokenize the block comment delimiters\n\t// we're also using a trick here because the only difference between a\n\t// block opener and a block closer is the leading `/` before `wp:` (and\n\t// a closer has no attributes). we can trap them both and process the\n\t// match back in JavaScript to see which one it was.\n\tconst matches = tokenizer.exec( document );\n\n\t// We have no more tokens.\n\tif ( null === matches ) {\n\t\treturn [ 'no-more-tokens', '', null, 0, 0 ];\n\t}\n\n\tconst startedAt = matches.index;\n\tconst [\n\t\tmatch,\n\t\tcloserMatch,\n\t\tnamespaceMatch,\n\t\tnameMatch,\n\t\tattrsMatch /* Internal/unused. */,\n\t\t,\n\t\tvoidMatch,\n\t] = matches;\n\n\tconst length = match.length;\n\tconst isCloser = !! closerMatch;\n\tconst isVoid = !! voidMatch;\n\tconst namespace = namespaceMatch || 'core/';\n\tconst name = namespace + nameMatch;\n\tconst hasAttrs = !! attrsMatch;\n\tconst attrs = hasAttrs ? parseJSON( attrsMatch ) : {};\n\n\t// This state isn't allowed\n\t// This is an error.\n\tif ( isCloser && ( isVoid || hasAttrs ) ) {\n\t\t// We can ignore them since they don't hurt anything\n\t\t// we may warn against this at some point or reject it.\n\t}\n\n\tif ( isVoid ) {\n\t\treturn [ 'void-block', name, attrs, startedAt, length ];\n\t}\n\n\tif ( isCloser ) {\n\t\treturn [ 'block-closer', name, null, startedAt, length ];\n\t}\n\n\treturn [ 'block-opener', name, attrs, startedAt, length ];\n}\n\n/**\n * Adds a freeform block to the output.\n *\n * @param rawLength Optional length of the raw HTML to include as freeform content.\n */\nfunction addFreeform( rawLength?: number ) {\n\tconst length = rawLength ? rawLength : document.length - offset;\n\n\tif ( 0 === length ) {\n\t\treturn;\n\t}\n\n\toutput.push( Freeform( document.substr( offset, length ) ) );\n}\n\n/**\n * Adds inner block to the parent block.\n *\n * @param block The inner block to be added to the parent.\n * @param tokenStart The start offset of the block token in the document.\n * @param tokenLength The total length of the block token.\n * @param lastOffset Optional offset marking the end of the current block,\n * used to update the parent's HTML content boundaries.\n */\nfunction addInnerBlock(\n\tblock: ParsedBlock,\n\ttokenStart: number,\n\ttokenLength: number,\n\tlastOffset?: number\n) {\n\tconst parent = stack[ stack.length - 1 ];\n\tparent.block.innerBlocks.push( block );\n\tconst html = document.substr(\n\t\tparent.prevOffset,\n\t\ttokenStart - parent.prevOffset\n\t);\n\n\tif ( html ) {\n\t\tparent.block.innerHTML += html;\n\t\tparent.block.innerContent.push( html );\n\t}\n\n\tparent.block.innerContent.push( null );\n\tparent.prevOffset = lastOffset ? lastOffset : tokenStart + tokenLength;\n}\n\n/**\n * Adds block from the stack to the output.\n *\n * @param endOffset Optional offset marking the end of the block's HTML content.\n */\nfunction addBlockFromStack( endOffset?: number ) {\n\tconst { block, leadingHtmlStart, prevOffset, tokenStart } =\n\t\tstack.pop() as ParsedFrame;\n\n\tconst html = endOffset\n\t\t? document.substr( prevOffset, endOffset - prevOffset )\n\t\t: document.substr( prevOffset );\n\n\tif ( html ) {\n\t\tblock.innerHTML += html;\n\t\tblock.innerContent.push( html );\n\t}\n\n\tif ( null !== leadingHtmlStart ) {\n\t\toutput.push(\n\t\t\tFreeform(\n\t\t\t\tdocument.substr(\n\t\t\t\t\tleadingHtmlStart,\n\t\t\t\t\ttokenStart - leadingHtmlStart\n\t\t\t\t)\n\t\t\t)\n\t\t);\n\t}\n\n\toutput.push( block );\n}\n"], | ||
| "mappings": "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", | ||
| "names": [] | ||
| } |
| <?php | ||
| /** | ||
| * Block Serialization Parser | ||
| * | ||
| * @package WordPress | ||
| */ | ||
| /** | ||
| * Class WP_Block_Parser_Block | ||
| * | ||
| * Holds the block structure in memory | ||
| * | ||
| * @since 5.0.0 | ||
| */ | ||
| class WP_Block_Parser_Block { | ||
| /** | ||
| * Name of block | ||
| * | ||
| * @example "core/paragraph" | ||
| * | ||
| * @since 5.0.0 | ||
| * @var string | ||
| */ | ||
| public $blockName; // phpcs:ignore WordPress.NamingConventions.ValidVariableName | ||
| /** | ||
| * Optional set of attributes from block comment delimiters | ||
| * | ||
| * @example null | ||
| * @example array( 'columns' => 3 ) | ||
| * | ||
| * @since 5.0.0 | ||
| * @var array|null | ||
| */ | ||
| public $attrs; | ||
| /** | ||
| * List of inner blocks (of this same class) | ||
| * | ||
| * @since 5.0.0 | ||
| * @var WP_Block_Parser_Block[] | ||
| */ | ||
| public $innerBlocks; // phpcs:ignore WordPress.NamingConventions.ValidVariableName | ||
| /** | ||
| * Resultant HTML from inside block comment delimiters | ||
| * after removing inner blocks | ||
| * | ||
| * @example "...Just <!-- wp:test /--> testing..." -> "Just testing..." | ||
| * | ||
| * @since 5.0.0 | ||
| * @var string | ||
| */ | ||
| public $innerHTML; // phpcs:ignore WordPress.NamingConventions.ValidVariableName | ||
| /** | ||
| * List of string fragments and null markers where inner blocks were found | ||
| * | ||
| * @example array( | ||
| * 'innerHTML' => 'BeforeInnerAfter', | ||
| * 'innerBlocks' => array( block, block ), | ||
| * 'innerContent' => array( 'Before', null, 'Inner', null, 'After' ), | ||
| * ) | ||
| * | ||
| * @since 5.0.0 | ||
| * @var array | ||
| */ | ||
| public $innerContent; // phpcs:ignore WordPress.NamingConventions.ValidVariableName | ||
| /** | ||
| * Constructor. | ||
| * | ||
| * Will populate object properties from the provided arguments. | ||
| * | ||
| * @since 5.0.0 | ||
| * | ||
| * @param string $name Name of block. | ||
| * @param array $attrs Optional set of attributes from block comment delimiters. | ||
| * @param array $inner_blocks List of inner blocks (of this same class). | ||
| * @param string $inner_html Resultant HTML from inside block comment delimiters after removing inner blocks. | ||
| * @param array $inner_content List of string fragments and null markers where inner blocks were found. | ||
| */ | ||
| public function __construct( $name, $attrs, $inner_blocks, $inner_html, $inner_content ) { | ||
| $this->blockName = $name; // phpcs:ignore WordPress.NamingConventions.ValidVariableName | ||
| $this->attrs = $attrs; | ||
| $this->innerBlocks = $inner_blocks; // phpcs:ignore WordPress.NamingConventions.ValidVariableName | ||
| $this->innerHTML = $inner_html; // phpcs:ignore WordPress.NamingConventions.ValidVariableName | ||
| $this->innerContent = $inner_content; // phpcs:ignore WordPress.NamingConventions.ValidVariableName | ||
| } | ||
| } |
| <?php | ||
| /** | ||
| * Block Serialization Parser | ||
| * | ||
| * @package WordPress | ||
| */ | ||
| /** | ||
| * Class WP_Block_Parser_Frame | ||
| * | ||
| * Holds partial blocks in memory while parsing | ||
| * | ||
| * @internal | ||
| * @since 5.0.0 | ||
| */ | ||
| class WP_Block_Parser_Frame { | ||
| /** | ||
| * Full or partial block | ||
| * | ||
| * @since 5.0.0 | ||
| * @var WP_Block_Parser_Block | ||
| */ | ||
| public $block; | ||
| /** | ||
| * Byte offset into document for start of parse token | ||
| * | ||
| * @since 5.0.0 | ||
| * @var int | ||
| */ | ||
| public $token_start; | ||
| /** | ||
| * Byte length of entire parse token string | ||
| * | ||
| * @since 5.0.0 | ||
| * @var int | ||
| */ | ||
| public $token_length; | ||
| /** | ||
| * Byte offset into document for after parse token ends | ||
| * (used during reconstruction of stack into parse production) | ||
| * | ||
| * @since 5.0.0 | ||
| * @var int | ||
| */ | ||
| public $prev_offset; | ||
| /** | ||
| * Byte offset into document where leading HTML before token starts | ||
| * | ||
| * @since 5.0.0 | ||
| * @var int | ||
| */ | ||
| public $leading_html_start; | ||
| /** | ||
| * Constructor | ||
| * | ||
| * Will populate object properties from the provided arguments. | ||
| * | ||
| * @since 5.0.0 | ||
| * | ||
| * @param WP_Block_Parser_Block $block Full or partial block. | ||
| * @param int $token_start Byte offset into document for start of parse token. | ||
| * @param int $token_length Byte length of entire parse token string. | ||
| * @param int|null $prev_offset Optional. Byte offset into document for after parse token ends. Default null. | ||
| * @param int|null $leading_html_start Optional. Byte offset into document where leading HTML before token starts. | ||
| * Default null. | ||
| */ | ||
| public function __construct( $block, $token_start, $token_length, $prev_offset = null, $leading_html_start = null ) { | ||
| $this->block = $block; | ||
| $this->token_start = $token_start; | ||
| $this->token_length = $token_length; | ||
| $this->prev_offset = isset( $prev_offset ) ? $prev_offset : $token_start + $token_length; | ||
| $this->leading_html_start = $leading_html_start; | ||
| } | ||
| } |
| <?php | ||
| /** | ||
| * Block Serialization Parser | ||
| * | ||
| * @package WordPress | ||
| */ | ||
| /** | ||
| * Class WP_Block_Parser | ||
| * | ||
| * Parses a document and constructs a list of parsed block objects | ||
| * | ||
| * @since 5.0.0 | ||
| * @since 4.0.0 returns arrays not objects, all attributes are arrays | ||
| */ | ||
| class WP_Block_Parser { | ||
| /** | ||
| * Input document being parsed | ||
| * | ||
| * @example "Pre-text\n<!-- wp:paragraph -->This is inside a block!<!-- /wp:paragraph -->" | ||
| * | ||
| * @since 5.0.0 | ||
| * @var string | ||
| */ | ||
| public $document; | ||
| /** | ||
| * Tracks parsing progress through document | ||
| * | ||
| * @since 5.0.0 | ||
| * @var int | ||
| */ | ||
| public $offset; | ||
| /** | ||
| * List of parsed blocks | ||
| * | ||
| * @since 5.0.0 | ||
| * @var array[] | ||
| */ | ||
| public $output; | ||
| /** | ||
| * Stack of partially-parsed structures in memory during parse | ||
| * | ||
| * @since 5.0.0 | ||
| * @var WP_Block_Parser_Frame[] | ||
| */ | ||
| public $stack; | ||
| /** | ||
| * Parses a document and returns a list of block structures | ||
| * | ||
| * When encountering an invalid parse will return a best-effort | ||
| * parse. In contrast to the specification parser this does not | ||
| * return an error on invalid inputs. | ||
| * | ||
| * @since 5.0.0 | ||
| * | ||
| * @param string $document Input document being parsed. | ||
| * @return array[] | ||
| */ | ||
| public function parse( $document ) { | ||
| $this->document = $document; | ||
| $this->offset = 0; | ||
| $this->output = array(); | ||
| $this->stack = array(); | ||
| while ( $this->proceed() ) { | ||
| continue; | ||
| } | ||
| return $this->output; | ||
| } | ||
| /** | ||
| * Processes the next token from the input document | ||
| * and returns whether to proceed eating more tokens | ||
| * | ||
| * This is the "next step" function that essentially | ||
| * takes a token as its input and decides what to do | ||
| * with that token before descending deeper into a | ||
| * nested block tree or continuing along the document | ||
| * or breaking out of a level of nesting. | ||
| * | ||
| * @internal | ||
| * @since 5.0.0 | ||
| * @return bool | ||
| */ | ||
| public function proceed() { | ||
| $next_token = $this->next_token(); | ||
| list( $token_type, $block_name, $attrs, $start_offset, $token_length ) = $next_token; | ||
| $stack_depth = count( $this->stack ); | ||
| // we may have some HTML soup before the next block. | ||
| $leading_html_start = $start_offset > $this->offset ? $this->offset : null; | ||
| switch ( $token_type ) { | ||
| case 'no-more-tokens': | ||
| // if not in a block then flush output. | ||
| if ( 0 === $stack_depth ) { | ||
| $this->add_freeform(); | ||
| return false; | ||
| } | ||
| /* | ||
| * Otherwise we have a problem | ||
| * This is an error | ||
| * | ||
| * we have options | ||
| * - treat it all as freeform text | ||
| * - assume an implicit closer (easiest when not nesting) | ||
| */ | ||
| // for the easy case we'll assume an implicit closer. | ||
| if ( 1 === $stack_depth ) { | ||
| $this->add_block_from_stack(); | ||
| return false; | ||
| } | ||
| /* | ||
| * for the nested case where it's more difficult we'll | ||
| * have to assume that multiple closers are missing | ||
| * and so we'll collapse the whole stack piecewise | ||
| */ | ||
| while ( 0 < count( $this->stack ) ) { | ||
| $this->add_block_from_stack(); | ||
| } | ||
| return false; | ||
| case 'void-block': | ||
| /* | ||
| * easy case is if we stumbled upon a void block | ||
| * in the top-level of the document | ||
| */ | ||
| if ( 0 === $stack_depth ) { | ||
| if ( isset( $leading_html_start ) ) { | ||
| $this->output[] = (array) $this->freeform( | ||
| substr( | ||
| $this->document, | ||
| $leading_html_start, | ||
| $start_offset - $leading_html_start | ||
| ) | ||
| ); | ||
| } | ||
| $this->output[] = (array) new WP_Block_Parser_Block( $block_name, $attrs, array(), '', array() ); | ||
| $this->offset = $start_offset + $token_length; | ||
| return true; | ||
| } | ||
| // otherwise we found an inner block. | ||
| $this->add_inner_block( | ||
| new WP_Block_Parser_Block( $block_name, $attrs, array(), '', array() ), | ||
| $start_offset, | ||
| $token_length | ||
| ); | ||
| $this->offset = $start_offset + $token_length; | ||
| return true; | ||
| case 'block-opener': | ||
| // track all newly-opened blocks on the stack. | ||
| array_push( | ||
| $this->stack, | ||
| new WP_Block_Parser_Frame( | ||
| new WP_Block_Parser_Block( $block_name, $attrs, array(), '', array() ), | ||
| $start_offset, | ||
| $token_length, | ||
| $start_offset + $token_length, | ||
| $leading_html_start | ||
| ) | ||
| ); | ||
| $this->offset = $start_offset + $token_length; | ||
| return true; | ||
| case 'block-closer': | ||
| /* | ||
| * if we're missing an opener we're in trouble | ||
| * This is an error | ||
| */ | ||
| if ( 0 === $stack_depth ) { | ||
| /* | ||
| * we have options | ||
| * - assume an implicit opener | ||
| * - assume _this_ is the opener | ||
| * - give up and close out the document | ||
| */ | ||
| $this->add_freeform(); | ||
| return false; | ||
| } | ||
| // if we're not nesting then this is easy - close the block. | ||
| if ( 1 === $stack_depth ) { | ||
| $this->add_block_from_stack( $start_offset ); | ||
| $this->offset = $start_offset + $token_length; | ||
| return true; | ||
| } | ||
| /* | ||
| * otherwise we're nested and we have to close out the current | ||
| * block and add it as a new innerBlock to the parent | ||
| */ | ||
| $stack_top = array_pop( $this->stack ); | ||
| $html = substr( $this->document, $stack_top->prev_offset, $start_offset - $stack_top->prev_offset ); | ||
| $stack_top->block->innerHTML .= $html; | ||
| $stack_top->block->innerContent[] = $html; | ||
| $stack_top->prev_offset = $start_offset + $token_length; | ||
| $this->add_inner_block( | ||
| $stack_top->block, | ||
| $stack_top->token_start, | ||
| $stack_top->token_length, | ||
| $start_offset + $token_length | ||
| ); | ||
| $this->offset = $start_offset + $token_length; | ||
| return true; | ||
| default: | ||
| // This is an error. | ||
| $this->add_freeform(); | ||
| return false; | ||
| } | ||
| } | ||
| /** | ||
| * Scans the document from where we last left off | ||
| * and finds the next valid token to parse if it exists | ||
| * | ||
| * Returns the type of the find: kind of find, block information, attributes | ||
| * | ||
| * @internal | ||
| * @since 5.0.0 | ||
| * @since 4.6.1 fixed a bug in attribute parsing which caused catastrophic backtracking on invalid block comments | ||
| * @return array | ||
| */ | ||
| public function next_token() { | ||
| $matches = null; | ||
| /* | ||
| * aye the magic | ||
| * we're using a single RegExp to tokenize the block comment delimiters | ||
| * we're also using a trick here because the only difference between a | ||
| * block opener and a block closer is the leading `/` before `wp:` (and | ||
| * a closer has no attributes). we can trap them both and process the | ||
| * match back in PHP to see which one it was. | ||
| */ | ||
| $has_match = preg_match( | ||
| '/<!--\s+(?P<closer>\/)?wp:(?P<namespace>[a-z][a-z0-9_-]*\/)?(?P<name>[a-z][a-z0-9_-]*)\s+(?P<attrs>{(?:(?:[^}]+|}+(?=})|(?!}\s+\/?-->).)*+)?}\s+)?(?P<void>\/)?-->/s', | ||
| $this->document, | ||
| $matches, | ||
| PREG_OFFSET_CAPTURE, | ||
| $this->offset | ||
| ); | ||
| // if we get here we probably have catastrophic backtracking or out-of-memory in the PCRE. | ||
| if ( false === $has_match ) { | ||
| return array( 'no-more-tokens', null, null, null, null ); | ||
| } | ||
| // we have no more tokens. | ||
| if ( 0 === $has_match ) { | ||
| return array( 'no-more-tokens', null, null, null, null ); | ||
| } | ||
| list( $match, $started_at ) = $matches[0]; | ||
| $length = strlen( $match ); | ||
| $is_closer = isset( $matches['closer'] ) && -1 !== $matches['closer'][1]; | ||
| $is_void = isset( $matches['void'] ) && -1 !== $matches['void'][1]; | ||
| $namespace = $matches['namespace']; | ||
| $namespace = ( isset( $namespace ) && -1 !== $namespace[1] ) ? $namespace[0] : 'core/'; | ||
| $name = $namespace . $matches['name'][0]; | ||
| $has_attrs = isset( $matches['attrs'] ) && -1 !== $matches['attrs'][1]; | ||
| /* | ||
| * Fun fact! It's not trivial in PHP to create "an empty associative array" since all arrays | ||
| * are associative arrays. If we use `array()` we get a JSON `[]` | ||
| */ | ||
| $attrs = $has_attrs | ||
| ? json_decode( $matches['attrs'][0], /* as-associative */ true ) | ||
| : array(); | ||
| /* | ||
| * This state isn't allowed | ||
| * This is an error | ||
| */ | ||
| if ( $is_closer && ( $is_void || $has_attrs ) ) { | ||
| // we can ignore them since they don't hurt anything. | ||
| } | ||
| if ( $is_void ) { | ||
| return array( 'void-block', $name, $attrs, $started_at, $length ); | ||
| } | ||
| if ( $is_closer ) { | ||
| return array( 'block-closer', $name, null, $started_at, $length ); | ||
| } | ||
| return array( 'block-opener', $name, $attrs, $started_at, $length ); | ||
| } | ||
| /** | ||
| * Returns a new block object for freeform HTML | ||
| * | ||
| * @internal | ||
| * @since 5.0.0 | ||
| * | ||
| * @param string $inner_html HTML content of block. | ||
| * @return WP_Block_Parser_Block freeform block object. | ||
| */ | ||
| public function freeform( $inner_html ) { | ||
| return new WP_Block_Parser_Block( null, array(), array(), $inner_html, array( $inner_html ) ); | ||
| } | ||
| /** | ||
| * Pushes a length of text from the input document | ||
| * to the output list as a freeform block. | ||
| * | ||
| * @internal | ||
| * @since 5.0.0 | ||
| * @param null $length how many bytes of document text to output. | ||
| */ | ||
| public function add_freeform( $length = null ) { | ||
| $length = $length ? $length : strlen( $this->document ) - $this->offset; | ||
| if ( 0 === $length ) { | ||
| return; | ||
| } | ||
| $this->output[] = (array) $this->freeform( substr( $this->document, $this->offset, $length ) ); | ||
| } | ||
| /** | ||
| * Given a block structure from memory pushes | ||
| * a new block to the output list. | ||
| * | ||
| * @internal | ||
| * @since 5.0.0 | ||
| * @param WP_Block_Parser_Block $block The block to add to the output. | ||
| * @param int $token_start Byte offset into the document where the first token for the block starts. | ||
| * @param int $token_length Byte length of entire block from start of opening token to end of closing token. | ||
| * @param int|null $last_offset Last byte offset into document if continuing form earlier output. | ||
| */ | ||
| public function add_inner_block( WP_Block_Parser_Block $block, $token_start, $token_length, $last_offset = null ) { | ||
| $parent = $this->stack[ count( $this->stack ) - 1 ]; | ||
| $parent->block->innerBlocks[] = (array) $block; | ||
| $html = substr( $this->document, $parent->prev_offset, $token_start - $parent->prev_offset ); | ||
| if ( ! empty( $html ) ) { | ||
| $parent->block->innerHTML .= $html; | ||
| $parent->block->innerContent[] = $html; | ||
| } | ||
| $parent->block->innerContent[] = null; | ||
| $parent->prev_offset = $last_offset ? $last_offset : $token_start + $token_length; | ||
| } | ||
| /** | ||
| * Pushes the top block from the parsing stack to the output list. | ||
| * | ||
| * @internal | ||
| * @since 5.0.0 | ||
| * @param int|null $end_offset byte offset into document for where we should stop sending text output as HTML. | ||
| */ | ||
| public function add_block_from_stack( $end_offset = null ) { | ||
| $stack_top = array_pop( $this->stack ); | ||
| $prev_offset = $stack_top->prev_offset; | ||
| $html = isset( $end_offset ) | ||
| ? substr( $this->document, $prev_offset, $end_offset - $prev_offset ) | ||
| : substr( $this->document, $prev_offset ); | ||
| if ( ! empty( $html ) ) { | ||
| $stack_top->block->innerHTML .= $html; | ||
| $stack_top->block->innerContent[] = $html; | ||
| } | ||
| if ( isset( $stack_top->leading_html_start ) ) { | ||
| $this->output[] = (array) $this->freeform( | ||
| substr( | ||
| $this->document, | ||
| $stack_top->leading_html_start, | ||
| $stack_top->token_start - $stack_top->leading_html_start | ||
| ) | ||
| ); | ||
| } | ||
| $this->output[] = (array) $stack_top->block; | ||
| } | ||
| } | ||
| /** | ||
| * WP_Block_Parser_Block class. | ||
| * | ||
| * Required for backward compatibility in WordPress Core. | ||
| */ | ||
| require_once __DIR__ . '/class-wp-block-parser-block.php'; | ||
| /** | ||
| * WP_Block_Parser_Frame class. | ||
| * | ||
| * Required for backward compatibility in WordPress Core. | ||
| */ | ||
| require_once __DIR__ . '/class-wp-block-parser-frame.php'; |
-11
| <?php | ||
| /** | ||
| * Block Serialization Parser | ||
| * | ||
| * @package WordPress | ||
| */ | ||
| // Require files. | ||
| require_once __DIR__ . '/class-wp-block-parser-block.php'; | ||
| require_once __DIR__ . '/class-wp-block-parser-frame.php'; | ||
| require_once __DIR__ . '/class-wp-block-parser.php'; |
| /** | ||
| * External dependencies | ||
| */ | ||
| import path from 'path'; | ||
| /** | ||
| * WordPress dependencies | ||
| */ | ||
| import { | ||
| jsTester, | ||
| phpTester, | ||
| } from '@wordpress/block-serialization-spec-parser/shared-tests'; | ||
| /** | ||
| * Internal dependencies | ||
| */ | ||
| import { parse } from '../src'; | ||
| describe( 'block-serialization-default-parser-js', jsTester( parse ) ); // eslint-disable-line jest/valid-describe-callback | ||
| phpTester( | ||
| 'block-serialization-default-parser-php', | ||
| path.join( __dirname, 'test-parser.php' ) | ||
| ); |
| <?php | ||
| /** | ||
| * PHP Test Helper | ||
| * | ||
| * Facilitates running PHP parser against same tests as the JS parser implementation. | ||
| * | ||
| * @package gutenberg | ||
| */ | ||
| // Include the default parser. | ||
| require_once __DIR__ . '/../parser.php'; | ||
| $parser = new WP_Block_Parser(); | ||
| echo json_encode( $parser->parse( file_get_contents( 'php://stdin' ) ) ); |
| { | ||
| "$schema": "https://json.schemastore.org/tsconfig.json", | ||
| "extends": "../../tsconfig.base.json" | ||
| } |
Sorry, the diff of this file is not supported yet
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
Yes
NaN115128
-21.98%11
-42.11%982
-2.39%