From 8517ff19eb6788af094ad3eb92e7f905a7a6421a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kry=C5=A1tof=20Mat=C4=9Bj?= Date: Sat, 11 May 2024 22:24:06 +0200 Subject: [PATCH] Add fixtures generator --- .../RubiconApp.xcodeproj/project.pbxproj | 4 +- .../Rubicon/Domain/ArgumentDeclaration.swift | 1 + .../Rubicon/Domain/FunctionDeclaration.swift | 17 +++ .../Rubicon/Generator/ArgumentGenerator.swift | 18 ++- .../Generator/DefaultValueGenerator.swift | 36 ++++++ .../Rubicon/Generator/DummyGenerator.swift | 6 +- .../Generator/ExtensionGenerator.swift | 25 +++++ .../Rubicon/Generator/FunctionGenerator.swift | 41 +++++-- Sources/Rubicon/Generator/InitGenerator.swift | 6 +- Sources/Rubicon/Generator/SpyGenerator.swift | 6 +- .../Generator/StructStubGenerator.swift | 73 ++++++++++++ Sources/Rubicon/Generator/StubGenerator.swift | 6 +- Sources/Rubicon/Integration/Rubicon.swift | 85 ++++++++++++-- .../Syntactic analysis/StructParser.swift | 85 ++++++++++++++ .../Generator/ArgumentGeneratorTests.swift | 23 ++++ .../DefaultValueGeneratorTests.swift | 49 +++++++++ .../Generator/DummyGeneratorTests.swift | 7 +- .../Generator/ExtensionGeneratorTests.swift | 30 +++++ .../Generator/FunctionGeneratorTests.swift | 48 ++++++-- .../Generator/InitGeneratorTests.swift | 3 +- .../Generator/StructStubGeneratorTests.swift | 104 ++++++++++++++++++ .../StructStubIntegrationTests.swift | 53 +++++++++ .../FunctionDeclarationParserTests.swift | 6 +- .../ProtocolParserTests.swift | 2 + .../StructParserTests.swift | 57 ++++++++++ 25 files changed, 753 insertions(+), 38 deletions(-) create mode 100644 Sources/Rubicon/Generator/DefaultValueGenerator.swift create mode 100644 Sources/Rubicon/Generator/ExtensionGenerator.swift create mode 100644 Sources/Rubicon/Generator/StructStubGenerator.swift create mode 100644 Sources/Rubicon/Syntactic analysis/StructParser.swift create mode 100644 Tests/RubiconTests/Generator/DefaultValueGeneratorTests.swift create mode 100644 Tests/RubiconTests/Generator/ExtensionGeneratorTests.swift create mode 100644 Tests/RubiconTests/Generator/StructStubGeneratorTests.swift create mode 100644 Tests/RubiconTests/Integration/StructStubIntegrationTests.swift create mode 100644 Tests/RubiconTests/Syntactic analysis/StructParserTests.swift diff --git a/Application/RubiconApp.xcodeproj/project.pbxproj b/Application/RubiconApp.xcodeproj/project.pbxproj index b30b275..e0e597e 100644 --- a/Application/RubiconApp.xcodeproj/project.pbxproj +++ b/Application/RubiconApp.xcodeproj/project.pbxproj @@ -491,7 +491,7 @@ "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Mac Developer"; CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 46; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=macosx*]" = 473264X5JK; GENERATE_INFOPLIST_FILE = YES; @@ -525,7 +525,7 @@ "CODE_SIGN_IDENTITY[sdk=macosx*]" = "3rd Party Mac Developer Application"; CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 46; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=macosx*]" = 473264X5JK; GENERATE_INFOPLIST_FILE = YES; diff --git a/Sources/Rubicon/Domain/ArgumentDeclaration.swift b/Sources/Rubicon/Domain/ArgumentDeclaration.swift index 6746561..9460fc4 100644 --- a/Sources/Rubicon/Domain/ArgumentDeclaration.swift +++ b/Sources/Rubicon/Domain/ArgumentDeclaration.swift @@ -10,4 +10,5 @@ struct ArgumentDeclaration: Equatable { var label: String? var name: String var type: TypeDeclaration + var defaultValue: String? } diff --git a/Sources/Rubicon/Domain/FunctionDeclaration.swift b/Sources/Rubicon/Domain/FunctionDeclaration.swift index e816947..01a859e 100644 --- a/Sources/Rubicon/Domain/FunctionDeclaration.swift +++ b/Sources/Rubicon/Domain/FunctionDeclaration.swift @@ -11,5 +11,22 @@ struct FunctionDeclaration: Equatable { let arguments: [ArgumentDeclaration] let isThrowing: Bool let isAsync: Bool + let isStatic: Bool let returnType: TypeDeclaration? + + init( + name: String, + arguments: [ArgumentDeclaration], + isThrowing: Bool, + isAsync: Bool, + isStatic: Bool = false, + returnType: TypeDeclaration? + ) { + self.name = name + self.arguments = arguments + self.isThrowing = isThrowing + self.isAsync = isAsync + self.isStatic = isStatic + self.returnType = returnType + } } diff --git a/Sources/Rubicon/Generator/ArgumentGenerator.swift b/Sources/Rubicon/Generator/ArgumentGenerator.swift index 34b949d..be272ca 100644 --- a/Sources/Rubicon/Generator/ArgumentGenerator.swift +++ b/Sources/Rubicon/Generator/ArgumentGenerator.swift @@ -10,10 +10,24 @@ final class ArgumentGeneratorImpl: ArgumentGenerator { } func makeCode(from declaration: ArgumentDeclaration) -> String { + let label = makeLabel(from: declaration) + let defaultValue = makeDefaultValue(from: declaration) + return "\(label)\(declaration.name): \(typeGenerator.makeArgumentCode(from: declaration.type))\(defaultValue)" + } + + private func makeLabel(from declaration: ArgumentDeclaration) -> String { if let label = declaration.label { - return "\(label) \(declaration.name): \(typeGenerator.makeArgumentCode(from: declaration.type))" + return "\(label) " + } else { + return "" + } + } + + private func makeDefaultValue(from declaration: ArgumentDeclaration) -> String { + if let defaultValue = declaration.defaultValue { + return " = \(defaultValue)" } else { - return "\(declaration.name): \(typeGenerator.makeArgumentCode(from: declaration.type))" + return "" } } } diff --git a/Sources/Rubicon/Generator/DefaultValueGenerator.swift b/Sources/Rubicon/Generator/DefaultValueGenerator.swift new file mode 100644 index 0000000..032d1c5 --- /dev/null +++ b/Sources/Rubicon/Generator/DefaultValueGenerator.swift @@ -0,0 +1,36 @@ +import Foundation + +protocol DefaultValueGenerator { + func makeDefaultValue(for varDeclaration: VarDeclaration) -> String +} + +final class DefaultValueGeneratorImpl: DefaultValueGenerator { + private let unknownDefaultType: String + private let customDefaultTypes: [String: String] + + init(unknownDefaultType: String, customDefaultTypes: [String: String]) { + self.unknownDefaultType = unknownDefaultType + self.customDefaultTypes = customDefaultTypes + } + + func makeDefaultValue(for varDeclaration: VarDeclaration) -> String { + if let customValue = customDefaultTypes[varDeclaration.type.name] { + return customValue + } + + guard !varDeclaration.type.isOptional else { + return "nil" + } + + switch varDeclaration.type.name { + case "String": + return "\"\(varDeclaration.identifier)\"" + case "Int": + return "0" + case "Bool": + return "false" + default: + return unknownDefaultType + } + } +} diff --git a/Sources/Rubicon/Generator/DummyGenerator.swift b/Sources/Rubicon/Generator/DummyGenerator.swift index 62fc963..7e958f5 100644 --- a/Sources/Rubicon/Generator/DummyGenerator.swift +++ b/Sources/Rubicon/Generator/DummyGenerator.swift @@ -38,7 +38,11 @@ final class DummyGenerator { content.append(initGenerator.makeCode(with: [], isAddingDefaultValueToOptionalsEnabled: false)) content += protocolType.functions.map { - return functionGenerator.makeCode(from: $0, content: ["fatalError()"]) + return functionGenerator.makeCode( + from: $0, + content: ["fatalError()"], + isEachArgumentOnNewLineEnabled: false + ) } let normalizedContent = content.filter({ !$0.isEmpty }) diff --git a/Sources/Rubicon/Generator/ExtensionGenerator.swift b/Sources/Rubicon/Generator/ExtensionGenerator.swift new file mode 100644 index 0000000..e3fbe71 --- /dev/null +++ b/Sources/Rubicon/Generator/ExtensionGenerator.swift @@ -0,0 +1,25 @@ +protocol ExtensionGenerator { + func make(name: String, content: [String]) -> [String] +} + +final class ExtensionGeneratorImpl: ExtensionGenerator { + private let accessLevelGenerator: AccessLevelGenerator + private let indentationGenerator: IndentationGenerator + + init( + accessLevelGenerator: AccessLevelGenerator, + indentationGenerator: IndentationGenerator + ) { + self.accessLevelGenerator = accessLevelGenerator + self.indentationGenerator = indentationGenerator + } + + func make(name: String, content: [String]) -> [String] { + let content = content.map(indentationGenerator.indenting) + return [ + "\(accessLevelGenerator.makeClassAccessLevel())extension \(name) {" + ] + content + [ + "}", + ] + } +} diff --git a/Sources/Rubicon/Generator/FunctionGenerator.swift b/Sources/Rubicon/Generator/FunctionGenerator.swift index 181fcbb..04cc8db 100644 --- a/Sources/Rubicon/Generator/FunctionGenerator.swift +++ b/Sources/Rubicon/Generator/FunctionGenerator.swift @@ -1,5 +1,5 @@ protocol FunctionGenerator { - func makeCode(from declaration: FunctionDeclaration, content: [String]) -> [String] + func makeCode(from declaration: FunctionDeclaration, content: [String], isEachArgumentOnNewLineEnabled: Bool) -> [String] } final class FunctionGeneratorImpl: FunctionGenerator { @@ -20,17 +20,44 @@ final class FunctionGeneratorImpl: FunctionGenerator { self.indentationGenerator = indentationGenerator } - func makeCode(from declaration: FunctionDeclaration, content: [String]) -> [String] { - let returnString = makeReturn(from: declaration) - let arguments = declaration.arguments.map(argumentGenerator.makeCode(from:)).joined(separator: ", ") + func makeCode(from declaration: FunctionDeclaration, content: [String], isEachArgumentOnNewLineEnabled: Bool) -> [String] { let content = content.map(indentationGenerator.indenting) - return [ - "\(accessLevelGenerator.makeContentAccessLevel())func \(declaration.name)(\(arguments)) \(returnString){", - ] + content + [ + let arguments = makeArguments(from: declaration) + let header = makeHeader(from: declaration, arguments: arguments, isEachArgumentOnNewLineEnabled: isEachArgumentOnNewLineEnabled) + let normalizedHeader = isEachArgumentOnNewLineEnabled ? header : [header.joined()] + return normalizedHeader + content + [ "}", ] } + private func makeArguments(from declaration: FunctionDeclaration) -> [String] { + let arguments = declaration.arguments.map(argumentGenerator.makeCode(from:)) + var normalizedArguments = [String]() + + for (index, argument) in arguments.enumerated() { + normalizedArguments.append(index == arguments.endIndex - 1 ? argument : argument + ",") + } + + return normalizedArguments + } + + private func makeHeader(from declaration: FunctionDeclaration, arguments: [String], isEachArgumentOnNewLineEnabled: Bool) -> [String] { + let staticString = declaration.isStatic ? "static " : "" + let returnString = makeReturn(from: declaration) + let prefix = "\(accessLevelGenerator.makeContentAccessLevel())\(staticString)func \(declaration.name)(" + let suffix = ") \(returnString){" + + if isEachArgumentOnNewLineEnabled { + return [prefix] + arguments.map(indentationGenerator.indenting) + [suffix] + } else { + return [[ + prefix, + arguments.joined(separator: " "), + suffix + ].joined()] + } + } + private func makeReturn(from declaration: FunctionDeclaration) -> String { var result = "" diff --git a/Sources/Rubicon/Generator/InitGenerator.swift b/Sources/Rubicon/Generator/InitGenerator.swift index 81a67c8..3fc4973 100644 --- a/Sources/Rubicon/Generator/InitGenerator.swift +++ b/Sources/Rubicon/Generator/InitGenerator.swift @@ -44,10 +44,10 @@ final class InitGeneratorImpl: InitGenerator { } private func makeInitArgument(from variable: VarDeclaration, isAddingDefaultValueToOptionalsEnabled: Bool) -> String { - let defaultValue = isAddingDefaultValueToOptionalsEnabled && variable.type.isOptional ? " = nil" : "" + let defaultValue = isAddingDefaultValueToOptionalsEnabled && variable.type.isOptional ? "nil" : nil let prefix = variable.type.isOptional ? [] : variable.type.prefix let type = TypeDeclaration(name: variable.type.name, isOptional: variable.type.isOptional, prefix: prefix) - let declaration = ArgumentDeclaration(name: variable.identifier, type: type) - return argumentGenerator.makeCode(from: declaration) + defaultValue + let declaration = ArgumentDeclaration(name: variable.identifier, type: type, defaultValue: defaultValue) + return argumentGenerator.makeCode(from: declaration) } } diff --git a/Sources/Rubicon/Generator/SpyGenerator.swift b/Sources/Rubicon/Generator/SpyGenerator.swift index 620c932..3baf8a9 100644 --- a/Sources/Rubicon/Generator/SpyGenerator.swift +++ b/Sources/Rubicon/Generator/SpyGenerator.swift @@ -143,6 +143,10 @@ final class SpyGenerator { content.append("return \(name)Return") } - return functionGenerator.makeCode(from: declaration, content: content) + return functionGenerator.makeCode( + from: declaration, + content: content, + isEachArgumentOnNewLineEnabled: false + ) } } diff --git a/Sources/Rubicon/Generator/StructStubGenerator.swift b/Sources/Rubicon/Generator/StructStubGenerator.swift new file mode 100644 index 0000000..d4dcacc --- /dev/null +++ b/Sources/Rubicon/Generator/StructStubGenerator.swift @@ -0,0 +1,73 @@ +protocol StructStubGenerator { + func generate(from structType: StructDeclaration, functionName: String) -> String +} + +final class StructStubGeneratorImpl: StructStubGenerator { + private let extensionGenerator: ExtensionGenerator + private let functionGenerator: FunctionGenerator + private let indentationGenerator: IndentationGenerator + private let defaultValueGenerator: DefaultValueGenerator + + init( + extensionGenerator: ExtensionGenerator, + functionGenerator: FunctionGenerator, + indentationGenerator: IndentationGenerator, + defaultValueGenerator: DefaultValueGenerator + ) { + self.extensionGenerator = extensionGenerator + self.functionGenerator = functionGenerator + self.indentationGenerator = indentationGenerator + self.defaultValueGenerator = defaultValueGenerator + } + + func generate(from structType: StructDeclaration, functionName: String) -> String { + let content = generateBody(from: structType, functionName: functionName) + return extensionGenerator.make( + name: structType.name, + content: content + ).joined(separator: "\n") + "\n" + } + + private func generateBody(from structType: StructDeclaration, functionName: String) -> [String] { + let content = makeContent(from: structType) + let functionDeclaration = FunctionDeclaration( + name: functionName, + arguments: structType.variables.map(makeArgument), + isThrowing: false, + isAsync: false, + isStatic: true, + returnType: TypeDeclaration(name: structType.name, isOptional: false, prefix: []) + ) + return functionGenerator.makeCode( + from: functionDeclaration, + content: content, + isEachArgumentOnNewLineEnabled: true + ) + } + + private func makeContent(from structType: StructDeclaration) -> [String] { + let variables = structType.variables.enumerated().map{ makeAssigment(of: $1, isLast: $0 == structType.variables.endIndex - 1) } + + guard !variables.isEmpty else { + return ["return \(structType.name)()"] + } + + return [ + "return \(structType.name)(" + ] + variables.map(indentationGenerator.indenting) + [ + ")" + ] + } + + private func makeAssigment(of variable: VarDeclaration, isLast: Bool) -> String { + return "\(variable.identifier): \(variable.identifier)\(isLast ? "" : ",")" + } + + private func makeArgument(from varDeclaration: VarDeclaration) -> ArgumentDeclaration { + ArgumentDeclaration( + name: varDeclaration.identifier, + type: varDeclaration.type, + defaultValue: defaultValueGenerator.makeDefaultValue(for: varDeclaration) + ) + } +} diff --git a/Sources/Rubicon/Generator/StubGenerator.swift b/Sources/Rubicon/Generator/StubGenerator.swift index 8b5c281..28a1366 100644 --- a/Sources/Rubicon/Generator/StubGenerator.swift +++ b/Sources/Rubicon/Generator/StubGenerator.swift @@ -89,6 +89,10 @@ final class StubGenerator { content.append("return \(name)Return") } - return functionGenerator.makeCode(from: declaration, content: content) + return functionGenerator.makeCode( + from: declaration, + content: content, + isEachArgumentOnNewLineEnabled: false + ) } } diff --git a/Sources/Rubicon/Integration/Rubicon.swift b/Sources/Rubicon/Integration/Rubicon.swift index 58129aa..83b7a66 100644 --- a/Sources/Rubicon/Integration/Rubicon.swift +++ b/Sources/Rubicon/Integration/Rubicon.swift @@ -33,6 +33,28 @@ public struct StubConfiguration { } } +public struct StructStubConfiguration { + public let accessLevel: AccessLevel + public let indentStep: String + public let functionName: String + public let defaultValue: String + public let customDefaultValues: [String: String] + + public init( + accessLevel: AccessLevel, + indentStep: String, + functionName: String, + defaultValue: String, + customDefaultValues: [String : String] + ) { + self.accessLevel = accessLevel + self.indentStep = indentStep + self.functionName = functionName + self.defaultValue = defaultValue + self.customDefaultValues = customDefaultValues + } +} + public final class Rubicon { public init() {} @@ -47,6 +69,7 @@ public final class Rubicon { let indentationGenerator: IndentationGenerator let initGenerator: InitGenerator let structGenerator: StructGenerator + let typeGenerator: TypeGenerator } public func makeDummy(code: String, accessLevel: AccessLevel, indentStep: String) -> [String] { @@ -179,25 +202,23 @@ public final class Rubicon { functionNameGenerator: functionNameGenerator, indentationGenerator: indentationGenerator, initGenerator: initGenerator, - structGenerator: structGenerator + structGenerator: structGenerator, + typeGenerator: typeGenerator ) } private func makeParser() -> ProtocolParser { - let typeDeclarationParser = TypeDeclarationParserImpl() + let argumentDeclarationParser = ArgumentDeclarationParserImpl( - typeDeclarationParser: typeDeclarationParser + typeDeclarationParser: makeTypeParser() ) let functionParser = FunctionDeclarationParserImpl( - typeDeclarationParser: typeDeclarationParser, + typeDeclarationParser: makeTypeParser(), argumentDeclarationParser: argumentDeclarationParser ) - let varParser = VarDeclarationParserImpl( - typeDeclarationParser: typeDeclarationParser - ) return ProtocolParserImpl( functionParser: functionParser, - varParser: varParser + varParser: makeVarParser() ) } @@ -206,4 +227,52 @@ public final class Rubicon { let tearDownInteractor = TearDownInteractor(nilableVariablesParser: parser) return try tearDownInteractor.execute(text: text, spacing: spacing) } + + public func makeStructStub(code: String, configuration: StructStubConfiguration) -> [String] { + let parser = StructParserImpl( + varParser: makeVarParser() + ) + let structStubGenerator = makeStructStubGenerator(for: configuration) + + do { + let structDeclarations = try parser.parse(text: code) + return structDeclarations.map{ structStubGenerator.generate(from: $0, functionName: configuration.functionName) } + } catch { + return [] + } + } + + private func makeVarParser() -> VarDeclarationParser { + return VarDeclarationParserImpl( + typeDeclarationParser: makeTypeParser() + ) + } + + private func makeTypeParser() -> TypeDeclarationParser { + return TypeDeclarationParserImpl() + } + + private func makeStructStubGenerator(for configuration: StructStubConfiguration) -> StructStubGenerator { + let dependencies = makeDependencies( + for: configuration.accessLevel, + indentStep: configuration.indentStep + ) + return StructStubGeneratorImpl( + extensionGenerator: ExtensionGeneratorImpl( + accessLevelGenerator: dependencies.accessLevelGenerator, + indentationGenerator: dependencies.indentationGenerator + ), + functionGenerator: FunctionGeneratorImpl( + accessLevelGenerator: dependencies.accessLevelGenerator, + typeGenerator: dependencies.typeGenerator, + argumentGenerator: dependencies.argumentGenerator, + indentationGenerator: dependencies.indentationGenerator + ), + indentationGenerator: dependencies.indentationGenerator, + defaultValueGenerator: DefaultValueGeneratorImpl( + unknownDefaultType: configuration.defaultValue, + customDefaultTypes: configuration.customDefaultValues + ) + ) + } } diff --git a/Sources/Rubicon/Syntactic analysis/StructParser.swift b/Sources/Rubicon/Syntactic analysis/StructParser.swift new file mode 100644 index 0000000..7379f6a --- /dev/null +++ b/Sources/Rubicon/Syntactic analysis/StructParser.swift @@ -0,0 +1,85 @@ +import SwiftParser +import SwiftSyntax + +protocol StructParser { + func parse(text: String) throws -> [StructDeclaration] +} + +final class StructParserImpl: StructParser { + private let varParser: VarDeclarationParser + + init( + varParser: VarDeclarationParser + ) { + self.varParser = varParser + } + + func parse(text: String) throws -> [StructDeclaration] { + let text = SwiftParser.Parser.parse(source: text) + let visitor = StructVisitor( + varParser: varParser + ) + return visitor.execute(node: text) + } +} + +private class StructVisitor: SyntaxVisitor { + private let varParser: VarDeclarationParser + private var result = [StructDeclaration]() + + public init( + varParser: VarDeclarationParser + ) { + self.varParser = varParser + super.init(viewMode: .sourceAccurate) + } + + func execute(node: some SyntaxProtocol) -> [StructDeclaration] { + walk(node) + return result + } + + override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind { + let varsVisitor = VariablesVisitor(varParser: varParser) + + result.append( + StructDeclaration( + name: node.name.text, + variables: varsVisitor.execute(node: node) + ) + ) + return .visitChildren + } + + private func parseParent(from inheridedTypeSyntax: InheritedTypeSyntax) -> String? { + guard let type = inheridedTypeSyntax.type.as(IdentifierTypeSyntax.self) else { + return nil + } + + return type.name.text + } +} + + +private class VariablesVisitor: SyntaxVisitor { + private let varParser: VarDeclarationParser + private var result = [VarDeclaration]() + + public init(varParser: VarDeclarationParser) { + self.varParser = varParser + super.init(viewMode: .sourceAccurate) + } + + func execute(node: some SyntaxProtocol) -> [VarDeclaration] { + walk(node) + return result + } + + override func visit(_ node: VariableDeclSyntax) -> SyntaxVisitorContinueKind { + do { + let declaration = try varParser.parse(node: node) + result.append(declaration) + } catch {} + return .visitChildren + } +} diff --git a/Tests/RubiconTests/Generator/ArgumentGeneratorTests.swift b/Tests/RubiconTests/Generator/ArgumentGeneratorTests.swift index 02472d6..4dd4238 100644 --- a/Tests/RubiconTests/Generator/ArgumentGeneratorTests.swift +++ b/Tests/RubiconTests/Generator/ArgumentGeneratorTests.swift @@ -29,4 +29,27 @@ final class ArgumentGeneratorTests: XCTestCase { XCTAssertEqual(code, "label name: Type") } + + func test_givenTypeWithDefaultValue_whenMakeCode_thenReturnCode() { + let declaration = ArgumentDeclaration.makeStub( + label: nil, + defaultValue: "default" + ) + + let code = sut.makeCode(from: declaration) + + XCTAssertEqual(code, "name: Type = default") + + } + + func test_givenTypeWithDefaultValueAndLabel_whenMakeCode_thenReturnCode() { + let declaration = ArgumentDeclaration.makeStub( + defaultValue: "default" + ) + + let code = sut.makeCode(from: declaration) + + XCTAssertEqual(code, "label name: Type = default") + + } } diff --git a/Tests/RubiconTests/Generator/DefaultValueGeneratorTests.swift b/Tests/RubiconTests/Generator/DefaultValueGeneratorTests.swift new file mode 100644 index 0000000..edc9e8b --- /dev/null +++ b/Tests/RubiconTests/Generator/DefaultValueGeneratorTests.swift @@ -0,0 +1,49 @@ +@testable import Rubicon +import XCTest + +final class DefaultValueGeneratorTests: XCTestCase { + private var sut: DefaultValueGeneratorImpl! + + override func setUp() { + super.setUp() + sut = DefaultValueGeneratorImpl(unknownDefaultType: "default", customDefaultTypes: [:]) + } + + func test_givenUnknownType_whenMakeDefaultValue_thenReturnDefault() { + let value = sut.makeDefaultValue(for: .makeStub(type: .makeStub(name: "Unknown"))) + + XCTAssertEqual(value, "default") + } + + func test_givenStringType_whenMakeDefaultValue_thenReturnIdentifier() { + let value = sut.makeDefaultValue(for: .makeStub(identifier: "name", type: .makeStub(name: "String"))) + + XCTAssertEqual(value, "\"name\"") + } + + func test_givenOptionalType_whenMakeDefaultValue_thenReturnNil() { + let value = sut.makeDefaultValue(for: .makeStub(type: .makeStub(isOptional: true))) + + XCTAssertEqual(value, "nil") + } + + func test_givenIntType_whenMakeDefaultValue_thenReturnZero() { + let value = sut.makeDefaultValue(for: .makeStub(identifier: "name", type: .makeStub(name: "Int"))) + + XCTAssertEqual(value, "0") + } + + func test_givenBoolType_whenMakeDefaultValue_thenReturnFalse() { + let value = sut.makeDefaultValue(for: .makeStub(type: .makeStub(name: "Bool"))) + + XCTAssertEqual(value, "false") + } + + func test_givenCustomDefaultTypeAndCustomType_whenMakeDefaultValue_thenReturnCustomValue() { + sut = DefaultValueGeneratorImpl(unknownDefaultType: "default", customDefaultTypes: ["A": "b"]) + + let value = sut.makeDefaultValue(for: .makeStub(type: .makeStub(name: "A"))) + + XCTAssertEqual(value, "b") + } +} diff --git a/Tests/RubiconTests/Generator/DummyGeneratorTests.swift b/Tests/RubiconTests/Generator/DummyGeneratorTests.swift index 6c2a9ab..03f4226 100644 --- a/Tests/RubiconTests/Generator/DummyGeneratorTests.swift +++ b/Tests/RubiconTests/Generator/DummyGeneratorTests.swift @@ -169,17 +169,18 @@ final class FunctionGeneratorSpy: FunctionGenerator { struct MakeCode { let declaration: FunctionDeclaration let content: [String] + let isEachArgumentOnNewLineEnabled: Bool } - var makeCode = [MakeCode]() var makeCodeReturn: [String] + var makeCode = [MakeCode]() init(makeCodeReturn: [String]) { self.makeCodeReturn = makeCodeReturn } - func makeCode(from declaration: FunctionDeclaration, content: [String]) -> [String] { - let item = MakeCode(declaration: declaration, content: content) + func makeCode(from declaration: FunctionDeclaration, content: [String], isEachArgumentOnNewLineEnabled: Bool) -> [String] { + let item = MakeCode(declaration: declaration, content: content, isEachArgumentOnNewLineEnabled: isEachArgumentOnNewLineEnabled) makeCode.append(item) return makeCodeReturn } diff --git a/Tests/RubiconTests/Generator/ExtensionGeneratorTests.swift b/Tests/RubiconTests/Generator/ExtensionGeneratorTests.swift new file mode 100644 index 0000000..64307bd --- /dev/null +++ b/Tests/RubiconTests/Generator/ExtensionGeneratorTests.swift @@ -0,0 +1,30 @@ +@testable import Rubicon +import XCTest + +final class ExtensionGeneratorTests: XCTestCase { + private var accessLevelGeneratorSpy: AccessLevelGeneratorSpy! + private var indentationGeneratorStub: IndentationGeneratorStub! + private var sut: ExtensionGeneratorImpl! + + override func setUp() { + super.setUp() + accessLevelGeneratorSpy = AccessLevelGeneratorSpy(makeClassAccessLevelReturn: "accessLevel ", makeContentAccessLevelReturn: "") + indentationGeneratorStub = IndentationGeneratorStub() + sut = ExtensionGeneratorImpl( + accessLevelGenerator: accessLevelGeneratorSpy, + indentationGenerator: indentationGeneratorStub + ) + } + + func test_whenGenerate_thenGenerateCode() { + let code = sut.make(name: "String", content: ["content", "content2"]) + + equal(code, rows: [ + "accessLevel extension String {", + "-content", + "-content2", + "}", + ]) + XCTAssertEqual(accessLevelGeneratorSpy.makeClassAccessLevelCount, 1) + } +} diff --git a/Tests/RubiconTests/Generator/FunctionGeneratorTests.swift b/Tests/RubiconTests/Generator/FunctionGeneratorTests.swift index d9b3054..e7cdb58 100644 --- a/Tests/RubiconTests/Generator/FunctionGeneratorTests.swift +++ b/Tests/RubiconTests/Generator/FunctionGeneratorTests.swift @@ -25,7 +25,7 @@ final class FunctionGeneratorTests: XCTestCase { func test_whenGenerate_thenGenerateCode() { let declaration = FunctionDeclaration.makeStub() - let code = sut.makeCode(from: declaration, content: ["content", "content2"]) + let code = sut.makeCode(from: declaration, content: ["content", "content2"], isEachArgumentOnNewLineEnabled: false) equal(code, rows: [ "accessLevel func name() {", @@ -39,7 +39,7 @@ final class FunctionGeneratorTests: XCTestCase { func test_givenThrow_whenGenerate_thenGenerateCode() { let declaration = FunctionDeclaration.makeStub(isThrowing: true) - let code = sut.makeCode(from: declaration, content: ["content"]) + let code = sut.makeCode(from: declaration, content: ["content"], isEachArgumentOnNewLineEnabled: false) equal(code, rows: [ "accessLevel func name() throws {", @@ -51,7 +51,7 @@ final class FunctionGeneratorTests: XCTestCase { func test_givenAsync_whenGenerate_thenGenerateCode() { let declaration = FunctionDeclaration.makeStub(isAsync: true) - let code = sut.makeCode(from: declaration, content: ["content"]) + let code = sut.makeCode(from: declaration, content: ["content"], isEachArgumentOnNewLineEnabled: false) equal(code, rows: [ "accessLevel func name() async {", @@ -63,7 +63,7 @@ final class FunctionGeneratorTests: XCTestCase { func test_givenThrowAndAsync_whenGenerate_thenGenerateCode() { let declaration = FunctionDeclaration.makeStub(isThrowing: true, isAsync: true) - let code = sut.makeCode(from: declaration, content: ["content"]) + let code = sut.makeCode(from: declaration, content: ["content"], isEachArgumentOnNewLineEnabled: false) equal(code, rows: [ "accessLevel func name() async throws {", @@ -75,7 +75,7 @@ final class FunctionGeneratorTests: XCTestCase { func test_givenThrowAndAsyncAndResult_whenGenerate_thenGenerateCode() { let declaration = FunctionDeclaration.makeStub(isThrowing: true, isAsync: true, returnType: .makeStub()) - let code = sut.makeCode(from: declaration, content: ["content"]) + let code = sut.makeCode(from: declaration, content: ["content"], isEachArgumentOnNewLineEnabled: false) equal(code, rows: [ "accessLevel func name() async throws -> Type {", @@ -87,7 +87,7 @@ final class FunctionGeneratorTests: XCTestCase { func test_givenArgument_whenGenerate_thenGenerateCode() { let declaration = FunctionDeclaration.makeStub(arguments: [.makeStub()]) - let code = sut.makeCode(from: declaration, content: ["content"]) + let code = sut.makeCode(from: declaration, content: ["content"], isEachArgumentOnNewLineEnabled: false) equal(code, rows: [ "accessLevel func name(argument) {", @@ -101,7 +101,7 @@ final class FunctionGeneratorTests: XCTestCase { func test_givenArguments_whenGenerate_thenGenerateCode() { let declaration = FunctionDeclaration.makeStub(arguments: [.makeStub(), .makeStub(label: "a", name: "b", type: .makeStub())]) - let code = sut.makeCode(from: declaration, content: ["content"]) + let code = sut.makeCode(from: declaration, content: ["content"], isEachArgumentOnNewLineEnabled: false) equal(code, rows: [ "accessLevel func name(argument, argument) {", @@ -110,6 +110,40 @@ final class FunctionGeneratorTests: XCTestCase { ]) XCTAssertEqual(argumentGeneratorSpy.makeCode.count, 2) } + + func test_givenStaticFunction_whenGenerate_thenGenerateCode() { + let declaration = FunctionDeclaration.makeStub( + isStatic: true + ) + + let code = sut.makeCode(from: declaration, content: ["content"], isEachArgumentOnNewLineEnabled: false) + + equal(code, rows: [ + "accessLevel static func name() {", + "-content", + "}", + ]) + } + + func test_givenIsEachArgumentOnNewLineEnabled_whenGenerate_thenGenerateCode() { + let declaration = FunctionDeclaration.makeStub(arguments: [ + .makeStub(), + .makeStub(), + .makeStub(defaultValue: ".abc()") + ]) + + let code = sut.makeCode(from: declaration, content: ["content"], isEachArgumentOnNewLineEnabled: true) + + equal(code, rows: [ + "accessLevel func name(", + "-argument,", + "-argument,", + "-argument", + ") {", + "-content", + "}", + ]) + } } final class ArgumentGeneratorSpy: ArgumentGenerator { diff --git a/Tests/RubiconTests/Generator/InitGeneratorTests.swift b/Tests/RubiconTests/Generator/InitGeneratorTests.swift index 8c77f1f..d0a43f5 100644 --- a/Tests/RubiconTests/Generator/InitGeneratorTests.swift +++ b/Tests/RubiconTests/Generator/InitGeneratorTests.swift @@ -55,6 +55,7 @@ final class InitGeneratorTests: XCTestCase { ]) XCTAssertEqual(argumentGeneratorSpy.makeCode.count, 2) XCTAssertEqual(argumentGeneratorSpy.makeCode.first?.declaration, .makeStub(label: nil, name: "identifier", type: .makeStub())) + XCTAssertEqual(argumentGeneratorSpy.makeCode.first?.declaration, .makeStub(label: nil, name: "identifier", type: .makeStub())) } func test_givenOptionalVariable_whenMakeCode_thenMakeInit() { @@ -88,7 +89,7 @@ final class InitGeneratorTests: XCTestCase { ) equal(result, rows: [ - "accessLevel init(argument, argument = nil) {", + "accessLevel init(argument, argument) {", "-self.identifier = identifier", "-self.identifier = identifier", "}", diff --git a/Tests/RubiconTests/Generator/StructStubGeneratorTests.swift b/Tests/RubiconTests/Generator/StructStubGeneratorTests.swift new file mode 100644 index 0000000..a311a44 --- /dev/null +++ b/Tests/RubiconTests/Generator/StructStubGeneratorTests.swift @@ -0,0 +1,104 @@ +@testable import Rubicon +import XCTest + +final class StructStubGeneratorTests: XCTestCase { + private var extensionGeneratorSpy: ExtensionGeneratorSpy! + private var functionGeneratorSpy: FunctionGeneratorSpy! + private var indentationGeneratorStub: IndentationGeneratorStub! + private var defaultValueGeneratorSpy: DefaultValueGeneratorSpy! + private var sut: StructStubGeneratorImpl! + + override func setUp() { + super.setUp() + extensionGeneratorSpy = ExtensionGeneratorSpy(makeReturn: ["extension"]) + functionGeneratorSpy = FunctionGeneratorSpy(makeCodeReturn: ["function"]) + indentationGeneratorStub = IndentationGeneratorStub() + defaultValueGeneratorSpy = DefaultValueGeneratorSpy(makeDefaultValueReturn: "default") + sut = StructStubGeneratorImpl( + extensionGenerator: extensionGeneratorSpy, + functionGenerator: functionGeneratorSpy, + indentationGenerator: IndentationGeneratorStub(), + defaultValueGenerator: defaultValueGeneratorSpy + ) + } + + func test_givenEmptyStruct_whenMakeCode_thenReturnCode() { + let declaration = StructDeclaration.makeStub(variables: []) + + let code = sut.generate(from: declaration, functionName: "functionName") + + XCTAssertEqual(code, "extension\n") + XCTAssertEqual(extensionGeneratorSpy.make.count, 1) + XCTAssertEqual(extensionGeneratorSpy.make.first?.content, ["function"]) + XCTAssertEqual(functionGeneratorSpy.makeCode.count, 1) + XCTAssertEqual(functionGeneratorSpy.makeCode.first?.declaration.name, "functionName") + XCTAssertEqual(functionGeneratorSpy.makeCode.first?.declaration.isThrowing, false) + XCTAssertEqual(functionGeneratorSpy.makeCode.first?.declaration.isAsync, false) + XCTAssertEqual(functionGeneratorSpy.makeCode.first?.declaration.isStatic, true) + XCTAssertEqual(functionGeneratorSpy.makeCode.first?.declaration.arguments.count, 0) + XCTAssertEqual(functionGeneratorSpy.makeCode.first?.declaration.returnType, .makeStub(name: "StructName")) + XCTAssertEqual(functionGeneratorSpy.makeCode.first?.content, [ + "return StructName()" + ]) + } + + func test_givenVariableStruct_whenMakeCode_thenReturnCode() { + let declaration = StructDeclaration.makeStub(variables: [ + .makeStub(), + .makeStub() + ]) + + let code = sut.generate(from: declaration, functionName: "functionName") + + XCTAssertEqual(functionGeneratorSpy.makeCode.first?.declaration.arguments.count, 2) + XCTAssertEqual(functionGeneratorSpy.makeCode.first?.declaration.arguments.first?.name, "identifier") + XCTAssertEqual(functionGeneratorSpy.makeCode.first?.declaration.arguments.first?.label, nil) + XCTAssertEqual(functionGeneratorSpy.makeCode.first?.declaration.arguments.first?.type, .makeStub()) + XCTAssertEqual(functionGeneratorSpy.makeCode.first?.declaration.arguments.first?.defaultValue, "default") + XCTAssertEqual(functionGeneratorSpy.makeCode.first?.content, [ + "return StructName(", + "-identifier: identifier,", + "-identifier: identifier", + ")" + ]) + } +} + +final class ExtensionGeneratorSpy: ExtensionGenerator { + struct Make { + let name: String + let content: [String] + } + + var makeReturn: [String] + var make = [Make]() + + init(makeReturn: [String]) { + self.makeReturn = makeReturn + } + + func make(name: String, content: [String]) -> [String] { + let item = Make(name: name, content: content) + make.append(item) + return makeReturn + } +} + +final class DefaultValueGeneratorSpy: DefaultValueGenerator { + struct MakeDefaultValue { + let varDeclaration: VarDeclaration + } + + var makeDefaultValueReturn: String + var makeDefaultValue = [MakeDefaultValue]() + + init(makeDefaultValueReturn: String) { + self.makeDefaultValueReturn = makeDefaultValueReturn + } + + func makeDefaultValue(for varDeclaration: VarDeclaration) -> String { + let item = MakeDefaultValue(varDeclaration: varDeclaration) + makeDefaultValue.append(item) + return makeDefaultValueReturn + } +} diff --git a/Tests/RubiconTests/Integration/StructStubIntegrationTests.swift b/Tests/RubiconTests/Integration/StructStubIntegrationTests.swift new file mode 100644 index 0000000..e3851e4 --- /dev/null +++ b/Tests/RubiconTests/Integration/StructStubIntegrationTests.swift @@ -0,0 +1,53 @@ +import Rubicon +import XCTest + +final class StructStubIntegrationTests: XCTestCase { + func test_givenStruct_whenMakeStructStub_thenReturnStub() { + let code = """ + struct Car { + let lenght: Int + let name: String + let isRed: Bool + let seats: [Seat] + let driver: Driver + """ + let sut = Rubicon() + + let result = sut.makeStructStub(code: code, configuration: .makeStub()) + + print(result) + equal(string: result.first ?? "", rows: [ + "public extension Car {", + "-public static func makeStub(", + "--lenght: Int = 0,", + "--name: String = \"name\",", + "--isRed: Bool = false,", + "--seats: [Seat] = .makeStub(),", + "--driver: Driver = Carl", + "-) -> Car {", + "--return Car(", + "---lenght: lenght,", + "---name: name,", + "---isRed: isRed,", + "---seats: seats,", + "---driver: driver", + "--)", + "-}", + "}", + "", + ]) + } +} + + +extension StructStubConfiguration { + static func makeStub() -> StructStubConfiguration { + StructStubConfiguration( + accessLevel: .public, + indentStep: "-", + functionName: "makeStub", + defaultValue: ".makeStub()", + customDefaultValues: ["Driver": "Carl"] + ) + } +} diff --git a/Tests/RubiconTests/Syntactic analysis/FunctionDeclarationParserTests.swift b/Tests/RubiconTests/Syntactic analysis/FunctionDeclarationParserTests.swift index 444efb6..42d7eec 100644 --- a/Tests/RubiconTests/Syntactic analysis/FunctionDeclarationParserTests.swift +++ b/Tests/RubiconTests/Syntactic analysis/FunctionDeclarationParserTests.swift @@ -128,12 +128,14 @@ extension ArgumentDeclaration { static func makeStub( label: String? = "label", name: String = "name", - type: TypeDeclaration = .makeStub() + type: TypeDeclaration = .makeStub(), + defaultValue: String? = nil ) -> ArgumentDeclaration { return ArgumentDeclaration( label: label, name: name, - type: type + type: type, + defaultValue: defaultValue ) } } diff --git a/Tests/RubiconTests/Syntactic analysis/ProtocolParserTests.swift b/Tests/RubiconTests/Syntactic analysis/ProtocolParserTests.swift index 53af754..6631cba 100644 --- a/Tests/RubiconTests/Syntactic analysis/ProtocolParserTests.swift +++ b/Tests/RubiconTests/Syntactic analysis/ProtocolParserTests.swift @@ -131,6 +131,7 @@ extension FunctionDeclaration { arguments: [ArgumentDeclaration] = [], isThrowing: Bool = false, isAsync: Bool = false, + isStatic: Bool = false, returnType: TypeDeclaration? = nil ) -> FunctionDeclaration { return FunctionDeclaration( @@ -138,6 +139,7 @@ extension FunctionDeclaration { arguments: arguments, isThrowing: isThrowing, isAsync: isAsync, + isStatic: isStatic, returnType: returnType ) } diff --git a/Tests/RubiconTests/Syntactic analysis/StructParserTests.swift b/Tests/RubiconTests/Syntactic analysis/StructParserTests.swift new file mode 100644 index 0000000..77742f0 --- /dev/null +++ b/Tests/RubiconTests/Syntactic analysis/StructParserTests.swift @@ -0,0 +1,57 @@ +@testable import Rubicon +import SwiftParser +import SwiftSyntax +import XCTest + +final class StructParserTests: XCTestCase { + private var varParserSpy: VarDeclarationParserSpy! + private var sut: StructParserImpl! + + override func setUp() { + super.setUp() + varParserSpy = VarDeclarationParserSpy(parseReturn: .makeStub()) + sut = StructParserImpl( + varParser: varParserSpy + ) + } + + func test_givenNoStruct_whenParse_thenReturnEmptyResult() throws { + let text = """ + """ + + let structs = try sut.parse(text: text) + + XCTAssertEqual(structs.count, 0) + } + + func test_givenEmptyStruct_whenParse_thenReturnStruct() throws { + let text = """ + struct A { + } + """ + + let structs = try sut.parse(text: text) + + XCTAssertEqual(structs.count, 1) + XCTAssertEqual(structs.first?.name, "A") + XCTAssertEqual(structs.first?.variables.count, 0) + } + + func test_givenStructWithVariables_whenParse_thenReturnStruct() throws { + let text = """ + struct A { + var a: Int + let b: Int + } + """ + + let structs = try sut.parse(text: text) + + XCTAssertEqual(structs.count, 1) + XCTAssertEqual(structs.first?.name, "A") + XCTAssertEqual(structs.first?.variables.count, 2) + XCTAssertEqual(structs.first?.variables.first, .makeStub()) + XCTAssertEqual(varParserSpy.parse.count, 2) + XCTAssertEqual(varParserSpy.parse.first?.node.description, "\n var a: Int") + } +}