-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #71 from raptorxcz/feature/enum
Add enum stubs
- Loading branch information
Showing
6 changed files
with
444 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
public struct EnumDeclaration: Equatable { | ||
public let name: String | ||
public let cases: [String] | ||
public let notes: [String] | ||
public let accessLevel: AccessLevel | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
public protocol EnumStubGenerator { | ||
func generate(from enumType: EnumDeclaration, functionName: String) -> String | ||
} | ||
|
||
final class EnumStubGeneratorImpl: EnumStubGenerator { | ||
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 enumType: EnumDeclaration, functionName: String) -> String { | ||
let content = generateBody(from: enumType, functionName: functionName) | ||
return extensionGenerator.make( | ||
name: enumType.name, | ||
content: content | ||
).joined(separator: "\n") + "\n" | ||
} | ||
|
||
private func generateBody(from enumType: EnumDeclaration, functionName: String) -> [String] { | ||
let content = makeContent(from: enumType) | ||
let functionDeclaration = FunctionDeclaration( | ||
name: functionName, | ||
arguments: [], | ||
isThrowing: false, | ||
isAsync: false, | ||
isStatic: true, | ||
returnType: TypeDeclaration(name: enumType.name, prefix: [], composedType: .plain) | ||
) | ||
return functionGenerator.makeCode( | ||
from: functionDeclaration, | ||
content: content, | ||
isEachArgumentOnNewLineEnabled: true | ||
) | ||
} | ||
|
||
private func makeContent(from enumType: EnumDeclaration) -> [String] { | ||
let firstCase = enumType.cases.first.map { "." + $0 } ?? "" | ||
return [ | ||
"return \(firstCase)" | ||
] | ||
} | ||
|
||
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) | ||
) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
import SwiftParser | ||
import SwiftSyntax | ||
|
||
public protocol EnumParser { | ||
func parse(text: String) throws -> [EnumDeclaration] | ||
} | ||
|
||
final class EnumParserImpl: EnumParser { | ||
func parse(text: String) throws -> [EnumDeclaration] { | ||
let text = SwiftParser.Parser.parse(source: text) | ||
let visitor = EnumVisitor( | ||
nestedInItemsNames: [] | ||
) | ||
return visitor.execute(node: text) | ||
} | ||
} | ||
|
||
private class EnumVisitor: SyntaxVisitor { | ||
private var result = [EnumDeclaration]() | ||
private var nestedInItemsNames: [String] | ||
|
||
public init( | ||
nestedInItemsNames: [String] | ||
) { | ||
self.nestedInItemsNames = nestedInItemsNames | ||
super.init(viewMode: .sourceAccurate) | ||
} | ||
|
||
func execute(node: some SyntaxProtocol) -> [EnumDeclaration] { | ||
walk(node) | ||
return result | ||
} | ||
|
||
override func visit(_ node: EnumDeclSyntax) -> SyntaxVisitorContinueKind { | ||
|
||
let name = node.name.text | ||
|
||
result.append( | ||
EnumDeclaration( | ||
name: (nestedInItemsNames + [name]).joined(separator: "."), | ||
cases: parseCases(node: node.memberBlock), | ||
notes: node.leadingTrivia.pieces.compactMap(makeLineComment), | ||
accessLevel: parseAccessLevel(from: node) | ||
) | ||
) | ||
|
||
let subVisitor = EnumVisitor(nestedInItemsNames: nestedInItemsNames + [name]) | ||
result += subVisitor.execute(node: node.memberBlock.members) | ||
|
||
return .skipChildren | ||
} | ||
|
||
private func parseCases(node: MemberBlockSyntax) -> [String] { | ||
let casesParser = CasesVisitor(viewMode: .sourceAccurate) | ||
return casesParser.execute(node: node.members) | ||
} | ||
|
||
private func parseAccessLevel(from node: EnumDeclSyntax) -> 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? { | ||
guard let type = inheridedTypeSyntax.type.as(IdentifierTypeSyntax.self) else { | ||
return nil | ||
} | ||
|
||
return type.name.text | ||
} | ||
|
||
override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind { | ||
let name = node.name.text | ||
let subVisitor = EnumVisitor(nestedInItemsNames: nestedInItemsNames + [name]) | ||
result += subVisitor.execute(node: node.memberBlock.members) | ||
return .skipChildren | ||
} | ||
|
||
override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind { | ||
let name = node.name.text | ||
let subVisitor = EnumVisitor(nestedInItemsNames: nestedInItemsNames + [name]) | ||
result += subVisitor.execute(node: node.memberBlock.members) | ||
return .skipChildren | ||
} | ||
} | ||
|
||
private class CasesVisitor: SyntaxVisitor { | ||
private var result = [String]() | ||
|
||
func execute(node: some SyntaxProtocol) -> [String] { | ||
walk(node) | ||
return result | ||
} | ||
|
||
override func visit(_ node: EnumCaseDeclSyntax) -> SyntaxVisitorContinueKind { | ||
let declaration = node.elements.map(\.name.text) | ||
result += declaration | ||
return .visitChildren | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
@testable import Rubicon | ||
import XCTest | ||
|
||
final class EnumStubGeneratorTests: XCTestCase { | ||
private var extensionGeneratorSpy: ExtensionGeneratorSpy! | ||
private var functionGeneratorSpy: FunctionGeneratorSpy! | ||
private var indentationGeneratorStub: IndentationGeneratorStub! | ||
private var defaultValueGeneratorSpy: DefaultValueGeneratorSpy! | ||
private var sut: EnumStubGeneratorImpl! | ||
|
||
override func setUp() { | ||
super.setUp() | ||
extensionGeneratorSpy = ExtensionGeneratorSpy(makeReturn: ["extension"]) | ||
functionGeneratorSpy = FunctionGeneratorSpy(makeCodeReturn: ["function"]) | ||
indentationGeneratorStub = IndentationGeneratorStub() | ||
defaultValueGeneratorSpy = DefaultValueGeneratorSpy(makeDefaultValueReturn: "default") | ||
sut = EnumStubGeneratorImpl( | ||
extensionGenerator: extensionGeneratorSpy, | ||
functionGenerator: functionGeneratorSpy, | ||
indentationGenerator: IndentationGeneratorStub(), | ||
defaultValueGenerator: defaultValueGeneratorSpy | ||
) | ||
} | ||
|
||
func test_givenEmptyEnum_whenMakeCode_thenReturnCode() { | ||
let declaration = EnumDeclaration.makeStub(cases: []) | ||
|
||
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: "EnumName")) | ||
XCTAssertEqual(functionGeneratorSpy.makeCode.first?.content, ["return "]) | ||
} | ||
|
||
func test_givenVariableEnum_whenMakeCode_thenReturnCode() { | ||
let declaration = EnumDeclaration.makeStub(cases: [ | ||
"a", | ||
"b" | ||
]) | ||
|
||
_ = sut.generate(from: declaration, functionName: "functionName") | ||
|
||
XCTAssertEqual(functionGeneratorSpy.makeCode.first?.content, ["return .a"]) | ||
} | ||
} | ||
|
||
extension EnumDeclaration { | ||
static func makeStub( | ||
cases: [String] = [] | ||
) -> EnumDeclaration { | ||
return EnumDeclaration( | ||
name: "EnumName", | ||
cases: cases, | ||
notes: [ | ||
"note1", | ||
"note2" | ||
], | ||
accessLevel: .internal | ||
) | ||
} | ||
} |
Oops, something went wrong.