Sources/MockoloFramework/Parsers/SwiftSyntaxExtensions.swift (791 lines of code) (raw):
//
// Copyright (c) 2018. Uber Technologies
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Algorithms
import Foundation
import SwiftSyntax
import SwiftParser
extension Parser {
public static func parse(_ path: String) -> SourceFileSyntax {
guard let fileData = FileManager.default.contents(atPath: path) else {
fatalError("Retrieving contents of \(path) failed")
}
return fileData.withUnsafeBytes { buf in
parse(source: buf.bindMemory(to: UInt8.self))
}
}
}
extension SyntaxProtocol {
var offset: Int64 {
return Int64(self.position.utf8Offset)
}
var length: Int64 {
return Int64(self.totalLength.utf8Length)
}
}
extension DeclModifierListSyntax {
var acl: String {
for modifier in self {
for token in modifier.tokens(viewMode: .sourceAccurate) {
switch token.tokenKind {
case .keyword(.private),
.keyword(.fileprivate),
.keyword(.internal),
.keyword(.package),
.keyword(.public),
.keyword(.open):
return token.text
default:
return ""
}
}
}
return ""
}
var isStatic: Bool {
return self.tokens(viewMode: .sourceAccurate).contains {$0.tokenKind == .keyword(.static) }
}
var isRequired: Bool {
return self.tokens(viewMode: .sourceAccurate).contains {$0.text == String.required }
}
var isConvenience: Bool {
return self.tokens(viewMode: .sourceAccurate).contains {$0.text == String.convenience }
}
var isOverride: Bool {
return self.tokens(viewMode: .sourceAccurate).contains {$0.text == String.override }
}
var isFinal: Bool {
return self.tokens(viewMode: .sourceAccurate).contains {$0.text == String.final }
}
var isPrivate: Bool {
return self.tokens(viewMode: .sourceAccurate).contains {$0.tokenKind == .keyword(.private) || $0.tokenKind == .keyword(.fileprivate) }
}
var isPublic: Bool {
return self.tokens(viewMode: .sourceAccurate).contains {$0.tokenKind == .keyword(.public) }
}
}
extension InheritanceClauseSyntax {
var types: [String] {
var list = [String]()
for element in self.inheritedTypes {
let elementNameList = parseElementType(type: element.type)
list.append(contentsOf: elementNameList)
}
return list
}
private func parseElementType(type: TypeSyntax) -> [String] {
if let simpleTypeIdentifier = type.as(IdentifierTypeSyntax.self) {
// example: `protocol A: B {}`
return [simpleTypeIdentifier.name.text]
} else if let tupleType = type.as(TupleTypeSyntax.self) {
// example: `protocol A: (B) {}`
return tupleType.elements.map(\.type).map(parseElementType(type:)).flatMap { $0 }
} else if let compositionType = type.as(CompositionTypeSyntax.self) {
// example: `protocol A: B & C {}`
return compositionType.elements.map(\.type).map(parseElementType(type:)).flatMap { $0 }
} else if let attributedType = type.as(AttributedTypeSyntax.self) {
// example: `protocol A: @unchecked B {}`
if let baseType = attributedType.baseType.as(IdentifierTypeSyntax.self) {
return [baseType.name.text]
}
}
return []
}
}
extension MemberBlockItemSyntax {
private func validateMember(_ modifiers: DeclModifierListSyntax?, _ declKind: NominalTypeDeclKind, processed: Bool) -> Bool {
if let mods = modifiers {
if !processed && mods.isPrivate || mods.isStatic && declKind == .class {
return false
}
}
return true
}
private func validateInit(_ initDecl: InitializerDeclSyntax, _ declKind: NominalTypeDeclKind, processed: Bool) -> Bool {
let modifiers = initDecl.modifiers
let isRequired = modifiers.isRequired
if processed {
return isRequired
}
let isConvenience = modifiers.isConvenience
let isPrivate = modifiers.isPrivate
if isConvenience || isPrivate {
return false
}
return true
}
private func memberAcl(_ modifiers: DeclModifierListSyntax?, _ encloserAcl: String, _ declKind: NominalTypeDeclKind) -> String {
if declKind == .protocol {
return encloserAcl
}
return modifiers?.acl ?? ""
}
func transformToModel(with encloserAcl: String, declKind: NominalTypeDeclKind, metadata: AnnotationMetadata?, processed: Bool) -> (Model, String?, Bool)? {
if let varMember = self.decl.as(VariableDeclSyntax.self) {
if validateMember(varMember.modifiers, declKind, processed: processed) {
let acl = memberAcl(varMember.modifiers, encloserAcl, declKind)
if let item = varMember.models(with: acl, metadata: metadata, processed: processed).first {
return (item, varMember.attributes.trimmedDescription, false)
}
}
} else if let funcMember = self.decl.as(FunctionDeclSyntax.self) {
if validateMember(funcMember.modifiers, declKind, processed: processed) {
let acl = memberAcl(funcMember.modifiers, encloserAcl, declKind)
let item = funcMember.model(with: acl, declKind: declKind, funcsWithArgsHistory: metadata?.funcsWithArgsHistory, customModifiers: metadata?.modifiers, processed: processed)
return (item, funcMember.attributes.trimmedDescription, false)
}
} else if let subscriptMember = self.decl.as(SubscriptDeclSyntax.self) {
if validateMember(subscriptMember.modifiers, declKind, processed: processed) {
let acl = memberAcl(subscriptMember.modifiers, encloserAcl, declKind)
let item = subscriptMember.model(with: acl, declKind: declKind, processed: processed)
return (item, subscriptMember.attributes.trimmedDescription, false)
}
} else if let initMember = self.decl.as(InitializerDeclSyntax.self) {
if validateInit(initMember, declKind, processed: processed) {
let acl = memberAcl(initMember.modifiers, encloserAcl, declKind)
let item = initMember.model(with: acl, declKind: declKind, processed: processed)
return (item, initMember.attributes.trimmedDescription, true)
}
} else if let patMember = self.decl.as(AssociatedTypeDeclSyntax.self) {
let acl = memberAcl(patMember.modifiers, encloserAcl, declKind)
let item = patMember.model(with: acl, declKind: declKind, overrides: metadata?.typeAliases)
return (item, patMember.attributes.trimmedDescription, false)
} else if let taMember = self.decl.as(TypeAliasDeclSyntax.self) {
let acl = memberAcl(taMember.modifiers, encloserAcl, declKind)
let item = taMember.model(with: acl, declKind: declKind, overrides: metadata?.typeAliases, processed: processed)
return (item, taMember.attributes.trimmedDescription, false)
} else if let ifMacroMember = self.decl.as(IfConfigDeclSyntax.self) {
let (item, attr, initFlag) = ifMacroMember.model(with: encloserAcl, declKind: declKind, metadata: metadata, processed: processed)
return (item, attr, initFlag)
}
return nil
}
}
extension MemberBlockItemListSyntax {
var hasBlankInit: Bool {
for member in self {
if let varMember = member.decl.as(VariableDeclSyntax.self) {
for v in varMember.bindings {
if let name = v.pattern.firstToken(viewMode: .sourceAccurate)?.text {
if name == String.hasBlankInit {
return true
}
}
}
}
}
return false
}
func memberData(with encloserAcl: String, declKind: NominalTypeDeclKind, metadata: AnnotationMetadata?, processed: Bool) -> EntityNodeSubContainer {
var attributeList = [String]()
var memberList = [Model]()
var hasInit = false
for m in self {
if let (item, attr, initFlag) = m.transformToModel(with: encloserAcl, declKind: declKind, metadata: metadata, processed: processed) {
memberList.append(item)
if let attrDesc = attr {
attributeList.append(attrDesc)
}
hasInit = hasInit || initFlag
}
}
return EntityNodeSubContainer(attributes: attributeList, members: memberList, hasInit: hasInit)
}
}
extension IfConfigDeclSyntax {
func model(with encloserAcl: String, declKind: NominalTypeDeclKind, metadata: AnnotationMetadata?, processed: Bool) -> (Model, String?, Bool) {
var subModels = [Model]()
var attrDesc: String?
var hasInit = false
var name = ""
for cl in self.clauses {
if let desc = cl.condition?.description {
if let list = cl.elements?.as(MemberBlockItemListSyntax.self) {
name = desc
for element in list {
if let (item, attr, initFlag) = element.transformToModel(with: encloserAcl, declKind: declKind, metadata: metadata, processed: processed) {
subModels.append(item)
if let attr = attr, attr.contains(String.available) {
attrDesc = attr
}
hasInit = hasInit || initFlag
}
}
}
}
}
let uniqueSubModels = uniqueEntities(
in: subModels,
exclude: [:],
fullnames: []
).sorted(path: \.value.offset, fallback: \.key)
let macroModel = IfMacroModel(name: name, offset: self.offset, entities: uniqueSubModels)
return (macroModel, attrDesc, hasInit)
}
}
extension ProtocolDeclSyntax: EntityNode {
var namespaces: [String] {
return findNamespaces(parent: parent)
}
var nameText: String {
return name.text
}
var mayHaveGlobalActor: Bool {
return attributes.mayHaveGlobalActor
}
var accessLevel: String {
return self.modifiers.acl
}
var declKind: NominalTypeDeclKind {
return .protocol
}
var isPrivate: Bool {
return self.modifiers.isPrivate
}
var inheritedTypes: [String] {
return inheritanceClause?.types ?? []
}
var genericWhereConstraints: [String] {
return genericWhereClause?.requirements.map { $0.with(\.trailingComma, nil).trimmedDescription } ?? []
}
var attributesDescription: String {
self.attributes.trimmedDescription
}
func annotationMetadata(with annotation: String) -> AnnotationMetadata? {
let trivias = [
leadingTrivia,
protocolKeyword.leadingTrivia,
modifiers.leadingTrivia,
] + attributes.map(\.leadingTrivia)
return trivias.firstNonNil { $0.annotationMetadata(with: annotation) }
}
var hasBlankInit: Bool {
return false
}
func subContainer(metadata: AnnotationMetadata?, declKind: NominalTypeDeclKind, path: String?, isProcessed: Bool) -> EntityNodeSubContainer {
return self.memberBlock.members.memberData(with: accessLevel, declKind: declKind, metadata: metadata, processed: isProcessed)
}
}
extension ClassDeclSyntax: EntityNode {
var namespaces: [String] {
return findNamespaces(parent: parent)
}
var nameText: String {
return name.text
}
var mayHaveGlobalActor: Bool {
return attributes.mayHaveGlobalActor
}
var accessLevel: String {
return self.modifiers.acl
}
var declKind: NominalTypeDeclKind {
return .class
}
var inheritedTypes: [String] {
return inheritanceClause?.types ?? []
}
var genericWhereConstraints: [String] {
return genericWhereClause?.requirements.map { $0.with(\.trailingComma, nil).trimmedDescription } ?? []
}
var attributesDescription: String {
self.attributes.trimmedDescription
}
var isFinal: Bool {
return self.modifiers.isFinal
}
var isPrivate: Bool {
return self.modifiers.isPrivate
}
var isPublic: Bool {
return self.modifiers.isPublic
}
var hasBlankInit: Bool {
return self.memberBlock.members.hasBlankInit
}
func annotationMetadata(with annotation: String) -> AnnotationMetadata? {
let trivias = [
leadingTrivia,
classKeyword.leadingTrivia,
modifiers.leadingTrivia,
] + attributes.map(\.leadingTrivia)
return trivias.firstNonNil { $0.annotationMetadata(with: annotation) }
}
func subContainer(metadata: AnnotationMetadata?, declKind: NominalTypeDeclKind, path: String?, isProcessed: Bool) -> EntityNodeSubContainer {
return self.memberBlock.members.memberData(with: accessLevel, declKind: declKind, metadata: nil, processed: isProcessed)
}
}
fileprivate func findNamespaces(parent: Syntax?) -> [String] {
guard let parent else {
return []
}
return sequence(first: parent, next: \.parent)
.compactMap { element in
if let decl = element.as(StructDeclSyntax.self) {
return decl.name.trimmedDescription
} else if let decl = element.as(EnumDeclSyntax.self) {
return decl.name.trimmedDescription
} else if let decl = element.as(ClassDeclSyntax.self) {
return decl.name.trimmedDescription
} else if let decl = element.as(ActorDeclSyntax.self) {
return decl.name.trimmedDescription
} else if let decl = element.as(ExtensionDeclSyntax.self) {
return decl.extendedType.trimmedDescription
} else {
return nil
}
}
.reversed()
}
extension AttributeListSyntax {
fileprivate var mayHaveGlobalActor: Bool {
let wellKnownGlobalActor: Set<String> = [.mainActor]
return self.contains { element in
switch element {
case .attribute(let attribute):
return wellKnownGlobalActor.contains(attribute.attributeName.trimmedDescription)
case .ifConfigDecl(let ifConfig):
return ifConfig.clauses.contains { clause in
if case .attributes(let attributes) = clause.elements {
return attributes.mayHaveGlobalActor
}
return false
}
}
}
}
}
extension VariableDeclSyntax {
func models(with acl: String, metadata: AnnotationMetadata?, processed: Bool) -> [Model] {
// Detect whether it's static
let isStatic = self.modifiers.isStatic
// Need to access pattern bindings to get name, type, and other info of a var decl
let varmodels = self.bindings.compactMap { (v: PatternBindingSyntax) -> Model in
let name = v.pattern.trimmedDescription
var typeName: String?
var potentialInitParam = false
// Get the type info and whether it can be a var param for an initializer
if let vtype = v.typeAnnotation?.type.trimmedDescription {
potentialInitParam = name.canBeInitParam(type: vtype, isStatic: isStatic)
typeName = vtype
}
let storageKind: VariableModel.MockStorageKind
switch v.accessorBlock?.accessors {
case .accessors(let accessorDecls):
if accessorDecls.contains(where: { $0.accessorSpecifier.tokenKind == .keyword(.set) }) {
storageKind = .stored(needsSetCount: true)
} else if let getterDecl = accessorDecls.first(where: { $0.accessorSpecifier.tokenKind == .keyword(.get) }) {
if getterDecl.body == nil { // is protoccol
var getterEffects = VariableModel.GetterEffects.empty
if getterDecl.effectSpecifiers?.asyncSpecifier != nil {
getterEffects.isAsync = true
}
if let `throws` = getterDecl.effectSpecifiers?.throwsClause {
getterEffects.throwing = .init(`throws`)
}
if getterEffects == .empty {
storageKind = .stored(needsSetCount: false)
} else {
storageKind = .computed(getterEffects)
}
} else { // is class
storageKind = .computed(.empty)
}
} else {
// will never happens
storageKind = .stored(needsSetCount: false) // fallback
}
case .getter:
storageKind = .computed(.empty)
case nil:
storageKind = .stored(needsSetCount: true)
}
return VariableModel(name: name,
type: typeName.map { SwiftType($0) },
acl: acl,
isStatic: isStatic,
storageKind: storageKind,
canBeInitParam: potentialInitParam,
offset: v.offset,
rxTypes: metadata?.varTypes,
customModifiers: metadata?.modifiers,
modelDescription: self.description,
combineType: metadata?.combineTypes?[name] ?? metadata?.combineTypes?["all"],
processed: processed)
}
return varmodels
}
}
extension SubscriptDeclSyntax {
func model(with acl: String, declKind: NominalTypeDeclKind, processed: Bool) -> Model {
let isStatic = self.modifiers.isStatic
let params = self.parameterClause.parameters.enumerated().compactMap {
$1.model(inInit: false, declKind: declKind, index: $0)
}
let genericTypeParams = self.genericParameterClause?.parameters.compactMap { $0.model(inInit: false) } ?? []
let genericWhereClause = self.genericWhereClause?.description
let subscriptModel = MethodModel(name: self.subscriptKeyword.text,
typeName: self.returnClause.type.description,
kind: .subscriptKind,
acl: acl,
genericTypeParams: genericTypeParams,
genericWhereClause: genericWhereClause,
params: params,
isAsync: false,
throwing: .none,
isStatic: isStatic,
offset: self.offset,
length: self.length,
funcsWithArgsHistory: [],
customModifiers: [:],
modelDescription: self.description,
processed: processed)
return subscriptModel
}
}
extension FunctionDeclSyntax {
func model(with acl: String, declKind: NominalTypeDeclKind, funcsWithArgsHistory: [String]?, customModifiers: [String : Modifier]?, processed: Bool) -> Model {
let isStatic = self.modifiers.isStatic
let params = self.signature.parameterClause.parameters.enumerated().compactMap {
$1.model(inInit: false, declKind: declKind, index: $0)
}
let genericTypeParams = self.genericParameterClause?.parameters.compactMap { $0.model(inInit: false) } ?? []
let genericWhereClause = self.genericWhereClause?.description
let funcmodel = MethodModel(name: self.name.description,
typeName: self.signature.returnClause?.type.description,
kind: .funcKind,
acl: acl,
genericTypeParams: genericTypeParams,
genericWhereClause: genericWhereClause,
params: params,
isAsync: self.signature.effectSpecifiers?.asyncSpecifier != nil,
throwing: .init(self.signature.effectSpecifiers?.throwsClause),
isStatic: isStatic,
offset: self.offset,
length: self.length,
funcsWithArgsHistory: funcsWithArgsHistory ?? [],
customModifiers: customModifiers ?? [:],
modelDescription: self.description,
processed: processed)
return funcmodel
}
}
extension InitializerDeclSyntax {
func isRequired(with declKind: NominalTypeDeclKind) -> Bool {
switch declKind {
case .class:
if modifiers.isConvenience {
return false
}
return modifiers.isRequired
case .protocol:
return true
default:
return false // Other types do not support inheritance
}
}
func model(with acl: String, declKind: NominalTypeDeclKind, processed: Bool) -> Model {
let requiredInit = isRequired(with: declKind)
let params = self.signature.parameterClause.parameters.enumerated().compactMap {
$1.model(inInit: true, declKind: declKind, index: $0)
}
let genericTypeParams = self.genericParameterClause?.parameters.compactMap { $0.model(inInit: true) } ?? []
let genericWhereClause = self.genericWhereClause?.description
return MethodModel(name: "init",
typeName: nil,
kind: .initKind(required: requiredInit, override: declKind == .class),
acl: acl,
genericTypeParams: genericTypeParams,
genericWhereClause: genericWhereClause,
params: params,
isAsync: self.signature.effectSpecifiers?.asyncSpecifier != nil,
throwing: .init(self.signature.effectSpecifiers?.throwsClause),
isStatic: false,
offset: self.offset,
length: self.length,
funcsWithArgsHistory: [],
customModifiers: [:],
modelDescription: self.description,
processed: processed)
}
}
extension GenericParameterSyntax {
func model(inInit: Bool) -> ParamModel {
return ParamModel(label: "",
name: self.name.text,
type: SwiftType(self.inheritedType?.trimmedDescription ?? .voidType),
isGeneric: true,
inInit: inInit,
needsVarDecl: false,
offset: self.offset,
length: self.length)
}
}
extension FunctionParameterSyntax {
func model(inInit: Bool, declKind: NominalTypeDeclKind, index: Int) -> ParamModel {
let label: String
let name: String
// Get label and name of args
let first = self.firstName.text
if let second = self.secondName?.text {
label = first
if second == "_" {
name = "_\(index)"
} else {
name = second
}
} else {
if first == "_" {
label = first
name = "_\(index)"
} else {
label = ""
name = first
}
}
var type = self.type.trimmedDescription
if ellipsis != nil {
type.append("...")
}
return ParamModel(label: label,
name: name,
type: SwiftType(type),
isGeneric: false,
inInit: inInit,
needsVarDecl: declKind == .protocol,
offset: self.offset,
length: self.length)
}
}
extension AssociatedTypeDeclSyntax {
func model(with acl: String, declKind: NominalTypeDeclKind, overrides: [String: String]?) -> Model {
if let overrideType = overrides?[self.name.text] {
return TypeAliasModel(
name: self.name.text,
typeName: overrideType,
acl: acl,
offset: self.offset,
length: self.length,
modelDescription: nil,
processed: false
)
}
return AssociatedTypeModel(name: self.name.text,
inheritances: self.inheritanceClause?.inheritedTypes.map { $0.with(\.trailingComma, nil).trimmedDescription } ?? [],
defaultTypeName: self.initializer?.value.trimmedDescription,
whereConstraints: self.genericWhereClause?.requirements.map { $0.with(\.trailingComma, nil).trimmedDescription } ?? [],
acl: acl,
offset: self.offset,
length: self.length)
}
}
extension TypeAliasDeclSyntax {
func model(with acl: String, declKind: NominalTypeDeclKind, overrides: [String: String]?, processed: Bool) -> Model {
return TypeAliasModel(name: self.name.text,
typeName: overrides?[self.name.text] ?? self.initializer.value.description,
acl: acl,
offset: self.offset,
length: self.length,
modelDescription: self.description,
useDescription: true,
processed: processed)
}
}
final class EntityVisitor: SyntaxVisitor {
var entities: [Entity] = []
var imports: [String: [String]] = [:]
let annotation: String
let fileMacro: String
let path: String
let declType: FindTargetDeclType
init(_ path: String, annotation: String = "", fileMacro: String?, declType: FindTargetDeclType) {
self.annotation = annotation
self.fileMacro = fileMacro ?? ""
self.path = path
self.declType = declType
super.init(viewMode: .sourceAccurate)
}
override func visit(_ node: ProtocolDeclSyntax) -> SyntaxVisitorContinueKind {
let metadata = node.annotationMetadata(with: annotation)
if let ent = Entity.node(with: node, filepath: path, isPrivate: node.isPrivate, isFinal: false, metadata: metadata, processed: false) {
entities.append(ent)
}
return .skipChildren
}
override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind {
return node.genericParameterClause != nil ? .skipChildren : .visitChildren
}
override func visit(_ node: EnumDeclSyntax) -> SyntaxVisitorContinueKind {
return node.genericParameterClause != nil ? .skipChildren : .visitChildren
}
override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind {
if node.nameText.hasSuffix("Mock") {
// this mock class node must be public else wouldn't have compiled before
if let ent = Entity.node(with: node, filepath: path, isPrivate: node.isPrivate, isFinal: false, metadata: nil, processed: true) {
entities.append(ent)
}
} else {
if declType == .classType || declType == .all {
let metadata = node.annotationMetadata(with: annotation)
if let ent = Entity.node(with: node, filepath: path, isPrivate: node.isPrivate, isFinal: node.isFinal, metadata: metadata, processed: false) {
entities.append(ent)
}
}
}
return node.genericParameterClause != nil ? .skipChildren : .visitChildren
}
override func visit(_ node: ActorDeclSyntax) -> SyntaxVisitorContinueKind {
return node.genericParameterClause != nil ? .skipChildren : .visitChildren
}
override func visit(_ node: ImportDeclSyntax) -> SyntaxVisitorContinueKind {
if let ret = node.path.firstToken(viewMode: .sourceAccurate)?.text {
let desc = node.importKeyword.text + " " + ret
imports["", default: []].append(desc)
}
return .skipChildren
}
override func visit(_ node: IfConfigDeclSyntax) -> SyntaxVisitorContinueKind {
for cl in node.clauses {
let macroName: String
if let conditionDescription = cl.condition?.trimmedDescription {
macroName = conditionDescription
} else {
return .visitChildren
}
guard macroName != fileMacro else { return .visitChildren }
if let list = cl.elements?.as(CodeBlockItemListSyntax.self) {
for el in list {
if let importItem = el.item.as(ImportDeclSyntax.self) {
let key = macroName
if imports[key] == nil {
imports[key] = []
}
imports[key]?.append(importItem.trimmedDescription)
} else if let nested = el.item.as(IfConfigDeclSyntax.self) {
let key = macroName
if imports[key] == nil {
imports[key] = []
}
imports[key]?.append(nested.trimmedDescription)
} else {
return .visitChildren
}
}
}
}
return .skipChildren
}
override func visit(_ node: InitializerDeclSyntax) -> SyntaxVisitorContinueKind {
return .skipChildren
}
override func visit(_ node: FunctionDeclSyntax) -> SyntaxVisitorContinueKind {
return .skipChildren
}
override func visit(_ node: VariableDeclSyntax) -> SyntaxVisitorContinueKind {
return .skipChildren
}
}
extension Trivia {
// This parses arguments in annotation which can be used to override certain types.
//
// E.g. given /// @mockable(typealias: T = Any; U = AnyObject), it returns
// a dictionary: [T: Any, U: AnyObject] which will be used to override inhertied types
// of typealias decls for T and U.
private func metadata(with annotation: String, in val: String) -> AnnotationMetadata? {
guard val.contains(annotation) else {
return nil
}
let comps = val.components(separatedBy: annotation)
var ret = AnnotationMetadata()
guard var argsStr = comps.last, !argsStr.isEmpty else {
return ret
}
if argsStr.hasPrefix("(") {
argsStr.removeFirst()
}
if argsStr.hasSuffix(")") {
argsStr.removeLast()
}
if let arguments = parseArguments(argsStr, identifier: .typealiasColon) {
ret.typeAliases = arguments
}
if let arguments = parseArguments(argsStr, identifier: .moduleColon) {
ret.module = arguments[.prefix]
}
if let arguments = parseArguments(argsStr, identifier: .overrideColon) {
ret.nameOverride = arguments[.name]
}
if let arguments = parseArguments(argsStr, identifier: .rxColon) {
ret.varTypes = arguments
}
if let arguments = parseArguments(argsStr, identifier: .varColon) {
if ret.varTypes == nil {
ret.varTypes = arguments
} else {
ret.varTypes?.merge(arguments, uniquingKeysWith: {$1})
}
}
if let arguments = parseArguments(argsStr, identifier: .historyColon) {
ret.funcsWithArgsHistory = arguments.compactMap { k, v in v == "true" ? k : nil }
}
if let arguments = parseArguments(argsStr, identifier: .combineColon) {
ret.combineTypes = ret.combineTypes ?? [String: CombineType]()
let currentValueSubjectStr = CombineType.currentValueSubject.typeName.lowercased()
for pair in arguments {
if pair.value.hasPrefix("@") {
let parts = pair.value.split(separator: " ")
if parts.count == 2 {
ret.combineTypes?[pair.key] = .property(wrapper: String(parts[0]), name: String(parts[1]))
continue
}
}
if pair.value.lowercased() == currentValueSubjectStr {
ret.combineTypes?[pair.key] = .currentValueSubject
} else {
ret.combineTypes?[pair.key] = .passthroughSubject
}
}
}
if let arguments = parseArguments(argsStr, identifier: .modifiersColon) {
var modifiers: [String: Modifier] = [:]
for tuple in arguments {
guard let modifier: Modifier = Modifier(rawValue: tuple.value) else {
continue
}
modifiers[tuple.key] = modifier
}
ret.modifiers = modifiers
}
return ret
}
private func parseArguments(_ argsStr: String, identifier: String) -> [String: String]? {
guard
argsStr.contains(identifier),
let subStr = argsStr.components(separatedBy: identifier).last,
!subStr.isEmpty
else {
return nil
}
return subStr.arguments(with: .annotationArgDelimiter)
}
// Looks up an annotation (e.g. /// @mockable) and its arguments if any.
// See metadata(with:, in:) for more info on the annotation arguments.
func annotationMetadata(with annotation: String) -> AnnotationMetadata? {
guard !annotation.isEmpty else { return nil }
var ret: AnnotationMetadata?
for trivia in self {
switch trivia {
case .docLineComment(let val):
ret = metadata(with: annotation, in: val)
if ret != nil {
return ret
}
case .docBlockComment(let val):
ret = metadata(with: annotation, in: val)
if ret != nil {
return ret
}
default:
continue
}
}
return nil
}
}
extension ThrowingKind {
fileprivate init(_ syntax: ThrowsClauseSyntax?) {
guard let syntax else {
self = .none
return
}
if syntax.throwsSpecifier.tokenKind == .keyword(.rethrows) {
self = .rethrows
} else {
if let type = syntax.type {
self = .typed(errorType: type.trimmedDescription)
} else {
self = .any
}
}
}
}