Skip to content

Conversation

rlf-doordash
Copy link

Right now there are only 2 ways of excluding files managed by an enum. The logic deciding between those 2 is split along several places of code, which makes it difficult to standardize between them and pose a scalability challenge if we were to add new options.

This PR creates a common protocol among those 2 options (by prefix and by expanding subpaths) and refactors them so their logic can be called generically instead of exposing inner implementation details in different places of code

@SwiftLintBot
Copy link

SwiftLintBot commented Jul 18, 2025

1 Warning
⚠️ If this is a user-facing change, please include a CHANGELOG entry to credit yourself!
You can find it at CHANGELOG.md.
18 Messages
📖 Building this branch resulted in a binary size of 25468.62 KiB vs 25468.68 KiB when built on main (-1% smaller).
📖 Linting Aerial with this PR took 1.05 s vs 1.06 s on main (0% faster).
📖 Linting Alamofire with this PR took 1.41 s vs 1.41 s on main (0% slower).
📖 Linting Brave with this PR took 9.52 s vs 9.53 s on main (0% faster).
📖 Linting DuckDuckGo with this PR took 27.78 s vs 27.81 s on main (0% faster).
📖 Linting Firefox with this PR took 14.66 s vs 14.68 s on main (0% faster).
📖 Linting Kickstarter with this PR took 10.81 s vs 10.8 s on main (0% slower).
📖 Linting Moya with this PR took 0.56 s vs 0.56 s on main (0% slower).
📖 Linting NetNewsWire with this PR took 3.22 s vs 3.23 s on main (0% faster).
📖 Linting Nimble with this PR took 0.85 s vs 0.85 s on main (0% slower).
📖 Linting PocketCasts with this PR took 9.4 s vs 9.56 s on main (1% faster).
📖 Linting Quick with this PR took 0.49 s vs 0.49 s on main (0% slower).
📖 Linting Realm with this PR took 4.9 s vs 4.89 s on main (0% slower).
📖 Linting Sourcery with this PR took 2.47 s vs 2.5 s on main (1% faster).
📖 Linting Swift with this PR took 5.71 s vs 5.73 s on main (0% faster).
📖 Linting VLC with this PR took 1.53 s vs 1.52 s on main (0% slower).
📖 Linting Wire with this PR took 23.09 s vs 23.05 s on main (0% slower).
📖 Linting WordPress with this PR took 15.55 s vs 15.55 s on main (0% slower).

Here's an example of your CHANGELOG entry:

* Refactor Exclude by behavior into a Strategy pattern.  
  [rlf-doordash](https://github.com/rlf-doordash)
  [#issue_number](https://github.com/realm/SwiftLint/issues/issue_number)

note: There are two invisible spaces after the entry's text.

Generated by 🚫 Danger

@rlf-doordash
Copy link
Author

@SimplyDanny Do you happen to know how to get a review on proposed changes?

@SimplyDanny
Copy link
Collaborator

@SimplyDanny Do you happen to know how to get a review on proposed changes?

I can have a look. But it'll take a while.

@rlf-doordash
Copy link
Author

@SimplyDanny Do you happen to know how to get a review on proposed changes?

I can have a look. But it'll take a while.

Sounds good, would appreciate it. This is part of a larger improvement I have prepared to allow faster parsing of large codebases :D

Copy link
Collaborator

@SimplyDanny SimplyDanny left a comment

Choose a reason for hiding this comment

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

Looks good to me apart from a few nits.

Feel free to declare the new types in a single file. For relatively short implementations, you get a better overview when types are not too distributed.

import Foundation

class ExcludeByStrategyFactory {
static func createExcludeByStrategy(options: LintOrAnalyzeOptions,
Copy link
Collaborator

@SimplyDanny SimplyDanny Jul 23, 2025

Choose a reason for hiding this comment

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

Could be part of the protocol as an extension. Otherwise, enums are typically the go-to types for factories.

Copy link
Author

Choose a reason for hiding this comment

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

What do you have in mind? Something like:

enum ExcludeByStrategyType {
    case excludeByPrefix(ExcludeByPrefixStrategy)
    case excludeByPathsByExpandingSubPaths(ExcludeByPathsByExpandingSubPaths)
    
    static func createExcludeByStrategy(options: LintOrAnalyzeOptions,
                                        configuration: Configuration,
                                        fileManager: some LintableFileManager = FileManager.default)
    -> ExcludeByStrategyType {
        if options.useExcludingByPrefix {
            let strategy = ExcludeByPrefixStrategy(excludedPaths: configuration.excludedPaths)
            return .excludeByPrefix(strategy)
        }

        let strategy = ExcludeByPathsByExpandingSubPaths(configuration: configuration, fileManager: fileManager)
        return .excludeByPathsByExpandingSubPaths(strategy)
    }
    
    var strategy: any ExcludeByStrategy {
        switch self {
        case .excludeByPrefix(let strategy):
            return strategy
        case .excludeByPathsByExpandingSubPaths(let strategy):
            return strategy
        }
    }
}

And use it like
ExcludeByStrategyType.createExcludeByStrategy(options: options, configuration: self).strategy ?

It cannot be an extension of the protocol as it fails on call, cannot call static funcs in meta types

Copy link
Collaborator

Choose a reason for hiding this comment

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

This is what I had in mind:

protocol ExcludeByStrategy {}

struct ExcludeByPathsByExpandingSubPaths: ExcludeByStrategy {}
struct ExcludeByPrefixStrategy: ExcludeByStrategy {}

extension ExcludeByStrategy {
  static func create(_ b: Bool) -> any ExcludeByStrategy {
    if b {
      ExcludeByPathsByExpandingSubPaths()
    } else {
      ExcludeByPrefixStrategy()
    }
  }
}

@SimplyDanny
Copy link
Collaborator

@SimplyDanny Do you happen to know how to get a review on proposed changes?

I can have a look. But it'll take a while.

Sounds good, would appreciate it. This is part of a larger improvement I have prepared to allow faster parsing of large codebases :D

That motivated me come back to this PR faster. 😉

@rlf-doordash
Copy link
Author

@SimplyDanny Do you happen to know how to get a review on proposed changes?

I can have a look. But it'll take a while.

Sounds good, would appreciate it. This is part of a larger improvement I have prepared to allow faster parsing of large codebases :D

That motivated me come back to this PR faster. 😉

Sorry, I was out and it has taken a while to get back on this :( I am expectin g to be able to put some time into this for the next couple of weeks tho!

inPath path: String,
forceExclude: Bool,
excludeBy: ExcludeBy,
excludeBy: any ExcludeByStrategy,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can also be some, can't it?

import Foundation

class ExcludeByStrategyFactory {
static func createExcludeByStrategy(options: LintOrAnalyzeOptions,
Copy link
Collaborator

Choose a reason for hiding this comment

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

This is what I had in mind:

protocol ExcludeByStrategy {}

struct ExcludeByPathsByExpandingSubPaths: ExcludeByStrategy {}
struct ExcludeByPrefixStrategy: ExcludeByStrategy {}

extension ExcludeByStrategy {
  static func create(_ b: Bool) -> any ExcludeByStrategy {
    if b {
      ExcludeByPathsByExpandingSubPaths()
    } else {
      ExcludeByPrefixStrategy()
    }
  }
}

/// - parameter paths: The input paths to filter.
///
/// - returns: The input paths after removing the excluded paths.
struct ExcludeByPathsByExpandingSubPaths: ExcludeByStrategy {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
struct ExcludeByPathsByExpandingSubPaths: ExcludeByStrategy {
struct ExcludeByExpandingSubPathsStrategy: ExcludeByStrategy {

Comment on lines +3 to +4
/// For cases when excluded directories contain many lintable files (e. g. Pods) it works faster than default
/// algorithm `filterExcludedPaths`.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
/// For cases when excluded directories contain many lintable files (e. g. Pods) it works faster than default
/// algorithm `filterExcludedPaths`.
/// For cases when excluded directories contain many lintable files (e. g. Pods) it works faster than the
/// default `filterExcludedPaths` strategy.

let excludedPaths: [String]

init(configuration: Configuration, fileManager: some LintableFileManager = FileManager.default) {
self.excludedPaths = configuration.excludedPaths
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can call the other init.

@@ -0,0 +1,28 @@
import Foundation

enum ExcludeByStrategyType {
Copy link
Collaborator

Choose a reason for hiding this comment

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

The enum was only to introduce a namespace containing the factory method. We don't really need that, especially not the cases, which are sufficiently described by the two strategies conforming to the protocol.

As written in the other comment, the static factory method can be part of the protocol itself via an extension.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants