Skip to content

Commit

Permalink
Merge pull request #69 from raptorxcz/feature/struct-stubs
Browse files Browse the repository at this point in the history
Extend struct stub options
  • Loading branch information
raptorxcz authored Jun 20, 2024
2 parents 511d162 + 1ac4bbe commit bd37d2c
Show file tree
Hide file tree
Showing 9 changed files with 197 additions and 16 deletions.
4 changes: 3 additions & 1 deletion Sources/Rubicon/Domain/StructDeclaration.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
struct StructDeclaration: Equatable {
public struct StructDeclaration: Equatable {
let name: String
let variables: [VarDeclaration]
public let notes: [String]
public let accessLevel: AccessLevel
}
4 changes: 3 additions & 1 deletion Sources/Rubicon/Generator/ProtocolGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ final class ProtocolGeneratorImpl: ProtocolGenerator {
}

private func makeParentClause(from declaration: ProtocolDeclaration, stub: String) -> String {
if let parent = declaration.parents.first, declaration.parents.count == 1 {
let normalizedParents = declaration.parents.filter { $0 != "AnyObject" }

if let parent = normalizedParents.first, normalizedParents.count == 1 {
return ": \(parent)\(stub), "
} else {
return ": "
Expand Down
6 changes: 5 additions & 1 deletion Sources/Rubicon/Generator/SpyGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,11 @@ final class SpyGenerator {
let name = functionNameGenerator.makeStructUniqueName(for: declaration, in: protocolDeclaration.functions)
let structDeclaration = StructDeclaration(
name: name,
variables: declaration.arguments.map{ VarDeclaration(isConstant: true, identifier: $0.name, type: $0.type) }
variables: declaration.arguments.map{
VarDeclaration(isConstant: true, identifier: $0.name, type: $0.type)
},
notes: [],
accessLevel: .internal
)
return structGenerator.makeCode(from: structDeclaration)
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/Rubicon/Generator/StructStubGenerator.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
protocol StructStubGenerator {
public protocol StructStubGenerator {
func generate(from structType: StructDeclaration, functionName: String) -> String
}

Expand Down
12 changes: 8 additions & 4 deletions Sources/Rubicon/Integration/Rubicon.swift
Original file line number Diff line number Diff line change
Expand Up @@ -229,9 +229,7 @@ public final class Rubicon {
}

public func makeStructStub(code: String, configuration: StructStubConfiguration) -> [String] {
let parser = StructParserImpl(
varParser: makeVarParser()
)
let parser = makeStuctParser()
let structStubGenerator = makeStructStubGenerator(for: configuration)

do {
Expand All @@ -242,6 +240,12 @@ public final class Rubicon {
}
}

public func makeStuctParser() -> StructParser {
return StructParserImpl(
varParser: makeVarParser()
)
}

private func makeVarParser() -> VarDeclarationParser {
return VarDeclarationParserImpl(
typeDeclarationParser: makeTypeParser()
Expand All @@ -252,7 +256,7 @@ public final class Rubicon {
return TypeDeclarationParserImpl()
}

private func makeStructStubGenerator(for configuration: StructStubConfiguration) -> StructStubGenerator {
public func makeStructStubGenerator(for configuration: StructStubConfiguration) -> StructStubGenerator {
let dependencies = makeDependencies(
for: configuration.accessLevel,
indentStep: configuration.indentStep
Expand Down
59 changes: 52 additions & 7 deletions Sources/Rubicon/Syntactic analysis/StructParser.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import SwiftParser
import SwiftSyntax

protocol StructParser {
public protocol StructParser {
func parse(text: String) throws -> [StructDeclaration]
}

Expand All @@ -17,7 +17,8 @@ final class StructParserImpl: StructParser {
func parse(text: String) throws -> [StructDeclaration] {
let text = SwiftParser.Parser.parse(source: text)
let visitor = StructVisitor(
varParser: varParser
varParser: varParser,
nestedInItemsNames: []
)
return visitor.execute(node: text)
}
Expand All @@ -26,11 +27,14 @@ final class StructParserImpl: StructParser {
private class StructVisitor: SyntaxVisitor {
private let varParser: VarDeclarationParser
private var result = [StructDeclaration]()
private var nestedInItemsNames: [String]

public init(
varParser: VarDeclarationParser
varParser: VarDeclarationParser,
nestedInItemsNames: [String]
) {
self.varParser = varParser
self.nestedInItemsNames = nestedInItemsNames
super.init(viewMode: .sourceAccurate)
}

Expand All @@ -41,14 +45,42 @@ private class StructVisitor: SyntaxVisitor {

override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind {
let varsVisitor = VariablesVisitor(varParser: varParser)
let name = node.name.text

result.append(
StructDeclaration(
name: node.name.text,
variables: varsVisitor.execute(node: node)
name: (nestedInItemsNames + [name]).joined(separator: "."),
variables: varsVisitor.execute(node: node),
notes: node.leadingTrivia.pieces.compactMap(makeLineComment),
accessLevel: parseAccessLevel(from: node)
)
)
return .visitChildren

let subVisitor = StructVisitor(varParser: varParser, nestedInItemsNames: nestedInItemsNames + [name])
result += subVisitor.execute(node: node.memberBlock.members)

return .skipChildren
}

private func parseAccessLevel(from node: StructDeclSyntax) -> AccessLevel {
let modifiers = node.modifiers.map { $0.name.tokenKind }

if modifiers.contains(.keyword(.public)) {
return .public
} else if modifiers.contains(.keyword(.private)) {
return .private
} else {
return .internal
}
}

private func makeLineComment(from triviaPiece: TriviaPiece) -> String? {
switch triviaPiece {
case .lineComment(let text):
text
default:
nil
}
}

private func parseParent(from inheridedTypeSyntax: InheritedTypeSyntax) -> String? {
Expand All @@ -58,8 +90,21 @@ private class StructVisitor: SyntaxVisitor {

return type.name.text
}
}

override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind {
let name = node.name.text
let subVisitor = StructVisitor(varParser: varParser, nestedInItemsNames: nestedInItemsNames + [name])
result += subVisitor.execute(node: node.memberBlock.members)
return .skipChildren
}

override func visit(_ node: EnumDeclSyntax) -> SyntaxVisitorContinueKind {
let name = node.name.text
let subVisitor = StructVisitor(varParser: varParser, nestedInItemsNames: nestedInItemsNames + [name])
result += subVisitor.execute(node: node.memberBlock.members)
return .skipChildren
}
}

private class VariablesVisitor: SyntaxVisitor {
private let varParser: VarDeclarationParser
Expand Down
11 changes: 11 additions & 0 deletions Tests/RubiconTests/Generator/ProtocolGeneratorTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,17 @@ final class ProtocolGeneratorTests: XCTestCase {
])
XCTAssertEqual(accessLevelGeneratorSpy.makeClassAccessLevelCount, 1)
}

func test_givenProtocolAnyObjectParent_whenGenerate_thenGenerateCode() {
let code = sut.makeProtocol(from: .makeStub(parents: ["AnyObject"]), stub: "Dummy", content: ["content"])

equal(code, rows: [
"accessLevel final class NameDummy: Name {",
"-content",
"}",
])
XCTAssertEqual(accessLevelGeneratorSpy.makeClassAccessLevelCount, 1)
}
}

func equal(string: String?, rows: [String], line: UInt = #line, file: StaticString = #file) {
Expand Down
7 changes: 6 additions & 1 deletion Tests/RubiconTests/Generator/StructGeneratorTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,12 @@ extension StructDeclaration {
) -> StructDeclaration {
return StructDeclaration(
name: "StructName",
variables: variables
variables: variables,
notes: [
"note1",
"note2"
],
accessLevel: .internal
)
}
}
108 changes: 108 additions & 0 deletions Tests/RubiconTests/Syntactic analysis/StructParserTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ final class StructParserTests: XCTestCase {
XCTAssertEqual(structs.count, 1)
XCTAssertEqual(structs.first?.name, "A")
XCTAssertEqual(structs.first?.variables.count, 0)
XCTAssertEqual(structs.first?.notes, [])
XCTAssertEqual(structs.first?.accessLevel, .internal)
}

func test_givenStructWithVariables_whenParse_thenReturnStruct() throws {
Expand All @@ -54,4 +56,110 @@ final class StructParserTests: XCTestCase {
XCTAssertEqual(varParserSpy.parse.count, 2)
XCTAssertEqual(varParserSpy.parse.first?.node.description, "\n var a: Int")
}

func test_givenStructNestedInClass_whenParse_thenReturnStruct() throws {
let text = """
class B {
struct A {
}
}
"""

let structs = try sut.parse(text: text)

XCTAssertEqual(structs.count, 1)
XCTAssertEqual(structs.first?.name, "B.A")
XCTAssertEqual(structs.first?.variables.count, 0)
}

func test_givenStructNestedInEnum_whenParse_thenReturnStruct() throws {
let text = """
enum B {
struct A {
}
}
"""

let structs = try sut.parse(text: text)

XCTAssertEqual(structs.count, 1)
XCTAssertEqual(structs.first?.name, "B.A")
XCTAssertEqual(structs.first?.variables.count, 0)
}

func test_givenStructNestedInStruct_whenParse_thenReturnStruct() throws {
let text = """
struct B {
struct A {
}
}
"""

let structs = try sut.parse(text: text)

XCTAssertEqual(structs.count, 2)
XCTAssertEqual(structs.first?.name, "B")
XCTAssertEqual(structs.first?.variables.count, 0)
XCTAssertEqual(structs.last?.name, "B.A")
XCTAssertEqual(structs.last?.variables.count, 0)
}

func test_givenStructNestedMultipleItems_whenParse_thenReturnStruct() throws {
let text = """
class B {
enum C {
struct D {
struct A {
}
}
}
}
"""

let structs = try sut.parse(text: text)

XCTAssertEqual(structs.count, 2)
XCTAssertEqual(structs.first?.name, "B.C.D")
XCTAssertEqual(structs.first?.variables.count, 0)
XCTAssertEqual(structs.last?.name, "B.C.D.A")
}

func test_givenEmptyWithNote_whenParse_thenReturnStruct() throws {
let text = """
// NOTE
struct A {
}
"""

let structs = try sut.parse(text: text)

XCTAssertEqual(structs.count, 1)
XCTAssertEqual(structs.first?.name, "A")
XCTAssertEqual(structs.first?.variables.count, 0)
XCTAssertEqual(structs.first?.notes, ["// NOTE"])
}

func test_givenPublicStruct_whenParse_thenReturnStruct() throws {
let text = """
public struct A {
}
"""

let structs = try sut.parse(text: text)


XCTAssertEqual(structs.first?.accessLevel, .public)
}

func test_givenPrivateStruct_whenParse_thenReturnStruct() throws {
let text = """
private struct A {
}
"""

let structs = try sut.parse(text: text)


XCTAssertEqual(structs.first?.accessLevel, .private)
}
}

0 comments on commit bd37d2c

Please sign in to comment.