UNPKG

@wordpress/block-library

Version:
8 lines (7 loc) 6.07 kB
{ "version": 3, "sources": ["../../src/pattern/recursion-detector.js"], "sourcesContent": ["/**\n * THIS MODULE IS INTENTIONALLY KEPT WITHIN THE PATTERN BLOCK'S SOURCE.\n *\n * This is because this approach for preventing infinite loops due to\n * recursively rendering blocks is specific to the way that the `core/pattern`\n * block behaves in the editor. Any other block types that deal with recursion\n * SHOULD USE THE STANDARD METHOD for avoiding loops:\n *\n * @see https://github.com/WordPress/gutenberg/pull/31455\n * @see packages/block-editor/src/components/recursion-provider/README.md\n */\n\n/**\n * WordPress dependencies\n */\nimport { useRegistry } from '@wordpress/data';\n\n/**\n * Naming is hard.\n *\n * @see useParsePatternDependencies\n *\n * @type {WeakMap<Object, Function>}\n */\nconst cachedParsers = new WeakMap();\n\n/**\n * Hook used by PatternEdit to parse block patterns. It returns a function that\n * takes a pattern and returns nothing but throws an error if the pattern is\n * recursive.\n *\n * @example\n * ```js\n * const parsePatternDependencies = useParsePatternDependencies();\n * parsePatternDependencies( selectedPattern );\n * ```\n *\n * @see parsePatternDependencies\n *\n * @return {Function} A function to parse block patterns.\n */\nexport function useParsePatternDependencies() {\n\tconst registry = useRegistry();\n\n\t// Instead of caching maps, go straight to the point and cache bound\n\t// functions. Each of those functions is bound to a different Map that will\n\t// keep track of patterns in the context of the given registry.\n\tif ( ! cachedParsers.has( registry ) ) {\n\t\tconst deps = new Map();\n\t\tcachedParsers.set(\n\t\t\tregistry,\n\t\t\tparsePatternDependencies.bind( null, deps )\n\t\t);\n\t}\n\treturn cachedParsers.get( registry );\n}\n\n/**\n * Parse a given pattern and traverse its contents to detect any subsequent\n * patterns on which it may depend. Such occurrences will be added to an\n * internal dependency graph. If a circular dependency is detected, an\n * error will be thrown.\n *\n * EXPORTED FOR TESTING PURPOSES ONLY.\n *\n * @param {Map<string, Set<string>>} deps Map of pattern dependencies.\n * @param {Object} pattern Pattern.\n * @param {string} pattern.name Pattern name.\n * @param {Array} pattern.blocks Pattern's block list.\n *\n * @throws {Error} If a circular dependency is detected.\n */\nexport function parsePatternDependencies( deps, { name, blocks } ) {\n\tconst queue = [ ...blocks ];\n\twhile ( queue.length ) {\n\t\tconst block = queue.shift();\n\t\tfor ( const innerBlock of block.innerBlocks ?? [] ) {\n\t\t\tqueue.unshift( innerBlock );\n\t\t}\n\t\tif ( block.name === 'core/pattern' ) {\n\t\t\tregisterDependency( deps, name, block.attributes.slug );\n\t\t}\n\t}\n}\n\n/**\n * Declare that pattern `a` depends on pattern `b`. If a circular\n * dependency is detected, an error will be thrown.\n *\n * EXPORTED FOR TESTING PURPOSES ONLY.\n *\n * @param {Map<string, Set<string>>} deps Map of pattern dependencies.\n * @param {string} a Slug for pattern A.\n * @param {string} b Slug for pattern B.\n *\n * @throws {Error} If a circular dependency is detected.\n */\nexport function registerDependency( deps, a, b ) {\n\tif ( ! deps.has( a ) ) {\n\t\tdeps.set( a, new Set() );\n\t}\n\tdeps.get( a ).add( b );\n\tif ( hasCycle( deps, a ) ) {\n\t\tthrow new TypeError(\n\t\t\t`Pattern ${ a } has a circular dependency and cannot be rendered.`\n\t\t);\n\t}\n}\n\n/**\n * Determine if a given pattern has circular dependencies on other patterns.\n * This will be determined by running a depth-first search on the current state\n * of the graph represented by `patternDependencies`.\n *\n * @param {Map<string, Set<string>>} deps Map of pattern dependencies.\n * @param {string} slug Pattern slug.\n * @param {Set<string>} [visitedNodes] Set to track visited nodes in the graph.\n * @param {Set<string>} [currentPath] Set to track and backtrack graph paths.\n * @return {boolean} Whether any cycle was found.\n */\nfunction hasCycle(\n\tdeps,\n\tslug,\n\tvisitedNodes = new Set(),\n\tcurrentPath = new Set()\n) {\n\tvisitedNodes.add( slug );\n\tcurrentPath.add( slug );\n\n\tconst dependencies = deps.get( slug ) ?? new Set();\n\n\tfor ( const dependency of dependencies ) {\n\t\tif ( ! visitedNodes.has( dependency ) ) {\n\t\t\tif ( hasCycle( deps, dependency, visitedNodes, currentPath ) ) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t} else if ( currentPath.has( dependency ) ) {\n\t\t\treturn true;\n\t\t}\n\t}\n\n\t// Remove the current node from the current path when backtracking\n\tcurrentPath.delete( slug );\n\treturn false;\n}\n"], "mappings": ";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAeA,kBAA4B;AAS5B,IAAM,gBAAgB,oBAAI,QAAQ;AAiB3B,SAAS,8BAA8B;AAC7C,QAAM,eAAW,yBAAY;AAK7B,MAAK,CAAE,cAAc,IAAK,QAAS,GAAI;AACtC,UAAM,OAAO,oBAAI,IAAI;AACrB,kBAAc;AAAA,MACb;AAAA,MACA,yBAAyB,KAAM,MAAM,IAAK;AAAA,IAC3C;AAAA,EACD;AACA,SAAO,cAAc,IAAK,QAAS;AACpC;AAiBO,SAAS,yBAA0B,MAAM,EAAE,MAAM,OAAO,GAAI;AAClE,QAAM,QAAQ,CAAE,GAAG,MAAO;AAC1B,SAAQ,MAAM,QAAS;AACtB,UAAM,QAAQ,MAAM,MAAM;AAC1B,eAAY,cAAc,MAAM,eAAe,CAAC,GAAI;AACnD,YAAM,QAAS,UAAW;AAAA,IAC3B;AACA,QAAK,MAAM,SAAS,gBAAiB;AACpC,yBAAoB,MAAM,MAAM,MAAM,WAAW,IAAK;AAAA,IACvD;AAAA,EACD;AACD;AAcO,SAAS,mBAAoB,MAAM,GAAG,GAAI;AAChD,MAAK,CAAE,KAAK,IAAK,CAAE,GAAI;AACtB,SAAK,IAAK,GAAG,oBAAI,IAAI,CAAE;AAAA,EACxB;AACA,OAAK,IAAK,CAAE,EAAE,IAAK,CAAE;AACrB,MAAK,SAAU,MAAM,CAAE,GAAI;AAC1B,UAAM,IAAI;AAAA,MACT,WAAY,CAAE;AAAA,IACf;AAAA,EACD;AACD;AAaA,SAAS,SACR,MACA,MACA,eAAe,oBAAI,IAAI,GACvB,cAAc,oBAAI,IAAI,GACrB;AACD,eAAa,IAAK,IAAK;AACvB,cAAY,IAAK,IAAK;AAEtB,QAAM,eAAe,KAAK,IAAK,IAAK,KAAK,oBAAI,IAAI;AAEjD,aAAY,cAAc,cAAe;AACxC,QAAK,CAAE,aAAa,IAAK,UAAW,GAAI;AACvC,UAAK,SAAU,MAAM,YAAY,cAAc,WAAY,GAAI;AAC9D,eAAO;AAAA,MACR;AAAA,IACD,WAAY,YAAY,IAAK,UAAW,GAAI;AAC3C,aAAO;AAAA,IACR;AAAA,EACD;AAGA,cAAY,OAAQ,IAAK;AACzB,SAAO;AACR;", "names": [] }