-
Notifications
You must be signed in to change notification settings - Fork 243
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Ability To Turn Off Formatting For Subdirectory #873
Open
samdeane
wants to merge
26
commits into
swiftlang:main
Choose a base branch
from
elegantchaos:main
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
26 commits
Select commit
Hold shift + click to select a range
eb29cb8
Initial stab at option to disable all formatting.
samdeane 356b55f
Updated config documentation in README.
samdeane 021d6b3
Fixed typo, initialiser.
samdeane 3e4c5d2
Documented allDisabled key
samdeane 8bade83
Emit unmodified source if allDisabled is true.
samdeane aac6fe8
Don't lint if allDisabled is set.
samdeane a7d4c24
Change flag to skipAll.
samdeane 3046d92
Tweaked docs
samdeane bb4382e
Check for suppression file first.
samdeane 17dc1bd
Use .swift-format-ignore as the file name.
samdeane cf27a3c
Move suppression logic up to FileIterator
samdeane 7b22d51
Removed spurious newlines
samdeane fb8b06f
Filter urls to remove ignored ones.
samdeane 5d85e22
Tweaked readme.
samdeane 9ab91a5
Removed obsolete addition.
samdeane 7b35513
Oops - skipping initial directory...
samdeane b2975de
Documentation tweaks.
samdeane ed1d3c4
Add minimal IgnoreFile abstraction.
samdeane 3bc036e
Comment for IgnoreFile class
samdeane cff2b22
Added IgnoreFile unit tests
samdeane e9861ca
Added test for nested directory.
samdeane ba343ca
Added string constructor. Don't skip dir.
samdeane c9a14e3
Removed test resources
samdeane 707061f
fixed nested test with explanatory comment
samdeane 85840d6
Fixed merge conflict
samdeane 5b5db57
Actually, this test should fail (currently)
samdeane File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
//===----------------------------------------------------------------------===// | ||
// | ||
// This source file is part of the Swift.org open source project | ||
// | ||
// Copyright (c) 2014 - 2024 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 | ||
|
||
/// A file that describes which files and directories should be ignored by the formatter. | ||
/// In the future, this file may contain complex rules for ignoring files, based | ||
/// on pattern matching file paths. | ||
/// | ||
/// Currently, the only valid content for an ignore file is a single asterisk "*", | ||
/// optionally surrounded by whitespace. | ||
public class IgnoreFile { | ||
/// Name of the ignore file to look for. | ||
/// The presence of this file in a directory will cause the formatter | ||
/// to skip formatting files in that directory and its subdirectories. | ||
public static let standardFileName = ".swift-format-ignore" | ||
|
||
/// Errors that can be thrown by the IgnoreFile initializer. | ||
public enum Error: Swift.Error { | ||
/// Error thrown when initialising with invalid content. | ||
case invalidContent | ||
|
||
/// Error thrown when we fail to initialise with the given URL. | ||
case invalidFile(URL, Swift.Error) | ||
} | ||
|
||
/// Create an instance from a string. | ||
/// Returns nil if the content is not valid. | ||
public init(_ content: String) throws { | ||
guard content.trimmingCharacters(in: .whitespacesAndNewlines) == "*" else { | ||
throw Error.invalidContent | ||
} | ||
} | ||
|
||
/// Create an instance from the contents of the file at the given URL. | ||
/// Throws an error if the file content can't be read, or is not valid. | ||
public convenience init(contentsOf url: URL) throws { | ||
do { | ||
try self.init(try String(contentsOf: url, encoding: .utf8)) | ||
} catch { | ||
throw Error.invalidFile(url, error) | ||
} | ||
} | ||
|
||
/// Create an instance for the given directory, if a valid | ||
/// ignore file with the standard name is found in that directory. | ||
/// Returns nil if no ignore file is found. | ||
/// Throws an error if an invalid ignore file is found. | ||
/// | ||
/// Note that this initializer does not search parent directories for ignore files. | ||
public convenience init?(forDirectory directory: URL) throws { | ||
let url = directory.appendingPathComponent(IgnoreFile.standardFileName) | ||
|
||
do { | ||
try self.init(contentsOf: url) | ||
} catch { | ||
if case let Error.invalidFile(_, underlying) = error, (underlying as NSError).domain == NSCocoaErrorDomain, | ||
(underlying as NSError).code == NSFileReadNoSuchFileError | ||
{ | ||
return nil | ||
} | ||
throw error | ||
} | ||
} | ||
|
||
/// Create an instance to use for the given URL. | ||
/// We search for an ignore file starting from the given URL's container, | ||
/// and moving up the directory tree, until we reach the root directory. | ||
/// Returns nil if no ignore file is found. | ||
/// Throws an error if an invalid ignore file is found somewhere | ||
/// in the directory tree. | ||
/// | ||
/// Note that we start the search from the given URL's **container**, | ||
/// not the URL itself; the URL passed in is expected to be for a file. | ||
/// If you pass a directory URL, the search will not include the contents | ||
/// of that directory. | ||
public convenience init?(for url: URL) throws { | ||
guard !url.isRoot else { | ||
return nil | ||
} | ||
|
||
var containingDirectory = url.absoluteURL.standardized | ||
repeat { | ||
containingDirectory.deleteLastPathComponent() | ||
let url = containingDirectory.appendingPathComponent(IgnoreFile.standardFileName) | ||
if FileManager.default.isReadableFile(atPath: url.path) { | ||
try self.init(contentsOf: url) | ||
return | ||
} | ||
} while !containingDirectory.isRoot | ||
return nil | ||
} | ||
|
||
/// Should the given URL be processed? | ||
/// Currently the only valid ignore file content is "*", | ||
/// which means that all files should be ignored. | ||
func shouldProcess(_ url: URL) -> Bool { | ||
return false | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
@_spi(Internal) import SwiftFormat | ||
import XCTest | ||
|
||
final class IgnoreFileTests: XCTestCase { | ||
var testTreeURL: URL? | ||
|
||
/// Description of a file or directory tree to create for testing. | ||
enum TestTree { | ||
case file(String, String) | ||
case directory(String, [TestTree]) | ||
} | ||
|
||
override func tearDown() { | ||
// Clean up any test tree after each test. | ||
if let testTreeURL { | ||
// try? FileManager.default.removeItem(at: testTreeURL) | ||
} | ||
} | ||
|
||
/// Make a temporary directory tree for testing. | ||
/// Returns the URL of the root directory. | ||
/// The tree will be cleaned up after the test. | ||
/// If a tree is already set up, it will be cleaned up first. | ||
func makeTempTree(_ tree: TestTree) throws -> URL { | ||
if let testTreeURL { | ||
try? FileManager.default.removeItem(at: testTreeURL) | ||
} | ||
let tempDir = FileManager.default.temporaryDirectory | ||
let tempURL = tempDir.appendingPathComponent(UUID().uuidString) | ||
try FileManager.default.createDirectory(at: tempURL, withIntermediateDirectories: true) | ||
try writeTree(tree, to: tempURL) | ||
testTreeURL = tempURL | ||
return tempURL | ||
} | ||
|
||
/// Write a file or directory tree to the given root URL. | ||
func writeTree(_ tree: TestTree, to root: URL) throws { | ||
switch tree { | ||
case let .file(name, contents): | ||
print("Writing file \(name) to \(root)") | ||
try contents.write(to: root.appendingPathComponent(name), atomically: true, encoding: .utf8) | ||
case let .directory(name, children): | ||
let directory = root.appendingPathComponent(name) | ||
try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true) | ||
for child in children { | ||
try writeTree(child, to: directory) | ||
} | ||
} | ||
} | ||
|
||
func testMissingIgnoreFile() throws { | ||
let url = URL(filePath: "/") | ||
XCTAssertNil(try IgnoreFile(forDirectory: url)) | ||
XCTAssertNil(try IgnoreFile(for: url.appending(path: "file.swift"))) | ||
} | ||
|
||
func testValidIgnoreFile() throws { | ||
let url = try makeTempTree(.file(IgnoreFile.standardFileName, "*")) | ||
XCTAssertNotNil(try IgnoreFile(forDirectory: url)) | ||
XCTAssertNotNil(try IgnoreFile(for: url.appending(path: "file.swift"))) | ||
} | ||
|
||
func testInvalidIgnoreFile() throws { | ||
let url = try makeTempTree(.file(IgnoreFile.standardFileName, "this is an invalid pattern")) | ||
XCTAssertThrowsError(try IgnoreFile(forDirectory: url)) | ||
XCTAssertThrowsError(try IgnoreFile(for: url.appending(path: "file.swift"))) | ||
} | ||
|
||
func testEmptyIgnoreFile() throws { | ||
XCTAssertThrowsError(try IgnoreFile("")) | ||
} | ||
|
||
func testNestedIgnoreFile() throws { | ||
let url = try makeTempTree(.file(IgnoreFile.standardFileName, "*")) | ||
let fileInSubdirectory = url.appendingPathComponent("subdirectory").appending(path: "file.swift") | ||
XCTAssertNotNil(try IgnoreFile(for: fileInSubdirectory)) | ||
} | ||
|
||
func testIterateWithIgnoreFile() throws { | ||
let url = try makeTempTree(.file(IgnoreFile.standardFileName, "*")) | ||
let iterator = FileIterator(urls: [url], followSymlinks: false) | ||
let files = Array(iterator) | ||
XCTAssertEqual(files.count, 0) | ||
} | ||
|
||
func testIterateWithInvalidIgnoreFile() throws { | ||
let url = try makeTempTree(.file(IgnoreFile.standardFileName, "this file is invalid")) | ||
let iterator = FileIterator(urls: [url], followSymlinks: false) | ||
let files = Array(iterator) | ||
XCTAssertEqual(files.count, 1) | ||
XCTAssertTrue(files.first?.lastPathComponent == IgnoreFile.standardFileName) | ||
} | ||
|
||
func testIterateWithNestedIgnoreFile() throws { | ||
let url = try makeTempTree( | ||
.directory( | ||
"Source", | ||
[ | ||
.directory( | ||
"Ignored", | ||
[ | ||
.file(IgnoreFile.standardFileName, "*"), | ||
.file("file.swift", "contents"), | ||
] | ||
), | ||
.directory( | ||
"Not Ignored", | ||
[ | ||
.file("file.swift", "contents") | ||
] | ||
), | ||
] | ||
) | ||
) | ||
|
||
XCTAssertNil(try IgnoreFile(forDirectory: url)) | ||
XCTAssertNil(try IgnoreFile(for: url.appending(path: "Source/file.swift"))) | ||
XCTAssertNotNil(try IgnoreFile(for: url.appending(path: "Source/Ignored/file.swift"))) | ||
let iterator = FileIterator(urls: [url], followSymlinks: false) | ||
let files = Array(iterator) | ||
|
||
XCTAssertEqual(files.count, 1) | ||
XCTAssertEqual(files.first?.lastPathComponent, "file.swift") | ||
} | ||
|
||
} |
1 change: 1 addition & 0 deletions
1
Tests/SwiftFormatTests/Resources/Ignore Files/nested/.swift-format-ignore
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
* |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you add a test case that ensures this diagnostic gets produced?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you have existing code to test the front end output over a directory?
If you do, then sure.
If not, I think adding that code is outside the scope of this change and would be better off as a follow-up issue.