Skip to content
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
wants to merge 22 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 18 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 14 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -200,19 +200,24 @@ configuration, by redirecting it to a file and editing it.

### Configuring the Command Line Tool

For any source file being checked or formatted, `swift-format` looks for a
JSON-formatted file named `.swift-format` in the same directory. If one is
found, then that file is loaded to determine the tool's configuration. If the
file is not found, then it looks in the parent directory, and so on.
For any source file being checked or formatted, `swift-format` looks for
configuration files in the same directory, and parent directories.

If no configuration file is found, a default configuration is used. The
settings in the default configuration can be viewed by running
`swift-format dump-configuration`, which will dump it to standard
output.
If it finds a file named `.swift-format-ignore`, its contents will determine
which files in that directory will be ignored by `swift-format`. Currently
the only supported option is `*`, which ignores all files.

If it finds a JSON-formatted file called `.swift-format`, then that
file is loaded to determine the tool's configuration.

If no configuration file is found at any level, a default configuration
is used. The settings in the default configuration can be viewed by
running `swift-format dump-configuration`, which will dump it to
standard output.

If the `--configuration <file>` option is passed to `swift-format`, then that
configuration will be used unconditionally and the file system will not be
searched.
searched for `.swift-format` files.

See [Documentation/Configuration.md](Documentation/Configuration.md) for a
description of the configuration file format and the settings that are
Expand Down
14 changes: 0 additions & 14 deletions Sources/SwiftFormat/API/Configuration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -472,17 +472,3 @@ public struct NoAssignmentInExpressionsConfiguration: Codable, Equatable {

public init() {}
}

fileprivate extension URL {
var isRoot: Bool {
#if os(Windows)
// FIXME: We should call into Windows' native check to check if this path is a root once https://github.com/swiftlang/swift-foundation/issues/976 is fixed.
// https://github.com/swiftlang/swift-format/issues/844
return self.pathComponents.count <= 1
#else
// On Linux, we may end up with an string for the path due to https://github.com/swiftlang/swift-foundation/issues/980
// TODO: Remove the check for "" once https://github.com/swiftlang/swift-foundation/issues/980 is fixed.
return self.path == "/" || self.path == ""
#endif
}
}
32 changes: 31 additions & 1 deletion Sources/SwiftFormat/Utilities/FileIterator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ import Foundation
@_spi(Internal)
public struct FileIterator: Sequence, IteratorProtocol {

/// 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.
fileprivate static let ignoreFileName = ".swift-format-ignore"

/// List of file and directory URLs to iterate over.
private let urls: [URL]

Expand Down Expand Up @@ -48,7 +53,7 @@ public struct FileIterator: Sequence, IteratorProtocol {
/// The given URLs may be files or directories. If they are directories, the iterator will recurse
/// into them.
public init(urls: [URL], followSymlinks: Bool) {
self.urls = urls
self.urls = urls.filter(inputShouldBeProcessed(at:))
self.urlIterator = self.urls.makeIterator()
self.followSymlinks = followSymlinks
}
Expand Down Expand Up @@ -83,6 +88,11 @@ public struct FileIterator: Sequence, IteratorProtocol {
fallthrough

case .typeDirectory:
let ignoreFile = next.appendingPathComponent(Self.ignoreFileName)
if FileManager.default.fileExists(atPath: ignoreFile.path) {
continue
}

dirIterator = FileManager.default.enumerator(
at: next,
includingPropertiesForKeys: nil,
Expand Down Expand Up @@ -169,3 +179,23 @@ private func fileType(at url: URL) -> FileAttributeType? {
// Linux.
return try? FileManager.default.attributesOfItem(atPath: url.path)[.type] as? FileAttributeType
}

/// Returns true if the file should be processed.
/// Directories are always processed.
/// Other files are processed if there is not a
/// ignore file in the containing directory or any of its parents.
private func inputShouldBeProcessed(at url: URL) -> Bool {
guard fileType(at: url) != .typeDirectory else {
return true
}

var containingDirectory = url.absoluteURL.standardized
repeat {
containingDirectory.deleteLastPathComponent()
let candidateFile = containingDirectory.appendingPathComponent(FileIterator.ignoreFileName)
if FileManager.default.isReadableFile(atPath: candidateFile.path) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More than just checking for the presence of the file, I think we go the extra step of validating that it only contains * so that users aren't surprised later when we add more functionality and it subtly changes the behavior based on those contents. If it contains anything else, we should throw an error saying it's the only supported form (and turn that into a nice human-readable error message when we exit).

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we want to upgrade to gitignore like sytanx in the future, should we make the default value **/*?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAIK, * is sufficient because according to the documentation:

  • "An asterisk '*' matches anything except a slash."
  • "If there is a separator at the beginning or middle (or both) of the pattern, [...]. Otherwise the pattern may also match at any level below the .gitignore level."
  • "If there is a separator at the end of the pattern then the pattern will only match directories, otherwise the pattern can match both files and directories."

Copy link
Author

@samdeane samdeane Nov 27, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, got distracted, but am looking at this now.

One complication is that FileIterator doesn't throw, and instead relies on the caller to generate diagnostics for non-existent paths.

In order to report invalid ignore files inside FileIterator we'd either need to be able to:

  • throw an error from it
  • be able to emit diagnostics directly from it
  • return the path of the invalid ignore file (or a special pseudo-path), as if they were to be processed, then spot that path in the front end and emit an "invalid ignore file" diagnostic

I've gone for the third option, but it feels like a bit of a hack.

return false
}
} while !containingDirectory.isRoot
return true
}
27 changes: 27 additions & 0 deletions Sources/SwiftFormat/Utilities/URL+IsRoot.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//===----------------------------------------------------------------------===//
//
// 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

extension URL {
var isRoot: Bool {
#if os(Windows)
// FIXME: We should call into Windows' native check to check if this path is a root once https://github.com/swiftlang/swift-foundation/issues/976 is fixed.
// https://github.com/swiftlang/swift-format/issues/844
return self.pathComponents.count <= 1
#else
// On Linux, we may end up with an string for the path due to https://github.com/swiftlang/swift-foundation/issues/980
// TODO: Remove the check for "" once https://github.com/swiftlang/swift-foundation/issues/980 is fixed.
return self.path == "/" || self.path == ""
#endif
}
}