realm / SwiftLint

A tool to enforce Swift style and conventions.
https://realm.github.io/SwiftLint
MIT License
18.61k stars 2.22k forks source link

swiftlint analyze appears to stop collecting after 12 files with Swift 5.10 #5517

Closed chandlerwall closed 5 months ago

chandlerwall commented 6 months ago

New Issue Checklist

Describe the bug

After upgrading to Xcode 15.3 and Swift 5.10, swiftlint analyze appears to stop collecting files. For my environment, the console output stops updating after collecting 12 files. If I adjust included or excluded to 12 or fewer files, I can reliably avoid the issue.

Complete output when running SwiftLint, including the stack trace and command used
MusicStacks-lint > swiftlint analyze --compiler-log-path .build/build.log
Analyzing Swift files in current working directory
Collecting 'View+hidden.swift' (2/30)
Collecting 'SomethingsWrong.swift' (1/30)
Collecting 'View+foregroundStyle.swift' (4/30)
Collecting 'View+dimensionOverlay.swift' (5/30)
Collecting 'ShowInMusicMenuButton.swift' (3/30)
Collecting 'Modules/Sources/StyleGuide/_exported.swift' (6/30)
Collecting 'View+firstTextCenterline.swift' (8/30)
Collecting 'ToolbarMenuButton.swift' (9/30)
Collecting 'ExplicitTitle.swift' (7/30)
Collecting 'View+enabled.swift' (10/30)
Collecting 'HeaderTitle.swift' (11/30)
Collecting 'LabelStyle.swift' (12/30)

Environment

chandlerwall commented 6 months ago

I checked out SwiftLint's source to debug the issue a bit and found the cause: SwiftLintCore.SwiftVersion does not compare string versions reliably, preventing version-specific workarounds from being applied.

https://github.com/realm/SwiftLint/blob/6d2e58271ebc14c37bf76d7c9f4082cc15bad718/Source/SwiftLintCore/Models/SwiftVersion.swift#L5-L17

For Swift 5.10, version-specific workarounds are not applied correctly. Specifically, LintableFilesVisitor.parallel is assigned true instead of false when SwiftVersion.current < .fiveDotSix returns an incorrect value.

https://github.com/realm/SwiftLint/blob/6d2e58271ebc14c37bf76d7c9f4082cc15bad718/Source/swiftlint/Helpers/LintableFilesVisitor.swift#L99-L108

I replaced SwiftVersion with the following:

/// A value describing the version of the Swift compiler.
public struct SwiftVersion: RawRepresentable, Codable, Comparable, Sendable {
    public typealias RawValue = String

    public let rawValue: String

    public init(rawValue: String) {
        self.rawValue = rawValue
    }

  public static func < (lhs: SwiftVersion, rhs: SwiftVersion) -> Bool {
      func versionComponents(from version: String) -> [Int] {
          return version.components(separatedBy: ".").compactMap { Int($0) }
      }

      let lhsComponents = versionComponents(from: lhs.rawValue)
      let rhsComponents = versionComponents(from: rhs.rawValue)

      for (lhsComponent, rhsComponent) in zip(lhsComponents, rhsComponents) {
          if lhsComponent < rhsComponent {
              return true
          } else if lhsComponent > rhsComponent {
              return false
          }
      }

      return lhsComponents.count < rhsComponents.count
  }
}

Note: I used Claude to implement static func < (lhs:rhs:). I wanted to save time while troubleshooting. The implementation may or may not handle edge cases correctly. I only verified the implementation for my environment.

With the changes in place, I'm able to run swiftlint analyze on a larger set of files. The run below was limited to 30 files. I also verified the result on a run with 350+ files.

Analyzing Swift files in current working directory
Collecting 'Modules/Sources/StyleGuide/_exported.swift' (1/30)
Collecting 'SomethingsWrong.swift' (2/30)
Collecting 'ToolbarMenuButton.swift' (3/30)
Collecting 'ShowInMusicMenuButton.swift' (4/30)
Collecting 'View+hidden.swift' (5/30)
Collecting 'View+foregroundStyle.swift' (6/30)
Collecting 'View+dimensionOverlay.swift' (7/30)
Collecting 'View+enabled.swift' (8/30)
Collecting 'View+firstTextCenterline.swift' (9/30)
Collecting 'HeaderTitle.swift' (10/30)
Collecting 'ExplicitTitle.swift' (11/30)
Collecting 'LabelStyle.swift' (12/30)
Collecting 'StyleGuide.swift' (13/30)
Collecting 'ActiveTrackIcon.swift' (14/30)
Collecting 'FontPreview.swift' (15/30)
Collecting 'CountsView.swift' (16/30)
Collecting 'NoSearchResults.swift' (17/30)
Collecting 'LibraryIcon.swift' (18/30)
Collecting 'PresentationState.swift' (19/30)
Collecting 'AlertPresentation.swift' (20/30)
Collecting 'ConfirmationDialogPresentation.swift' (21/30)
Collecting 'Symbol.swift' (22/30)
Collecting 'SearchControllerProxy.swift' (23/30)
Collecting 'EnvironmentAction.swift' (24/30)
Collecting 'Resolvable.swift' (25/30)
Collecting 'SearchableUIHostingController.swift' (26/30)
Collecting 'SearchScope.swift' (27/30)
Collecting 'RelativeTimeframe.swift' (28/30)
Collecting 'SimilarityResult.swift' (29/30)
Collecting 'Counts.swift' (30/30)
Analyzing 'Modules/Sources/StyleGuide/_exported.swift' (1/30)
Analyzing 'SomethingsWrong.swift' (2/30)
Analyzing 'ToolbarMenuButton.swift' (3/30)
Analyzing 'ShowInMusicMenuButton.swift' (4/30)
Analyzing 'View+hidden.swift' (5/30)
Analyzing 'View+foregroundStyle.swift' (6/30)
Analyzing 'View+dimensionOverlay.swift' (7/30)
Analyzing 'View+enabled.swift' (8/30)
Analyzing 'View+firstTextCenterline.swift' (9/30)
Analyzing 'HeaderTitle.swift' (10/30)
Analyzing 'ExplicitTitle.swift' (11/30)
Analyzing 'LabelStyle.swift' (12/30)
Analyzing 'StyleGuide.swift' (13/30)
Analyzing 'ActiveTrackIcon.swift' (14/30)
Analyzing 'FontPreview.swift' (15/30)
Analyzing 'CountsView.swift' (16/30)
Analyzing 'NoSearchResults.swift' (17/30)
Analyzing 'LibraryIcon.swift' (18/30)
Analyzing 'PresentationState.swift' (19/30)
Analyzing 'AlertPresentation.swift' (20/30)
Analyzing 'ConfirmationDialogPresentation.swift' (21/30)
Analyzing 'Symbol.swift' (22/30)
Analyzing 'SearchControllerProxy.swift' (23/30)
Analyzing 'EnvironmentAction.swift' (24/30)
Analyzing 'Resolvable.swift' (25/30)
Analyzing 'SearchableUIHostingController.swift' (26/30)
Analyzing 'SearchScope.swift' (27/30)
Analyzing 'RelativeTimeframe.swift' (28/30)
Analyzing 'SimilarityResult.swift' (29/30)
Analyzing 'Counts.swift' (30/30)
Done analyzing! Found 0 violations, 0 serious in 30 files.
SimplyDanny commented 6 months ago

Can the stop of analysis be associated with a specific file maybe?

chandlerwall commented 6 months ago

Can the stop of analysis be associated with a specific file maybe?

I started my troubleshooting by adjusting included and excluded to find a problematic file. I started with a small number of files and gradually included more files until the issue occurred. I thought I found the problematic file. I added more files and noticed the issue again. I tested different combinations and realized I could move "problematic" files from excluded to included without triggering the issue. As long as I keep the file count very low, the command finished. As soon as I added an extra file, the command chokes.

SimplyDanny commented 6 months ago

Forget my previous question. You were obviously a little faster with the detailed report of your investigation. I didn't notice it while crafting my message.

Your reasoning makes a lot of sense. Looks like SwiftVersion wasn't developed with version components >9 in mind or the implementors didn't expect such high numbers.

Would you like to open a PR to fix the comparison algorithm?

chandlerwall commented 6 months ago

Yes, I will prepare a PR with a fix for the comparison algorithm. Appreciate the quick responses!

NachoSoto commented 6 months ago

I'm seeing the exact same thing. For me it stops after 14 files, no matter what file that is.