|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160 |
- /**
- * Copyright (c) Facebook, Inc. and its affiliates.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root directory of this source tree.
- *
- * @format
- * @flow strict-local
- * @emails oncall+draft_js
- *
- * This is unstable and not part of the public API and should not be used by
- * production systems. This file may be update/removed without notice.
- */
- import type { BlockMap } from "./BlockMap";
- import type ContentBlockNode from "./ContentBlockNode";
-
- const warning = require("fbjs/lib/warning");
-
- const DraftTreeInvariants = {
- /**
- * Check if the block is valid
- */
- isValidBlock(block: ContentBlockNode, blockMap: BlockMap): boolean {
- const key = block.getKey(); // is its parent's child
-
- const parentKey = block.getParentKey();
-
- if (parentKey != null) {
- const parent = blockMap.get(parentKey);
-
- if (!parent.getChildKeys().includes(key)) {
- warning(true, 'Tree is missing parent -> child pointer on %s', key);
- return false;
- }
- } // is its children's parent
-
-
- const children = block.getChildKeys().map(k => blockMap.get(k));
-
- if (!children.every(c => c.getParentKey() === key)) {
- warning(true, 'Tree is missing child -> parent pointer on %s', key);
- return false;
- } // is its previous sibling's next sibling
-
-
- const prevSiblingKey = block.getPrevSiblingKey();
-
- if (prevSiblingKey != null) {
- const prevSibling = blockMap.get(prevSiblingKey);
-
- if (prevSibling.getNextSiblingKey() !== key) {
- warning(true, "Tree is missing nextSibling pointer on %s's prevSibling", key);
- return false;
- }
- } // is its next sibling's previous sibling
-
-
- const nextSiblingKey = block.getNextSiblingKey();
-
- if (nextSiblingKey != null) {
- const nextSibling = blockMap.get(nextSiblingKey);
-
- if (nextSibling.getPrevSiblingKey() !== key) {
- warning(true, "Tree is missing prevSibling pointer on %s's nextSibling", key);
- return false;
- }
- } // no 2-node cycles
-
-
- if (nextSiblingKey !== null && prevSiblingKey !== null) {
- if (prevSiblingKey === nextSiblingKey) {
- warning(true, 'Tree has a two-node cycle at %s', key);
- return false;
- }
- } // if it's a leaf node, it has text but no children
-
-
- if (block.text != '') {
- if (block.getChildKeys().size > 0) {
- warning(true, 'Leaf node %s has children', key);
- return false;
- }
- }
-
- return true;
- },
-
- /**
- * Checks that this is a connected tree on all the blocks
- * starting from the first block, traversing nextSibling and child pointers
- * should be a tree (preorder traversal - parent, then children)
- * num of connected node === number of blocks
- */
- isConnectedTree(blockMap: BlockMap): boolean {
- // exactly one node has no previous sibling + no parent
- const eligibleFirstNodes = blockMap.toArray().filter(block => block.getParentKey() == null && block.getPrevSiblingKey() == null);
-
- if (eligibleFirstNodes.length !== 1) {
- warning(true, 'Tree is not connected. More or less than one first node');
- return false;
- }
-
- const firstNode = eligibleFirstNodes.shift();
- let nodesSeen = 0;
- let currentKey = firstNode.getKey();
- const visitedStack = [];
-
- while (currentKey != null) {
- const currentNode = blockMap.get(currentKey);
- const childKeys = currentNode.getChildKeys();
- const nextSiblingKey = currentNode.getNextSiblingKey(); // if the node has children, add parent's next sibling to stack and go to children
-
- if (childKeys.size > 0) {
- if (nextSiblingKey != null) {
- visitedStack.unshift(nextSiblingKey);
- }
-
- const children = childKeys.map(k => blockMap.get(k));
- const firstNode = children.find(block => block.getPrevSiblingKey() == null);
-
- if (firstNode == null) {
- warning(true, '%s has no first child', currentKey);
- return false;
- }
-
- currentKey = firstNode.getKey(); // TODO(T32490138): Deal with multi-node cycles here
- } else {
- if (currentNode.getNextSiblingKey() != null) {
- currentKey = currentNode.getNextSiblingKey();
- } else {
- currentKey = visitedStack.shift();
- }
- }
-
- nodesSeen++;
- }
-
- if (nodesSeen !== blockMap.size) {
- warning(true, 'Tree is not connected. %s nodes were seen instead of %s', nodesSeen, blockMap.size);
- return false;
- }
-
- return true;
- },
-
- /**
- * Checks that the block map is a connected tree with valid blocks
- */
- isValidTree(blockMap: BlockMap): boolean {
- const blocks = blockMap.toArray();
-
- if (!blocks.every(block => this.isValidBlock(block, blockMap))) {
- return false;
- }
-
- return this.isConnectedTree(blockMap);
- }
-
- };
- module.exports = DraftTreeInvariants;
|