//===----------------------------------------------------------------------===// // // This source file is part of the Swift.org open source project // // Copyright (c) 2014 - 2019 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See https://swift.org/LICENSE.txt for license information // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// import Foundation import SwiftDiagnostics import SwiftOperators import SwiftSyntax /// Diagnoses and reports problems in Swift source code or syntax trees according to the Swift style /// guidelines. public final class SwiftLinter { /// The configuration settings that control the linter's behavior. public let configuration: Configuration /// A callback that will be notified with any findings encountered during linting. public let findingConsumer: (Finding) -> Void /// Advanced options that are useful when debugging the linter's behavior but are not meant for /// general use. public var debugOptions: DebugOptions = [] /// Creates a new Swift code linter with the given configuration. /// /// - Parameters: /// - configuration: The configuration settings that control the linter's behavior. /// - findingConsumer: A callback that will be notified with any findings encountered during /// linting. public init(configuration: Configuration, findingConsumer: @escaping (Finding) -> Void) { self.configuration = configuration self.findingConsumer = findingConsumer } /// Lints the Swift code at the given file URL. /// /// This form of the `lint` function automatically folds expressions using the default operator /// set defined in Swift. If you need more control over this—for example, to provide the correct /// precedence relationships for custom operators—you must parse and fold the syntax tree /// manually and then call ``lint(syntax:assumingFileURL:)``. /// /// - Parameters: /// - url: The URL of the file containing the code to format. /// - parsingDiagnosticHandler: An optional callback that will be notified if there are any /// errors when parsing the source code. /// - Throws: If an unrecoverable error occurs when formatting the code. public func lint( contentsOf url: URL, parsingDiagnosticHandler: ((Diagnostic, SourceLocation) -> Void)? = nil ) throws { guard FileManager.default.isReadableFile(atPath: url.path) else { throw SwiftFormatError.fileNotReadable } var isDir: ObjCBool = false if FileManager.default.fileExists(atPath: url.path, isDirectory: &isDir), isDir.boolValue { throw SwiftFormatError.isDirectory } try lint( source: String(contentsOf: url, encoding: .utf8), assumingFileURL: url, parsingDiagnosticHandler: parsingDiagnosticHandler) } /// Lints the given Swift source code. /// /// This form of the `lint` function automatically folds expressions using the default operator /// set defined in Swift. If you need more control over this—for example, to provide the correct /// precedence relationships for custom operators—you must parse and fold the syntax tree /// manually and then call ``lint(syntax:assumingFileURL:)``. /// /// - Parameters: /// - source: The Swift source code to be linted. /// - url: A file URL denoting the filename/path that should be assumed for this source code. /// - parsingDiagnosticHandler: An optional callback that will be notified if there are any /// errors when parsing the source code. /// - Throws: If an unrecoverable error occurs when formatting the code. public func lint( source: String, assumingFileURL url: URL, parsingDiagnosticHandler: ((Diagnostic, SourceLocation) -> Void)? = nil ) throws { // If the file or input string is completely empty, do nothing. This prevents even a trailing // newline from being diagnosed for an empty file. (This is consistent with clang-format, which // also does not touch an empty file even if the setting to add trailing newlines is enabled.) guard !source.isEmpty else { return } let sourceFile = try parseAndEmitDiagnostics( source: source, operatorTable: .standardOperators, assumingFileURL: url, parsingDiagnosticHandler: parsingDiagnosticHandler) try lint( syntax: sourceFile, operatorTable: .standardOperators, assumingFileURL: url, source: source) } /// Lints the given Swift syntax tree. /// /// This form of the `lint` function does not perform any additional processing on the given /// syntax tree. The tree **must** have all expressions folded using an `OperatorTable`, and no /// detection of warnings/errors is performed. /// /// - Note: The linter may be faster using the source text, if it's available. /// /// - Parameters: /// - syntax: The Swift syntax tree to be converted to be linted. /// - operatorTable: The table that defines the operators and their precedence relationships. /// This must be the same operator table that was used to fold the expressions in the `syntax` /// argument. /// - url: A file URL denoting the filename/path that should be assumed for this syntax tree. /// - Throws: If an unrecoverable error occurs when formatting the code. public func lint( syntax: SourceFileSyntax, operatorTable: OperatorTable, assumingFileURL url: URL ) throws { try lint(syntax: syntax, operatorTable: operatorTable, assumingFileURL: url, source: nil) } private func lint( syntax: SourceFileSyntax, operatorTable: OperatorTable, assumingFileURL url: URL, source: String? ) throws { let context = Context( configuration: configuration, operatorTable: operatorTable, findingConsumer: findingConsumer, fileURL: url, sourceFileSyntax: syntax, source: source, ruleNameCache: ruleNameCache) let pipeline = LintPipeline(context: context) pipeline.walk(Syntax(syntax)) if debugOptions.contains(.disablePrettyPrint) { return } // Perform whitespace linting by comparing the input source text with the output of the // pretty-printer. let printer = PrettyPrinter( context: context, node: Syntax(syntax), printTokenStream: debugOptions.contains(.dumpTokenStream), whitespaceOnly: true) let formatted = printer.prettyPrint() let ws = WhitespaceLinter(user: syntax.description, formatted: formatted, context: context) ws.lint() } }