Sources/MockoloFramework/Parsers/SourceParser.swift (71 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 Foundation
import SwiftSyntax
import SwiftParser
public enum FindTargetDeclType {
case protocolType, classType, other, all
}
public class SourceParser {
public init() {}
/// Parses processed decls (mock classes) and calls a completion block
/// @param paths File paths containing processed mocks
/// @param fileMacro: File level macro
/// @param completion:The block to be executed on completion
public func parseProcessedDecls(_ paths: [String],
fileMacro: String?,
completion: @escaping ([Entity], ImportMap?) -> ()) {
scan(paths) { (path, lock) in
self.generateASTs(path, annotation: "", fileMacro: fileMacro, declType: .classType, lock: lock, completion: completion)
}
}
/// Parses decls (protocol, class) with annotations (/// @mockable) and calls a completion block
/// @param paths File/dir paths containing types with mock annotation
/// @param isDirs:True if paths are dir paths
/// @param exclusionSuffixess List of file suffixes to exclude when processing
/// @param annotation The mock annotation
/// @param fileMacro: File level macro
/// @param declType: The declaration type, e.g. protocol, class.
/// @param completion:The block to be executed on completion
public func parseDecls(_ paths: [String],
isDirs: Bool,
exclusionSuffixes: [String],
annotation: String,
fileMacro: String?,
declType: FindTargetDeclType,
completion: @escaping ([Entity], ImportMap?) -> ()) {
guard !paths.isEmpty else { return }
scan(paths, isDirectory: isDirs) { (path, lock) in
self.generateASTs(path,
exclusionSuffixes: exclusionSuffixes,
annotation: annotation,
fileMacro: fileMacro,
declType: declType,
lock: lock,
completion: completion)
}
}
private func generateASTs(_ path: String,
exclusionSuffixes: [String] = [],
annotation: String,
fileMacro: String?,
declType: FindTargetDeclType,
lock: NSLock?,
completion: @escaping ([Entity], ImportMap?) -> ()) {
guard path.shouldParse(with: exclusionSuffixes) else { return }
if !annotation.isEmpty {
if declType == .protocolType, !containsDecl(String.protocolDecl, in: path) {
return
}
if declType == .classType, !containsDecl(String.classDecl, in: path) {
return
}
if declType == .all, !containsDecl(String.protocolDecl, in: path), !containsDecl(String.classDecl, in: path) {
return
}
}
var results = [Entity]()
let node = Parser.parse(path)
let treeVisitor = EntityVisitor(path, annotation: annotation, fileMacro: fileMacro, declType: declType)
treeVisitor.walk(node)
let ret = treeVisitor.entities
results.append(contentsOf: ret)
let importMap = treeVisitor.imports
lock?.lock()
defer {lock?.unlock()}
completion(results, [path: importMap])
}
private func containsDecl(_ decl: Data?, in path: String) -> Bool {
guard let decl = decl else { return false }
guard let content = FileManager.default.contents(atPath: path) else {
fatalError("Retrieving contents of \(path) failed")
}
return content.range(of: decl) != nil
}
}