diff --git a/README.md b/README.md index 6cb7934af..2494403d3 100644 --- a/README.md +++ b/README.md @@ -82,6 +82,9 @@ as well as the following command line options: * `-i/--in-place`: Overwrites the input files when formatting instead of printing the results to standard output. +* `-p/--parallel`: Process files in parallel, simultaneously across + multiple cores. + * `-r/--recursive`: If specified, then the tool will process `.swift` source files in any directories listed on the command line and their descendants. Without this flag, it is an error to list a directory on the command line. diff --git a/Sources/swift-format/Frontend/Frontend.swift b/Sources/swift-format/Frontend/Frontend.swift index 26f085be7..fce1563ca 100644 --- a/Sources/swift-format/Frontend/Frontend.swift +++ b/Sources/swift-format/Frontend/Frontend.swift @@ -92,7 +92,7 @@ class Frontend { if paths.isEmpty { processStandardInput() } else { - processPaths(paths) + processPaths(paths, parallel: lintFormatOptions.parallel) } } @@ -124,28 +124,41 @@ class Frontend { } /// Processes source content from a list of files and/or directories provided as paths. - private func processPaths(_ paths: [String]) { + private func processPaths(_ paths: [String], parallel: Bool) { precondition( !paths.isEmpty, "processPaths(_:) should only be called when paths is non-empty.") - for path in FileIterator(paths: paths) { - guard let sourceFile = FileHandle(forReadingAtPath: path) else { - diagnosticEngine.diagnose(Diagnostic.Message(.error, "Unable to open \(path)")) - continue + if parallel { + let allFilePaths = Array(FileIterator(paths: paths)) + DispatchQueue.concurrentPerform(iterations: allFilePaths.count) { index in + let path = allFilePaths[index] + openAndProcess(path) } - - guard let configuration = configuration( - atPath: lintFormatOptions.configurationPath, orInferredFromSwiftFileAtPath: path) - else { - // Already diagnosed in the called method. - continue + } else { + for path in FileIterator(paths: paths) { + openAndProcess(path) } + } + } + + /// Read and process the given path, optionally synchronizing diagnostic output. + private func openAndProcess(_ path: String) -> Void { + guard let sourceFile = FileHandle(forReadingAtPath: path) else { + diagnosticEngine.diagnose(Diagnostic.Message(.error, "Unable to open \(path)")) + return + } - let fileToProcess = FileToProcess( - fileHandle: sourceFile, path: path, configuration: configuration) - processFile(fileToProcess) + guard let configuration = configuration( + atPath: lintFormatOptions.configurationPath, orInferredFromSwiftFileAtPath: path) + else { + // Already diagnosed in the called method. + return } + + let fileToProcess = FileToProcess( + fileHandle: sourceFile, path: path, configuration: configuration) + processFile(fileToProcess) } /// Returns the configuration that applies to the given `.swift` source file, when an explicit diff --git a/Sources/swift-format/Subcommands/LintFormatOptions.swift b/Sources/swift-format/Subcommands/LintFormatOptions.swift index d841f7607..e726dd614 100644 --- a/Sources/swift-format/Subcommands/LintFormatOptions.swift +++ b/Sources/swift-format/Subcommands/LintFormatOptions.swift @@ -49,6 +49,12 @@ struct LintFormatOptions: ParsableArguments { """) var ignoreUnparsableFiles: Bool = false + /// Whether or not to run the formatter/linter in parallel. + @Flag( + name: .shortAndLong, + help: "Process files in parallel, simultaneously across multiple cores.") + var parallel: Bool = false + /// The list of paths to Swift source files that should be formatted or linted. @Argument(help: "Zero or more input filenames.") var paths: [String] = []