Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement snake_case Conversion and fix inifinite loops #9

Merged
merged 3 commits into from
Jul 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions Sources/RevolutionKit/Rewriter/TestMethodNameConverter.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import Foundation

struct TestMethodNameConverter {
let shouldStripPrefix: Bool
private let testPrefix = "test"
private let snakeCasePrefix = "test_"

/// Strip first `test` prefix from test case names.
/// `testCamelCase` -> `camelCase`
/// `test_snake_case` -> `snake_case`
/// `test` -> `test`
func convert(_ testMethodName: String) -> String {
guard shouldStripPrefix else { return testMethodName }

if testMethodName.hasPrefix(snakeCasePrefix) {
return testMethodName.strippedFirst(snakeCasePrefix.count)
} else if testMethodName == testPrefix {
return testMethodName
} else if testMethodName.hasPrefix(testPrefix) {
return testMethodName
.strippedFirst(testPrefix.count)
.lowercasedFirstLetter()
}
assertionFailure("Unexpected call")
return testMethodName
}
}

extension String {
fileprivate func strippedFirst(_ k: Int) -> String {
var mutating = self
mutating.removeFirst(k)
return mutating
}

fileprivate func lowercasedFirstLetter() -> String {
guard let firstLetter = first else { return self }
return firstLetter.lowercased() + self[index(after: startIndex)..<endIndex]
}
}
44 changes: 21 additions & 23 deletions Sources/RevolutionKit/Rewriter/XCTestRewriter+MethodVisitor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import SwiftSyntax
/// Visitor to rewrite XCTest's test methods to swift-testing.
extension XCTestRewriter {
func visitForTestFunctionDecl(_ node: FunctionDeclSyntax) -> DeclSyntax {
guard !node.hasTestMacroAttribute else { return super.visit(node) }
guard let methodKind = detectMethodKind(of: node) else {
return super.visit(node)
}
Expand All @@ -18,15 +19,17 @@ extension XCTestRewriter {
}
}

private var testMethodNameConverter: TestMethodNameConverter {
TestMethodNameConverter(
shouldStripPrefix: globalOptions.enableStrippingTestPrefix
)
}

/// Rewrite XCTest test case methods to swift-testing
/// func testExample() -> @Test func example()
private func rewriteTestCase(node: FunctionDeclSyntax) -> DeclSyntax {
let testCaseName = node.name.text
let newTestCaseName = if globalOptions.enableStrippingTestPrefix {
strippingTestPrefix(of: testCaseName)
} else {
testCaseName
}
let newTestCaseName = testMethodNameConverter.convert(testCaseName)

let testMacroAttribute = AttributeSyntax(
attributeName: IdentifierTypeSyntax(
Expand Down Expand Up @@ -91,24 +94,6 @@ extension XCTestRewriter {
return DeclSyntax(deinitializerDecl)
}

/// Strip first `test` prefix from test case names.
/// `testCamelCase` -> `camelCase`
/// `test` -> `test`
private func strippingTestPrefix(of testCaseName: String) -> String {
precondition(testCaseName.hasPrefix("test"))

return {
var convertedName = testCaseName
convertedName.removeFirst(4)

guard let firstCharacter = convertedName.first else {
return testCaseName
}

return firstCharacter.lowercased() + convertedName.dropFirst()
}()
}

/// Returns a kind of the method
private func detectMethodKind(of node: FunctionDeclSyntax) -> MethodKind? {
guard !isStaticMethod(node: node) else { return nil }
Expand Down Expand Up @@ -152,3 +137,16 @@ extension XCTestRewriter {
case tearDown
}
}

extension FunctionDeclSyntax {
fileprivate var hasTestMacroAttribute: Bool {
attributes.contains { attribute in
switch attribute {
case .attribute(let attributeNode):
let attributeName = attributeNode.attributeName.as(IdentifierTypeSyntax.self)?.name
return attributeName?.tokenKind == .identifier("Test")
case .ifConfigDecl: return false
}
}
}
}
21 changes: 21 additions & 0 deletions Tests/RevolutionKitTests/TestMethodNameConverterTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import Testing
@testable import RevolutionKit

struct TestMethodNameConverterTests {
private static let fixtures = [
(true, "testDoSomething", "doSomething"),
(false, "testDoSomething", "testDoSomething"),
(true, "test_do_something", "do_something"),
(false, "test_do_something", "test_do_something"),
(true, "test", "test"),
(false, "test", "test"),
]

@Test("TestMethodNameConverter can convert method names", arguments: fixtures)
func testConversion(enableStripping: Bool, input: String, expected: String) {
let converter = TestMethodNameConverter(shouldStripPrefix: enableStripping)

let actual = converter.convert(input)
#expect(actual == expected)
}
}
20 changes: 20 additions & 0 deletions Tests/RevolutionKitTests/TestMethodsTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,26 @@ private let testCaseConversionFixtures: [ConversionTestFixture] = [
}
"""
),
.init(
"""
func test_do_something() {
}
""",
"""
@Test func do_something() {
}
"""
),
.init(
"""
func test() {
}
""",
"""
@Test func test() {
}
"""
),
.init(
"""
@MainActor func testExample() {
Expand Down
Loading