ComponentKit/Core/Scope/CKTreeVerificationHelpers.mm (115 lines of code) (raw):

/* * Copyright (c) 2014-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. * */ #import "CKTreeVerificationHelpers.h" #import <queue> #import <RenderCore/RCAssert.h> #import <ComponentKit/CKMountable.h> #import <ComponentKit/RCComponentDescriptionHelper.h> #import <ComponentKit/CKComponentInternal.h> #import <ComponentKit/CKComponentScopeRoot.h> #import <ComponentKit/CKRootTreeNode.h> #import <ComponentKit/CKEmptyComponent.h> #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-function" static NSArray<id<CKMountable>> *generateComponentBacktrace(id<CKMountable> component, NSMapTable<id<CKMountable>, id<CKMountable>> *componentsToParentComponents) { NSMutableArray<id<CKMountable>> *componentBacktrace = [NSMutableArray arrayWithObject:component]; auto parentComponent = [componentsToParentComponents objectForKey:component]; while (parentComponent) { [componentBacktrace addObject:parentComponent]; parentComponent = [componentsToParentComponents objectForKey:parentComponent]; } return componentBacktrace; } CKDuplicateComponentInfo CKFindDuplicateComponent(const RCLayout &layout) { std::queue<const RCLayout> queue; NSMutableSet<id<NSObject>> *const previouslySeenComponent = [NSMutableSet new]; NSMapTable<id<CKMountable>, id<CKMountable>> *const componentsToParentComponents = [NSMapTable strongToStrongObjectsMapTable]; queue.push(layout); while (!queue.empty()) { const auto componentLayout = queue.front(); queue.pop(); auto const component = componentLayout.component; if (component.class == CKEmptyComponent.class) { continue; } if (component && [previouslySeenComponent containsObject:component]) { return { .component = component, .backtraceDescription = RCComponentBacktraceDescription(generateComponentBacktrace(component, componentsToParentComponents)), }; } if (component) { [previouslySeenComponent addObject:component]; } if (componentLayout.children) { for (const auto& childComponentLayout : *componentLayout.children) { queue.push(childComponentLayout.layout); [componentsToParentComponents setObject:componentLayout.component forKey:childComponentLayout.layout.component]; } } } return {}; } void CKDetectDuplicateComponent(const RCLayout &layout) { #if CK_ASSERTIONS_ENABLED auto const info = CKFindDuplicateComponent(layout); if (info.component) { RCCFailAssertWithCategory(RCComponentCompactDescription(info.component), @"Duplicate component in the tree. Attempting to use %@ more than once in the component tree can lead to an incorrect and unexpected behavior\n" @"Please make sure to create another instance of %@ if needed. \nComponent backtrace:\n%@", info.component.className, info.component.className, info.backtraceDescription); } #endif } #if CK_ASSERTIONS_ENABLED static void CKVerifyTreeNodeWithParent(const CKRootTreeNode &rootNode, const RCLayout &layout, CKTreeNode *parentNode) { if (layout.component == nil) { return; } CKTreeNode *treeNode = nil; if ([layout.component isKindOfClass:[CKComponent class]]) { auto const c = (CKComponent *)layout.component; if (c.treeNode) { treeNode = c.treeNode; auto const registeredParentNode = rootNode.parentForNodeIdentifier(treeNode.nodeIdentifier); if (registeredParentNode == nil) { RCCFailAssertWithCategory(RCComponentCompactDescription(c), @"Missing link from node to its parent on the CKRootTreeNode; \n" @"make sure your component returns all its children on the RCIterable methods.\n" @"Component:%@\n" @"Parent component:%@", c, parentNode.component); } else if (registeredParentNode != parentNode) { RCCFailAssertWithCategory(RCComponentCompactDescription(c), @"Incorrect link from node to its parent on the CKRootTreeNode; \n" @"make sure your component returns all its children on the RCIterable methods.\n" @"Component:%@\n" @"Parent component:%@\n" @"Registered parent component:%@", c, parentNode.component, registeredParentNode.component); } } } // Continue the check on the children; if the component has no tree node, pass the previous one. if (layout.children) { for (const auto &childLayout : *layout.children) { CKVerifyTreeNodeWithParent(rootNode, childLayout.layout, treeNode ?: parentNode); } } } #endif void CKVerifyTreeNodesToParentLinks(CKComponentScopeRoot *scopeRoot, const RCLayout &layout) { #if CK_ASSERTIONS_ENABLED if (scopeRoot.hasRenderComponentInTree) { CKVerifyTreeNodeWithParent(scopeRoot.rootNode, layout, scopeRoot.rootNode.node()); } #endif } #pragma clang diagnostic pop