Add command to lint the projects

This commit is contained in:
Pedro Piñera 2020-03-02 09:46:26 +01:00 committed by Pedro Piñera
parent 9803ab6290
commit 8370f38cf0
7 changed files with 157 additions and 23 deletions

View File

@ -116,7 +116,7 @@ public class Generator: Generating {
public func generateProject(at path: AbsolutePath) throws -> (AbsolutePath, Graphing) {
let tuistConfig = try graphLoader.loadTuistConfig(path: path)
try environmentLinter.lint(config: tuistConfig)
try environmentLinter.lint(config: tuistConfig).printAndThrowIfNeeded()
let (graph, project) = try graphLoader.loadProject(path: path)
try graphLinter.lint(graph: graph).printAndThrowIfNeeded()
@ -131,7 +131,7 @@ public class Generator: Generating {
public func generateProjectWorkspace(at path: AbsolutePath,
workspaceFiles: [AbsolutePath]) throws -> (AbsolutePath, Graphing) {
let tuistConfig = try graphLoader.loadTuistConfig(path: path)
try environmentLinter.lint(config: tuistConfig)
try environmentLinter.lint(config: tuistConfig).printAndThrowIfNeeded()
let (graph, project) = try graphLoader.loadProject(path: path)
try graphLinter.lint(graph: graph).printAndThrowIfNeeded()
@ -151,7 +151,7 @@ public class Generator: Generating {
public func generateWorkspace(at path: AbsolutePath,
workspaceFiles: [AbsolutePath]) throws -> (AbsolutePath, Graphing) {
let tuistConfig = try graphLoader.loadTuistConfig(path: path)
try environmentLinter.lint(config: tuistConfig)
try environmentLinter.lint(config: tuistConfig).printAndThrowIfNeeded()
let (graph, workspace) = try graphLoader.loadWorkspace(path: path)
try graphLinter.lint(graph: graph).printAndThrowIfNeeded()

View File

@ -2,25 +2,24 @@ import Foundation
import TuistCore
import TuistSupport
protocol EnvironmentLinting {
public protocol EnvironmentLinting {
/// Lints a given Tuist configuration.
///
/// - Parameter config: Tuist configuration to be linted against the system.
/// - Throws: An error if the validation fails.
func lint(config: TuistConfig) throws
/// - Returns: A list of linting issues.
func lint(config: TuistConfig) throws -> [LintingIssue]
}
class EnvironmentLinter: EnvironmentLinting {
/// Lints a given Tuist configuration.
///
/// - Parameter config: Tuist configuration to be linted against the system.
/// - Throws: An error if the validation fails.
func lint(config: TuistConfig) throws {
public class EnvironmentLinter: EnvironmentLinting {
/// Default constructor.
public init() {}
public func lint(config: TuistConfig) throws -> [LintingIssue] {
var issues = [LintingIssue]()
issues.append(contentsOf: try lintXcodeVersion(config: config))
try issues.printAndThrowIfNeeded()
return issues
}
/// Returns a linting issue if the selected version of Xcode is not compatible with the

View File

@ -3,12 +3,12 @@ import SPMUtility
import TuistCore
import TuistSupport
protocol GraphLinting: AnyObject {
public protocol GraphLinting: AnyObject {
func lint(graph: Graphing) -> [LintingIssue]
}
// swiftlint:disable type_body_length
class GraphLinter: GraphLinting {
public class GraphLinter: GraphLinting {
// MARK: - Attributes
private let projectLinter: ProjectLinting
@ -16,15 +16,20 @@ class GraphLinter: GraphLinting {
// MARK: - Init
init(projectLinter: ProjectLinting = ProjectLinter(),
staticProductsLinter: StaticProductsGraphLinting = StaticProductsGraphLinter()) {
public convenience init() {
self.init(projectLinter: ProjectLinter(),
staticProductsLinter: StaticProductsGraphLinter())
}
init(projectLinter: ProjectLinting,
staticProductsLinter: StaticProductsGraphLinting) {
self.projectLinter = projectLinter
self.staticProductsLinter = staticProductsLinter
}
// MARK: - GraphLinting
func lint(graph: Graphing) -> [LintingIssue] {
public func lint(graph: Graphing) -> [LintingIssue] {
var issues: [LintingIssue] = []
issues.append(contentsOf: graph.projects.flatMap(projectLinter.lint))
issues.append(contentsOf: lintDependencies(graph: graph))

View File

@ -28,6 +28,7 @@ public final class CommandRegistry {
register(command: GraphCommand.self)
register(command: EditCommand.self)
register(command: CacheCommand.self)
register(command: LintCommand.self)
register(rawCommand: BuildCommand.self)
}

View File

@ -0,0 +1,115 @@
import Basic
import Foundation
import SPMUtility
import TuistCore
import TuistGenerator
import TuistLoader
import TuistSupport
enum LintCommandError: FatalError, Equatable {
/// Thrown when neither a workspace or a project is found in the given path.
case manifestNotFound(AbsolutePath)
/// Error type.
var type: ErrorType {
switch self {
case .manifestNotFound:
return .abort
}
}
/// Description
var description: String {
switch self {
case let .manifestNotFound(path):
return "Couldn't find Project.swift nor Workspace.swift at \(path.pathString)"
}
}
}
/// Command that builds a target from the project in the current directory.
class LintCommand: NSObject, Command {
/// Command name.
static var command: String = "lint"
/// Command description.
static var overview: String = "Lints a workspace or a project that check whether they are well configured."
/// Graph linter
private let graphLinter: GraphLinting
private let environmentLinter: EnvironmentLinting
private let manifestLoading: ManifestLoading
private let graphLoader: GraphLoading
let pathArgument: OptionArgument<String>
/// Default constructor.
public required convenience init(parser: ArgumentParser) {
let manifestLoader = ManifestLoader()
let generatorModelLoader = GeneratorModelLoader(manifestLoader: manifestLoader,
manifestLinter: AnyManifestLinter())
self.init(graphLinter: GraphLinter(),
environmentLinter: EnvironmentLinter(),
manifestLoading: manifestLoader,
graphLoader: GraphLoader(modelLoader: generatorModelLoader),
parser: parser)
}
init(graphLinter: GraphLinting,
environmentLinter: EnvironmentLinting,
manifestLoading: ManifestLoading,
graphLoader: GraphLoading,
parser: ArgumentParser) {
let subParser = parser.add(subparser: LintCommand.command, overview: LintCommand.overview)
self.graphLinter = graphLinter
self.environmentLinter = environmentLinter
self.manifestLoading = manifestLoading
self.graphLoader = graphLoader
pathArgument = subParser.add(option: "--path",
shortName: "-p",
kind: String.self,
usage: "The path to the directory that contains the workspace or project to be linted",
completion: .filename)
}
func run(with arguments: ArgumentParser.Result) throws {
let path = self.path(arguments: arguments)
// Load graph
let manifests = manifestLoading.manifests(at: path)
var graph: Graphing!
Printer.shared.print(section: "Loading the dependency graph")
if manifests.contains(.workspace) {
Printer.shared.print("Loading workspace at \(path.pathString)")
(graph, _) = try graphLoader.loadWorkspace(path: path)
} else if manifests.contains(.project) {
Printer.shared.print("Loading project at \(path.pathString)")
(graph, _) = try graphLoader.loadProject(path: path)
} else {
throw LintCommandError.manifestNotFound(path)
}
Printer.shared.print(section: "Running linters")
let config = try graphLoader.loadTuistConfig(path: path)
var issues: [LintingIssue] = []
Printer.shared.print("Linting the environment")
issues.append(contentsOf: try environmentLinter.lint(config: config))
Printer.shared.print("Linting the loaded dependency graph")
issues.append(contentsOf: graphLinter.lint(graph: graph))
if issues.isEmpty {
Printer.shared.print(success: "No linting issues found")
} else {
try issues.printAndThrowIfNeeded()
}
}
private func path(arguments: ArgumentParser.Result) -> AbsolutePath {
if let path = arguments.get(pathArgument) {
return AbsolutePath(path, relativeTo: FileHandler.shared.currentPath)
} else {
return FileHandler.shared.currentPath
}
}
}

View File

@ -7,6 +7,22 @@ public protocol ManifestLinting {
func lint(project: ProjectDescription.Project) -> [LintingIssue]
}
public class AnyManifestLinter: ManifestLinting {
let lint: ((ProjectDescription.Project) -> [LintingIssue])?
public init(lint: ((ProjectDescription.Project) -> [LintingIssue])? = nil) {
self.lint = lint
}
public func lint(project: ProjectDescription.Project) -> [LintingIssue] {
if let lint = self.lint {
return lint(project)
} else {
return []
}
}
}
public class ManifestLinter: ManifestLinting {
public init() {}

View File

@ -3,13 +3,11 @@ import TuistCore
@testable import TuistGenerator
final class MockEnvironmentLinter: EnvironmentLinting {
var lintStub: Error?
var lintStub: [LintingIssue]?
var lintArgs: [TuistConfig] = []
func lint(config: TuistConfig) throws {
func lint(config: TuistConfig) throws -> [LintingIssue] {
lintArgs.append(config)
if let error = lintStub {
throw error
}
return lintStub ?? []
}
}