Skip to content
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
36 changes: 21 additions & 15 deletions Sources/MockoloFramework/Models/Import.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
// limitations under the License.
//

import SwiftSyntax

/// A structure defining an "import" statement parsed by `Generator`, including various modifiers.
struct Import: CustomStringConvertible {

Expand Down Expand Up @@ -92,22 +94,26 @@ extension Import {
return new
}

/// Creates an `Import` by parsing a `String` provided by `Generator`.
init?(line: String) {
guard let importSpaceRange = line.range(of: String.importSpace) else { return nil }

let firstNewlineIndex = line.firstIndex(of: "\n")
let lastNewlineIndex = line.lastIndex(of: "\n")
let startIndex = firstNewlineIndex ?? line.startIndex
let endIndex = lastNewlineIndex ?? line.endIndex

moduleName = String(line[importSpaceRange.upperBound..<endIndex])

if importSpaceRange.lowerBound == startIndex {
modifier = nil
/// Creates an `Import` from a parsed import declaration.
///
/// Attributes other than `@testable` (e.g. `@preconcurrency`, `@_spi(...)`,
/// `@diagnose(...)`, or attributes wrapped in `#if`) are not carried over.
init(_ node: ImportDeclSyntax) {
var moduleName = node.path.trimmedDescription
if let kind = node.importKindSpecifier {
moduleName = "\(kind.text) \(moduleName)"
}
self.moduleName = moduleName

let isTestable = node.attributes.contains {
$0.as(AttributeSyntax.self)?.attributeName.trimmedDescription == "testable"
}
if isTestable {
modifier = .testable
} else if let acl = node.modifiers.lazy.compactMap({ ACL(rawValue: $0.name.text) }).first {
modifier = .acl(acl)
} else {
let modifierEndIndex = line.index(before: importSpaceRange.lowerBound)
modifier = Modifier(rawValue: String(line[startIndex..<modifierEndIndex]))
modifier = nil
}
}
}
Expand Down
8 changes: 2 additions & 6 deletions Sources/MockoloFramework/Parsers/SwiftSyntaxExtensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -821,9 +821,7 @@ final class EntityVisitor: SyntaxVisitor {

override func visit(_ node: ImportDeclSyntax) -> SyntaxVisitorContinueKind {
// Top-level import (not inside #if)
if let `import` = Import(line: node.trimmedDescription) {
imports.append(.simple(`import`))
}
imports.append(.simple(Import(node)))
return .skipChildren
}

Expand Down Expand Up @@ -854,9 +852,7 @@ final class EntityVisitor: SyntaxVisitor {
for el in list {
if let importItem = el.item.as(ImportDeclSyntax.self) {
// Simple import
if let imp = Import(line: importItem.trimmedDescription) {
contents.append(.simple(imp))
}
contents.append(.simple(Import(importItem)))
} else if let nested = el.item.as(IfConfigDeclSyntax.self) {
// Nested #if block (recursive)
let nestedBlock = parseIfConfigDecl(nested)
Expand Down
10 changes: 10 additions & 0 deletions Tests/TestACLImports/ACLImportTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,14 @@ final class ACLImportTests: MockoloTestCase {
verify(srcContents: [FixtureACLImport.primary, FixtureACLImport.secondary],
dstContent: FixtureACLImport.primarySecondaryMock)
}

func testAttributedImports() {
verify(srcContent: FixtureACLImport.attributed,
dstContent: FixtureACLImport.attributedMock)
}

func testOpenAPIGeneratorImportHeader() {
verify(srcContent: FixtureACLImport.openAPIGeneratorHeader,
dstContent: FixtureACLImport.openAPIGeneratorHeaderMock)
}
}
67 changes: 67 additions & 0 deletions Tests/TestACLImports/FixtureACLImport.swift
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,71 @@ enum FixtureACLImport {

\(FixtureHelpers.someProtocol2Mock)
"""

/// Input with attributes on imports, including attributes on their own line.
static let attributed =
"""
@_spi(X) import A
@preconcurrency import B
@_implementationOnly import C
@testable import D
@_spi(Y) public import E
@preconcurrency
import F
@diagnose(UnusedImportAccess, as: ignored)
import G
#if canImport(H)
@preconcurrency
package import H
#endif

\(FixtureHelpers.someProtocol)
"""

/// Attributes other than `@testable` are dropped; access levels are kept.
static let attributedMock =
"""
import A
import B
import C
@testable import D
public import E
import F
import G
#if canImport(H)
package import H
#endif


\(FixtureHelpers.someProtocolMock)
"""

/// Import header emitted by swift-openapi-generator 1.14+.
static let openAPIGeneratorHeader =
"""
#if compiler(>=6.4)
@diagnose(UnusedImportAccess, as: ignored)
#endif
@_spi(Generated) public import OpenAPIRuntime
#if os(Linux)
@preconcurrency public import struct Foundation.URL
#else
public import struct Foundation.URL
#endif

\(FixtureHelpers.someProtocol)
"""

static let openAPIGeneratorHeaderMock =
"""
public import OpenAPIRuntime
#if os(Linux)
public import struct Foundation.URL
#else
public import struct Foundation.URL
#endif


\(FixtureHelpers.someProtocolMock)
"""
}
Loading