Generator/Sources/NeedleFramework/Parsing/Pluginized/Tasks/PluginizedASTDeclarationParserTask.swift (115 lines of code) (raw):

// // Copyright (c) 2018. Uber Technologies // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // import Concurrency import Foundation import SourceParsingFramework import SwiftSyntax /// The extended AST parser task that parses all components, dependency /// protocols declarations and import statements, including pluginized /// components. class PluginizedDeclarationsParserTask: AbstractTask<PluginizedDependencyGraphNode> { /// Initializer. /// /// - parameter ast: The AST of the file to parse. init(ast: AST) { self.ast = ast super.init(id: TaskIds.pluginizedDeclarationsParserTask.rawValue) } /// Execute the task and returns the dependency graph data model. /// /// - returns: Parsed `PluginizedDependencyGraphNode`. /// - throws: Any error occurred during execution. override func execute() throws -> PluginizedDependencyGraphNode { let baseTask = DeclarationsParserTask(ast: ast) let baseNode = try baseTask.execute() let visitor = PluginizedVisitor(sourceHash: ast.sourceHash, filePath: ast.filePath) visitor.walk(ast.sourceFileSyntax) let pluginizedComponents = visitor.pluginizedComponents let nonCoreComponents = visitor.nonCoreComponents let pluginExtensions = visitor.pluginExtensions return PluginizedDependencyGraphNode(pluginizedComponents: pluginizedComponents, nonCoreComponents: nonCoreComponents, pluginExtensions: pluginExtensions, components: baseNode.components, dependencies: baseNode.dependencies, imports: baseNode.imports + visitor.imports) } // MARK: - Private private let ast: AST } private final class PluginizedVisitor: BaseVisitor { private(set) var pluginExtensions: [PluginExtension] = [] private(set) var pluginizedComponents: [PluginizedASTComponent] = [] private(set) var nonCoreComponents: [ASTComponent] = [] private var currentPluginizedComponentName: String = "" private var currentNonCoreComponentName: String = "" private var currentPluginExtensionGenerics: (dependencyProtocolName: String, pluginExtensionName: String, nonCoreComponentName: String) = ("", "", "") private let sourceHash: String init(sourceHash: String, filePath: String) { self.sourceHash = sourceHash super.init(filePath: filePath) } override func visit(_ node: ProtocolDeclSyntax) -> SyntaxVisitorContinueKind { if node.isPluginExtension { currentEntityNode = node return .visitChildren } else { return .skipChildren } } override func visitPost(_ node: ProtocolDeclSyntax) { let protocolName = node.typeName if protocolName == currentEntityNode?.typeName { let pluginExtension = PluginExtension(name: protocolName, properties: propertiesDict[protocolName, default: []]) pluginExtensions.append(pluginExtension) } } override func visit(_ node: ClassDeclSyntax) ->SyntaxVisitorContinueKind { if node.isPluginizedComponent { isParsingComponentDeclarationLine = true currentEntityNode = node currentPluginizedComponentName = node.typeName return .visitChildren } else if node.isNonCoreComponent { isParsingComponentDeclarationLine = true currentEntityNode = node currentNonCoreComponentName = node.typeName return .visitChildren } else { return .skipChildren } } override func visitPost(_ node: ClassDeclSyntax) { let componentName = node.typeName if componentName == currentPluginizedComponentName { // Internal properties cannot be seen by the needle generated code, so leave them out let filteredProperties = propertiesDict[componentName, default: []].filter { property in !property.isInternal } let component = ASTComponent(name: componentName, dependencyProtocolName: currentPluginExtensionGenerics.dependencyProtocolName, isRoot: node.isRoot, sourceHash: sourceHash, filePath: filePath, properties: filteredProperties, expressionCallTypeNames: Array(componentToCallExprs[componentName, default: []]).sorted()) let pluginizedComponent = PluginizedASTComponent(data: component, pluginExtensionType: currentPluginExtensionGenerics.pluginExtensionName, nonCoreComponentType: currentPluginExtensionGenerics.nonCoreComponentName) pluginizedComponents.append(pluginizedComponent) } else if componentName == currentNonCoreComponentName { // Internal properties cannot be seen by the needle generated code, so leave them out let filteredProperties = propertiesDict[componentName, default: []].filter { property in !property.isInternal } let component = ASTComponent(name: componentName, dependencyProtocolName: currentDependencyProtocol ?? "", isRoot: false, sourceHash: sourceHash, filePath: filePath, properties: filteredProperties, expressionCallTypeNames: Array(componentToCallExprs[componentName, default: []]).sorted()) nonCoreComponents.append(component) } } override func visitPost(_ node: GenericArgumentListSyntax) { guard isParsingComponentDeclarationLine else { return } if currentEntityNode?.typeName == currentPluginizedComponentName { for (i, genericArgument) in node.enumerated() { let argumentName = genericArgument.argument.description.trimmed.removingModulePrefix switch i { case 0: currentPluginExtensionGenerics.dependencyProtocolName = argumentName case 1: currentPluginExtensionGenerics.pluginExtensionName = argumentName case 2: currentPluginExtensionGenerics.nonCoreComponentName = argumentName default: warning("Found more generic arguments than expected in \(currentEntityNode?.typeName ?? "UNKNOWN")") } } } else if currentEntityNode?.typeName == currentNonCoreComponentName { currentDependencyProtocol = node.first?.argument.description.trimmed.removingModulePrefix } } override func visit(_ node: ExtensionDeclSyntax) -> SyntaxVisitorContinueKind { return .skipChildren } }