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:
- Tests/SwiftLintFrameworkTests/Resources
analyzer_rules:
- unused_import
- unused_private_declaration
- unused_declaration
# - unused_import
# - unused_private_declaration
opt_in_rules:
- anyobject_protocol
- array_init

View File

@ -6,7 +6,13 @@
#### 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

View File

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

View File

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

View File

@ -15,7 +15,7 @@ let package = Package(
],
dependencies: [
.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/scottrhoyt/SwiftyTextTable.git", from: "0.9.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])
}
func parallelFlatMap<T>(transform: @escaping ((Element) -> [T])) -> [T] {
func parallelFlatMap<T>(transform: (Element) -> [T]) -> [T] {
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 }
}

View File

@ -50,6 +50,7 @@ private extension Rule {
// As we need the configuration to get custom identifiers.
// swiftlint:disable:next function_parameter_count
func lint(file: File, regions: [Region], benchmark: Bool,
storage: RuleStorage,
configuration: Configuration,
superfluousDisableCommandRule: SuperfluousDisableCommandRule?,
compilerArguments: [String]) -> LintResult? {
@ -63,10 +64,10 @@ private extension Rule {
let ruleTime: (String, Double)?
if benchmark {
let start = Date()
violations = validate(file: file, compilerArguments: compilerArguments)
violations = validate(file: file, using: storage, compilerArguments: compilerArguments)
ruleTime = (ruleID, -start.timeIntervalSinceNow)
} else {
violations = validate(file: file, compilerArguments: compilerArguments)
violations = validate(file: file, using: storage, compilerArguments: compilerArguments)
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 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
private let rules: [Rule]
private let cache: LinterCache?
private let configuration: Configuration
private let compilerArguments: [String]
public var styleViolations: [StyleViolation] {
return getStyleViolations().0
fileprivate init(from linter: Linter) {
file = linter.file
rules = linter.rules
cache = linter.cache
configuration = linter.configuration
compilerArguments = linter.compilerArguments
}
public var styleViolationsAndRuleTimes: ([StyleViolation], [(id: String, time: Double)]) {
return getStyleViolations(benchmark: true)
public func styleViolations(using storage: RuleStorage) -> [StyleViolation] {
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) {
return cached
}
@ -136,6 +185,7 @@ public struct Linter {
}) as? SuperfluousDisableCommandRule
let validationResults = rules.parallelCompactMap {
$0.lint(file: self.file, regions: regions, benchmark: benchmark,
storage: storage,
configuration: self.configuration,
superfluousDisableCommandRule: superfluousDisableCommandRule,
compilerArguments: self.compilerArguments)
@ -184,29 +234,14 @@ public struct Linter {
return (cachedViolations, ruleTimes)
}
public init(file: File, configuration: Configuration = Configuration()!, cache: LinterCache? = nil,
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] {
public func correct(using storage: RuleStorage) -> [Correction] {
if let violations = cachedStyleViolations()?.0, violations.isEmpty {
return []
}
var corrections = [Correction]()
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
if !newCorrections.isEmpty {
file.invalidateCache()

View File

@ -162,6 +162,7 @@ public let masterRuleList = RuleList(rules: [
UnusedCaptureListRule.self,
UnusedClosureParameterRule.self,
UnusedControlFlowLabelRule.self,
UnusedDeclarationRule.self,
UnusedEnumeratedRule.self,
UnusedImportRule.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) -> [StyleViolation]
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 {
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] {
return validate(file: file)
}
@ -22,6 +30,10 @@ extension Rule {
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 {
return (self as? CacheDescriptionProvider)?.cacheDescription ?? configurationDescription
}
@ -40,12 +52,18 @@ public protocol ConfigurationProviderRule: Rule {
public protocol CorrectableRule: Rule {
func correct(file: File, compilerArguments: [String]) -> [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 {
func correct(file: File, compilerArguments: [String]) -> [Correction] {
return correct(file: file)
}
func correct(file: File, using storage: RuleStorage, compilerArguments: [String]) -> [Correction] {
return correct(file: file, compilerArguments: compilerArguments)
}
}
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 {
init(configuration: Any) throws {

View File

@ -5,12 +5,6 @@ public protocol RuleConfiguration {
func isEqualTo(_ ruleConfiguration: RuleConfiguration) -> Bool
}
extension RuleConfiguration {
internal var cacheDescription: String {
return (self as? CacheDescriptionProvider)?.cacheDescription ?? consoleDescription
}
}
public extension RuleConfiguration where Self: Equatable {
func isEqualTo(_ ruleConfiguration: RuleConfiguration) -> Bool {
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<()>> {
return Configuration(options: options).visitLintableFiles(options: options, cache: nil) { linter in
let corrections = linter.correct()
let storage = RuleStorage()
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 {
let correctionLogs = corrections.map({ $0.consoleDescription })
queuedPrint(correctionLogs.joined(separator: "\n"))

View File

@ -8,8 +8,9 @@ struct AutoCorrectCommand: CommandProtocol {
func run(_ options: AutoCorrectOptions) -> Result<(), CommandantError<()>> {
let configuration = Configuration(options: options)
let visitor = options.visitor(with: configuration)
return configuration.visitLintableFiles(with: visitor).flatMap { files in
let storage = RuleStorage()
let visitor = options.visitor(with: configuration, storage: storage)
return configuration.visitLintableFiles(with: visitor, storage: storage).flatMap { files in
if !options.quiet {
let pluralSuffix = { (collection: [Any]) -> String in
return collection.count != 1 ? "s" : ""
@ -63,7 +64,7 @@ struct AutoCorrectOptions: OptionsProtocol {
<*> 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)
return LintableFilesVisitor(paths: paths, action: "Correcting", useSTDIN: false, quiet: quiet,
useScriptInputFiles: useScriptInputFiles, forceExclude: forceExclude, cache: cache,
@ -76,7 +77,7 @@ struct AutoCorrectOptions: OptionsProtocol {
linter.format(useTabs: false, indentWidth: count)
}
}
let corrections = linter.correct()
let corrections = linter.correct(using: storage)
if !corrections.isEmpty && !self.quiet {
let correctionLogs = corrections.map({ $0.consoleDescription })
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)
private func autoreleasepool(block: () -> Void) { block() }
private func autoreleasepool<T>(block: () -> T) -> T { return block() }
#endif
extension Configuration {
func visitLintableFiles(with visitor: LintableFilesVisitor) -> Result<[File], CommandantError<()>> {
return getFiles(with: visitor)
.flatMap { groupFiles($0, visitor: visitor) }
.flatMap { visit(filesPerConfiguration: $0, visitor: visitor) }
func visitLintableFiles(with visitor: LintableFilesVisitor, storage: RuleStorage)
-> Result<[File], CommandantError<()>> {
return getFiles(with: visitor)
.flatMap { groupFiles($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],
@ -82,29 +86,31 @@ extension Configuration {
return .success(groupedFiles)
}
private func outputFilename(for path: NSString, in root: NSString, duplicateFileNames: Set<String>) -> String {
let basename = path.lastPathComponent
private func outputFilename(for path: String, duplicateFileNames: Set<String>) -> String {
guard !duplicateFileNames.isEmpty else {
return path
}
let root = self.rootPath ?? FileManager.default.currentDirectoryPath.bridge().standardizingPath
let basename = path.bridge().lastPathComponent
if !duplicateFileNames.contains(basename) {
return basename
}
var pathComponents = path.pathComponents
for component in root.pathComponents where pathComponents.first == component {
var pathComponents = path.bridge().pathComponents
for component in root.bridge().pathComponents where pathComponents.first == component {
pathComponents.removeFirst()
}
return pathComponents.joined(separator: "/")
}
private func visit(filesPerConfiguration: [Configuration: [File]],
visitor: LintableFilesVisitor) -> Result<[File], CommandantError<()>> {
var fileIndex = 0
private func linters(for filesPerConfiguration: [Configuration: [File]],
visitor: LintableFilesVisitor) -> [Linter] {
let fileCount = filesPerConfiguration.reduce(0) { $0 + $1.value.count }
let root = FileManager.default.currentDirectoryPath.bridge().standardizingPath.bridge()
var filesAndConfigurations = [(File, Configuration)]()
var allFileNames = Set<String>()
var duplicateFileNames = Set<String>()
filesAndConfigurations.reserveCapacity(fileCount)
var linters = [Linter]()
linters.reserveCapacity(fileCount)
for (config, files) in filesPerConfiguration {
let newConfig: Configuration
if visitor.cache != nil {
@ -112,33 +118,79 @@ extension Configuration {
} else {
newConfig = config
}
filesAndConfigurations += files.map { ($0, newConfig) }
for file in files {
if let filename = file.path?.bridge().lastPathComponent {
if allFileNames.contains(filename) {
duplicateFileNames.insert(filename)
}
linters += files.map { visitor.linter(forFile: $0, configuration: newConfig) }
}
return linters
}
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))")
}
}
if visitor.parallel {
indexIncrementerQueue.sync(execute: increment)
} else {
increment()
}
}
}
let visit = { (file: File, configuration: Configuration) in
let printableFileName = (file.path?.bridge()).map { path in
self.outputFilename(for: path, in: root, duplicateFileNames: duplicateFileNames)
guard !skipFile else {
return nil
}
visitor.visit(file: file, config: configuration, outputFileName: printableFileName,
incrementIndex: { fileIndex += 1 }, progress: { "(\(fileIndex)/\(fileCount))" })
}
if visitor.parallel {
DispatchQueue.concurrentPerform(iterations: fileCount) { index in
let (file, config) = filesAndConfigurations[index]
visit(file, config)
return autoreleasepool {
linter.collect(into: storage)
}
} else {
filesAndConfigurations.forEach(visit)
}
return .success(filesAndConfigurations.compactMap({ $0.0 }))
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<()>> {
@ -189,9 +241,11 @@ extension Configuration {
cachePath: cachePath)
}
func visitLintableFiles(options: LintOrAnalyzeOptions, cache: LinterCache? = nil,
visitorBlock: @escaping (Linter) -> Void) -> Result<[File], CommandantError<()>> {
return LintableFilesVisitor.create(options, cache: cache, block: visitorBlock).flatMap(visitLintableFiles)
func visitLintableFiles(options: LintOrAnalyzeOptions, cache: LinterCache? = nil, storage: RuleStorage,
visitorBlock: @escaping (CollectedLinter) -> Void) -> Result<[File], CommandantError<()>> {
return LintableFilesVisitor.create(options, cache: cache, block: visitorBlock).flatMap({ visitor in
visitLintableFiles(with: visitor, storage: storage)
})
}
// MARK: AutoCorrect Command
@ -213,34 +267,22 @@ private func isConfigOptional() -> Bool {
return !CommandLine.arguments.contains("--config")
}
private extension LintableFilesVisitor {
func visit(file: File, config: Configuration, outputFileName: String?, incrementIndex: @escaping () -> Void,
progress: @escaping () -> 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())")
private struct DuplicateCollector {
var all = Set<String>()
var duplicates = Set<String>()
}
private extension Collection where Element == Linter {
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)
}
}
if parallel {
indexIncrementerQueue.sync(execute: increment)
} else {
increment()
result.all.insert(filename)
}
}
guard !skipFile else {
return
}
autoreleasepool {
block(linter(forFile: file, configuration: config))
}
return collector.duplicates
}
}

View File

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

View File

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

View File

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

View File

@ -69,7 +69,8 @@
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
useCustomWorkingDirectory = "YES"
customWorkingDirectory = "~/Projects/Lyft-iOS"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "NO"
debugXPCServices = "NO"
@ -88,9 +89,13 @@
</BuildableProductRunnable>
<CommandLineArguments>
<CommandLineArgument
argument = "lint --no-cache"
argument = "analyze --quiet --strict --compiler-log-path xcodebuild.log"
isEnabled = "YES">
</CommandLineArgument>
<CommandLineArgument
argument = "lint --no-cache"
isEnabled = "NO">
</CommandLineArgument>
</CommandLineArguments>
<EnvironmentVariables>
<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 {
static var allTests: [(String, (CollectionAlignmentRuleTests) -> () throws -> Void)] = [
("testWithDefaultConfiguration", testWithDefaultConfiguration),
@ -1381,6 +1390,12 @@ extension UnusedControlFlowLabelRuleTests {
]
}
extension UnusedDeclarationRuleTests {
static var allTests: [(String, (UnusedDeclarationRuleTests) -> () throws -> Void)] = [
("testWithDefaultConfiguration", testWithDefaultConfiguration)
]
}
extension UnusedEnumeratedRuleTests {
static var allTests: [(String, (UnusedEnumeratedRuleTests) -> () throws -> Void)] = [
("testWithDefaultConfiguration", testWithDefaultConfiguration)
@ -1532,6 +1547,7 @@ XCTMain([
testCase(ClosureEndIndentationRuleTests.allTests),
testCase(ClosureParameterPositionRuleTests.allTests),
testCase(ClosureSpacingRuleTests.allTests),
testCase(CollectingRuleTests.allTests),
testCase(CollectionAlignmentRuleTests.allTests),
testCase(ColonRuleTests.allTests),
testCase(CommaRuleTests.allTests),
@ -1700,6 +1716,7 @@ XCTMain([
testCase(UnusedCaptureListRuleTests.allTests),
testCase(UnusedClosureParameterRuleTests.allTests),
testCase(UnusedControlFlowLabelRuleTests.allTests),
testCase(UnusedDeclarationRuleTests.allTests),
testCase(UnusedEnumeratedRuleTests.allTests),
testCase(UnusedImportRuleTests.allTests),
testCase(UnusedOptionalBindingRuleTests.allTests),

View File

@ -714,6 +714,12 @@ class UnusedControlFlowLabelRuleTests: XCTestCase {
}
}
class UnusedDeclarationRuleTests: XCTestCase {
func testWithDefaultConfiguration() {
verifyRule(UnusedDeclarationRule.description)
}
}
class UnusedEnumeratedRuleTests: XCTestCase {
func testWithDefaultConfiguration() {
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"
}
private func violatingFuncWithParameters(_ parameters: String) -> String {
return funcWithParameters(parameters, violates: true)
}
class FunctionParameterCountRuleTests: XCTestCase {
func testWithDefaultConfiguration() {
verifyRule(FunctionParameterCountRule.description)
@ -48,10 +44,4 @@ class FunctionParameterCountRuleTests: XCTestCase {
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)
XCTAssert(swiftFiles.contains(where: { #file == $0.path }), "current file should be included")
let storage = RuleStorage()
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
violation.location.file!.withStaticString {
@ -30,7 +31,10 @@ class IntegrationTests: XCTestCase {
func testSwiftLintAutoCorrects() {
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 {
correction.location.file!.withStaticString {
XCTFail(correction.ruleDescription.description,

View File

@ -65,7 +65,8 @@ class SourceKitCrashTests: XCTestCase {
XCTFail("If this called, rule's SourceKitFreeRule is not properly configured")
}
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.assertHandler = nil
}

View File

@ -33,17 +33,78 @@ func violations(_ string: String, config: Configuration = Configuration()!,
let stringStrippingMarkers = string.replacingOccurrences(of: violationMarker, with: "")
guard requiresFileOnDisk else {
let file = File(contents: stringStrippingMarkers)
let linter = Linter(file: file, configuration: config)
return linter.styleViolations
let storage = RuleStorage()
let linter = Linter(file: file, configuration: config).collect(into: storage)
return linter.styleViolations(using: storage)
}
let file = File.temporary(withContents: stringStrippingMarkers)
let linter = Linter(file: file, configuration: config, compilerArguments: file.makeCompilerArguments())
return linter.styleViolations.map { violation in
let locationWithoutFile = Location(file: nil, line: violation.location.line,
character: violation.location.character)
return StyleViolation(ruleDescription: violation.ruleDescription, severity: violation.severity,
location: locationWithoutFile, reason: violation.reason)
let storage = RuleStorage()
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,
character: violation.location.character)
return StyleViolation(ruleDescription: violation.ruleDescription, severity: violation.severity,
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)
}
}
}
@ -98,8 +159,10 @@ private extension Configuration {
let expectedLocations = markerOffsets.map { Location(file: file, characterOffset: $0) }
let includeCompilerArguments = self.rules.contains(where: { $0 is AnalyzerRule })
let compilerArguments = includeCompilerArguments ? file.makeCompilerArguments() : []
let linter = Linter(file: file, configuration: self, compilerArguments: compilerArguments)
let corrections = linter.correct().sorted { $0.location < $1.location }
let storage = RuleStorage()
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 {
XCTAssertEqual(corrections.count, before != expected ? 1 : 0)
} else {

View File

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