Skip to content

Commit

Permalink
Merge pull request #101 from uber/generator-pluginized_parser-master
Browse files Browse the repository at this point in the history
Finish PluginizableDependencyGraphParser
  • Loading branch information
Rudro Samanta authored Jun 26, 2018
2 parents 6365916 + 0c5fd56 commit 76b0be5
Show file tree
Hide file tree
Showing 16 changed files with 378 additions and 19 deletions.
5 changes: 1 addition & 4 deletions Generator/Sources/NeedleFramework/Models/Component.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,7 @@ class ASTComponent {
let parentValues = parents.map { (parent: ASTComponent) -> Component in
parent.valueType
}
guard let dependency = dependencyProtocol else {
fatalError("\(self)'s dependency protocol data model is not yet linked.")
}
return Component(name: name, properties: properties, parents: parentValues, dependency: dependency)
return Component(name: name, properties: properties, parents: parentValues, dependency: dependencyProtocol!)
}

/// Initializer.
Expand Down
8 changes: 8 additions & 0 deletions Generator/Sources/NeedleFramework/Models/Dependency.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,12 @@ struct Dependency: Equatable {
let name: String
/// The list of dependency properties.
let properties: [Property]

/// Check if the dependency name is an empty dependency.
///
/// - returns: `true` if this dependency prootocol is the `EmptyDependency`.
/// `false` otherwise.
static func isEmptyDependency(name: String) -> Bool {
return name == "EmptyDependency"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ struct ProcessedDependencyProvider {
/// `true` if this provider's dependency prootocol is the `EmptyDependency`.
/// `false` otherwise.
var isEmptyDependency: Bool {
return unprocessed.dependency.name == "EmptyDependency"
return Dependency.isEmptyDependency(name: unprocessed.dependency.name)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@ class PluginizableASTComponent {
let nonCoreComponentType: String
/// The linked non-core component.
var nonCoreComponent: ASTComponent?
/// The linked plugin extension.
var pluginExtension: PluginExtension?

/// Convert the mutable reference type into a thread-safe value type.
var valueType: PluginizableComponent {
return PluginizableComponent(data: data.valueType, nonCoreComponent: nonCoreComponent!.valueType, pluginExtension: pluginExtension!)
}

/// Initializer.
///
Expand All @@ -51,4 +58,6 @@ struct PluginizableComponent {
let data: Component
/// The non-core component.
let nonCoreComponent: Component
/// The plugin extension.
let pluginExtension: PluginExtension
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,7 @@
// limitations under the License.
//

import Basic
import Foundation
import SourceKittenFramework

/// Errors that can occur during parsing of the dependency graph from
/// Swift sources.
Expand All @@ -39,7 +37,8 @@ class DependencyGraphParser {
/// in this list, the said file is excluded from parsing.
/// - parameter executor: The executor to use for concurrent processing
/// of files.
/// - returns: The list of component data models.
/// - returns: The list of component data models and sorted import
/// statements.
/// - throws: `DependencyGraphParserError.timeout` if parsing a Swift
/// source timed out.
func parse(from rootUrl: URL, excludingFilesWithSuffixes exclusionSuffixes: [String] = [], using executor: SequenceExecutor) throws -> (components: [Component], imports: [String]) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,7 @@
// limitations under the License.
//

import Basic
import Foundation
import SourceKittenFramework

/// The entry utility for the parsing phase. The parser deeply scans a
/// directory and parses the relevant Swift source files, and finally
Expand All @@ -32,10 +30,11 @@ class PluginizableDependencyGraphParser {
/// in this list, the said file is excluded from parsing.
/// - parameter executor: The executor to use for concurrent processing
/// of files.
/// - returns: The list of component data models.
/// - returns: The list of component data models, pluginized component
/// data models and sorted import statements.
/// - throws: `DependencyGraphParserError.timeout` if parsing a Swift
/// source timed out.
func parse(from rootUrl: URL, excludingFilesWithSuffixes exclusionSuffixes: [String] = [], using executor: SequenceExecutor) throws -> (components: [Component], imports: [String]) {
func parse(from rootUrl: URL, excludingFilesWithSuffixes exclusionSuffixes: [String] = [], using executor: SequenceExecutor) throws -> ([Component], [PluginizableComponent], [String]) {
let urlHandles: [UrlSequenceHandle] = enqueueParsingTasks(with: rootUrl, excludingFilesWithSuffixes: exclusionSuffixes, using: executor)
let (pluginizableComponents, nonCoreComponents, pluginExtensions, components, dependencies, imports) = try collectDataModels(with: urlHandles)
return process(pluginizableComponents, nonCoreComponents, pluginExtensions, components, dependencies, imports)
Expand Down Expand Up @@ -101,7 +100,7 @@ class PluginizableDependencyGraphParser {
return (pluginizableComponents, nonCoreComponents, pluginExtensions, components, dependencies, imports)
}

private func process(_ pluginizableComponents: [PluginizableASTComponent], _ nonCoreComponents: [ASTComponent], _ pluginExtensions: [PluginExtension], _ components: [ASTComponent], _ dependencies: [Dependency], _ imports: Set<String>) -> ([Component], [String]) {
private func process(_ pluginizableComponents: [PluginizableASTComponent], _ nonCoreComponents: [ASTComponent], _ pluginExtensions: [PluginExtension], _ components: [ASTComponent], _ dependencies: [Dependency], _ imports: Set<String>) -> ([Component], [PluginizableComponent], [String]) {
var allComponents = nonCoreComponents + components
let pluginizableComponentData = pluginizableComponents.map { (component: PluginizableASTComponent) -> ASTComponent in
component.data
Expand All @@ -111,7 +110,8 @@ class PluginizableDependencyGraphParser {
DuplicateValidator(components: allComponents, dependencies: dependencies),
ParentLinker(components: allComponents),
DependencyLinker(components: allComponents, dependencies: dependencies),
NonCoreComponentLinker(pluginizableComponents: pluginizableComponents, nonCoreComponents: nonCoreComponents)
NonCoreComponentLinker(pluginizableComponents: pluginizableComponents, nonCoreComponents: nonCoreComponents),
PluginExtensionLinker(pluginizableComponents: pluginizableComponents, pluginExtensions: pluginExtensions)
]
for processor in processors {
do {
Expand All @@ -121,12 +121,14 @@ class PluginizableDependencyGraphParser {
}
}

// TODO: This needs further processing.
let valueTypeComponents = components.map { (astComponent: ASTComponent) -> Component in
astComponent.valueType
}
let valueTypePluginizedComponents = pluginizableComponents.map { (astComponent: PluginizableASTComponent) -> PluginizableComponent in
return astComponent.valueType
}
let sortedImports = imports.sorted()
return (valueTypeComponents, sortedImports)
return (valueTypeComponents, valueTypePluginizedComponents, sortedImports)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ class NonCoreComponentLinker: Processor {

for pluginizableComponent in pluginizableComponents {
pluginizableComponent.nonCoreComponent = nonCoreMap[pluginizableComponent.nonCoreComponentType]
if pluginizableComponent.nonCoreComponent == nil {
throw ProcessingError.fail("Cannot find \(pluginizableComponent.data.name)'s non-core component with type name \(pluginizableComponent.nonCoreComponentType)")
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
//
// 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 Foundation

/// A processor that links pluginizable components with their plugin
/// extensions based on type name.
class PluginExtensionLinker: Processor {

/// Initializer.
///
/// - parameter pluginizableComponents: The pluginizable components to
/// link with plugin extensions.
/// - parameter pluginExtensions: The non-core components to link.
init(pluginizableComponents: [PluginizableASTComponent], pluginExtensions: [PluginExtension]) {
self.pluginizableComponents = pluginizableComponents
self.pluginExtensions = pluginExtensions
}

/// Process the data models.
///
/// - throws: `ProcessingError` if some pluginized components cannot
/// find matching plugin extensions.
func process() throws {
var extensionMap = [String: PluginExtension]()
for pluginExtension in pluginExtensions {
extensionMap[pluginExtension.name] = pluginExtension
}

for pluginizableComponent in pluginizableComponents {
pluginizableComponent.pluginExtension = extensionMap[pluginizableComponent.pluginExtensionType]
if pluginizableComponent.pluginExtension == nil {
throw ProcessingError.fail("Cannot find \(pluginizableComponent.data.name)'s plugin extension with type name \(pluginizableComponent.pluginExtensionType)")
}
}
}

// MARK: - Private

private let pluginizableComponents: [PluginizableASTComponent]
private let pluginExtensions: [PluginExtension]
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ class DependencyLinker: Processor {
for component in components {
if let dependency = nameToDependency[component.dependencyProtocolName] {
component.dependencyProtocol = dependency
} else {
throw ProcessingError.fail("Missing dependency protocol data model for \(component.dependencyProtocolName).")
} else if !Dependency.isEmptyDependency(name: component.dependencyProtocolName) {
throw ProcessingError.fail("Missing dependency protocol data model with name \(component.dependencyProtocolName), for \(component.name).")
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ protocol BExtension: PluginExtension {
var myPluginPoint: MyPluginPoint { get }
}

class SomePluginizedCompo: PluginizedComponent<ADependency, BExtension, SomeNonCoreComponent>, Stuff {
class SomePluginizedComp: PluginizedComponent<ADependency, BExtension, SomeNonCoreComponent>, Stuff {
var tv: Tv {
return LGOLEDTv()
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import UIKit
import RIBs; import Foundation

class ANonCoreComponent: NonCoreComponent<EmptyDependency> {}

protocol BExtension: PluginExtension {}

class SomePluginizedCompo: PluginizedComponent<ADependency, BExtension, ANonCoreComponent>, Stuff {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
//
// 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 XCTest
@testable import NeedleFramework

class DependencyLinkerTests: AbstractParserTests {

static var allTests = [
("test_process_withComponents_verifyLinkages", test_process_withComponents_verifyLinkages),
("test_process_withComponentsNoDependency_verifyError", test_process_withComponentsNoDependency_verifyError)
]

func test_process_withComponents_verifyLinkages() {
let component = ASTComponent(name: "SomeComp", dependencyProtocolName: "ItsDependency", properties: [], expressionCallTypeNames: [])
let dependency = Dependency(name: "ItsDependency", properties: [])

let linker = DependencyLinker(components: [component], dependencies: [dependency])

try! linker.process()

XCTAssertEqual(component.dependencyProtocol, dependency)
}

func test_process_withComponentsNoDependency_verifyError() {
let component = ASTComponent(name: "SomeComp", dependencyProtocolName: "ItsDependency", properties: [], expressionCallTypeNames: [])
let dependency = Dependency(name: "WrongDep", properties: [])

let linker = DependencyLinker(components: [component], dependencies: [dependency])

do {
try linker.process()
XCTFail()
} catch ProcessingError.fail(_) {
} catch {
XCTFail()
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
//
// 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 XCTest
@testable import NeedleFramework

class ParentLinkerTests: AbstractParserTests {

static var allTests = [
("test_process_withComponents_verifyLinkages", test_process_withComponents_verifyLinkages),
]

func test_process_withComponents_verifyLinkages() {
let parentComponent = ASTComponent(name: "ParentComp", dependencyProtocolName: "Doesn't matter", properties: [], expressionCallTypeNames: ["ChildComp", "someOtherStuff"])
let childComp = ASTComponent(name: "ChildComp", dependencyProtocolName: "Still doesn't matter", properties: [], expressionCallTypeNames: [])

let linker = ParentLinker(components: [parentComponent, childComp])
try! linker.process()

XCTAssertEqual(childComp.parents.count, 1)
XCTAssertTrue(childComp.parents[0] === parentComponent)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
//
// 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 XCTest
@testable import NeedleFramework

class NonCoreComponentLinkerTests: AbstractParserTests {

static var allTests = [
("test_process_withComponents_verifyLinkages", test_process_withComponents_verifyLinkages),
("test_process_withComponentsNoNonCoreComp_verifyError", test_process_withComponentsNoNonCoreComp_verifyError),
]

func test_process_withComponents_verifyLinkages() {
let data = ASTComponent(name: "SomePluginizedComp", dependencyProtocolName: "Doesn't matter", properties: [], expressionCallTypeNames: [])
let pluginizedComp = PluginizableASTComponent(data: data, pluginExtensionType: "Doesn't matter", nonCoreComponentType: "SomeComp")
let nonCoreComponent = ASTComponent(name: "SomeComp", dependencyProtocolName: "ItsDependency", properties: [], expressionCallTypeNames: [])

let linker = NonCoreComponentLinker(pluginizableComponents: [pluginizedComp], nonCoreComponents: [nonCoreComponent])

try! linker.process()

XCTAssertTrue(pluginizedComp.nonCoreComponent === nonCoreComponent)
}

func test_process_withComponentsNoNonCoreComp_verifyError() {
let data = ASTComponent(name: "SomePluginizedComp", dependencyProtocolName: "Doesn't matter", properties: [], expressionCallTypeNames: [])
let pluginizedComp = PluginizableASTComponent(data: data, pluginExtensionType: "Doesn't matter", nonCoreComponentType: "SomeComp")
let nonCoreComponent = ASTComponent(name: "WrongNonCoreComp", dependencyProtocolName: "ItsDependency", properties: [], expressionCallTypeNames: [])

let linker = NonCoreComponentLinker(pluginizableComponents: [pluginizedComp], nonCoreComponents: [nonCoreComponent])

do {
try linker.process()
XCTFail()
} catch ProcessingError.fail(_) {
} catch {
XCTFail()
}
}
}
Loading

0 comments on commit 76b0be5

Please sign in to comment.