Compare commits

...

19 Commits

Author SHA1 Message Date
JP Simard 61ed3d7ea9
Maybe use less memory 2019-07-16 21:34:17 -07:00
JP Simard 46959cbd1a
Add UnusedDeclarationRule 2019-07-15 21:14:05 -07:00
JP Simard 03398f4918
Re-add outputFilename 2019-07-13 11:14:11 -07:00
JP Simard 69c404c863
Move changelog entry 2019-07-13 11:14:11 -07:00
Elliott Williams 816785f8a0
Add AnyCollectingRule and isCollecting
All CollectingRules implement AnyCollectingRule, which is used to check
whether a linter will perform any collections and only print the
"Collecting" log message if so.
2019-07-13 11:14:11 -07:00
Elliott Williams 68e8b2b526
Document Linter / CollectedLinter relation 2019-07-13 11:14:10 -07:00
Elliott Williams c823057ed3
Make RuleStorage have dictionary semantics and check for collected info inside Rule 2019-07-13 11:14:10 -07:00
Elliott Williams d5b6b65c73
Refactor spacing, indenting, and rename collect to collectInfo 2019-07-13 11:14:09 -07:00
Elliott Williams f0444d67ba
Remove File+Hashable and bump SourceKitten 2019-07-13 11:14:09 -07:00
Elliott Williams 66f13bfc26
Use popen2 in oss-check
Ruby's documentation mentions that `popen3` requires that you flush
stderr to prevent deadlocking:

> You should be careful to avoid deadlocks. Since pipes are fixed length
> buffers, ::popen3(“prog”) {|i, o, e, t| o.read } deadlocks if the
> program generates too much output on stderr. You should read stdout and
> stderr simultaneously (using threads or IO.select). However, if you
> don't need stderr output, you can use ::popen2.
2019-07-13 11:14:09 -07:00
Elliott Williams 5f513eea45
Break Configuration.visit into multiple functions and fix index bug
The post-collection visitor in `Configuration.visit` was iterating over
the total fileCount given, not just the files that could be collected.
This meant than when a file was skipped, the visitor would end up
referencing a linter past the end of its array, causing a crash.
2019-07-13 11:14:09 -07:00
Elliott Williams e95b8c4e22
PR: Create File+Hashable, symlink Array+SwiftLint, fix nits 2019-07-13 11:14:07 -07:00
Elliott Williams 45d94c017a
Update changelog 2019-07-13 11:13:52 -07:00
Elliott Williams f0855fcbec
Test that CollectionCorrectableRule is called correctly 2019-07-13 11:13:50 -07:00
Elliott Williams 8569dd5df8
Make shim autorelease function generic over its return value 2019-07-13 10:32:12 -07:00
Elliott Williams aa8bc029cf
Add CollectingRuleTests 2019-07-13 10:32:12 -07:00
Elliott Williams de5651d806
Update tests to compile and pass
Publicize Array extensions

Address linting violations
2019-07-13 10:32:12 -07:00
Elliott Williams ba8bfcae22
Add RuleStorage and a pre-linting stage to run collection
In order for rules to collect arbitrary information about all files
being linted, a shared RuleStorage instance is defined in each command
and passed into the linter.

Linting now requires two "passes": once to call collect and populate the
storage (rules that are non-collecting do nothing here), and again to
call validate. The old Linter factory now creates a Prelinter, which can
collect for a file and produce a Linter that orchestrates all the
traditional validation/collection logic.

This design enforces that a file is only validated once it has been
collected; in turn, the file-visiting loop ensures that all files are
collected before the first is validated, so that the storage is fully
populated.

Use storage-backed correct method

Crash if storage for a rule is accessed prematurely

Key FileInfo by File

Rename Prelinter to Linter and Linter to CollectedLinter

Clean up rule protocols such that rule-facing storage methods are actually called

Make RuleStorage a reference type to solve mutating data races
2019-07-13 10:32:12 -07:00
Elliott Williams b6535eb73e
Add CollectingRule to support arbitrary pre-lint collection 2019-07-13 10:31:51 -07:00
29 changed files with 920 additions and 154 deletions

View File

@ -4,8 +4,9 @@ included:
excluded: excluded:
- Tests/SwiftLintFrameworkTests/Resources - Tests/SwiftLintFrameworkTests/Resources
analyzer_rules: analyzer_rules:
- unused_import - unused_declaration
- unused_private_declaration # - unused_import
# - unused_private_declaration
opt_in_rules: opt_in_rules:
- anyobject_protocol - anyobject_protocol
- array_init - array_init

View File

@ -6,7 +6,13 @@
#### Experimental #### Experimental
* None. * Add a two-stage `CollectingRule` protocol to support rules that collect data
from all files before validating. Collecting rules implement a `collect`
method which is called once for every file, before _any_ file is checked for
violations. By collecting, rules can be written which validate across
multiple files for things like unused declarations.
[Elliott Williams](https://github.com/elliottwilliams)
[#2431](https://github.com/realm/SwiftLint/issues/2431)
#### Enhancements #### Enhancements

View File

@ -62,8 +62,8 @@ test_tsan:
swift build --build-tests $(TSAN_SWIFT_BUILD_FLAGS) swift build --build-tests $(TSAN_SWIFT_BUILD_FLAGS)
DYLD_INSERT_LIBRARIES=$(TSAN_LIB) $(TSAN_XCTEST) $(TSAN_TEST_BUNDLE) DYLD_INSERT_LIBRARIES=$(TSAN_LIB) $(TSAN_XCTEST) $(TSAN_TEST_BUNDLE)
write_xcodebuild_log: bootstrap write_xcodebuild_log:
xcodebuild -workspace SwiftLint.xcworkspace -scheme swiftlint > xcodebuild.log xcodebuild -workspace SwiftLint.xcworkspace -scheme swiftlint clean build-for-testing > xcodebuild.log
analyze: write_xcodebuild_log analyze: write_xcodebuild_log
swift run -c release swiftlint analyze --strict --compiler-log-path xcodebuild.log swift run -c release swiftlint analyze --strict --compiler-log-path xcodebuild.log

View File

@ -39,11 +39,11 @@
}, },
{ {
"package": "SourceKitten", "package": "SourceKitten",
"repositoryURL": "https://github.com/jpsim/SourceKitten.git", "repositoryURL": "https://github.com/cltnschlosser/SourceKitten.git",
"state": { "state": {
"branch": null, "branch": "cs_sourceKitMemoryLeak2",
"revision": "fd9091759201473aa234c22322a3939615aef59a", "revision": "b6c9c6c56a898faabe2671152dfa963bb8d6f5e9",
"version": "0.23.2" "version": null
} }
}, },
{ {

View File

@ -15,7 +15,7 @@ let package = Package(
], ],
dependencies: [ dependencies: [
.package(url: "https://github.com/Carthage/Commandant.git", .upToNextMinor(from: "0.16.0")), .package(url: "https://github.com/Carthage/Commandant.git", .upToNextMinor(from: "0.16.0")),
.package(url: "https://github.com/jpsim/SourceKitten.git", from: "0.23.2"), .package(url: "https://github.com/cltnschlosser/SourceKitten.git", .branch("cs_sourceKitMemoryLeak2")),
.package(url: "https://github.com/jpsim/Yams.git", from: "2.0.0"), .package(url: "https://github.com/jpsim/Yams.git", from: "2.0.0"),
.package(url: "https://github.com/scottrhoyt/SwiftyTextTable.git", from: "0.9.0"), .package(url: "https://github.com/scottrhoyt/SwiftyTextTable.git", from: "0.9.0"),
] + (addCryptoSwift ? [.package(url: "https://github.com/krzyzanowskim/CryptoSwift.git", from: "1.0.0")] : []), ] + (addCryptoSwift ? [.package(url: "https://github.com/krzyzanowskim/CryptoSwift.git", from: "1.0.0")] : []),

View File

@ -45,11 +45,11 @@ extension Array {
return (copy[0..<pivot], copy[pivot..<count]) return (copy[0..<pivot], copy[pivot..<count])
} }
func parallelFlatMap<T>(transform: @escaping ((Element) -> [T])) -> [T] { func parallelFlatMap<T>(transform: (Element) -> [T]) -> [T] {
return parallelMap(transform: transform).flatMap { $0 } return parallelMap(transform: transform).flatMap { $0 }
} }
func parallelCompactMap<T>(transform: @escaping ((Element) -> T?)) -> [T] { func parallelCompactMap<T>(transform: (Element) -> T?) -> [T] {
return parallelMap(transform: transform).compactMap { $0 } return parallelMap(transform: transform).compactMap { $0 }
} }

View File

@ -50,6 +50,7 @@ private extension Rule {
// As we need the configuration to get custom identifiers. // As we need the configuration to get custom identifiers.
// swiftlint:disable:next function_parameter_count // swiftlint:disable:next function_parameter_count
func lint(file: File, regions: [Region], benchmark: Bool, func lint(file: File, regions: [Region], benchmark: Bool,
storage: RuleStorage,
configuration: Configuration, configuration: Configuration,
superfluousDisableCommandRule: SuperfluousDisableCommandRule?, superfluousDisableCommandRule: SuperfluousDisableCommandRule?,
compilerArguments: [String]) -> LintResult? { compilerArguments: [String]) -> LintResult? {
@ -63,10 +64,10 @@ private extension Rule {
let ruleTime: (String, Double)? let ruleTime: (String, Double)?
if benchmark { if benchmark {
let start = Date() let start = Date()
violations = validate(file: file, compilerArguments: compilerArguments) violations = validate(file: file, using: storage, compilerArguments: compilerArguments)
ruleTime = (ruleID, -start.timeIntervalSinceNow) ruleTime = (ruleID, -start.timeIntervalSinceNow)
} else { } else {
violations = validate(file: file, compilerArguments: compilerArguments) violations = validate(file: file, using: storage, compilerArguments: compilerArguments)
ruleTime = nil ruleTime = nil
} }
@ -107,22 +108,70 @@ private extension Rule {
} }
} }
/// Represents a file that can be linted for style violations and corrections after being collected.
public struct Linter { public struct Linter {
public let file: File
public var isCollecting: Bool
fileprivate let rules: [Rule]
fileprivate let cache: LinterCache?
fileprivate let configuration: Configuration
fileprivate let compilerArguments: [String]
public init(file: File, configuration: Configuration = Configuration()!, cache: LinterCache? = nil,
compilerArguments: [String] = []) {
self.file = file
self.cache = cache
self.configuration = configuration
self.compilerArguments = compilerArguments
let rules = configuration.rules.filter { rule in
if compilerArguments.isEmpty {
return !(rule is AnalyzerRule)
} else {
return rule is AnalyzerRule
}
}
self.rules = rules
self.isCollecting = rules.contains(where: { $0 is AnyCollectingRule })
}
/// Returns a linter capable of checking for violations after running each rule's collection step.
public func collect(into storage: RuleStorage) -> CollectedLinter {
DispatchQueue.concurrentPerform(iterations: rules.count) { idx in
rules[idx].collectInfo(for: file, into: storage, compilerArguments: compilerArguments)
}
return CollectedLinter(from: self)
}
}
/// Represents a file that can compute style violations and corrections for a list of rules.
///
/// A `CollectedLinter` is only created after a `Linter` has run its collection steps in `Linter.collect(into:)`.
public struct CollectedLinter {
public let file: File public let file: File
private let rules: [Rule] private let rules: [Rule]
private let cache: LinterCache? private let cache: LinterCache?
private let configuration: Configuration private let configuration: Configuration
private let compilerArguments: [String] private let compilerArguments: [String]
public var styleViolations: [StyleViolation] { fileprivate init(from linter: Linter) {
return getStyleViolations().0 file = linter.file
rules = linter.rules
cache = linter.cache
configuration = linter.configuration
compilerArguments = linter.compilerArguments
} }
public var styleViolationsAndRuleTimes: ([StyleViolation], [(id: String, time: Double)]) { public func styleViolations(using storage: RuleStorage) -> [StyleViolation] {
return getStyleViolations(benchmark: true) return getStyleViolations(using: storage).0
} }
private func getStyleViolations(benchmark: Bool = false) -> ([StyleViolation], [(id: String, time: Double)]) { public func styleViolationsAndRuleTimes(using storage: RuleStorage)
-> ([StyleViolation], [(id: String, time: Double)]) {
return getStyleViolations(using: storage, benchmark: true)
}
private func getStyleViolations(using storage: RuleStorage,
benchmark: Bool = false) -> ([StyleViolation], [(id: String, time: Double)]) {
if let cached = cachedStyleViolations(benchmark: benchmark) { if let cached = cachedStyleViolations(benchmark: benchmark) {
return cached return cached
} }
@ -136,6 +185,7 @@ public struct Linter {
}) as? SuperfluousDisableCommandRule }) as? SuperfluousDisableCommandRule
let validationResults = rules.parallelCompactMap { let validationResults = rules.parallelCompactMap {
$0.lint(file: self.file, regions: regions, benchmark: benchmark, $0.lint(file: self.file, regions: regions, benchmark: benchmark,
storage: storage,
configuration: self.configuration, configuration: self.configuration,
superfluousDisableCommandRule: superfluousDisableCommandRule, superfluousDisableCommandRule: superfluousDisableCommandRule,
compilerArguments: self.compilerArguments) compilerArguments: self.compilerArguments)
@ -184,29 +234,14 @@ public struct Linter {
return (cachedViolations, ruleTimes) return (cachedViolations, ruleTimes)
} }
public init(file: File, configuration: Configuration = Configuration()!, cache: LinterCache? = nil, public func correct(using storage: RuleStorage) -> [Correction] {
compilerArguments: [String] = []) {
self.file = file
self.configuration = configuration
self.cache = cache
self.compilerArguments = compilerArguments
self.rules = configuration.rules.filter { rule in
if compilerArguments.isEmpty {
return !(rule is AnalyzerRule)
} else {
return rule is AnalyzerRule
}
}
}
public func correct() -> [Correction] {
if let violations = cachedStyleViolations()?.0, violations.isEmpty { if let violations = cachedStyleViolations()?.0, violations.isEmpty {
return [] return []
} }
var corrections = [Correction]() var corrections = [Correction]()
for rule in rules.compactMap({ $0 as? CorrectableRule }) { for rule in rules.compactMap({ $0 as? CorrectableRule }) {
let newCorrections = rule.correct(file: file, compilerArguments: compilerArguments) let newCorrections = rule.correct(file: file, using: storage, compilerArguments: compilerArguments)
corrections += newCorrections corrections += newCorrections
if !newCorrections.isEmpty { if !newCorrections.isEmpty {
file.invalidateCache() file.invalidateCache()

View File

@ -162,6 +162,7 @@ public let masterRuleList = RuleList(rules: [
UnusedCaptureListRule.self, UnusedCaptureListRule.self,
UnusedClosureParameterRule.self, UnusedClosureParameterRule.self,
UnusedControlFlowLabelRule.self, UnusedControlFlowLabelRule.self,
UnusedDeclarationRule.self,
UnusedEnumeratedRule.self, UnusedEnumeratedRule.self,
UnusedImportRule.self, UnusedImportRule.self,
UnusedOptionalBindingRule.self, UnusedOptionalBindingRule.self,

View File

@ -0,0 +1,24 @@
import Dispatch
import SourceKittenFramework
public class RuleStorage {
private var storage: [ObjectIdentifier: [File: Any]]
private let access = DispatchQueue(label: "io.realm.swiftlint.ruleStorageAccess", attributes: .concurrent)
public init() {
storage = [:]
}
func collect<R: CollectingRule>(info: R.FileInfo, for file: File, in rule: R) {
let key = ObjectIdentifier(R.self)
access.sync(flags: .barrier) {
storage[key, default: [:]][file] = info
}
}
func collectedInfo<R: CollectingRule>(for rule: R) -> [File: R.FileInfo]? {
return access.sync {
storage[ObjectIdentifier(R.self)] as? [File: R.FileInfo]
}
}
}

View File

@ -11,9 +11,17 @@ public protocol Rule {
func validate(file: File, compilerArguments: [String]) -> [StyleViolation] func validate(file: File, compilerArguments: [String]) -> [StyleViolation]
func validate(file: File) -> [StyleViolation] func validate(file: File) -> [StyleViolation]
func isEqualTo(_ rule: Rule) -> Bool func isEqualTo(_ rule: Rule) -> Bool
// These are called by the linter and are always implemented in extensions.
func collectInfo(for file: File, into storage: RuleStorage, compilerArguments: [String])
func validate(file: File, using storage: RuleStorage, compilerArguments: [String]) -> [StyleViolation]
} }
extension Rule { extension Rule {
public func validate(file: File, using storage: RuleStorage, compilerArguments: [String]) -> [StyleViolation] {
return validate(file: file, compilerArguments: compilerArguments)
}
public func validate(file: File, compilerArguments: [String]) -> [StyleViolation] { public func validate(file: File, compilerArguments: [String]) -> [StyleViolation] {
return validate(file: file) return validate(file: file)
} }
@ -22,6 +30,10 @@ extension Rule {
return type(of: self).description == type(of: rule).description return type(of: self).description == type(of: rule).description
} }
public func collectInfo(for file: File, into storage: RuleStorage, compilerArguments: [String]) {
// no-op: only CollectingRules mutate their storage
}
internal var cacheDescription: String { internal var cacheDescription: String {
return (self as? CacheDescriptionProvider)?.cacheDescription ?? configurationDescription return (self as? CacheDescriptionProvider)?.cacheDescription ?? configurationDescription
} }
@ -40,12 +52,18 @@ public protocol ConfigurationProviderRule: Rule {
public protocol CorrectableRule: Rule { public protocol CorrectableRule: Rule {
func correct(file: File, compilerArguments: [String]) -> [Correction] func correct(file: File, compilerArguments: [String]) -> [Correction]
func correct(file: File) -> [Correction] func correct(file: File) -> [Correction]
// Called by the linter and are always implemented in extensions.
func correct(file: File, using storage: RuleStorage, compilerArguments: [String]) -> [Correction]
} }
public extension CorrectableRule { public extension CorrectableRule {
func correct(file: File, compilerArguments: [String]) -> [Correction] { func correct(file: File, compilerArguments: [String]) -> [Correction] {
return correct(file: file) return correct(file: file)
} }
func correct(file: File, using storage: RuleStorage, compilerArguments: [String]) -> [Correction] {
return correct(file: file, compilerArguments: compilerArguments)
}
} }
public protocol SubstitutionCorrectableRule: CorrectableRule { public protocol SubstitutionCorrectableRule: CorrectableRule {
@ -117,7 +135,96 @@ public extension AnalyzerRule where Self: CorrectableRule {
} }
} }
// MARK: - ConfigurationProviderRule conformance to Configurable // MARK: - Collecting rules
/// Type-erased protocol used to check whether a rule is collectable.
public protocol AnyCollectingRule: Rule { }
public protocol CollectingRule: AnyCollectingRule {
associatedtype FileInfo
func collectInfo(for file: File, compilerArguments: [String]) -> FileInfo
func collectInfo(for file: File) -> FileInfo
func validate(file: File, collectedInfo: [File: FileInfo], compilerArguments: [String]) -> [StyleViolation]
func validate(file: File, collectedInfo: [File: FileInfo]) -> [StyleViolation]
}
public extension CollectingRule {
func collectInfo(for file: File, into storage: RuleStorage, compilerArguments: [String]) {
storage.collect(info: collectInfo(for: file, compilerArguments: compilerArguments),
for: file, in: self)
}
func validate(file: File, using storage: RuleStorage, compilerArguments: [String]) -> [StyleViolation] {
guard let info = storage.collectedInfo(for: self) else {
queuedFatalError("Attempt to validate a CollectingRule before collecting info for it")
}
return validate(file: file, collectedInfo: info, compilerArguments: compilerArguments)
}
func collectInfo(for file: File, compilerArguments: [String]) -> FileInfo {
return collectInfo(for: file)
}
func validate(file: File, collectedInfo: [File: FileInfo], compilerArguments: [String]) -> [StyleViolation] {
return validate(file: file, collectedInfo: collectedInfo)
}
func validate(file: File) -> [StyleViolation] {
queuedFatalError("Must call `validate(file:collectedInfo:)` for CollectingRule")
}
func validate(file: File, compilerArguments: [String]) -> [StyleViolation] {
queuedFatalError("Must call `validate(file:collectedInfo:compilerArguments:)` for CollectingRule")
}
}
public extension CollectingRule where Self: AnalyzerRule {
func collectInfo(for file: File) -> FileInfo {
queuedFatalError(
"Must call `collect(infoFor:compilerArguments:)` for AnalyzerRule & CollectingRule"
)
}
func validate(file: File) -> [StyleViolation] {
queuedFatalError(
"Must call `validate(file:collectedInfo:compilerArguments:)` for AnalyzerRule & CollectingRule"
)
}
func validate(file: File, collectedInfo: [File: FileInfo]) -> [StyleViolation] {
queuedFatalError(
"Must call `validate(file:collectedInfo:compilerArguments:)` for AnalyzerRule & CollectingRule"
)
}
}
public protocol CollectingCorrectableRule: CollectingRule, CorrectableRule {
func correct(file: File, collectedInfo: [File: FileInfo], compilerArguments: [String]) -> [Correction]
func correct(file: File, collectedInfo: [File: FileInfo]) -> [Correction]
}
public extension CollectingCorrectableRule {
func correct(file: File, collectedInfo: [File: FileInfo], compilerArguments: [String]) -> [Correction] {
return correct(file: file, collectedInfo: collectedInfo)
}
func correct(file: File, using storage: RuleStorage, compilerArguments: [String]) -> [Correction] {
guard let info = storage.collectedInfo(for: self) else {
queuedFatalError("Attempt to correct a CollectingRule before collecting info for it")
}
return correct(file: file, collectedInfo: info, compilerArguments: compilerArguments)
}
func correct(file: File) -> [Correction] {
queuedFatalError("Must call `correct(file:collectedInfo:)` for AnalyzerRule")
}
func correct(file: File, compilerArguments: [String]) -> [Correction] {
queuedFatalError("Must call `correct(file:collectedInfo:compilerArguments:)` for AnalyzerRule")
}
}
public extension CollectingCorrectableRule where Self: AnalyzerRule {
func correct(file: File) -> [Correction] {
queuedFatalError("Must call `correct(file:collectedInfo:compilerArguments:)` for AnalyzerRule")
}
func correct(file: File, compilerArguments: [String]) -> [Correction] {
queuedFatalError("Must call `correct(file:collectedInfo:compilerArguments:)` for AnalyzerRule")
}
func correct(file: File, collectedInfo: [File: FileInfo]) -> [Correction] {
queuedFatalError("Must call `correct(file:collectedInfo:compilerArguments:)` for AnalyzerRule")
}
}
public extension ConfigurationProviderRule { public extension ConfigurationProviderRule {
init(configuration: Any) throws { init(configuration: Any) throws {

View File

@ -5,12 +5,6 @@ public protocol RuleConfiguration {
func isEqualTo(_ ruleConfiguration: RuleConfiguration) -> Bool func isEqualTo(_ ruleConfiguration: RuleConfiguration) -> Bool
} }
extension RuleConfiguration {
internal var cacheDescription: String {
return (self as? CacheDescriptionProvider)?.cacheDescription ?? consoleDescription
}
}
public extension RuleConfiguration where Self: Equatable { public extension RuleConfiguration where Self: Equatable {
func isEqualTo(_ ruleConfiguration: RuleConfiguration) -> Bool { func isEqualTo(_ ruleConfiguration: RuleConfiguration) -> Bool {
return self == ruleConfiguration as? Self return self == ruleConfiguration as? Self

View File

@ -0,0 +1,277 @@
import Foundation
import SourceKittenFramework
public struct UnusedDeclarationRule: AutomaticTestableRule, ConfigurationProviderRule, AnalyzerRule, CollectingRule {
public struct FileUSRs {
var referenced: Set<String>
var declared: [(usr: String, nameOffset: Int)]
var testCaseUSRs: Set<String>
}
public typealias FileInfo = FileUSRs
public var configuration = UnusedDeclarationConfiguration(severity: .error, includePublicAndOpen: false)
public init() {}
public static let description = RuleDescription(
identifier: "unused_declaration",
name: "Unused Declaration",
description: "Declarations should be referenced at least once within all files linted.",
kind: .lint,
nonTriggeringExamples: [
"""
let kConstant = 0
_ = kConstant
""",
"""
struct Item {}
struct ResponseModel: Codable {
let items: [Item]
enum CodingKeys: String, CodingKey {
case items = "ResponseItems"
}
}
_ = ResponseModel(items: [Item()]).items
""",
"""
class ResponseModel {
@objc func foo() {
}
}
_ = ResponseModel()
"""
],
triggeringExamples: [
"""
let kConstant = 0
""",
"""
struct ResponseModel: Codable {
let items: [Item]
enum CodingKeys: String {
case items = "ResponseItems"
}
}
""",
"""
class ResponseModel {
func foo() {
}
}
"""
],
requiresFileOnDisk: true
)
public func collectInfo(for file: File, compilerArguments: [String]) -> UnusedDeclarationRule.FileUSRs {
guard !compilerArguments.isEmpty else {
queuedPrintError("""
Attempted to lint file at path '\(file.path ?? "...")' with the \
\(type(of: self).description.identifier) rule without any compiler arguments.
""")
return FileUSRs(referenced: [], declared: [], testCaseUSRs: [])
}
let allCursorInfo = file.allCursorInfo(compilerArguments: compilerArguments)
return FileUSRs(referenced: Set(File.referencedUSRs(allCursorInfo: allCursorInfo)),
declared: File.declaredUSRs(allCursorInfo: allCursorInfo,
includePublicAndOpen: configuration.includePublicAndOpen),
testCaseUSRs: File.testCaseUSRs(allCursorInfo: allCursorInfo))
}
public func validate(file: File, collectedInfo: [File: UnusedDeclarationRule.FileUSRs],
compilerArguments: [String]) -> [StyleViolation] {
let allReferencedUSRs = collectedInfo.values.reduce(into: Set()) { $0.formUnion($1.referenced) }
let allTestCaseUSRs = collectedInfo.values.reduce(into: Set()) { $0.formUnion($1.testCaseUSRs) }
return violationOffsets(in: file, compilerArguments: compilerArguments,
declaredUSRs: collectedInfo[file]?.declared ?? [],
allReferencedUSRs: allReferencedUSRs,
allTestCaseUSRs: allTestCaseUSRs)
.map {
StyleViolation(ruleDescription: type(of: self).description,
severity: configuration.severity,
location: Location(file: file, byteOffset: $0))
}
}
private func violationOffsets(in file: File, compilerArguments: [String],
declaredUSRs: [(usr: String, nameOffset: Int)],
allReferencedUSRs: Set<String>,
allTestCaseUSRs: Set<String>) -> [Int] {
// Unused declarations are:
// 1. all declarations
// 2. minus all references
// 3. minus all XCTestCase subclasses
// 4. minus all XCTest test functions
let unusedDeclarations = declaredUSRs
.filter { !allReferencedUSRs.contains($0.usr) }
.filter { !allTestCaseUSRs.contains($0.usr) }
.filter { declaredUSR in
return !allTestCaseUSRs.contains(where: { testCaseUSR in
return declaredUSR.usr.hasPrefix(testCaseUSR + "(im)test") ||
declaredUSR.usr.hasPrefix(
testCaseUSR.replacingOccurrences(of: "@M@", with: "@CM@") + "(im)test"
)
})
}
return unusedDeclarations.map { $0.nameOffset }
}
}
// MARK: - File Extensions
private extension File {
func allCursorInfo(compilerArguments: [String]) -> [[String: SourceKitRepresentable]] {
guard let path = path, let editorOpen = try? Request.editorOpen(file: self).sendIfNotDisabled() else {
return []
}
return syntaxMap.tokens.compactMap { token in
guard let kind = SyntaxKind(rawValue: token.type), !syntaxKindsToSkip.contains(kind) else {
return nil
}
let offset = Int64(token.offset)
let request = Request.cursorInfo(file: path, offset: offset, arguments: compilerArguments)
guard var cursorInfo = try? request.sendIfNotDisabled() else {
return nil
}
if let acl = File.aclAtOffset(offset, substructureElement: editorOpen) {
cursorInfo["key.accessibility"] = acl
}
cursorInfo["swiftlint.offset"] = offset
return cursorInfo
}
}
static func declaredUSRs(allCursorInfo: [[String: SourceKitRepresentable]], includePublicAndOpen: Bool)
-> [(usr: String, nameOffset: Int)] {
return allCursorInfo.compactMap { cursorInfo in
return declaredUSRAndOffset(cursorInfo: cursorInfo, includePublicAndOpen: includePublicAndOpen)
}
}
static func referencedUSRs(allCursorInfo: [[String: SourceKitRepresentable]]) -> [String] {
return allCursorInfo.compactMap(referencedUSR)
}
static func testCaseUSRs(allCursorInfo: [[String: SourceKitRepresentable]]) -> Set<String> {
return Set(allCursorInfo.compactMap(testCaseUSR))
}
private static func declaredUSRAndOffset(cursorInfo: [String: SourceKitRepresentable], includePublicAndOpen: Bool)
-> (usr: String, nameOffset: Int)? {
if let offset = cursorInfo["swiftlint.offset"] as? Int64,
let usr = cursorInfo["key.usr"] as? String,
let kind = (cursorInfo["key.kind"] as? String).flatMap(SwiftDeclarationKind.init(rawValue:)),
!declarationKindsToSkip.contains(kind),
let acl = (cursorInfo["key.accessibility"] as? String).flatMap(AccessControlLevel.init(rawValue:)),
includePublicAndOpen || [.internal, .private, .fileprivate].contains(acl) {
// Skip declarations marked as @IBOutlet, @IBAction or @objc
// since those might not be referenced in code, but only dynamically (e.g. Interface Builder)
if let annotatedDecl = cursorInfo["key.annotated_decl"] as? String,
["@IBOutlet", "@IBAction", "@objc", "@IBInspectable"].contains(where: annotatedDecl.contains) {
return nil
}
// Classes marked as @UIApplicationMain are used by the operating system as the entry point into the app.
if let annotatedDecl = cursorInfo["key.annotated_decl"] as? String,
annotatedDecl.contains("@UIApplicationMain") {
return nil
}
// Skip declarations that override another. This works for both subclass overrides &
// protocol extension overrides.
if cursorInfo["key.overrides"] != nil {
return nil
}
// Sometimes default protocol implementations don't have `key.overrides` set but they do have
// `key.related_decls`.
if cursorInfo["key.related_decls"] != nil {
return nil
}
// Skip CodingKeys as they are used
if kind == .enum,
cursorInfo.name == "CodingKeys",
let annotatedDecl = cursorInfo["key.annotated_decl"] as? String,
annotatedDecl.contains("usr=\"s:s9CodingKeyP\">CodingKey<") {
return nil
}
return (usr, Int(offset))
}
return nil
}
private static func referencedUSR(cursorInfo: [String: SourceKitRepresentable]) -> String? {
if let usr = cursorInfo["key.usr"] as? String,
let kind = cursorInfo["key.kind"] as? String,
kind.contains("source.lang.swift.ref") {
return usr
}
return nil
}
private static func testCaseUSR(cursorInfo: [String: SourceKitRepresentable]) -> String? {
if let kind = (cursorInfo["key.kind"] as? String).flatMap(SwiftDeclarationKind.init(rawValue:)),
kind == .class,
let annotatedDecl = cursorInfo["key.annotated_decl"] as? String,
annotatedDecl.contains("<Type usr=\"c:objc(cs)XCTestCase\">XCTestCase</Type>"),
let usr = cursorInfo["key.usr"] as? String {
return usr
}
return nil
}
private static func aclAtOffset(_ offset: Int64, substructureElement: [String: SourceKitRepresentable]) -> String? {
if let nameOffset = substructureElement["key.nameoffset"] as? Int64,
nameOffset == offset,
let acl = substructureElement["key.accessibility"] as? String {
return acl
}
if let substructure = substructureElement[SwiftDocKey.substructure.rawValue] as? [SourceKitRepresentable] {
let nestedSubstructure = substructure.compactMap({ $0 as? [String: SourceKitRepresentable] })
for child in nestedSubstructure {
if let acl = File.aclAtOffset(offset, substructureElement: child) {
return acl
}
}
}
return nil
}
}
// Skip initializers, deinit, enum cases and subscripts since we can't reliably detect if they're used.
private let declarationKindsToSkip: Set<SwiftDeclarationKind> = [
.functionConstructor,
.functionDestructor,
.enumelement,
.functionSubscript
]
/// Skip syntax kinds that won't respond to cursor info requests.
private let syntaxKindsToSkip: Set<SyntaxKind> = [
.attributeBuiltin,
.attributeID,
.comment,
.commentMark,
.commentURL,
.buildconfigID,
.buildconfigKeyword,
.docComment,
.docCommentField,
.keyword,
.number,
.string,
.stringInterpolationAnchor
]

View File

@ -0,0 +1,43 @@
private enum ConfigurationKey: String {
case severity = "severity"
case includePublicAndOpen = "include_public_and_open"
}
public struct UnusedDeclarationConfiguration: RuleConfiguration, Equatable {
private(set) var includePublicAndOpen: Bool
private(set) var severity: ViolationSeverity
public var consoleDescription: String {
return "\(ConfigurationKey.severity.rawValue): \(severity.rawValue), " +
"\(ConfigurationKey.includePublicAndOpen.rawValue): \(includePublicAndOpen)"
}
public init(severity: ViolationSeverity, includePublicAndOpen: Bool) {
self.includePublicAndOpen = includePublicAndOpen
self.severity = severity
}
public mutating func apply(configuration: Any) throws {
guard let configDict = configuration as? [String: Any], !configDict.isEmpty else {
throw ConfigurationError.unknownConfiguration
}
for (string, value) in configDict {
guard let key = ConfigurationKey(rawValue: string) else {
throw ConfigurationError.unknownConfiguration
}
switch (key, value) {
case (.severity, let stringValue as String):
if let severityValue = ViolationSeverity(rawValue: stringValue) {
severity = severityValue
} else {
throw ConfigurationError.unknownConfiguration
}
case (.includePublicAndOpen, let boolValue as Bool):
includePublicAndOpen = boolValue
default:
throw ConfigurationError.unknownConfiguration
}
}
}
}

View File

@ -16,8 +16,10 @@ struct AnalyzeCommand: CommandProtocol {
} }
private func autocorrect(_ options: LintOrAnalyzeOptions) -> Result<(), CommandantError<()>> { private func autocorrect(_ options: LintOrAnalyzeOptions) -> Result<(), CommandantError<()>> {
return Configuration(options: options).visitLintableFiles(options: options, cache: nil) { linter in let storage = RuleStorage()
let corrections = linter.correct() let configuration = Configuration(options: options)
return configuration.visitLintableFiles(options: options, cache: nil, storage: storage) { linter in
let corrections = linter.correct(using: storage)
if !corrections.isEmpty && !options.quiet { if !corrections.isEmpty && !options.quiet {
let correctionLogs = corrections.map({ $0.consoleDescription }) let correctionLogs = corrections.map({ $0.consoleDescription })
queuedPrint(correctionLogs.joined(separator: "\n")) queuedPrint(correctionLogs.joined(separator: "\n"))

View File

@ -8,8 +8,9 @@ struct AutoCorrectCommand: CommandProtocol {
func run(_ options: AutoCorrectOptions) -> Result<(), CommandantError<()>> { func run(_ options: AutoCorrectOptions) -> Result<(), CommandantError<()>> {
let configuration = Configuration(options: options) let configuration = Configuration(options: options)
let visitor = options.visitor(with: configuration) let storage = RuleStorage()
return configuration.visitLintableFiles(with: visitor).flatMap { files in let visitor = options.visitor(with: configuration, storage: storage)
return configuration.visitLintableFiles(with: visitor, storage: storage).flatMap { files in
if !options.quiet { if !options.quiet {
let pluralSuffix = { (collection: [Any]) -> String in let pluralSuffix = { (collection: [Any]) -> String in
return collection.count != 1 ? "s" : "" return collection.count != 1 ? "s" : ""
@ -63,7 +64,7 @@ struct AutoCorrectOptions: OptionsProtocol {
<*> mode <| pathsArgument(action: "correct") <*> mode <| pathsArgument(action: "correct")
} }
fileprivate func visitor(with configuration: Configuration) -> LintableFilesVisitor { fileprivate func visitor(with configuration: Configuration, storage: RuleStorage) -> LintableFilesVisitor {
let cache = ignoreCache ? nil : LinterCache(configuration: configuration) let cache = ignoreCache ? nil : LinterCache(configuration: configuration)
return LintableFilesVisitor(paths: paths, action: "Correcting", useSTDIN: false, quiet: quiet, return LintableFilesVisitor(paths: paths, action: "Correcting", useSTDIN: false, quiet: quiet,
useScriptInputFiles: useScriptInputFiles, forceExclude: forceExclude, cache: cache, useScriptInputFiles: useScriptInputFiles, forceExclude: forceExclude, cache: cache,
@ -76,7 +77,7 @@ struct AutoCorrectOptions: OptionsProtocol {
linter.format(useTabs: false, indentWidth: count) linter.format(useTabs: false, indentWidth: count)
} }
} }
let corrections = linter.correct() let corrections = linter.correct(using: storage)
if !corrections.isEmpty && !self.quiet { if !corrections.isEmpty && !self.quiet {
let correctionLogs = corrections.map({ $0.consoleDescription }) let correctionLogs = corrections.map({ $0.consoleDescription })
queuedPrint(correctionLogs.joined(separator: "\n")) queuedPrint(correctionLogs.joined(separator: "\n"))

View File

@ -0,0 +1 @@
../../SwiftLintFramework/Extensions/Array+SwiftLint.swift

View File

@ -45,14 +45,18 @@ private func scriptInputFiles() -> Result<[File], CommandantError<()>> {
} }
#if os(Linux) #if os(Linux)
private func autoreleasepool(block: () -> Void) { block() } private func autoreleasepool<T>(block: () -> T) -> T { return block() }
#endif #endif
extension Configuration { extension Configuration {
func visitLintableFiles(with visitor: LintableFilesVisitor) -> Result<[File], CommandantError<()>> { func visitLintableFiles(with visitor: LintableFilesVisitor, storage: RuleStorage)
-> Result<[File], CommandantError<()>> {
return getFiles(with: visitor) return getFiles(with: visitor)
.flatMap { groupFiles($0, visitor: visitor) } .flatMap { groupFiles($0, visitor: visitor) }
.flatMap { visit(filesPerConfiguration: $0, visitor: visitor) } .map { linters(for: $0, visitor: visitor) }
.map { ($0, $0.duplicateFileNames) }
.map { collect(linters: $0.0, visitor: visitor, storage: storage, duplicateFileNames: $0.1) }
.map { visit(linters: $0.0, visitor: visitor, storage: storage, duplicateFileNames: $0.1) }
} }
private func groupFiles(_ files: [File], private func groupFiles(_ files: [File],
@ -82,29 +86,31 @@ extension Configuration {
return .success(groupedFiles) return .success(groupedFiles)
} }
private func outputFilename(for path: NSString, in root: NSString, duplicateFileNames: Set<String>) -> String { private func outputFilename(for path: String, duplicateFileNames: Set<String>) -> String {
let basename = path.lastPathComponent guard !duplicateFileNames.isEmpty else {
return path
}
let root = self.rootPath ?? FileManager.default.currentDirectoryPath.bridge().standardizingPath
let basename = path.bridge().lastPathComponent
if !duplicateFileNames.contains(basename) { if !duplicateFileNames.contains(basename) {
return basename return basename
} }
var pathComponents = path.pathComponents var pathComponents = path.bridge().pathComponents
for component in root.pathComponents where pathComponents.first == component { for component in root.bridge().pathComponents where pathComponents.first == component {
pathComponents.removeFirst() pathComponents.removeFirst()
} }
return pathComponents.joined(separator: "/") return pathComponents.joined(separator: "/")
} }
private func visit(filesPerConfiguration: [Configuration: [File]], private func linters(for filesPerConfiguration: [Configuration: [File]],
visitor: LintableFilesVisitor) -> Result<[File], CommandantError<()>> { visitor: LintableFilesVisitor) -> [Linter] {
var fileIndex = 0
let fileCount = filesPerConfiguration.reduce(0) { $0 + $1.value.count } let fileCount = filesPerConfiguration.reduce(0) { $0 + $1.value.count }
let root = FileManager.default.currentDirectoryPath.bridge().standardizingPath.bridge()
var filesAndConfigurations = [(File, Configuration)]() var linters = [Linter]()
var allFileNames = Set<String>() linters.reserveCapacity(fileCount)
var duplicateFileNames = Set<String>()
filesAndConfigurations.reserveCapacity(fileCount)
for (config, files) in filesPerConfiguration { for (config, files) in filesPerConfiguration {
let newConfig: Configuration let newConfig: Configuration
if visitor.cache != nil { if visitor.cache != nil {
@ -112,33 +118,79 @@ extension Configuration {
} else { } else {
newConfig = config newConfig = config
} }
filesAndConfigurations += files.map { ($0, newConfig) } linters += files.map { visitor.linter(forFile: $0, configuration: newConfig) }
for file in files { }
if let filename = file.path?.bridge().lastPathComponent { return linters
if allFileNames.contains(filename) {
duplicateFileNames.insert(filename)
} }
allFileNames.insert(filename) private func collect(linters: [Linter],
visitor: LintableFilesVisitor,
storage: RuleStorage,
duplicateFileNames: Set<String>) -> ([CollectedLinter], Set<String>) {
var collected = 0
let total = linters.filter({ $0.isCollecting }).count
let collect = { (linter: Linter) -> CollectedLinter? in
let skipFile = visitor.shouldSkipFile(atPath: linter.file.path)
if !visitor.quiet, linter.isCollecting, let filePath = linter.file.path {
let outputFilename = self.outputFilename(for: filePath, duplicateFileNames: duplicateFileNames)
let increment = {
collected += 1
if skipFile {
queuedPrintError("""
Skipping '\(outputFilename)' (\(collected)/\(total)) \
because its compiler arguments could not be found
""")
} else {
queuedPrintError("Collecting '\(outputFilename)' (\(collected)/\(total))")
} }
} }
}
let visit = { (file: File, configuration: Configuration) in
let printableFileName = (file.path?.bridge()).map { path in
self.outputFilename(for: path, in: root, duplicateFileNames: duplicateFileNames)
}
visitor.visit(file: file, config: configuration, outputFileName: printableFileName,
incrementIndex: { fileIndex += 1 }, progress: { "(\(fileIndex)/\(fileCount))" })
}
if visitor.parallel { if visitor.parallel {
DispatchQueue.concurrentPerform(iterations: fileCount) { index in indexIncrementerQueue.sync(execute: increment)
let (file, config) = filesAndConfigurations[index]
visit(file, config)
}
} else { } else {
filesAndConfigurations.forEach(visit) increment()
} }
return .success(filesAndConfigurations.compactMap({ $0.0 })) }
guard !skipFile else {
return nil
}
return autoreleasepool {
linter.collect(into: storage)
}
}
let collectedLinters = visitor.parallel ?
linters.parallelCompactMap(transform: collect) :
linters.compactMap(collect)
return (collectedLinters, duplicateFileNames)
}
private func visit(linters: [CollectedLinter],
visitor: LintableFilesVisitor,
storage: RuleStorage,
duplicateFileNames: Set<String>) -> [File] {
var visited = 0
let visit = { (linter: CollectedLinter) -> File in
if !visitor.quiet, let filePath = linter.file.path {
let outputFilename = self.outputFilename(for: filePath, duplicateFileNames: duplicateFileNames)
let increment = {
visited += 1
queuedPrintError("\(visitor.action) '\(outputFilename)' (\(visited)/\(linters.count))")
}
if visitor.parallel {
indexIncrementerQueue.sync(execute: increment)
} else {
increment()
}
}
autoreleasepool {
visitor.block(linter)
}
return linter.file
}
return visitor.parallel ? linters.parallelMap(transform: visit) : linters.map(visit)
} }
fileprivate func getFiles(with visitor: LintableFilesVisitor) -> Result<[File], CommandantError<()>> { fileprivate func getFiles(with visitor: LintableFilesVisitor) -> Result<[File], CommandantError<()>> {
@ -189,9 +241,11 @@ extension Configuration {
cachePath: cachePath) cachePath: cachePath)
} }
func visitLintableFiles(options: LintOrAnalyzeOptions, cache: LinterCache? = nil, func visitLintableFiles(options: LintOrAnalyzeOptions, cache: LinterCache? = nil, storage: RuleStorage,
visitorBlock: @escaping (Linter) -> Void) -> Result<[File], CommandantError<()>> { visitorBlock: @escaping (CollectedLinter) -> Void) -> Result<[File], CommandantError<()>> {
return LintableFilesVisitor.create(options, cache: cache, block: visitorBlock).flatMap(visitLintableFiles) return LintableFilesVisitor.create(options, cache: cache, block: visitorBlock).flatMap({ visitor in
visitLintableFiles(with: visitor, storage: storage)
})
} }
// MARK: AutoCorrect Command // MARK: AutoCorrect Command
@ -213,34 +267,22 @@ private func isConfigOptional() -> Bool {
return !CommandLine.arguments.contains("--config") return !CommandLine.arguments.contains("--config")
} }
private extension LintableFilesVisitor { private struct DuplicateCollector {
func visit(file: File, config: Configuration, outputFileName: String?, incrementIndex: @escaping () -> Void, var all = Set<String>()
progress: @escaping () -> String) { var duplicates = Set<String>()
let skipFile = shouldSkipFile(atPath: file.path)
if !quiet, let outputFileName = outputFileName {
let increment = {
incrementIndex()
if skipFile {
queuedPrintError("""
Skipping '\(outputFileName)' \(progress()) because its compiler arguments could not be found
""")
} else {
queuedPrintError("\(self.action) '\(outputFileName)' \(progress())")
}
}
if parallel {
indexIncrementerQueue.sync(execute: increment)
} else {
increment()
}
} }
guard !skipFile else { private extension Collection where Element == Linter {
return var duplicateFileNames: Set<String> {
let collector = reduce(into: DuplicateCollector()) { result, linter in
if let filename = linter.file.path?.bridge().lastPathComponent {
if result.all.contains(filename) {
result.duplicates.insert(filename)
} }
autoreleasepool { result.all.insert(filename)
block(linter(forFile: file, configuration: config))
} }
} }
return collector.duplicates
}
} }

View File

@ -23,15 +23,16 @@ struct LintOrAnalyzeCommand {
var fileBenchmark = Benchmark(name: "files") var fileBenchmark = Benchmark(name: "files")
var ruleBenchmark = Benchmark(name: "rules") var ruleBenchmark = Benchmark(name: "rules")
var violations = [StyleViolation]() var violations = [StyleViolation]()
let storage = RuleStorage()
let configuration = Configuration(options: options) let configuration = Configuration(options: options)
let reporter = reporterFrom(optionsReporter: options.reporter, configuration: configuration) let reporter = reporterFrom(optionsReporter: options.reporter, configuration: configuration)
let cache = options.ignoreCache ? nil : LinterCache(configuration: configuration) let cache = options.ignoreCache ? nil : LinterCache(configuration: configuration)
let visitorMutationQueue = DispatchQueue(label: "io.realm.swiftlint.lintVisitorMutation") let visitorMutationQueue = DispatchQueue(label: "io.realm.swiftlint.lintVisitorMutation")
return configuration.visitLintableFiles(options: options, cache: cache) { linter in return configuration.visitLintableFiles(options: options, cache: cache, storage: storage) { linter in
let currentViolations: [StyleViolation] let currentViolations: [StyleViolation]
if options.benchmark { if options.benchmark {
let start = Date() let start = Date()
let (violationsBeforeLeniency, currentRuleTimes) = linter.styleViolationsAndRuleTimes let (violationsBeforeLeniency, currentRuleTimes) = linter.styleViolationsAndRuleTimes(using: storage)
currentViolations = applyLeniency(options: options, violations: violationsBeforeLeniency) currentViolations = applyLeniency(options: options, violations: violationsBeforeLeniency)
visitorMutationQueue.sync { visitorMutationQueue.sync {
fileBenchmark.record(file: linter.file, from: start) fileBenchmark.record(file: linter.file, from: start)
@ -39,7 +40,7 @@ struct LintOrAnalyzeCommand {
violations += currentViolations violations += currentViolations
} }
} else { } else {
currentViolations = applyLeniency(options: options, violations: linter.styleViolations) currentViolations = applyLeniency(options: options, violations: linter.styleViolations(using: storage))
visitorMutationQueue.sync { visitorMutationQueue.sync {
violations += currentViolations violations += currentViolations
} }

View File

@ -19,10 +19,10 @@ struct LintableFilesVisitor {
let cache: LinterCache? let cache: LinterCache?
let parallel: Bool let parallel: Bool
let mode: LintOrAnalyzeModeWithCompilerArguments let mode: LintOrAnalyzeModeWithCompilerArguments
let block: (Linter) -> Void let block: (CollectedLinter) -> Void
init(paths: [String], action: String, useSTDIN: Bool, quiet: Bool, useScriptInputFiles: Bool, forceExclude: Bool, init(paths: [String], action: String, useSTDIN: Bool, quiet: Bool, useScriptInputFiles: Bool, forceExclude: Bool,
cache: LinterCache?, parallel: Bool, block: @escaping (Linter) -> Void) { cache: LinterCache?, parallel: Bool, block: @escaping (CollectedLinter) -> Void) {
self.paths = paths self.paths = paths
self.action = action self.action = action
self.useSTDIN = useSTDIN self.useSTDIN = useSTDIN
@ -37,7 +37,7 @@ struct LintableFilesVisitor {
private init(paths: [String], action: String, useSTDIN: Bool, quiet: Bool, useScriptInputFiles: Bool, private init(paths: [String], action: String, useSTDIN: Bool, quiet: Bool, useScriptInputFiles: Bool,
forceExclude: Bool, cache: LinterCache?, compilerLogContents: String, forceExclude: Bool, cache: LinterCache?, compilerLogContents: String,
block: @escaping (Linter) -> Void) { block: @escaping (CollectedLinter) -> Void) {
self.paths = paths self.paths = paths
self.action = action self.action = action
self.useSTDIN = useSTDIN self.useSTDIN = useSTDIN
@ -56,7 +56,7 @@ struct LintableFilesVisitor {
self.block = block self.block = block
} }
static func create(_ options: LintOrAnalyzeOptions, cache: LinterCache?, block: @escaping (Linter) -> Void) static func create(_ options: LintOrAnalyzeOptions, cache: LinterCache?, block: @escaping (CollectedLinter) -> Void)
-> Result<LintableFilesVisitor, CommandantError<()>> { -> Result<LintableFilesVisitor, CommandantError<()>> {
let compilerLogContents: String let compilerLogContents: String
if options.mode == .lint { if options.mode == .lint {

View File

@ -35,6 +35,9 @@
1F11B3CF1C252F23002E8FA8 /* ClosingBraceRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F11B3CE1C252F23002E8FA8 /* ClosingBraceRule.swift */; }; 1F11B3CF1C252F23002E8FA8 /* ClosingBraceRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F11B3CE1C252F23002E8FA8 /* ClosingBraceRule.swift */; };
24B4DF0D1D6DFDE90097803B /* RedundantNilCoalescingRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24B4DF0B1D6DFA370097803B /* RedundantNilCoalescingRule.swift */; }; 24B4DF0D1D6DFDE90097803B /* RedundantNilCoalescingRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24B4DF0B1D6DFA370097803B /* RedundantNilCoalescingRule.swift */; };
24E17F721B14BB3F008195BE /* File+Cache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24E17F701B1481FF008195BE /* File+Cache.swift */; }; 24E17F721B14BB3F008195BE /* File+Cache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24E17F701B1481FF008195BE /* File+Cache.swift */; };
260F66A0225C5B6D00407CF5 /* CollectingRuleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 260F669F225C5B6D00407CF5 /* CollectingRuleTests.swift */; };
264E080F2248BA2800ADC4C5 /* RuleStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 264E080E2248BA2800ADC4C5 /* RuleStorage.swift */; };
26CE462D228532CD00264485 /* Array+SwiftLint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26CE462C228532CD00264485 /* Array+SwiftLint.swift */; };
287F8B642230843000BDC504 /* NSLocalizedStringRequireBundleRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 287F8B62223083ED00BDC504 /* NSLocalizedStringRequireBundleRule.swift */; }; 287F8B642230843000BDC504 /* NSLocalizedStringRequireBundleRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 287F8B62223083ED00BDC504 /* NSLocalizedStringRequireBundleRule.swift */; };
2882895F222975D00037CF5F /* NSObjectPreferIsEqualRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2882895B22287C9C0037CF5F /* NSObjectPreferIsEqualRule.swift */; }; 2882895F222975D00037CF5F /* NSObjectPreferIsEqualRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2882895B22287C9C0037CF5F /* NSObjectPreferIsEqualRule.swift */; };
288289602229776C0037CF5F /* NSObjectPreferIsEqualRuleExamples.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2882895D22287E2C0037CF5F /* NSObjectPreferIsEqualRuleExamples.swift */; }; 288289602229776C0037CF5F /* NSObjectPreferIsEqualRuleExamples.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2882895D22287E2C0037CF5F /* NSObjectPreferIsEqualRuleExamples.swift */; };
@ -174,6 +177,7 @@
85DA81321D6B471000951BC4 /* MarkRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 856651A61D6B395F005E6B29 /* MarkRule.swift */; }; 85DA81321D6B471000951BC4 /* MarkRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 856651A61D6B395F005E6B29 /* MarkRule.swift */; };
8B01E4FD20A41C8700C9233E /* FunctionParameterCountConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B01E4FB20A4183C00C9233E /* FunctionParameterCountConfiguration.swift */; }; 8B01E4FD20A41C8700C9233E /* FunctionParameterCountConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B01E4FB20A4183C00C9233E /* FunctionParameterCountConfiguration.swift */; };
8B01E50220A4349100C9233E /* FunctionParameterCountRuleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B01E4FF20A4340A00C9233E /* FunctionParameterCountRuleTests.swift */; }; 8B01E50220A4349100C9233E /* FunctionParameterCountRuleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B01E4FF20A4340A00C9233E /* FunctionParameterCountRuleTests.swift */; };
8F0856EB22DA8508001FF4D4 /* UnusedDeclarationRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F0856EA22DA8508001FF4D4 /* UnusedDeclarationRule.swift */; };
8F2CC1CB20A6A070006ED34F /* FileNameConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F2CC1CA20A6A070006ED34F /* FileNameConfiguration.swift */; }; 8F2CC1CB20A6A070006ED34F /* FileNameConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F2CC1CA20A6A070006ED34F /* FileNameConfiguration.swift */; };
8F2CC1CD20A6A189006ED34F /* FileNameRuleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F2CC1CC20A6A189006ED34F /* FileNameRuleTests.swift */; }; 8F2CC1CD20A6A189006ED34F /* FileNameRuleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F2CC1CC20A6A189006ED34F /* FileNameRuleTests.swift */; };
8F6AA75B211905B8009BA28A /* LintableFilesVisitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F6AA75A211905B8009BA28A /* LintableFilesVisitor.swift */; }; 8F6AA75B211905B8009BA28A /* LintableFilesVisitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F6AA75A211905B8009BA28A /* LintableFilesVisitor.swift */; };
@ -186,6 +190,7 @@
8FD216CC205584AF008ED13F /* CharacterSet+SwiftLint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FD216CB205584AF008ED13F /* CharacterSet+SwiftLint.swift */; }; 8FD216CC205584AF008ED13F /* CharacterSet+SwiftLint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FD216CB205584AF008ED13F /* CharacterSet+SwiftLint.swift */; };
8FDF482C2122476D00521605 /* AnalyzeCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FDF482B2122476D00521605 /* AnalyzeCommand.swift */; }; 8FDF482C2122476D00521605 /* AnalyzeCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FDF482B2122476D00521605 /* AnalyzeCommand.swift */; };
8FDF482E21234BFF00521605 /* LintOrAnalyzeCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FDF482D21234BFF00521605 /* LintOrAnalyzeCommand.swift */; }; 8FDF482E21234BFF00521605 /* LintOrAnalyzeCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FDF482D21234BFF00521605 /* LintOrAnalyzeCommand.swift */; };
8FE3CCBC22DBF8D000B8EA87 /* UnusedDeclarationConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FE3CCBB22DBF8D000B8EA87 /* UnusedDeclarationConfiguration.swift */; };
92CCB2D71E1EEFA300C8E5A3 /* UnusedOptionalBindingRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92CCB2D61E1EEFA300C8E5A3 /* UnusedOptionalBindingRule.swift */; }; 92CCB2D71E1EEFA300C8E5A3 /* UnusedOptionalBindingRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92CCB2D61E1EEFA300C8E5A3 /* UnusedOptionalBindingRule.swift */; };
93E0C3CE1D67BD7F007FA25D /* ConditionalReturnsOnNewlineRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93E0C3CD1D67BD7F007FA25D /* ConditionalReturnsOnNewlineRule.swift */; }; 93E0C3CE1D67BD7F007FA25D /* ConditionalReturnsOnNewlineRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93E0C3CD1D67BD7F007FA25D /* ConditionalReturnsOnNewlineRule.swift */; };
A1A6F3F21EE319ED00A9F9E2 /* ObjectLiteralConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1A6F3F11EE319ED00A9F9E2 /* ObjectLiteralConfiguration.swift */; }; A1A6F3F21EE319ED00A9F9E2 /* ObjectLiteralConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1A6F3F11EE319ED00A9F9E2 /* ObjectLiteralConfiguration.swift */; };
@ -506,6 +511,9 @@
1F11B3CE1C252F23002E8FA8 /* ClosingBraceRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClosingBraceRule.swift; sourceTree = "<group>"; }; 1F11B3CE1C252F23002E8FA8 /* ClosingBraceRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClosingBraceRule.swift; sourceTree = "<group>"; };
24B4DF0B1D6DFA370097803B /* RedundantNilCoalescingRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantNilCoalescingRule.swift; sourceTree = "<group>"; }; 24B4DF0B1D6DFA370097803B /* RedundantNilCoalescingRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantNilCoalescingRule.swift; sourceTree = "<group>"; };
24E17F701B1481FF008195BE /* File+Cache.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "File+Cache.swift"; sourceTree = "<group>"; }; 24E17F701B1481FF008195BE /* File+Cache.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "File+Cache.swift"; sourceTree = "<group>"; };
260F669F225C5B6D00407CF5 /* CollectingRuleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectingRuleTests.swift; sourceTree = "<group>"; };
264E080E2248BA2800ADC4C5 /* RuleStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuleStorage.swift; sourceTree = "<group>"; };
26CE462C228532CD00264485 /* Array+SwiftLint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "Array+SwiftLint.swift"; path = "../../SwiftLintFramework/Extensions/Array+SwiftLint.swift"; sourceTree = "<group>"; };
287F8B62223083ED00BDC504 /* NSLocalizedStringRequireBundleRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSLocalizedStringRequireBundleRule.swift; sourceTree = "<group>"; }; 287F8B62223083ED00BDC504 /* NSLocalizedStringRequireBundleRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSLocalizedStringRequireBundleRule.swift; sourceTree = "<group>"; };
2882895B22287C9C0037CF5F /* NSObjectPreferIsEqualRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSObjectPreferIsEqualRule.swift; sourceTree = "<group>"; }; 2882895B22287C9C0037CF5F /* NSObjectPreferIsEqualRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSObjectPreferIsEqualRule.swift; sourceTree = "<group>"; };
2882895D22287E2C0037CF5F /* NSObjectPreferIsEqualRuleExamples.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSObjectPreferIsEqualRuleExamples.swift; sourceTree = "<group>"; }; 2882895D22287E2C0037CF5F /* NSObjectPreferIsEqualRuleExamples.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSObjectPreferIsEqualRuleExamples.swift; sourceTree = "<group>"; };
@ -644,6 +652,7 @@
856651A61D6B395F005E6B29 /* MarkRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MarkRule.swift; sourceTree = "<group>"; }; 856651A61D6B395F005E6B29 /* MarkRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MarkRule.swift; sourceTree = "<group>"; };
8B01E4FB20A4183C00C9233E /* FunctionParameterCountConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FunctionParameterCountConfiguration.swift; sourceTree = "<group>"; }; 8B01E4FB20A4183C00C9233E /* FunctionParameterCountConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FunctionParameterCountConfiguration.swift; sourceTree = "<group>"; };
8B01E4FF20A4340A00C9233E /* FunctionParameterCountRuleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FunctionParameterCountRuleTests.swift; sourceTree = "<group>"; }; 8B01E4FF20A4340A00C9233E /* FunctionParameterCountRuleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FunctionParameterCountRuleTests.swift; sourceTree = "<group>"; };
8F0856EA22DA8508001FF4D4 /* UnusedDeclarationRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnusedDeclarationRule.swift; sourceTree = "<group>"; };
8F2CC1CA20A6A070006ED34F /* FileNameConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileNameConfiguration.swift; sourceTree = "<group>"; }; 8F2CC1CA20A6A070006ED34F /* FileNameConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileNameConfiguration.swift; sourceTree = "<group>"; };
8F2CC1CC20A6A189006ED34F /* FileNameRuleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileNameRuleTests.swift; sourceTree = "<group>"; }; 8F2CC1CC20A6A189006ED34F /* FileNameRuleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileNameRuleTests.swift; sourceTree = "<group>"; };
8F6AA75A211905B8009BA28A /* LintableFilesVisitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LintableFilesVisitor.swift; sourceTree = "<group>"; }; 8F6AA75A211905B8009BA28A /* LintableFilesVisitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LintableFilesVisitor.swift; sourceTree = "<group>"; };
@ -656,6 +665,7 @@
8FD216CB205584AF008ED13F /* CharacterSet+SwiftLint.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CharacterSet+SwiftLint.swift"; sourceTree = "<group>"; }; 8FD216CB205584AF008ED13F /* CharacterSet+SwiftLint.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CharacterSet+SwiftLint.swift"; sourceTree = "<group>"; };
8FDF482B2122476D00521605 /* AnalyzeCommand.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnalyzeCommand.swift; sourceTree = "<group>"; }; 8FDF482B2122476D00521605 /* AnalyzeCommand.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnalyzeCommand.swift; sourceTree = "<group>"; };
8FDF482D21234BFF00521605 /* LintOrAnalyzeCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LintOrAnalyzeCommand.swift; sourceTree = "<group>"; }; 8FDF482D21234BFF00521605 /* LintOrAnalyzeCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LintOrAnalyzeCommand.swift; sourceTree = "<group>"; };
8FE3CCBB22DBF8D000B8EA87 /* UnusedDeclarationConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnusedDeclarationConfiguration.swift; sourceTree = "<group>"; };
92CCB2D61E1EEFA300C8E5A3 /* UnusedOptionalBindingRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnusedOptionalBindingRule.swift; sourceTree = "<group>"; }; 92CCB2D61E1EEFA300C8E5A3 /* UnusedOptionalBindingRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnusedOptionalBindingRule.swift; sourceTree = "<group>"; };
93E0C3CD1D67BD7F007FA25D /* ConditionalReturnsOnNewlineRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConditionalReturnsOnNewlineRule.swift; sourceTree = "<group>"; }; 93E0C3CD1D67BD7F007FA25D /* ConditionalReturnsOnNewlineRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConditionalReturnsOnNewlineRule.swift; sourceTree = "<group>"; };
A1A6F3F11EE319ED00A9F9E2 /* ObjectLiteralConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObjectLiteralConfiguration.swift; sourceTree = "<group>"; }; A1A6F3F11EE319ED00A9F9E2 /* ObjectLiteralConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObjectLiteralConfiguration.swift; sourceTree = "<group>"; };
@ -1017,6 +1027,7 @@
BF48D2D61CBCCA5F0080BDAE /* TrailingWhitespaceConfiguration.swift */, BF48D2D61CBCCA5F0080BDAE /* TrailingWhitespaceConfiguration.swift */,
82DB55FF21008F54001C62FF /* TypeContentsOrderConfiguration.swift */, 82DB55FF21008F54001C62FF /* TypeContentsOrderConfiguration.swift */,
CE8178EB1EAC02CD0063186E /* UnusedOptionalBindingConfiguration.swift */, CE8178EB1EAC02CD0063186E /* UnusedOptionalBindingConfiguration.swift */,
8FE3CCBB22DBF8D000B8EA87 /* UnusedDeclarationConfiguration.swift */,
006204DA1E1E48F900FFFBE1 /* VerticalWhitespaceConfiguration.swift */, 006204DA1E1E48F900FFFBE1 /* VerticalWhitespaceConfiguration.swift */,
); );
path = RuleConfigurations; path = RuleConfigurations;
@ -1121,6 +1132,7 @@
7565E5F02262BA0900B0597C /* UnusedCaptureListRule.swift */, 7565E5F02262BA0900B0597C /* UnusedCaptureListRule.swift */,
D40AD0891E032F9700F48C30 /* UnusedClosureParameterRule.swift */, D40AD0891E032F9700F48C30 /* UnusedClosureParameterRule.swift */,
D4D7320C21E15ED4001C07D9 /* UnusedControlFlowLabelRule.swift */, D4D7320C21E15ED4001C07D9 /* UnusedControlFlowLabelRule.swift */,
8F0856EA22DA8508001FF4D4 /* UnusedDeclarationRule.swift */,
8F715B82213B528B00427BD9 /* UnusedImportRule.swift */, 8F715B82213B528B00427BD9 /* UnusedImportRule.swift */,
D4BED5F72278AECC00D86BCE /* UnownedVariableCaptureRule.swift */, D4BED5F72278AECC00D86BCE /* UnownedVariableCaptureRule.swift */,
8F6B3153213CDCD100858E44 /* UnusedPrivateDeclarationRule.swift */, 8F6B3153213CDCD100858E44 /* UnusedPrivateDeclarationRule.swift */,
@ -1422,9 +1434,11 @@
children = ( children = (
D4998DE61DF191380006E05D /* AttributesRuleTests.swift */, D4998DE61DF191380006E05D /* AttributesRuleTests.swift */,
D4EAB3A320E9948D0051C09A /* AutomaticRuleTests.generated.swift */, D4EAB3A320E9948D0051C09A /* AutomaticRuleTests.generated.swift */,
260F669F225C5B6D00407CF5 /* CollectingRuleTests.swift */,
7578C915214173BE0080FEC9 /* CollectionAlignmentRuleTests.swift */, 7578C915214173BE0080FEC9 /* CollectionAlignmentRuleTests.swift */,
D43B04651E071ED3004016AF /* ColonRuleTests.swift */, D43B04651E071ED3004016AF /* ColonRuleTests.swift */,
E81ADD731ED6052F000CD451 /* CommandTests.swift */, E81ADD731ED6052F000CD451 /* CommandTests.swift */,
BCB68282216213130078E4C3 /* CompilerProtocolInitRuleTests.swift */,
820F451D21073D7200AA056A /* ConditionalReturnsOnNewlineRuleTests.swift */, 820F451D21073D7200AA056A /* ConditionalReturnsOnNewlineRuleTests.swift */,
D0D1212219E878CC005E4BAA /* Configuration */, D0D1212219E878CC005E4BAA /* Configuration */,
C2B3C15F2106F78100088928 /* ConfigurationAliasesTests.swift */, C2B3C15F2106F78100088928 /* ConfigurationAliasesTests.swift */,
@ -1435,12 +1449,12 @@
3BB47D861C51DE6E00AE6A10 /* CustomRulesTests.swift */, 3BB47D861C51DE6E00AE6A10 /* CustomRulesTests.swift */,
67932E2C1E54AF4B00CB0629 /* CyclomaticComplexityConfigurationTests.swift */, 67932E2C1E54AF4B00CB0629 /* CyclomaticComplexityConfigurationTests.swift */,
67EB4DFB1E4CD7F5004E9ACD /* CyclomaticComplexityRuleTests.swift */, 67EB4DFB1E4CD7F5004E9ACD /* CyclomaticComplexityRuleTests.swift */,
CCD8B87720559C4A00B75847 /* DisableAllTests.swift */,
D41985EC21FAD033003BE2B7 /* DeploymentTargetConfigurationTests.swift */, D41985EC21FAD033003BE2B7 /* DeploymentTargetConfigurationTests.swift */,
D41985EE21FAD5E8003BE2B7 /* DeploymentTargetRuleTests.swift */, D41985EE21FAD5E8003BE2B7 /* DeploymentTargetRuleTests.swift */,
CCD8B87720559C4A00B75847 /* DisableAllTests.swift */,
62AF35D71F30B183009B11EE /* DiscouragedDirectInitRuleTests.swift */, 62AF35D71F30B183009B11EE /* DiscouragedDirectInitRuleTests.swift */,
D48B51221F4F5E4B0068AB98 /* DocumentationTests.swift */,
D414D6AB21D0B77F00960935 /* DiscouragedObjectLiteralRuleTests.swift */, D414D6AB21D0B77F00960935 /* DiscouragedObjectLiteralRuleTests.swift */,
D48B51221F4F5E4B0068AB98 /* DocumentationTests.swift */,
125CE52E20425EFD001635E5 /* ExplicitTypeInterfaceConfigurationTests.swift */, 125CE52E20425EFD001635E5 /* ExplicitTypeInterfaceConfigurationTests.swift */,
12E3D4DB2042729300B3E30E /* ExplicitTypeInterfaceRuleTests.swift */, 12E3D4DB2042729300B3E30E /* ExplicitTypeInterfaceRuleTests.swift */,
02FD8AEE1BFC18D60014BFFB /* ExtendedNSStringTests.swift */, 02FD8AEE1BFC18D60014BFFB /* ExtendedNSStringTests.swift */,
@ -1481,9 +1495,9 @@
787CDE3A208F9C34005F3D2F /* SwitchCaseAlignmentRuleTests.swift */, 787CDE3A208F9C34005F3D2F /* SwitchCaseAlignmentRuleTests.swift */,
E81224991B04F85B001783D2 /* TestHelpers.swift */, E81224991B04F85B001783D2 /* TestHelpers.swift */,
D4DB92241E628898005DE9C1 /* TodoRuleTests.swift */, D4DB92241E628898005DE9C1 /* TodoRuleTests.swift */,
C9802F2E1E0C8AEE008AB27F /* TrailingCommaRuleTests.swift */,
D450D1E121F19B1E00E60010 /* TrailingClosureConfigurationTests.swift */, D450D1E121F19B1E00E60010 /* TrailingClosureConfigurationTests.swift */,
D450D1DE21F19A9400E60010 /* TrailingClosureRuleTests.swift */, D450D1DE21F19A9400E60010 /* TrailingClosureRuleTests.swift */,
C9802F2E1E0C8AEE008AB27F /* TrailingCommaRuleTests.swift */,
D4F5851320E99A720085C6D8 /* TrailingWhitespaceTests.swift */, D4F5851320E99A720085C6D8 /* TrailingWhitespaceTests.swift */,
820F451B2107292500AA056A /* TypeContentsOrderRuleTests.swift */, 820F451B2107292500AA056A /* TypeContentsOrderRuleTests.swift */,
3B20CD0B1EB699C20069EF2E /* TypeNameRuleTests.swift */, 3B20CD0B1EB699C20069EF2E /* TypeNameRuleTests.swift */,
@ -1493,7 +1507,6 @@
627C7A312004F9290053C79D /* XCTSpecificMatcherRuleTests.swift */, 627C7A312004F9290053C79D /* XCTSpecificMatcherRuleTests.swift */,
3B30C4A01C3785B300E04027 /* YamlParserTests.swift */, 3B30C4A01C3785B300E04027 /* YamlParserTests.swift */,
3B12C9C21C320A53000B423F /* YamlSwiftLintTests.swift */, 3B12C9C21C320A53000B423F /* YamlSwiftLintTests.swift */,
BCB68282216213130078E4C3 /* CompilerProtocolInitRuleTests.swift */,
); );
name = SwiftLintFrameworkTests; name = SwiftLintFrameworkTests;
path = Tests/SwiftLintFrameworkTests; path = Tests/SwiftLintFrameworkTests;
@ -1598,6 +1611,7 @@
D4C27BFD1E12D53F00DF713E /* Version.swift */, D4C27BFD1E12D53F00DF713E /* Version.swift */,
E88DEA701B09847500A66CB0 /* ViolationSeverity.swift */, E88DEA701B09847500A66CB0 /* ViolationSeverity.swift */,
3BD9CD3C1C37175B009A5D25 /* YamlParser.swift */, 3BD9CD3C1C37175B009A5D25 /* YamlParser.swift */,
264E080E2248BA2800ADC4C5 /* RuleStorage.swift */,
); );
path = Models; path = Models;
sourceTree = "<group>"; sourceTree = "<group>";
@ -1635,6 +1649,7 @@
E8B0677F1C13E48100E9E13F /* Extensions */ = { E8B0677F1C13E48100E9E13F /* Extensions */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
26CE462C228532CD00264485 /* Array+SwiftLint.swift */,
E8B067801C13E49600E9E13F /* Configuration+CommandLine.swift */, E8B067801C13E49600E9E13F /* Configuration+CommandLine.swift */,
E86E2B2D1E17443B001E823C /* Reporter+CommandLine.swift */, E86E2B2D1E17443B001E823C /* Reporter+CommandLine.swift */,
); );
@ -1951,6 +1966,7 @@
29FC197A21382C07006D208C /* DuplicateImportsRuleExamples.swift in Sources */, 29FC197A21382C07006D208C /* DuplicateImportsRuleExamples.swift in Sources */,
83D71E281B131ECE000395DE /* RuleDescription.swift in Sources */, 83D71E281B131ECE000395DE /* RuleDescription.swift in Sources */,
D4470D571EB69225008A1B2E /* ImplicitReturnRule.swift in Sources */, D4470D571EB69225008A1B2E /* ImplicitReturnRule.swift in Sources */,
8FE3CCBC22DBF8D000B8EA87 /* UnusedDeclarationConfiguration.swift in Sources */,
3B12C9C51C322032000B423F /* MasterRuleList.swift in Sources */, 3B12C9C51C322032000B423F /* MasterRuleList.swift in Sources */,
E812249C1B04FADC001783D2 /* Linter.swift in Sources */, E812249C1B04FADC001783D2 /* Linter.swift in Sources */,
1F11B3CF1C252F23002E8FA8 /* ClosingBraceRule.swift in Sources */, 1F11B3CF1C252F23002E8FA8 /* ClosingBraceRule.swift in Sources */,
@ -2125,6 +2141,8 @@
E88198421BEA929F00333A11 /* NestingRule.swift in Sources */, E88198421BEA929F00333A11 /* NestingRule.swift in Sources */,
D46A317F1F1CEDCD00AF914A /* UnneededParenthesesInClosureArgumentRule.swift in Sources */, D46A317F1F1CEDCD00AF914A /* UnneededParenthesesInClosureArgumentRule.swift in Sources */,
D4470D591EB6B4D1008A1B2E /* EmptyEnumArgumentsRule.swift in Sources */, D4470D591EB6B4D1008A1B2E /* EmptyEnumArgumentsRule.swift in Sources */,
8F0856EB22DA8508001FF4D4 /* UnusedDeclarationRule.swift in Sources */,
264E080F2248BA2800ADC4C5 /* RuleStorage.swift in Sources */,
627BC48D1F9405160004A6C2 /* QuickDiscouragedFocusedTestRule.swift in Sources */, 627BC48D1F9405160004A6C2 /* QuickDiscouragedFocusedTestRule.swift in Sources */,
3BB47D851C51D80000AE6A10 /* NSRegularExpression+SwiftLint.swift in Sources */, 3BB47D851C51D80000AE6A10 /* NSRegularExpression+SwiftLint.swift in Sources */,
E881985B1BEA974E00333A11 /* StatementPositionRule.swift in Sources */, E881985B1BEA974E00333A11 /* StatementPositionRule.swift in Sources */,
@ -2284,6 +2302,7 @@
C9802F2F1E0C8AEE008AB27F /* TrailingCommaRuleTests.swift in Sources */, C9802F2F1E0C8AEE008AB27F /* TrailingCommaRuleTests.swift in Sources */,
3B63D46F1E1F09DF0057BE35 /* LineLengthRuleTests.swift in Sources */, 3B63D46F1E1F09DF0057BE35 /* LineLengthRuleTests.swift in Sources */,
627C7A322004F9290053C79D /* XCTSpecificMatcherRuleTests.swift in Sources */, 627C7A322004F9290053C79D /* XCTSpecificMatcherRuleTests.swift in Sources */,
260F66A0225C5B6D00407CF5 /* CollectingRuleTests.swift in Sources */,
3BCC04D41C502BAB006073C3 /* RuleConfigurationTests.swift in Sources */, 3BCC04D41C502BAB006073C3 /* RuleConfigurationTests.swift in Sources */,
E809EDA31B8A73FB00399043 /* ConfigurationTests.swift in Sources */, E809EDA31B8A73FB00399043 /* ConfigurationTests.swift in Sources */,
8F2CC1CD20A6A189006ED34F /* FileNameRuleTests.swift in Sources */, 8F2CC1CD20A6A189006ED34F /* FileNameRuleTests.swift in Sources */,
@ -2301,6 +2320,7 @@
E802ED001C56A56000A35AE1 /* Benchmark.swift in Sources */, E802ED001C56A56000A35AE1 /* Benchmark.swift in Sources */,
E83A0B351A5D382B0041A60A /* VersionCommand.swift in Sources */, E83A0B351A5D382B0041A60A /* VersionCommand.swift in Sources */,
E81FB3E41C6D507B00DC988F /* CommonOptions.swift in Sources */, E81FB3E41C6D507B00DC988F /* CommonOptions.swift in Sources */,
26CE462D228532CD00264485 /* Array+SwiftLint.swift in Sources */,
E861519B1B0573B900C54AC0 /* LintCommand.swift in Sources */, E861519B1B0573B900C54AC0 /* LintCommand.swift in Sources */,
D0E7B65619E9C76900EDBA4D /* main.swift in Sources */, D0E7B65619E9C76900EDBA4D /* main.swift in Sources */,
83894F221B0C928A006214E1 /* RulesCommand.swift in Sources */, 83894F221B0C928A006214E1 /* RulesCommand.swift in Sources */,

View File

@ -69,7 +69,8 @@
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0" launchStyle = "0"
useCustomWorkingDirectory = "NO" useCustomWorkingDirectory = "YES"
customWorkingDirectory = "~/Projects/Lyft-iOS"
ignoresPersistentStateOnLaunch = "NO" ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "NO" debugDocumentVersioning = "NO"
debugXPCServices = "NO" debugXPCServices = "NO"
@ -88,9 +89,13 @@
</BuildableProductRunnable> </BuildableProductRunnable>
<CommandLineArguments> <CommandLineArguments>
<CommandLineArgument <CommandLineArgument
argument = "lint --no-cache" argument = "analyze --quiet --strict --compiler-log-path xcodebuild.log"
isEnabled = "YES"> isEnabled = "YES">
</CommandLineArgument> </CommandLineArgument>
<CommandLineArgument
argument = "lint --no-cache"
isEnabled = "NO">
</CommandLineArgument>
</CommandLineArguments> </CommandLineArguments>
<EnvironmentVariables> <EnvironmentVariables>
<EnvironmentVariable <EnvironmentVariable

View File

@ -69,6 +69,15 @@ extension ClosureSpacingRuleTests {
] ]
} }
extension CollectingRuleTests {
static var allTests: [(String, (CollectingRuleTests) -> () throws -> Void)] = [
("testCollectsIntoStorage", testCollectsIntoStorage),
("testCollectsAllFiles", testCollectsAllFiles),
("testCollectsAnalyzerFiles", testCollectsAnalyzerFiles),
("testCorrects", testCorrects)
]
}
extension CollectionAlignmentRuleTests { extension CollectionAlignmentRuleTests {
static var allTests: [(String, (CollectionAlignmentRuleTests) -> () throws -> Void)] = [ static var allTests: [(String, (CollectionAlignmentRuleTests) -> () throws -> Void)] = [
("testWithDefaultConfiguration", testWithDefaultConfiguration), ("testWithDefaultConfiguration", testWithDefaultConfiguration),
@ -1381,6 +1390,12 @@ extension UnusedControlFlowLabelRuleTests {
] ]
} }
extension UnusedDeclarationRuleTests {
static var allTests: [(String, (UnusedDeclarationRuleTests) -> () throws -> Void)] = [
("testWithDefaultConfiguration", testWithDefaultConfiguration)
]
}
extension UnusedEnumeratedRuleTests { extension UnusedEnumeratedRuleTests {
static var allTests: [(String, (UnusedEnumeratedRuleTests) -> () throws -> Void)] = [ static var allTests: [(String, (UnusedEnumeratedRuleTests) -> () throws -> Void)] = [
("testWithDefaultConfiguration", testWithDefaultConfiguration) ("testWithDefaultConfiguration", testWithDefaultConfiguration)
@ -1532,6 +1547,7 @@ XCTMain([
testCase(ClosureEndIndentationRuleTests.allTests), testCase(ClosureEndIndentationRuleTests.allTests),
testCase(ClosureParameterPositionRuleTests.allTests), testCase(ClosureParameterPositionRuleTests.allTests),
testCase(ClosureSpacingRuleTests.allTests), testCase(ClosureSpacingRuleTests.allTests),
testCase(CollectingRuleTests.allTests),
testCase(CollectionAlignmentRuleTests.allTests), testCase(CollectionAlignmentRuleTests.allTests),
testCase(ColonRuleTests.allTests), testCase(ColonRuleTests.allTests),
testCase(CommaRuleTests.allTests), testCase(CommaRuleTests.allTests),
@ -1700,6 +1716,7 @@ XCTMain([
testCase(UnusedCaptureListRuleTests.allTests), testCase(UnusedCaptureListRuleTests.allTests),
testCase(UnusedClosureParameterRuleTests.allTests), testCase(UnusedClosureParameterRuleTests.allTests),
testCase(UnusedControlFlowLabelRuleTests.allTests), testCase(UnusedControlFlowLabelRuleTests.allTests),
testCase(UnusedDeclarationRuleTests.allTests),
testCase(UnusedEnumeratedRuleTests.allTests), testCase(UnusedEnumeratedRuleTests.allTests),
testCase(UnusedImportRuleTests.allTests), testCase(UnusedImportRuleTests.allTests),
testCase(UnusedOptionalBindingRuleTests.allTests), testCase(UnusedOptionalBindingRuleTests.allTests),

View File

@ -714,6 +714,12 @@ class UnusedControlFlowLabelRuleTests: XCTestCase {
} }
} }
class UnusedDeclarationRuleTests: XCTestCase {
func testWithDefaultConfiguration() {
verifyRule(UnusedDeclarationRule.description)
}
}
class UnusedEnumeratedRuleTests: XCTestCase { class UnusedEnumeratedRuleTests: XCTestCase {
func testWithDefaultConfiguration() { func testWithDefaultConfiguration() {
verifyRule(UnusedEnumeratedRule.description) verifyRule(UnusedEnumeratedRule.description)

View File

@ -0,0 +1,125 @@
import SourceKittenFramework
@testable import SwiftLintFramework
import XCTest
// swiftlint:disable nesting
class CollectingRuleTests: XCTestCase {
func testCollectsIntoStorage() {
struct Spec: MockCollectingRule {
func collectInfo(for file: File) -> Int {
return 42
}
func validate(file: File, collectedInfo: [File: Int]) -> [StyleViolation] {
XCTAssertEqual(collectedInfo[file], 42)
return [StyleViolation(ruleDescription: Spec.description,
location: Location(file: file, byteOffset: 0))]
}
}
XCTAssertFalse(violations("", config: Spec.configuration!).isEmpty)
}
func testCollectsAllFiles() {
struct Spec: MockCollectingRule {
func collectInfo(for file: File) -> String {
return file.contents
}
func validate(file: File, collectedInfo: [File: String]) -> [StyleViolation] {
let values = collectedInfo.values
XCTAssertTrue(values.contains("foo"))
XCTAssertTrue(values.contains("bar"))
XCTAssertTrue(values.contains("baz"))
return [StyleViolation(ruleDescription: Spec.description,
location: Location(file: file, byteOffset: 0))]
}
}
let inputs = ["foo", "bar", "baz"]
XCTAssertEqual(inputs.violations(config: Spec.configuration!).count, inputs.count)
}
func testCollectsAnalyzerFiles() {
struct Spec: MockCollectingRule & AnalyzerRule {
func collectInfo(for file: File, compilerArguments: [String]) -> [String] {
return compilerArguments
}
func validate(file: File, collectedInfo: [File: [String]], compilerArguments: [String])
-> [StyleViolation] {
XCTAssertEqual(collectedInfo[file], compilerArguments)
return [StyleViolation(ruleDescription: Spec.description,
location: Location(file: file, byteOffset: 0))]
}
}
XCTAssertFalse(violations("", config: Spec.configuration!, requiresFileOnDisk: true).isEmpty)
}
func testCorrects() {
struct Spec: MockCollectingRule & CollectingCorrectableRule {
func collectInfo(for file: File) -> String {
return file.contents
}
func validate(file: File, collectedInfo: [File: String]) -> [StyleViolation] {
if collectedInfo[file] == "baz" {
return [StyleViolation(ruleDescription: Spec.description,
location: Location(file: file, byteOffset: 2))]
} else {
return []
}
}
func correct(file: File, collectedInfo: [File: String]) -> [Correction] {
if collectedInfo[file] == "baz" {
return [Correction(ruleDescription: Spec.description,
location: Location(file: file, byteOffset: 2))]
} else {
return []
}
}
}
struct AnalyzerSpec: MockCollectingRule & AnalyzerRule & CollectingCorrectableRule {
func collectInfo(for file: File, compilerArguments: [String]) -> String {
return file.contents
}
func validate(file: File, collectedInfo: [File: String], compilerArguments: [String])
-> [StyleViolation] {
if collectedInfo[file] == "baz" {
return [StyleViolation(ruleDescription: Spec.description,
location: Location(file: file, byteOffset: 2))]
} else {
return []
}
}
func correct(file: File, collectedInfo: [File: String], compilerArguments: [String]) -> [Correction] {
if collectedInfo[file] == "baz" {
return [Correction(ruleDescription: Spec.description,
location: Location(file: file, byteOffset: 2))]
} else {
return []
}
}
}
let inputs = ["foo", "baz"]
XCTAssertEqual(inputs.corrections(config: Spec.configuration!).count, 1)
XCTAssertEqual(inputs.corrections(config: AnalyzerSpec.configuration!, requiresFileOnDisk: true).count, 1)
}
}
private protocol MockCollectingRule: CollectingRule {}
extension MockCollectingRule {
var configurationDescription: String { return "N/A" }
static var description: RuleDescription {
return RuleDescription(identifier: "test_rule", name: "", description: "", kind: .lint)
}
static var configuration: Configuration? {
return Configuration(rulesMode: .whitelisted([description.identifier]), ruleList: RuleList(rules: self))
}
init(configuration: Any) throws { self.init() }
}

View File

@ -7,10 +7,6 @@ private func funcWithParameters(_ parameters: String, violates: Bool = false) ->
return "func \(marker)abc(\(parameters)) {}\n" return "func \(marker)abc(\(parameters)) {}\n"
} }
private func violatingFuncWithParameters(_ parameters: String) -> String {
return funcWithParameters(parameters, violates: true)
}
class FunctionParameterCountRuleTests: XCTestCase { class FunctionParameterCountRuleTests: XCTestCase {
func testWithDefaultConfiguration() { func testWithDefaultConfiguration() {
verifyRule(FunctionParameterCountRule.description) verifyRule(FunctionParameterCountRule.description)
@ -48,10 +44,4 @@ class FunctionParameterCountRuleTests: XCTestCase {
verifyRule(description, ruleConfiguration: ["ignores_default_parameters": false]) verifyRule(description, ruleConfiguration: ["ignores_default_parameters": false])
} }
private func violations(_ string: String) -> [StyleViolation] {
let config = makeConfig(nil, FunctionParameterCountRule.description.identifier)!
return SwiftLintFrameworkTests.violations(string, config: config)
}
} }

View File

@ -18,8 +18,9 @@ class IntegrationTests: XCTestCase {
let swiftFiles = config.lintableFiles(inPath: "", forceExclude: false) let swiftFiles = config.lintableFiles(inPath: "", forceExclude: false)
XCTAssert(swiftFiles.contains(where: { #file == $0.path }), "current file should be included") XCTAssert(swiftFiles.contains(where: { #file == $0.path }), "current file should be included")
let storage = RuleStorage()
let violations = swiftFiles.parallelFlatMap { let violations = swiftFiles.parallelFlatMap {
Linter(file: $0, configuration: config).styleViolations Linter(file: $0, configuration: config).collect(into: storage).styleViolations(using: storage)
} }
violations.forEach { violation in violations.forEach { violation in
violation.location.file!.withStaticString { violation.location.file!.withStaticString {
@ -30,7 +31,10 @@ class IntegrationTests: XCTestCase {
func testSwiftLintAutoCorrects() { func testSwiftLintAutoCorrects() {
let swiftFiles = config.lintableFiles(inPath: "", forceExclude: false) let swiftFiles = config.lintableFiles(inPath: "", forceExclude: false)
let corrections = swiftFiles.parallelFlatMap { Linter(file: $0, configuration: config).correct() } let storage = RuleStorage()
let corrections = swiftFiles.parallelFlatMap {
Linter(file: $0, configuration: config).collect(into: storage).correct(using: storage)
}
for correction in corrections { for correction in corrections {
correction.location.file!.withStaticString { correction.location.file!.withStaticString {
XCTFail(correction.ruleDescription.description, XCTFail(correction.ruleDescription.description,

View File

@ -65,7 +65,8 @@ class SourceKitCrashTests: XCTestCase {
XCTFail("If this called, rule's SourceKitFreeRule is not properly configured") XCTFail("If this called, rule's SourceKitFreeRule is not properly configured")
} }
let configuration = Configuration(rulesMode: .whitelisted(allRuleIdentifiers))! let configuration = Configuration(rulesMode: .whitelisted(allRuleIdentifiers))!
_ = Linter(file: file, configuration: configuration).styleViolations let storage = RuleStorage()
_ = Linter(file: file, configuration: configuration).collect(into: storage).styleViolations(using: storage)
file.sourcekitdFailed = false file.sourcekitdFailed = false
file.assertHandler = nil file.assertHandler = nil
} }

View File

@ -33,19 +33,80 @@ func violations(_ string: String, config: Configuration = Configuration()!,
let stringStrippingMarkers = string.replacingOccurrences(of: violationMarker, with: "") let stringStrippingMarkers = string.replacingOccurrences(of: violationMarker, with: "")
guard requiresFileOnDisk else { guard requiresFileOnDisk else {
let file = File(contents: stringStrippingMarkers) let file = File(contents: stringStrippingMarkers)
let linter = Linter(file: file, configuration: config) let storage = RuleStorage()
return linter.styleViolations let linter = Linter(file: file, configuration: config).collect(into: storage)
return linter.styleViolations(using: storage)
} }
let file = File.temporary(withContents: stringStrippingMarkers) let file = File.temporary(withContents: stringStrippingMarkers)
let linter = Linter(file: file, configuration: config, compilerArguments: file.makeCompilerArguments()) let storage = RuleStorage()
return linter.styleViolations.map { violation in let collecter = Linter(file: file, configuration: config, compilerArguments: file.makeCompilerArguments())
let linter = collecter.collect(into: storage)
return linter.styleViolations(using: storage).withoutFiles()
}
extension Collection where Element == String {
func violations(config: Configuration = Configuration()!, requiresFileOnDisk: Bool = false)
-> [StyleViolation] {
let makeFile = requiresFileOnDisk ? File.temporary : File.init(contents:)
return map(makeFile).violations(config: config, requiresFileOnDisk: requiresFileOnDisk)
}
func corrections(config: Configuration = Configuration()!, requiresFileOnDisk: Bool = false) -> [Correction] {
let makeFile = requiresFileOnDisk ? File.temporary : File.init(contents:)
return map(makeFile).corrections(config: config, requiresFileOnDisk: requiresFileOnDisk)
}
}
extension Collection where Element: File {
func violations(config: Configuration = Configuration()!, requiresFileOnDisk: Bool = false)
-> [StyleViolation] {
let storage = RuleStorage()
let violations = map({ file in
Linter(file: file, configuration: config,
compilerArguments: requiresFileOnDisk ? file.makeCompilerArguments() : [])
}).map({ linter in
linter.collect(into: storage)
}).flatMap({ linter in
linter.styleViolations(using: storage)
})
return requiresFileOnDisk ? violations.withoutFiles() : violations
}
func corrections(config: Configuration = Configuration()!, requiresFileOnDisk: Bool = false) -> [Correction] {
let storage = RuleStorage()
let corrections = map({ file in
Linter(file: file, configuration: config,
compilerArguments: requiresFileOnDisk ? file.makeCompilerArguments() : [])
}).map({ linter in
linter.collect(into: storage)
}).flatMap({ linter in
linter.correct(using: storage)
})
return requiresFileOnDisk ? corrections.withoutFiles() : corrections
}
}
private extension Collection where Element == StyleViolation {
func withoutFiles() -> [StyleViolation] {
return map { violation in
let locationWithoutFile = Location(file: nil, line: violation.location.line, let locationWithoutFile = Location(file: nil, line: violation.location.line,
character: violation.location.character) character: violation.location.character)
return StyleViolation(ruleDescription: violation.ruleDescription, severity: violation.severity, return StyleViolation(ruleDescription: violation.ruleDescription, severity: violation.severity,
location: locationWithoutFile, reason: violation.reason) location: locationWithoutFile, reason: violation.reason)
} }
} }
}
private extension Collection where Element == Correction {
func withoutFiles() -> [Correction] {
return map { correction in
let locationWithoutFile = Location(file: nil, line: correction.location.line,
character: correction.location.character)
return Correction(ruleDescription: correction.ruleDescription, location: locationWithoutFile)
}
}
}
private func cleanedContentsAndMarkerOffsets(from contents: String) -> (String, [Int]) { private func cleanedContentsAndMarkerOffsets(from contents: String) -> (String, [Int]) {
var contents = contents.bridge() var contents = contents.bridge()
@ -98,8 +159,10 @@ private extension Configuration {
let expectedLocations = markerOffsets.map { Location(file: file, characterOffset: $0) } let expectedLocations = markerOffsets.map { Location(file: file, characterOffset: $0) }
let includeCompilerArguments = self.rules.contains(where: { $0 is AnalyzerRule }) let includeCompilerArguments = self.rules.contains(where: { $0 is AnalyzerRule })
let compilerArguments = includeCompilerArguments ? file.makeCompilerArguments() : [] let compilerArguments = includeCompilerArguments ? file.makeCompilerArguments() : []
let linter = Linter(file: file, configuration: self, compilerArguments: compilerArguments) let storage = RuleStorage()
let corrections = linter.correct().sorted { $0.location < $1.location } let collecter = Linter(file: file, configuration: self, compilerArguments: compilerArguments)
let linter = collecter.collect(into: storage)
let corrections = linter.correct(using: storage).sorted { $0.location < $1.location }
if expectedLocations.isEmpty { if expectedLocations.isEmpty {
XCTAssertEqual(corrections.count, before != expected ? 1 : 0) XCTAssertEqual(corrections.count, before != expected ? 1 : 0)
} else { } else {

View File

@ -154,7 +154,7 @@ def generate_reports(branch)
command = '../builds/.build/release/swiftlint lint --no-cache --enable-all-rules --reporter xcode' command = '../builds/.build/release/swiftlint lint --no-cache --enable-all-rules --reporter xcode'
File.open("../#{branch}_reports/#{repo}.txt", 'w') do |file| File.open("../#{branch}_reports/#{repo}.txt", 'w') do |file|
puts "\n#{command}" if @options[:verbose] puts "\n#{command}" if @options[:verbose]
Open3.popen3(command) do |_, stdout, _, wait_thr| Open3.popen2(command) do |_, stdout, wait_thr|
while line = stdout.gets while line = stdout.gets
file.puts line file.puts line
end end
@ -170,7 +170,7 @@ def generate_reports(branch)
print "..#{i}" print "..#{i}"
start = Time.now start = Time.now
puts command if @options[:verbose] puts command if @options[:verbose]
Open3.popen3(command) { |_, stdout, _, _| stdout.read } Open3.popen2(command) { |_, stdout, _| stdout.read }
durations << Time.now - start durations << Time.now - start
end end
puts '' puts ''