Skip to content

Commit a829e96

Browse files
committed
Add ignore-unparsable-files flag to silence diagnostics for invalid syntax.
Without this flag, a diagnostic is raised for any file that contains invalid syntax. With the flag, files containing invalid syntax are silently ignored and output verbatim when output is expected (e.g. in format mode with in-place disabled). The Swift language evolves quickly, with new syntax becoming available and old syntax becoming invalid, and it's difficult to always have swift-format support cutting edge syntax. Before this change, the formatter correctly refused to format such files but the diagnostic to `stderr` would typically be seen as a fatal error by tooling that automates formatting. This flag provides a way to acknowledge the syntax may be invalid as a non-error condition.
1 parent 5e7ef93 commit a829e96

File tree

3 files changed

+45
-12
lines changed

3 files changed

+45
-12
lines changed

Sources/swift-format/CommandLineOptions.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,16 @@ struct SwiftFormatCommand: ParsableCommand {
7171
help: "Recursively run on '.swift' files in any provided directories.")
7272
var recursive: Bool
7373

74+
/// Whether unparsable files, due to syntax errors or unrecognized syntax, should be ignored or
75+
/// treated as containing an error. When ignored, unparsable files are output verbatim in format
76+
/// mode and no diagnostics are raised in lint mode. When not ignored, unparsable files raise a
77+
/// diagnostic in both format and lint mode.
78+
@Flag(help: """
79+
Ignores unparsable files, disabling all diagnostics and formatting for files that contain \
80+
invalid syntax.
81+
""")
82+
var ignoreUnparsableFiles: Bool
83+
7484
/// The list of paths to Swift source files that should be formatted or linted.
7585
@Argument(help: "One or more input filenames")
7686
var paths: [String]

Sources/swift-format/Run.swift

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,13 @@ import TSCBasic
2424
/// - configuration: The `Configuration` that contains user-specific settings.
2525
/// - sourceFile: A file handle from which to read the source code to be linted.
2626
/// - assumingFilename: The filename of the source file, used in diagnostic output.
27+
/// - ignoreUnparsableFiles: Whether or not to ignore files that contain syntax errors.
2728
/// - debugOptions: The set containing any debug options that were supplied on the command line.
2829
/// - diagnosticEngine: A diagnostic collector that handles diagnostic messages.
2930
/// - Returns: Zero if there were no lint errors, otherwise a non-zero number.
3031
func lintMain(
3132
configuration: Configuration, sourceFile: FileHandle, assumingFilename: String?,
32-
debugOptions: DebugOptions, diagnosticEngine: DiagnosticEngine
33+
ignoreUnparsableFiles: Bool, debugOptions: DebugOptions, diagnosticEngine: DiagnosticEngine
3334
) {
3435
let linter = SwiftLinter(configuration: configuration, diagnosticEngine: diagnosticEngine)
3536
linter.debugOptions = debugOptions
@@ -49,6 +50,9 @@ func lintMain(
4950
Diagnostic.Message(.error, "Unable to lint \(path): file is not readable or does not exist."))
5051
return
5152
} catch SwiftFormatError.fileContainsInvalidSyntax(let position) {
53+
guard !ignoreUnparsableFiles else {
54+
return
55+
}
5256
let path = assumingFileURL.path
5357
let location = SourceLocationConverter(file: path, source: source).location(for: position)
5458
diagnosticEngine.diagnose(
@@ -69,12 +73,13 @@ func lintMain(
6973
/// - sourceFile: A file handle from which to read the source code to be linted.
7074
/// - assumingFilename: The filename of the source file, used in diagnostic output.
7175
/// - inPlace: Whether or not to overwrite the current file when formatting.
76+
/// - ignoreUnparsableFiles: Whether or not to ignore files that contain syntax errors.
7277
/// - debugOptions: The set containing any debug options that were supplied on the command line.
7378
/// - diagnosticEngine: A diagnostic collector that handles diagnostic messages.
7479
/// - Returns: Zero if there were no format errors, otherwise a non-zero number.
7580
func formatMain(
7681
configuration: Configuration, sourceFile: FileHandle, assumingFilename: String?, inPlace: Bool,
77-
debugOptions: DebugOptions, diagnosticEngine: DiagnosticEngine
82+
ignoreUnparsableFiles: Bool, debugOptions: DebugOptions, diagnosticEngine: DiagnosticEngine
7883
) {
7984
// Even though `diagnosticEngine` is defined, it's use is reserved for fatal messages. Pass nil
8085
// to the formatter to suppress other messages since they will be fixed or can't be automatically
@@ -111,6 +116,15 @@ func formatMain(
111116
.error, "Unable to format \(path): file is not readable or does not exist."))
112117
return
113118
} catch SwiftFormatError.fileContainsInvalidSyntax(let position) {
119+
guard !ignoreUnparsableFiles else {
120+
guard !inPlace else {
121+
// For in-place mode, nothing is expected to stdout and the file shouldn't be modified.
122+
return
123+
}
124+
stdoutStream.write(source)
125+
stdoutStream.flush()
126+
return
127+
}
114128
let path = assumingFileURL.path
115129
let location = SourceLocationConverter(file: path, source: source).location(for: position)
116130
diagnosticEngine.diagnose(

Sources/swift-format/main.swift

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,13 @@
1010
//
1111
//===----------------------------------------------------------------------===//
1212

13+
import ArgumentParser
1314
import Foundation
1415
import SwiftFormat
1516
import SwiftFormatConfiguration
1617
import SwiftFormatCore
1718
import SwiftSyntax
1819
import TSCBasic
19-
import ArgumentParser
2020

2121
extension SwiftFormatCommand {
2222
func run() throws {
@@ -29,36 +29,44 @@ extension SwiftFormatCommand {
2929
formatMain(
3030
configuration: configuration, sourceFile: FileHandle.standardInput,
3131
assumingFilename: assumeFilename, inPlace: false,
32+
ignoreUnparsableFiles: ignoreUnparsableFiles,
3233
debugOptions: debugOptions, diagnosticEngine: diagnosticEngine)
3334
} else {
34-
try processSources(from: paths, configurationPath: configurationPath, diagnosticEngine: diagnosticEngine) {
35+
try processSources(
36+
from: paths, configurationPath: configurationPath, diagnosticEngine: diagnosticEngine
37+
) {
3538
(sourceFile, path, configuration) in
3639
formatMain(
3740
configuration: configuration, sourceFile: sourceFile, assumingFilename: path,
38-
inPlace: inPlace, debugOptions: debugOptions, diagnosticEngine: diagnosticEngine)
41+
inPlace: inPlace, ignoreUnparsableFiles: ignoreUnparsableFiles,
42+
debugOptions: debugOptions, diagnosticEngine: diagnosticEngine)
3943
}
4044
}
41-
45+
4246
case .lint:
4347
if paths.isEmpty {
4448
let configuration = try loadConfiguration(
4549
forSwiftFile: nil, configFilePath: configurationPath)
4650
lintMain(
47-
configuration: configuration, sourceFile: FileHandle.standardInput,
48-
assumingFilename: assumeFilename, debugOptions: debugOptions, diagnosticEngine: diagnosticEngine)
51+
configuration: configuration, sourceFile: FileHandle.standardInput,
52+
assumingFilename: assumeFilename, ignoreUnparsableFiles: ignoreUnparsableFiles,
53+
debugOptions: debugOptions, diagnosticEngine: diagnosticEngine)
4954
} else {
50-
try processSources(from: paths, configurationPath: configurationPath, diagnosticEngine: diagnosticEngine) {
55+
try processSources(
56+
from: paths, configurationPath: configurationPath, diagnosticEngine: diagnosticEngine
57+
) {
5158
(sourceFile, path, configuration) in
5259
lintMain(
5360
configuration: configuration, sourceFile: sourceFile, assumingFilename: path,
61+
ignoreUnparsableFiles: ignoreUnparsableFiles,
5462
debugOptions: debugOptions, diagnosticEngine: diagnosticEngine)
5563
}
5664
}
57-
65+
5866
case .dumpConfiguration:
5967
try dumpDefaultConfiguration()
6068
}
61-
69+
6270
// If any of the operations have generated diagnostics, exit with the
6371
// error status code.
6472
if !diagnosticEngine.diagnostics.isEmpty {
@@ -140,7 +148,8 @@ private func dumpDefaultConfiguration() throws {
140148
guard let jsonString = String(data: data, encoding: .utf8) else {
141149
// This should never happen, but let's make sure we fail more gracefully than crashing, just
142150
// in case.
143-
throw FormatError(message: "Could not dump the default configuration: the JSON was not valid UTF-8")
151+
throw FormatError(
152+
message: "Could not dump the default configuration: the JSON was not valid UTF-8")
144153
}
145154
print(jsonString)
146155
} catch {

0 commit comments

Comments
 (0)