From e72d09bc31edbbd2ace13716ffbf355f529e24bc Mon Sep 17 00:00:00 2001 From: J Cheyo Jimenez Date: Sat, 3 Jun 2017 18:16:56 -0700 Subject: [PATCH 1/5] added fileprivateRule --- CHANGELOG.md | 5 ++ .../Models/MasterRuleList.swift | 1 + .../Rules/FileprivateRule.swift | 53 +++++++++++ .../Rules/IdentifierNameRule.swift | 2 +- .../FileprivateConfiguration.swift | 90 +++++++++++++++++++ SwiftLint.xcodeproj/project.pbxproj | 8 ++ Tests/LinuxMain.swift | 2 + .../ConfigurationTests.swift | 4 +- .../LinterCacheTests.swift | 4 +- .../SwiftLintFrameworkTests/RulesTests.swift | 9 ++ 10 files changed, 173 insertions(+), 5 deletions(-) create mode 100644 Source/SwiftLintFramework/Rules/FileprivateRule.swift create mode 100644 Source/SwiftLintFramework/Rules/RuleConfigurations/FileprivateConfiguration.swift diff --git a/CHANGELOG.md b/CHANGELOG.md index 079405fdec..8062ee1b94 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -69,6 +69,11 @@ * Match `(Void)` as return type in the `void_return` rule. [Anders Hasselqvist](https://github.com/nevil) +* Added `fileprivateRule` to check for top-level usages of `fileprivate` and recommend `private` instead. This is inline with SE-0169's goal "for `fileprivate` to be used rarely". There is a also an "strict" option that will mark every `fileprivate` as a violation. + [Jose Cheyo Jimenez](https://github.com/masters3d) + [#1469](https://github.com/realm/SwiftLint/issues/1469) + [#1058](https://github.com/realm/SwiftLint/issues/1058) + * Add `multiline_parameters` opt-in rule that warns to either keep all the parameters of a method or function on the same line, or one per line. diff --git a/Source/SwiftLintFramework/Models/MasterRuleList.swift b/Source/SwiftLintFramework/Models/MasterRuleList.swift index 11dd1734ed..de3412d265 100644 --- a/Source/SwiftLintFramework/Models/MasterRuleList.swift +++ b/Source/SwiftLintFramework/Models/MasterRuleList.swift @@ -35,6 +35,7 @@ public let masterRuleList = RuleList(rules: [ FatalErrorMessageRule.self, FileHeaderRule.self, FileLengthRule.self, + FileprivateRule.self, FirstWhereRule.self, ForWhereRule.self, ForceCastRule.self, diff --git a/Source/SwiftLintFramework/Rules/FileprivateRule.swift b/Source/SwiftLintFramework/Rules/FileprivateRule.swift new file mode 100644 index 0000000000..76866e85a5 --- /dev/null +++ b/Source/SwiftLintFramework/Rules/FileprivateRule.swift @@ -0,0 +1,53 @@ +// +// FileprivateRule.swift +// SwiftLint +// +// Created by Jose Cheyo Jimenez on 05/02/17. +// Copyright © 2017 Realm. All rights reserved. +// + +import Foundation +import SourceKittenFramework + +public struct FileprivateRule: Rule, ConfigurationProviderRule { + public var configuration = FileprivateConfiguration(strict: false) + + public init() {} + + public static let description = FileprivateConfiguration.fileprivateLimited + + public func validate(file: File) -> [StyleViolation] { + if !configuration.strict { + let toplevel = file.structure.dictionary.substructure.flatMap({ $0.offset }) + let syntaxTokens = file.syntaxMap.tokens + let violationOffsets = toplevel.flatMap { (offSet) -> Int? in + let parts = syntaxTokens.partitioned { offSet <= $0.offset } + guard let lastKind = parts.first.last, + lastKind.type == SyntaxKind.attributeBuiltin.rawValue, + // Cut the amount of name-look-ups by first checking the char count + lastKind.length == "fileprivate".bridge().length, + // Get the actual name of the attibute + let aclName = file.contents.bridge() + .substringWithByteRange(start:lastKind.offset, length: lastKind.length), + // fileprivate(set) is not possible at toplevel + aclName == "fileprivate" + else { return nil } + return offSet + } + return violationOffsets.map({ StyleViolation( + ruleDescription: FileprivateConfiguration.fileprivateLimited, + location: Location(file: file, byteOffset: $0)) + }) + + } else { // Mark all fileprivate occurences as a violation + let fileprivates = file.match(pattern: "fileprivate", with: [.attributeBuiltin]).flatMap({ + file.contents.bridge().NSRangeToByteRange(start: $0.location, length: $0.length) + }).map({ $0.location }) + return fileprivates.map({ StyleViolation( + ruleDescription: FileprivateConfiguration.fileprivateDisallowed, + location: Location(file: file, byteOffset: $0)) + }) + } + + } +} diff --git a/Source/SwiftLintFramework/Rules/IdentifierNameRule.swift b/Source/SwiftLintFramework/Rules/IdentifierNameRule.swift index 1a973c6bca..22a26ffb34 100644 --- a/Source/SwiftLintFramework/Rules/IdentifierNameRule.swift +++ b/Source/SwiftLintFramework/Rules/IdentifierNameRule.swift @@ -116,7 +116,7 @@ public struct IdentifierNameRule: ASTRule, ConfigurationProviderRule { } } -fileprivate extension String { +private extension String { var isViolatingCase: Bool { let secondIndex = characters.index(after: startIndex) let firstCharacter = substring(to: secondIndex) diff --git a/Source/SwiftLintFramework/Rules/RuleConfigurations/FileprivateConfiguration.swift b/Source/SwiftLintFramework/Rules/RuleConfigurations/FileprivateConfiguration.swift new file mode 100644 index 0000000000..06e2950097 --- /dev/null +++ b/Source/SwiftLintFramework/Rules/RuleConfigurations/FileprivateConfiguration.swift @@ -0,0 +1,90 @@ +// +// FileprivateConfiguration.swift +// SwiftLint +// +// Created by Jose Cheyo Jimenez on 05/02/17. +// Copyright © 2017 Realm. All rights reserved. +// + +public struct FileprivateConfiguration: RuleConfiguration, Equatable { + private(set) var severityConfiguration = SeverityConfiguration(.warning) + private(set) var strict: Bool + + public var consoleDescription: String { + return severityConfiguration.consoleDescription + ", strict: \(strict)" + } + + public init(strict: Bool) { + self.strict = strict + } + + public mutating func apply(configuration: Any) throws { + guard let configuration = configuration as? [String: Any] else { + throw ConfigurationError.unknownConfiguration + } + + if let strict = configuration["strict"] as? Bool { + self.strict = strict + } + + if let severityString = configuration["severity"] as? String { + try severityConfiguration.apply(configuration: severityString) + } + } + + public static let fileprivateLimited = RuleDescription( + identifier: "fileprivate", + name: "Limit Fileprivate", + description: "Prefer private over fileprivate for top-level declarations", + nonTriggeringExamples: [ + "extension String {}", + "private extension String {}", + "public \n enum MyEnum {}", + "open extension \n String {}", + "internal extension String {}", + "extension String {\nfileprivate func Something(){}\n}", + "class MyClass {\nfileprivate let myInt = 4\n}", + "class MyClass {\nfileprivate(set) var myInt = 4\n}", + "struct Outter {\nstruct Inter {\nfileprivate struct Inner {}\n}\n}" + ], + triggeringExamples: [ + "fileprivate enum MyEnum {}", + "fileprivate extension String {}", + "fileprivate \n extension String {}", + "fileprivate extension \n String {}", + "fileprivate class MyClass {\nfileprivate(set) var myInt = 4\n}", + "fileprivate extension String {}" + ] + ) + + public static let fileprivateDisallowed = RuleDescription( + identifier: "fileprivate", + name: "Fileprivate Disallowed", + description: "Fileprivate should be rare. Consider refactoring", + nonTriggeringExamples: [ + "extension String {}", + "private extension String {}", + "public \n extension String {}", + "open extension \n String {}", + "internal extension String {}", + "" + ], + triggeringExamples: [ + "fileprivate extension String {}", + "fileprivate extension String {}", + "fileprivate \n extension String {}", + "fileprivate extension \n String {}", + "fileprivate extension String {}", + "extension String {\nfileprivate func Something(){}\n}", + "class MyClass {\nfileprivate let myInt = 4\n}", + "class MyClass {\nfileprivate(set) var myInt = 4\n}", + "struct Outter {\nstruct Inter {\nfileprivate struct Inner {}\n}\n}" + ] + ) + + public static func == (lhs: FileprivateConfiguration, + rhs: FileprivateConfiguration) -> Bool { + return lhs.strict == rhs.strict && + lhs.severityConfiguration == rhs.severityConfiguration + } +} diff --git a/SwiftLint.xcodeproj/project.pbxproj b/SwiftLint.xcodeproj/project.pbxproj index db5486712b..2b3a62f5b6 100644 --- a/SwiftLint.xcodeproj/project.pbxproj +++ b/SwiftLint.xcodeproj/project.pbxproj @@ -17,6 +17,8 @@ 094385011D5D2894009168CF /* WeakDelegateRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 094384FF1D5D2382009168CF /* WeakDelegateRule.swift */; }; 094385041D5D4F7C009168CF /* PrivateOutletRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 094385021D5D4F78009168CF /* PrivateOutletRule.swift */; }; 1E18574B1EADBA51004F89F7 /* NoExtensionAccessModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E18574A1EADBA51004F89F7 /* NoExtensionAccessModifier.swift */; }; + 1E3C2D711EE36C6F00C8386D /* FileprivateRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E3C2D701EE36C6F00C8386D /* FileprivateRule.swift */; }; + 1E3C2D731EE36D3500C8386D /* FileprivateConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E3C2D721EE36D3500C8386D /* FileprivateConfiguration.swift */; }; 1E82D5591D7775C7009553D7 /* ClosureSpacingRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E82D5581D7775C7009553D7 /* ClosureSpacingRule.swift */; }; 1EC163521D5992D900DD2928 /* VerticalWhitespaceRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EC163511D5992D900DD2928 /* VerticalWhitespaceRule.swift */; }; 1EF115921EB2AD5900E30140 /* ExplicitTopLevelACLRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EF115911EB2AD5900E30140 /* ExplicitTopLevelACLRule.swift */; }; @@ -304,6 +306,8 @@ 094384FF1D5D2382009168CF /* WeakDelegateRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WeakDelegateRule.swift; sourceTree = ""; }; 094385021D5D4F78009168CF /* PrivateOutletRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PrivateOutletRule.swift; sourceTree = ""; }; 1E18574A1EADBA51004F89F7 /* NoExtensionAccessModifier.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NoExtensionAccessModifier.swift; sourceTree = ""; }; + 1E3C2D701EE36C6F00C8386D /* FileprivateRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileprivateRule.swift; sourceTree = ""; }; + 1E3C2D721EE36D3500C8386D /* FileprivateConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileprivateConfiguration.swift; sourceTree = ""; }; 1E82D5581D7775C7009553D7 /* ClosureSpacingRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClosureSpacingRule.swift; sourceTree = ""; }; 1EC163511D5992D900DD2928 /* VerticalWhitespaceRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VerticalWhitespaceRule.swift; sourceTree = ""; }; 1EF115911EB2AD5900E30140 /* ExplicitTopLevelACLRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExplicitTopLevelACLRule.swift; sourceTree = ""; }; @@ -610,6 +614,7 @@ D43B04671E07228D004016AF /* ColonConfiguration.swift */, 67EB4DF81E4CC101004E9ACD /* CyclomaticComplexityConfiguration.swift */, D4C4A3511DEFBBB700E0E04C /* FileHeaderConfiguration.swift */, + 1E3C2D721EE36D3500C8386D /* FileprivateConfiguration.swift */, 47ACC8971E7DC74E0088EEB2 /* ImplicitlyUnwrappedOptionalConfiguration.swift */, 3B034B6C1E0BE544005D49A9 /* LineLengthConfiguration.swift */, 3BCC04D01C4F56D3006073C3 /* NameConfiguration.swift */, @@ -902,6 +907,7 @@ C3DE5DAA1E7DF99B00761483 /* FatalErrorMessageRule.swift */, D4C4A34D1DEA877200E0E04C /* FileHeaderRule.swift */, E88DEA891B0992B300A66CB0 /* FileLengthRule.swift */, + 1E3C2D701EE36C6F00C8386D /* FileprivateRule.swift */, D42D2B371E09CC0D00CD7A2E /* FirstWhereRule.swift */, E88DEA7F1B09903300A66CB0 /* ForceCastRule.swift */, E816194D1BFBFEAB00946723 /* ForceTryRule.swift */, @@ -1326,6 +1332,7 @@ D4B0226F1E0C75F9007E5297 /* VerticalParameterAlignmentRule.swift in Sources */, E8BDE3FF1EDF91B6002EC12F /* RuleList.swift in Sources */, D44254271DB9C15C00492EA4 /* SyntacticSugarRule.swift in Sources */, + 1E3C2D731EE36D3500C8386D /* FileprivateConfiguration.swift in Sources */, 006204DC1E1E492F00FFFBE1 /* VerticalWhitespaceConfiguration.swift in Sources */, E88198441BEA93D200333A11 /* ColonRule.swift in Sources */, E809EDA11B8A71DF00399043 /* Configuration.swift in Sources */, @@ -1394,6 +1401,7 @@ 1EF115921EB2AD5900E30140 /* ExplicitTopLevelACLRule.swift in Sources */, D41E7E0B1DF9DABB0065259A /* RedundantStringEnumValueRule.swift in Sources */, E88DEA711B09847500A66CB0 /* ViolationSeverity.swift in Sources */, + 1E3C2D711EE36C6F00C8386D /* FileprivateRule.swift in Sources */, B2902A0C1D66815600BFCCF7 /* PrivateUnitTestRule.swift in Sources */, D47A51101DB2DD4800A4CC21 /* AttributesRule.swift in Sources */, CE8178ED1EAC039D0063186E /* UnusedOptionalBindingConfiguration.swift in Sources */, diff --git a/Tests/LinuxMain.swift b/Tests/LinuxMain.swift index 2758b38601..dba125ba43 100644 --- a/Tests/LinuxMain.swift +++ b/Tests/LinuxMain.swift @@ -323,6 +323,8 @@ extension RulesTests { ("testExtensionAccessModifier", testExtensionAccessModifier), ("testFatalErrorMessage", testFatalErrorMessage), ("testFileLength", testFileLength), + ("testFileprivateRule", testFileprivateRule), + ("testFileprivateRuleWithConfig", testFileprivateRuleWithConfig), ("testFirstWhere", testFirstWhere), ("testForceCast", testForceCast), ("testForceTry", testForceTry), diff --git a/Tests/SwiftLintFrameworkTests/ConfigurationTests.swift b/Tests/SwiftLintFrameworkTests/ConfigurationTests.swift index 8309169596..78f41ec402 100644 --- a/Tests/SwiftLintFrameworkTests/ConfigurationTests.swift +++ b/Tests/SwiftLintFrameworkTests/ConfigurationTests.swift @@ -269,7 +269,7 @@ class ConfigurationTests: XCTestCase { // MARK: - ProjectMock Paths -fileprivate extension String { +private extension String { func stringByAppendingPathComponent(_ pathComponent: String) -> String { return bridge().appendingPathComponent(pathComponent) } @@ -285,7 +285,7 @@ extension XCTestCase { } } -fileprivate extension XCTestCase { +private extension XCTestCase { var projectMockPathLevel0: String { return bundlePath.stringByAppendingPathComponent("ProjectMock") diff --git a/Tests/SwiftLintFrameworkTests/LinterCacheTests.swift b/Tests/SwiftLintFrameworkTests/LinterCacheTests.swift index 13a87d6649..e05b7b7db2 100644 --- a/Tests/SwiftLintFrameworkTests/LinterCacheTests.swift +++ b/Tests/SwiftLintFrameworkTests/LinterCacheTests.swift @@ -10,7 +10,7 @@ import Foundation @testable import SwiftLintFramework import XCTest -fileprivate struct CacheTestHelper { +private struct CacheTestHelper { fileprivate let configuration: Configuration private let ruleList: RuleList @@ -60,7 +60,7 @@ fileprivate struct CacheTestHelper { } } -fileprivate class TestFileManager: LintableFileManager { +private class TestFileManager: LintableFileManager { fileprivate func filesToLint(inPath: String, rootDirectory: String? = nil) -> [String] { return [] } diff --git a/Tests/SwiftLintFrameworkTests/RulesTests.swift b/Tests/SwiftLintFrameworkTests/RulesTests.swift index 5d2d00012c..0babf255e1 100644 --- a/Tests/SwiftLintFrameworkTests/RulesTests.swift +++ b/Tests/SwiftLintFrameworkTests/RulesTests.swift @@ -101,6 +101,15 @@ class RulesTests: XCTestCase { testMultiByteOffsets: false) } + func testFileprivateRule() { + verifyRule(FileprivateConfiguration.fileprivateLimited) + } + + func testFileprivateRuleWithConfig() { + verifyRule(FileprivateConfiguration.fileprivateDisallowed, + ruleConfiguration: ["strict": true]) + } + func testFirstWhere() { verifyRule(FirstWhereRule.description) } From c3e4cac860972b97b53699973ee38cb403ff1b5f Mon Sep 17 00:00:00 2001 From: Marcelo Fabri Date: Mon, 3 Jul 2017 23:09:26 +0200 Subject: [PATCH 2/5] Fixes after rebase --- CHANGELOG.md | 14 +++++++++----- .../FileprivateConfiguration.swift | 2 ++ 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8062ee1b94..8599040c48 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,15 @@ [Marcelo Fabri](https://github.com/marcelofabri) [#1078](https://github.com/realm/SwiftLint/issues/1078) +* Added `fileprivate` rule to check for top-level usages of `fileprivate` + and recommend `private` instead. This is inline with SE-0169's goal + "for `fileprivate` to be used rarely". There is a also an "strict" option + that will mark every `fileprivate` as a violation. + [Jose Cheyo Jimenez](https://github.com/masters3d) + [Marcelo Fabri](https://github.com/marcelofabri) + [#1469](https://github.com/realm/SwiftLint/issues/1469) + [#1058](https://github.com/realm/SwiftLint/issues/1058) + ##### Bug Fixes * None. @@ -69,11 +78,6 @@ * Match `(Void)` as return type in the `void_return` rule. [Anders Hasselqvist](https://github.com/nevil) -* Added `fileprivateRule` to check for top-level usages of `fileprivate` and recommend `private` instead. This is inline with SE-0169's goal "for `fileprivate` to be used rarely". There is a also an "strict" option that will mark every `fileprivate` as a violation. - [Jose Cheyo Jimenez](https://github.com/masters3d) - [#1469](https://github.com/realm/SwiftLint/issues/1469) - [#1058](https://github.com/realm/SwiftLint/issues/1058) - * Add `multiline_parameters` opt-in rule that warns to either keep all the parameters of a method or function on the same line, or one per line. diff --git a/Source/SwiftLintFramework/Rules/RuleConfigurations/FileprivateConfiguration.swift b/Source/SwiftLintFramework/Rules/RuleConfigurations/FileprivateConfiguration.swift index 06e2950097..6a024643d3 100644 --- a/Source/SwiftLintFramework/Rules/RuleConfigurations/FileprivateConfiguration.swift +++ b/Source/SwiftLintFramework/Rules/RuleConfigurations/FileprivateConfiguration.swift @@ -36,6 +36,7 @@ public struct FileprivateConfiguration: RuleConfiguration, Equatable { identifier: "fileprivate", name: "Limit Fileprivate", description: "Prefer private over fileprivate for top-level declarations", + kind: .idiomatic, nonTriggeringExamples: [ "extension String {}", "private extension String {}", @@ -61,6 +62,7 @@ public struct FileprivateConfiguration: RuleConfiguration, Equatable { identifier: "fileprivate", name: "Fileprivate Disallowed", description: "Fileprivate should be rare. Consider refactoring", + kind: .idiomatic, nonTriggeringExamples: [ "extension String {}", "private extension String {}", From 1a73358c85acde2f4ae3d84fd48ff8023da0188b Mon Sep 17 00:00:00 2001 From: Marcelo Fabri Date: Mon, 3 Jul 2017 23:37:29 +0200 Subject: [PATCH 3/5] Refactoring rule --- .../Models/MasterRuleList.swift | 2 +- .../Rules/FileprivateRule.swift | 110 ++++++++++++------ .../FileprivateConfiguration.swift | 60 +--------- SwiftLint.xcodeproj/project.pbxproj | 8 +- Tests/LinuxMain.swift | 10 +- .../FilePrivateRuleTests.swift | 43 +++++++ .../SwiftLintFrameworkTests/RulesTests.swift | 9 -- 7 files changed, 139 insertions(+), 103 deletions(-) create mode 100644 Tests/SwiftLintFrameworkTests/FilePrivateRuleTests.swift diff --git a/Source/SwiftLintFramework/Models/MasterRuleList.swift b/Source/SwiftLintFramework/Models/MasterRuleList.swift index de3412d265..f9d7d324ea 100644 --- a/Source/SwiftLintFramework/Models/MasterRuleList.swift +++ b/Source/SwiftLintFramework/Models/MasterRuleList.swift @@ -35,7 +35,7 @@ public let masterRuleList = RuleList(rules: [ FatalErrorMessageRule.self, FileHeaderRule.self, FileLengthRule.self, - FileprivateRule.self, + FilePrivateRule.self, FirstWhereRule.self, ForWhereRule.self, ForceCastRule.self, diff --git a/Source/SwiftLintFramework/Rules/FileprivateRule.swift b/Source/SwiftLintFramework/Rules/FileprivateRule.swift index 76866e85a5..f28db8767c 100644 --- a/Source/SwiftLintFramework/Rules/FileprivateRule.swift +++ b/Source/SwiftLintFramework/Rules/FileprivateRule.swift @@ -1,5 +1,5 @@ // -// FileprivateRule.swift +// FilePrivateRule.swift // SwiftLint // // Created by Jose Cheyo Jimenez on 05/02/17. @@ -9,45 +9,89 @@ import Foundation import SourceKittenFramework -public struct FileprivateRule: Rule, ConfigurationProviderRule { - public var configuration = FileprivateConfiguration(strict: false) +public struct FilePrivateRule: Rule, ConfigurationProviderRule { + public var configuration = FilePrivateConfiguration(strict: false) public init() {} - public static let description = FileprivateConfiguration.fileprivateLimited + public static let description = RuleDescription( + identifier: "fileprivate", + name: "FilePrivate", + description: "Prefer private over fileprivate declarations.", + kind: .idiomatic, + nonTriggeringExamples: [ + "extension String {}", + "private extension String {}", + "public \n enum MyEnum {}", + "open extension \n String {}", + "internal extension String {}", + "extension String {\nfileprivate func Something(){}\n}", + "class MyClass {\nfileprivate let myInt = 4\n}", + "class MyClass {\nfileprivate(set) var myInt = 4\n}", + "struct Outter {\nstruct Inter {\nfileprivate struct Inner {}\n}\n}" + ], + triggeringExamples: [ + "fileprivate ↓enum MyEnum {}", + "fileprivate ↓extension String {}", + "fileprivate \n ↓extension String {}", + "fileprivate ↓extension \n String {}", + "fileprivate ↓class MyClass {\nfileprivate(set) var myInt = 4\n}", + "fileprivate ↓extension String {}" + ] + ) public func validate(file: File) -> [StyleViolation] { - if !configuration.strict { - let toplevel = file.structure.dictionary.substructure.flatMap({ $0.offset }) - let syntaxTokens = file.syntaxMap.tokens - let violationOffsets = toplevel.flatMap { (offSet) -> Int? in - let parts = syntaxTokens.partitioned { offSet <= $0.offset } - guard let lastKind = parts.first.last, - lastKind.type == SyntaxKind.attributeBuiltin.rawValue, - // Cut the amount of name-look-ups by first checking the char count - lastKind.length == "fileprivate".bridge().length, - // Get the actual name of the attibute - let aclName = file.contents.bridge() - .substringWithByteRange(start:lastKind.offset, length: lastKind.length), - // fileprivate(set) is not possible at toplevel - aclName == "fileprivate" - else { return nil } - return offSet + if configuration.strict { + return strictValidate(file: file) + } else { + return validateTopLevelDeclarations(file: file) + } + } + + private func validateTopLevelDeclarations(file: File) -> [StyleViolation] { + let offsets = file.structure.dictionary.substructure.flatMap { dictionary -> Int? in + guard let offset = dictionary.offset, + dictionary.accessibility.flatMap(AccessControlLevel.init(identifier:)) == .fileprivate else { + return nil } - return violationOffsets.map({ StyleViolation( - ruleDescription: FileprivateConfiguration.fileprivateLimited, - location: Location(file: file, byteOffset: $0)) - }) - - } else { // Mark all fileprivate occurences as a violation - let fileprivates = file.match(pattern: "fileprivate", with: [.attributeBuiltin]).flatMap({ - file.contents.bridge().NSRangeToByteRange(start: $0.location, length: $0.length) - }).map({ $0.location }) - return fileprivates.map({ StyleViolation( - ruleDescription: FileprivateConfiguration.fileprivateDisallowed, - location: Location(file: file, byteOffset: $0)) - }) + + return offset + } + validateTopLevelExtensions(file: file) + + return offsets.map { + StyleViolation(ruleDescription: type(of: self).description, + location: Location(file: file, byteOffset: $0)) } + } + private func validateTopLevelExtensions(file: File) -> [Int] { + let syntaxTokens = file.syntaxMap.tokens + let contents = file.contents.bridge() + + return file.structure.dictionary.substructure.flatMap { dictionary -> Int? in + guard dictionary.kind.flatMap(SwiftDeclarationKind.init) == .extension, + let offset = dictionary.offset else { + return nil + } + + let parts = syntaxTokens.prefix { offset > $0.offset } + guard let lastKind = parts.last, + SyntaxKind(rawValue: lastKind.type) == .attributeBuiltin, + let aclName = contents.substringWithByteRange(start:lastKind.offset, length: lastKind.length), + AccessControlLevel(description: aclName) == .fileprivate else { + return nil + } + + return offset + } + } + + private func strictValidate(file: File) -> [StyleViolation] { + // Mark all fileprivate occurences as a violation + return file.match(pattern: "fileprivate", with: [.attributeBuiltin]).map { + StyleViolation(ruleDescription: type(of: self).description, + location: Location(file: file, characterOffset: $0.location), + reason: "fileprivate should be avoided.") + } } } diff --git a/Source/SwiftLintFramework/Rules/RuleConfigurations/FileprivateConfiguration.swift b/Source/SwiftLintFramework/Rules/RuleConfigurations/FileprivateConfiguration.swift index 6a024643d3..3c7a96eb5b 100644 --- a/Source/SwiftLintFramework/Rules/RuleConfigurations/FileprivateConfiguration.swift +++ b/Source/SwiftLintFramework/Rules/RuleConfigurations/FileprivateConfiguration.swift @@ -1,12 +1,12 @@ // -// FileprivateConfiguration.swift +// FilePrivateConfiguration.swift // SwiftLint // // Created by Jose Cheyo Jimenez on 05/02/17. // Copyright © 2017 Realm. All rights reserved. // -public struct FileprivateConfiguration: RuleConfiguration, Equatable { +public struct FilePrivateConfiguration: RuleConfiguration, Equatable { private(set) var severityConfiguration = SeverityConfiguration(.warning) private(set) var strict: Bool @@ -32,60 +32,8 @@ public struct FileprivateConfiguration: RuleConfiguration, Equatable { } } - public static let fileprivateLimited = RuleDescription( - identifier: "fileprivate", - name: "Limit Fileprivate", - description: "Prefer private over fileprivate for top-level declarations", - kind: .idiomatic, - nonTriggeringExamples: [ - "extension String {}", - "private extension String {}", - "public \n enum MyEnum {}", - "open extension \n String {}", - "internal extension String {}", - "extension String {\nfileprivate func Something(){}\n}", - "class MyClass {\nfileprivate let myInt = 4\n}", - "class MyClass {\nfileprivate(set) var myInt = 4\n}", - "struct Outter {\nstruct Inter {\nfileprivate struct Inner {}\n}\n}" - ], - triggeringExamples: [ - "fileprivate enum MyEnum {}", - "fileprivate extension String {}", - "fileprivate \n extension String {}", - "fileprivate extension \n String {}", - "fileprivate class MyClass {\nfileprivate(set) var myInt = 4\n}", - "fileprivate extension String {}" - ] - ) - - public static let fileprivateDisallowed = RuleDescription( - identifier: "fileprivate", - name: "Fileprivate Disallowed", - description: "Fileprivate should be rare. Consider refactoring", - kind: .idiomatic, - nonTriggeringExamples: [ - "extension String {}", - "private extension String {}", - "public \n extension String {}", - "open extension \n String {}", - "internal extension String {}", - "" - ], - triggeringExamples: [ - "fileprivate extension String {}", - "fileprivate extension String {}", - "fileprivate \n extension String {}", - "fileprivate extension \n String {}", - "fileprivate extension String {}", - "extension String {\nfileprivate func Something(){}\n}", - "class MyClass {\nfileprivate let myInt = 4\n}", - "class MyClass {\nfileprivate(set) var myInt = 4\n}", - "struct Outter {\nstruct Inter {\nfileprivate struct Inner {}\n}\n}" - ] - ) - - public static func == (lhs: FileprivateConfiguration, - rhs: FileprivateConfiguration) -> Bool { + public static func == (lhs: FilePrivateConfiguration, + rhs: FilePrivateConfiguration) -> Bool { return lhs.strict == rhs.strict && lhs.severityConfiguration == rhs.severityConfiguration } diff --git a/SwiftLint.xcodeproj/project.pbxproj b/SwiftLint.xcodeproj/project.pbxproj index 2b3a62f5b6..91fc527ea2 100644 --- a/SwiftLint.xcodeproj/project.pbxproj +++ b/SwiftLint.xcodeproj/project.pbxproj @@ -119,6 +119,7 @@ D4130D991E16CC1300242361 /* TypeNameRuleExamples.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4130D981E16CC1300242361 /* TypeNameRuleExamples.swift */; }; D41B57781ED8CEE0007B0470 /* ExtensionAccessModifierRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D41B57771ED8CEE0007B0470 /* ExtensionAccessModifierRule.swift */; }; D41E7E0B1DF9DABB0065259A /* RedundantStringEnumValueRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D41E7E0A1DF9DABB0065259A /* RedundantStringEnumValueRule.swift */; }; + D42B45D71F0AEF980086B683 /* FilePrivateRuleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D42B45D61F0AEF980086B683 /* FilePrivateRuleTests.swift */; }; D42D2B381E09CC0D00CD7A2E /* FirstWhereRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D42D2B371E09CC0D00CD7A2E /* FirstWhereRule.swift */; }; D4348EEA1C46122C007707FB /* FunctionBodyLengthRuleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4348EE91C46122C007707FB /* FunctionBodyLengthRuleTests.swift */; }; D43B04641E0620AB004016AF /* UnusedEnumeratedRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D43B04631E0620AB004016AF /* UnusedEnumeratedRule.swift */; }; @@ -169,11 +170,11 @@ D4D5A5FF1E1F3A1C00D15E0C /* ShorthandOperatorRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4D5A5FE1E1F3A1C00D15E0C /* ShorthandOperatorRule.swift */; }; D4DA1DF41E17511D0037413D /* CompilerProtocolInitRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4DA1DF31E17511D0037413D /* CompilerProtocolInitRule.swift */; }; D4DA1DFA1E18D6200037413D /* LargeTupleRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4DA1DF91E18D6200037413D /* LargeTupleRule.swift */; }; + D4DA1DFC1E19CD300037413D /* RulesDocsCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4DA1DFB1E19CD300037413D /* RulesDocsCommand.swift */; }; D4DA1DFE1E1A10DB0037413D /* NumberSeparatorConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4DA1DFD1E1A10DB0037413D /* NumberSeparatorConfiguration.swift */; }; D4DABFD31E29B4A5009617B6 /* DiscardedNotificationCenterObserverRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4DABFD21E29B4A5009617B6 /* DiscardedNotificationCenterObserverRule.swift */; }; D4DABFD71E2C23B1009617B6 /* NotificationCenterDetachmentRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4DABFD61E2C23B1009617B6 /* NotificationCenterDetachmentRule.swift */; }; D4DABFD91E2C59BC009617B6 /* NotificationCenterDetachmentRuleExamples.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4DABFD81E2C59BC009617B6 /* NotificationCenterDetachmentRuleExamples.swift */; }; - D4DA1DFC1E19CD300037413D /* RulesDocsCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4DA1DFB1E19CD300037413D /* RulesDocsCommand.swift */; }; D4DAE8BC1DE14E8F00B0AE7A /* NimbleOperatorRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4DAE8BB1DE14E8F00B0AE7A /* NimbleOperatorRule.swift */; }; D4DB92251E628898005DE9C1 /* TodoRuleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4DB92241E628898005DE9C1 /* TodoRuleTests.swift */; }; D4FBADD01E00DA0400669C73 /* OperatorUsageWhitespaceRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4FBADCF1E00DA0400669C73 /* OperatorUsageWhitespaceRule.swift */; }; @@ -426,6 +427,7 @@ D4130D981E16CC1300242361 /* TypeNameRuleExamples.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TypeNameRuleExamples.swift; sourceTree = ""; }; D41B57771ED8CEE0007B0470 /* ExtensionAccessModifierRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExtensionAccessModifierRule.swift; sourceTree = ""; }; D41E7E0A1DF9DABB0065259A /* RedundantStringEnumValueRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantStringEnumValueRule.swift; sourceTree = ""; }; + D42B45D61F0AEF980086B683 /* FilePrivateRuleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FilePrivateRuleTests.swift; sourceTree = ""; }; D42D2B371E09CC0D00CD7A2E /* FirstWhereRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FirstWhereRule.swift; sourceTree = ""; }; D4348EE91C46122C007707FB /* FunctionBodyLengthRuleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FunctionBodyLengthRuleTests.swift; sourceTree = ""; }; D43B04631E0620AB004016AF /* UnusedEnumeratedRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnusedEnumeratedRule.swift; sourceTree = ""; }; @@ -476,11 +478,11 @@ D4D5A5FE1E1F3A1C00D15E0C /* ShorthandOperatorRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShorthandOperatorRule.swift; sourceTree = ""; }; D4DA1DF31E17511D0037413D /* CompilerProtocolInitRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CompilerProtocolInitRule.swift; sourceTree = ""; }; D4DA1DF91E18D6200037413D /* LargeTupleRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LargeTupleRule.swift; sourceTree = ""; }; + D4DA1DFB1E19CD300037413D /* RulesDocsCommand.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RulesDocsCommand.swift; sourceTree = ""; }; D4DA1DFD1E1A10DB0037413D /* NumberSeparatorConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NumberSeparatorConfiguration.swift; sourceTree = ""; }; D4DABFD21E29B4A5009617B6 /* DiscardedNotificationCenterObserverRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DiscardedNotificationCenterObserverRule.swift; sourceTree = ""; }; D4DABFD61E2C23B1009617B6 /* NotificationCenterDetachmentRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationCenterDetachmentRule.swift; sourceTree = ""; }; D4DABFD81E2C59BC009617B6 /* NotificationCenterDetachmentRuleExamples.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationCenterDetachmentRuleExamples.swift; sourceTree = ""; }; - D4DA1DFB1E19CD300037413D /* RulesDocsCommand.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RulesDocsCommand.swift; sourceTree = ""; }; D4DAE8BB1DE14E8F00B0AE7A /* NimbleOperatorRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NimbleOperatorRule.swift; sourceTree = ""; }; D4DB92241E628898005DE9C1 /* TodoRuleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TodoRuleTests.swift; sourceTree = ""; }; D4FBADCF1E00DA0400669C73 /* OperatorUsageWhitespaceRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OperatorUsageWhitespaceRule.swift; sourceTree = ""; }; @@ -803,6 +805,7 @@ 67EB4DFB1E4CD7F5004E9ACD /* CyclomaticComplexityRuleTests.swift */, 02FD8AEE1BFC18D60014BFFB /* ExtendedNSStringTests.swift */, D4998DE81DF194F20006E05D /* FileHeaderRuleTests.swift */, + D42B45D61F0AEF980086B683 /* FilePrivateRuleTests.swift */, D4348EE91C46122C007707FB /* FunctionBodyLengthRuleTests.swift */, 3B20CD091EB699380069EF2E /* GenericTypeNameRuleTests.swift */, 3B3A9A321EA3DFD90075B121 /* IdentifierNameRuleTests.swift */, @@ -1458,6 +1461,7 @@ E81ADD741ED6052F000CD451 /* CommandTests.swift in Sources */, 006204DE1E1E4E0A00FFFBE1 /* VerticalWhitespaceRuleTests.swift in Sources */, 02FD8AEF1BFC18D60014BFFB /* ExtendedNSStringTests.swift in Sources */, + D42B45D71F0AEF980086B683 /* FilePrivateRuleTests.swift in Sources */, D4CA758F1E2DEEA500A40E8A /* NumberSeparatorRuleTests.swift in Sources */, D4DB92251E628898005DE9C1 /* TodoRuleTests.swift in Sources */, D4348EEA1C46122C007707FB /* FunctionBodyLengthRuleTests.swift in Sources */, diff --git a/Tests/LinuxMain.swift b/Tests/LinuxMain.swift index dba125ba43..bdcbac4941 100644 --- a/Tests/LinuxMain.swift +++ b/Tests/LinuxMain.swift @@ -126,6 +126,13 @@ extension FileHeaderRuleTests { ] } +extension FilePrivateRuleTests { + static var allTests: [(String, (FilePrivateRuleTests) -> () throws -> Void)] = [ + ("testFilePrivateWithDefaultConfiguration", testFilePrivateWithDefaultConfiguration), + ("testFilePrivateWithStrictConfiguration", testFilePrivateWithStrictConfiguration) + ] +} + extension FunctionBodyLengthRuleTests { static var allTests: [(String, (FunctionBodyLengthRuleTests) -> () throws -> Void)] = [ ("testFunctionBodyLengths", testFunctionBodyLengths), @@ -323,8 +330,6 @@ extension RulesTests { ("testExtensionAccessModifier", testExtensionAccessModifier), ("testFatalErrorMessage", testFatalErrorMessage), ("testFileLength", testFileLength), - ("testFileprivateRule", testFileprivateRule), - ("testFileprivateRuleWithConfig", testFileprivateRuleWithConfig), ("testFirstWhere", testFirstWhere), ("testForceCast", testForceCast), ("testForceTry", testForceTry), @@ -450,6 +455,7 @@ XCTMain([ testCase(CyclomaticComplexityRuleTests.allTests), testCase(ExtendedNSStringTests.allTests), testCase(FileHeaderRuleTests.allTests), + testCase(FilePrivateRuleTests.allTests), testCase(FunctionBodyLengthRuleTests.allTests), testCase(GenericTypeNameRuleTests.allTests), testCase(IdentifierNameRuleTests.allTests), diff --git a/Tests/SwiftLintFrameworkTests/FilePrivateRuleTests.swift b/Tests/SwiftLintFrameworkTests/FilePrivateRuleTests.swift new file mode 100644 index 0000000000..5ddcdd3f41 --- /dev/null +++ b/Tests/SwiftLintFrameworkTests/FilePrivateRuleTests.swift @@ -0,0 +1,43 @@ +// +// FilePrivateRuleTests.swift +// SwiftLint +// +// Created by Marcelo Fabri on 03/07/17. +// Copyright © 2017 Realm. All rights reserved. +// + +import SwiftLintFramework +import XCTest + +class FilePrivateRuleTests: XCTestCase { + + func testFilePrivateWithDefaultConfiguration() { + verifyRule(FilePrivateRule.description) + } + + func testFilePrivateWithStrictConfiguration() { + let nonTriggeringExamples = [ + "extension String {}", + "private extension String {}", + "public \n extension String {}", + "open extension \n String {}", + "internal extension String {}" + ] + let triggeringExamples = [ + "↓fileprivate extension String {}", + "↓fileprivate extension String {}", + "↓fileprivate \n extension String {}", + "↓fileprivate extension \n String {}", + "↓fileprivate extension String {}", + "extension String {\n↓fileprivate func Something(){}\n}", + "class MyClass {\n↓fileprivate let myInt = 4\n}", + "class MyClass {\n↓fileprivate(set) var myInt = 4\n}", + "struct Outter {\nstruct Inter {\n↓fileprivate struct Inner {}\n}\n}" + ] + let description = FilePrivateRule.description + .with(nonTriggeringExamples: nonTriggeringExamples) + .with(triggeringExamples: triggeringExamples) + + verifyRule(description, ruleConfiguration: ["strict": true]) + } +} diff --git a/Tests/SwiftLintFrameworkTests/RulesTests.swift b/Tests/SwiftLintFrameworkTests/RulesTests.swift index 0babf255e1..5d2d00012c 100644 --- a/Tests/SwiftLintFrameworkTests/RulesTests.swift +++ b/Tests/SwiftLintFrameworkTests/RulesTests.swift @@ -101,15 +101,6 @@ class RulesTests: XCTestCase { testMultiByteOffsets: false) } - func testFileprivateRule() { - verifyRule(FileprivateConfiguration.fileprivateLimited) - } - - func testFileprivateRuleWithConfig() { - verifyRule(FileprivateConfiguration.fileprivateDisallowed, - ruleConfiguration: ["strict": true]) - } - func testFirstWhere() { verifyRule(FirstWhereRule.description) } From a9870d4d4763c345b8b776cf3d2b6e5179d7cada Mon Sep 17 00:00:00 2001 From: Marcelo Fabri Date: Mon, 3 Jul 2017 23:39:02 +0200 Subject: [PATCH 4/5] Rename rule --- ...leprivateRule.swift => FilePrivateRule.swift} | 0 ...tion.swift => FilePrivateConfiguration.swift} | 0 SwiftLint.xcodeproj/project.pbxproj | 16 ++++++++-------- 3 files changed, 8 insertions(+), 8 deletions(-) rename Source/SwiftLintFramework/Rules/{FileprivateRule.swift => FilePrivateRule.swift} (100%) rename Source/SwiftLintFramework/Rules/RuleConfigurations/{FileprivateConfiguration.swift => FilePrivateConfiguration.swift} (100%) diff --git a/Source/SwiftLintFramework/Rules/FileprivateRule.swift b/Source/SwiftLintFramework/Rules/FilePrivateRule.swift similarity index 100% rename from Source/SwiftLintFramework/Rules/FileprivateRule.swift rename to Source/SwiftLintFramework/Rules/FilePrivateRule.swift diff --git a/Source/SwiftLintFramework/Rules/RuleConfigurations/FileprivateConfiguration.swift b/Source/SwiftLintFramework/Rules/RuleConfigurations/FilePrivateConfiguration.swift similarity index 100% rename from Source/SwiftLintFramework/Rules/RuleConfigurations/FileprivateConfiguration.swift rename to Source/SwiftLintFramework/Rules/RuleConfigurations/FilePrivateConfiguration.swift diff --git a/SwiftLint.xcodeproj/project.pbxproj b/SwiftLint.xcodeproj/project.pbxproj index 91fc527ea2..5905912135 100644 --- a/SwiftLint.xcodeproj/project.pbxproj +++ b/SwiftLint.xcodeproj/project.pbxproj @@ -17,8 +17,8 @@ 094385011D5D2894009168CF /* WeakDelegateRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 094384FF1D5D2382009168CF /* WeakDelegateRule.swift */; }; 094385041D5D4F7C009168CF /* PrivateOutletRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 094385021D5D4F78009168CF /* PrivateOutletRule.swift */; }; 1E18574B1EADBA51004F89F7 /* NoExtensionAccessModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E18574A1EADBA51004F89F7 /* NoExtensionAccessModifier.swift */; }; - 1E3C2D711EE36C6F00C8386D /* FileprivateRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E3C2D701EE36C6F00C8386D /* FileprivateRule.swift */; }; - 1E3C2D731EE36D3500C8386D /* FileprivateConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E3C2D721EE36D3500C8386D /* FileprivateConfiguration.swift */; }; + 1E3C2D711EE36C6F00C8386D /* FilePrivateRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E3C2D701EE36C6F00C8386D /* FilePrivateRule.swift */; }; + 1E3C2D731EE36D3500C8386D /* FilePrivateConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E3C2D721EE36D3500C8386D /* FilePrivateConfiguration.swift */; }; 1E82D5591D7775C7009553D7 /* ClosureSpacingRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E82D5581D7775C7009553D7 /* ClosureSpacingRule.swift */; }; 1EC163521D5992D900DD2928 /* VerticalWhitespaceRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EC163511D5992D900DD2928 /* VerticalWhitespaceRule.swift */; }; 1EF115921EB2AD5900E30140 /* ExplicitTopLevelACLRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EF115911EB2AD5900E30140 /* ExplicitTopLevelACLRule.swift */; }; @@ -307,8 +307,8 @@ 094384FF1D5D2382009168CF /* WeakDelegateRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WeakDelegateRule.swift; sourceTree = ""; }; 094385021D5D4F78009168CF /* PrivateOutletRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PrivateOutletRule.swift; sourceTree = ""; }; 1E18574A1EADBA51004F89F7 /* NoExtensionAccessModifier.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NoExtensionAccessModifier.swift; sourceTree = ""; }; - 1E3C2D701EE36C6F00C8386D /* FileprivateRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileprivateRule.swift; sourceTree = ""; }; - 1E3C2D721EE36D3500C8386D /* FileprivateConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileprivateConfiguration.swift; sourceTree = ""; }; + 1E3C2D701EE36C6F00C8386D /* FilePrivateRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FilePrivateRule.swift; sourceTree = ""; }; + 1E3C2D721EE36D3500C8386D /* FilePrivateConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FilePrivateConfiguration.swift; sourceTree = ""; }; 1E82D5581D7775C7009553D7 /* ClosureSpacingRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClosureSpacingRule.swift; sourceTree = ""; }; 1EC163511D5992D900DD2928 /* VerticalWhitespaceRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VerticalWhitespaceRule.swift; sourceTree = ""; }; 1EF115911EB2AD5900E30140 /* ExplicitTopLevelACLRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExplicitTopLevelACLRule.swift; sourceTree = ""; }; @@ -616,7 +616,7 @@ D43B04671E07228D004016AF /* ColonConfiguration.swift */, 67EB4DF81E4CC101004E9ACD /* CyclomaticComplexityConfiguration.swift */, D4C4A3511DEFBBB700E0E04C /* FileHeaderConfiguration.swift */, - 1E3C2D721EE36D3500C8386D /* FileprivateConfiguration.swift */, + 1E3C2D721EE36D3500C8386D /* FilePrivateConfiguration.swift */, 47ACC8971E7DC74E0088EEB2 /* ImplicitlyUnwrappedOptionalConfiguration.swift */, 3B034B6C1E0BE544005D49A9 /* LineLengthConfiguration.swift */, 3BCC04D01C4F56D3006073C3 /* NameConfiguration.swift */, @@ -910,7 +910,7 @@ C3DE5DAA1E7DF99B00761483 /* FatalErrorMessageRule.swift */, D4C4A34D1DEA877200E0E04C /* FileHeaderRule.swift */, E88DEA891B0992B300A66CB0 /* FileLengthRule.swift */, - 1E3C2D701EE36C6F00C8386D /* FileprivateRule.swift */, + 1E3C2D701EE36C6F00C8386D /* FilePrivateRule.swift */, D42D2B371E09CC0D00CD7A2E /* FirstWhereRule.swift */, E88DEA7F1B09903300A66CB0 /* ForceCastRule.swift */, E816194D1BFBFEAB00946723 /* ForceTryRule.swift */, @@ -1335,7 +1335,7 @@ D4B0226F1E0C75F9007E5297 /* VerticalParameterAlignmentRule.swift in Sources */, E8BDE3FF1EDF91B6002EC12F /* RuleList.swift in Sources */, D44254271DB9C15C00492EA4 /* SyntacticSugarRule.swift in Sources */, - 1E3C2D731EE36D3500C8386D /* FileprivateConfiguration.swift in Sources */, + 1E3C2D731EE36D3500C8386D /* FilePrivateConfiguration.swift in Sources */, 006204DC1E1E492F00FFFBE1 /* VerticalWhitespaceConfiguration.swift in Sources */, E88198441BEA93D200333A11 /* ColonRule.swift in Sources */, E809EDA11B8A71DF00399043 /* Configuration.swift in Sources */, @@ -1404,7 +1404,7 @@ 1EF115921EB2AD5900E30140 /* ExplicitTopLevelACLRule.swift in Sources */, D41E7E0B1DF9DABB0065259A /* RedundantStringEnumValueRule.swift in Sources */, E88DEA711B09847500A66CB0 /* ViolationSeverity.swift in Sources */, - 1E3C2D711EE36C6F00C8386D /* FileprivateRule.swift in Sources */, + 1E3C2D711EE36C6F00C8386D /* FilePrivateRule.swift in Sources */, B2902A0C1D66815600BFCCF7 /* PrivateUnitTestRule.swift in Sources */, D47A51101DB2DD4800A4CC21 /* AttributesRule.swift in Sources */, CE8178ED1EAC039D0063186E /* UnusedOptionalBindingConfiguration.swift in Sources */, From 2c221b0ff02692c0070ee7828ad3f4ee39067fee Mon Sep 17 00:00:00 2001 From: Marcelo Fabri Date: Tue, 4 Jul 2017 00:09:11 +0200 Subject: [PATCH 5/5] Split rules in two --- CHANGELOG.md | 9 +- Rules.md | 188 +++++++++++++++++- .../Models/MasterRuleList.swift | 3 +- .../Rules/FilePrivateRule.swift | 97 --------- ...ft => NoExtensionAccessModifierRule.swift} | 12 +- .../Rules/PrivateOverFilePrivateRule.swift | 98 +++++++++ .../FilePrivateConfiguration.swift | 40 ---- .../Rules/StrictFilePrivateRule.swift | 47 +++++ SwiftLint.xcodeproj/project.pbxproj | 28 ++- Tests/LinuxMain.swift | 10 +- .../FilePrivateRuleTests.swift | 43 ---- .../SwiftLintFrameworkTests/RulesTests.swift | 8 + 12 files changed, 363 insertions(+), 220 deletions(-) delete mode 100644 Source/SwiftLintFramework/Rules/FilePrivateRule.swift rename Source/SwiftLintFramework/Rules/{NoExtensionAccessModifier.swift => NoExtensionAccessModifierRule.swift} (85%) create mode 100644 Source/SwiftLintFramework/Rules/PrivateOverFilePrivateRule.swift delete mode 100644 Source/SwiftLintFramework/Rules/RuleConfigurations/FilePrivateConfiguration.swift create mode 100644 Source/SwiftLintFramework/Rules/StrictFilePrivateRule.swift delete mode 100644 Tests/SwiftLintFrameworkTests/FilePrivateRuleTests.swift diff --git a/CHANGELOG.md b/CHANGELOG.md index 8599040c48..09c8fe0c54 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,10 +15,11 @@ [Marcelo Fabri](https://github.com/marcelofabri) [#1078](https://github.com/realm/SwiftLint/issues/1078) -* Added `fileprivate` rule to check for top-level usages of `fileprivate` - and recommend `private` instead. This is inline with SE-0169's goal - "for `fileprivate` to be used rarely". There is a also an "strict" option - that will mark every `fileprivate` as a violation. +* Add `private_over_fileprivate` correctable rule to check for top-level usages + of `fileprivate` and recommend `private` instead. This is inline with + SE-0169's goal "for `fileprivate` to be used rarely". There is a also a new + `strict_fileprivate` opt-in rule that will mark every `fileprivate` + as a violation (specially useful with Swift 4). [Jose Cheyo Jimenez](https://github.com/masters3d) [Marcelo Fabri](https://github.com/marcelofabri) [#1469](https://github.com/realm/SwiftLint/issues/1469) diff --git a/Rules.md b/Rules.md index 417edee178..5c6138abb2 100644 --- a/Rules.md +++ b/Rules.md @@ -59,6 +59,7 @@ * [Operator Function Whitespace](#operator-function-whitespace) * [Overridden methods call super](#overridden-methods-call-super) * [Private Outlets](#private-outlets) +* [Private over fileprivate](#private-over-fileprivate) * [Private Unit Test](#private-unit-test) * [Prohibited calls to super](#prohibited-calls-to-super) * [Protocol Property Accessors Order](#protocol-property-accessors-order) @@ -71,6 +72,7 @@ * [Shorthand Operator](#shorthand-operator) * [Sorted Imports](#sorted-imports) * [Statement Position](#statement-position) +* [Strict fileprivate](#strict-fileprivate) * [Switch Case on Newline](#switch-case-on-newline) * [Syntactic Sugar](#syntactic-sugar) * [Todo](#todo) @@ -5875,24 +5877,24 @@ extension String {} Triggering Examples ```swift -private extension String {} +↓private extension String {} ``` ```swift -public +↓public extension String {} ``` ```swift -open extension String {} +↓open extension String {} ``` ```swift -internal extension String {} +↓internal extension String {} ``` ```swift -fileprivate extension String {} +↓fileprivate extension String {} ``` @@ -6852,6 +6854,99 @@ class Foo { +## Private over fileprivate + +Identifier | Enabled by default | Supports autocorrection | Kind +--- | --- | --- | --- +`private_over_fileprivate` | Enabled | Yes | idiomatic + +Prefer `private` over `fileprivate` declarations. + +### Examples + +
+Non Triggering Examples + +```swift +extension String {} +``` + +```swift +private extension String {} +``` + +```swift +public + enum MyEnum {} +``` + +```swift +open extension + String {} +``` + +```swift +internal extension String {} +``` + +```swift +extension String { +fileprivate func Something(){} +} +``` + +```swift +class MyClass { +fileprivate let myInt = 4 +} +``` + +```swift +class MyClass { +fileprivate(set) var myInt = 4 +} +``` + +```swift +struct Outter { +struct Inter { +fileprivate struct Inner {} +} +} +``` + +
+
+Triggering Examples + +```swift +↓fileprivate enum MyEnum {} +``` + +```swift +↓fileprivate extension String {} +``` + +```swift +↓fileprivate + extension String {} +``` + +```swift +↓fileprivate extension + String {} +``` + +```swift +↓fileprivate class MyClass { +fileprivate(set) var myInt = 4 +} +``` + +
+ + + ## Private Unit Test Identifier | Enabled by default | Supports autocorrection | Kind @@ -7889,6 +7984,89 @@ catch { +## Strict fileprivate + +Identifier | Enabled by default | Supports autocorrection | Kind +--- | --- | --- | --- +`strict_fileprivate` | Disabled | No | idiomatic + +`fileprivate` should be avoided. + +### Examples + +
+Non Triggering Examples + +```swift +extension String {} +``` + +```swift +private extension String {} +``` + +```swift +public + extension String {} +``` + +```swift +open extension + String {} +``` + +```swift +internal extension String {} +``` + +
+
+Triggering Examples + +```swift +↓fileprivate extension String {} +``` + +```swift +↓fileprivate + extension String {} +``` + +```swift +↓fileprivate extension + String {} +``` + +```swift +extension String { +↓fileprivate func Something(){} +} +``` + +```swift +class MyClass { +↓fileprivate let myInt = 4 +} +``` + +```swift +class MyClass { +↓fileprivate(set) var myInt = 4 +} +``` + +```swift +struct Outter { +struct Inter { +↓fileprivate struct Inner {} +} +} +``` + +
+ + + ## Switch Case on Newline Identifier | Enabled by default | Supports autocorrection | Kind diff --git a/Source/SwiftLintFramework/Models/MasterRuleList.swift b/Source/SwiftLintFramework/Models/MasterRuleList.swift index f9d7d324ea..6e258f0905 100644 --- a/Source/SwiftLintFramework/Models/MasterRuleList.swift +++ b/Source/SwiftLintFramework/Models/MasterRuleList.swift @@ -35,7 +35,6 @@ public let masterRuleList = RuleList(rules: [ FatalErrorMessageRule.self, FileHeaderRule.self, FileLengthRule.self, - FilePrivateRule.self, FirstWhereRule.self, ForWhereRule.self, ForceCastRule.self, @@ -68,6 +67,7 @@ public let masterRuleList = RuleList(rules: [ OperatorUsageWhitespaceRule.self, OverriddenSuperCallRule.self, PrivateOutletRule.self, + PrivateOverFilePrivateRule.self, PrivateUnitTestRule.self, ProhibitedSuperRule.self, ProtocolPropertyAccessorsOrderRule.self, @@ -80,6 +80,7 @@ public let masterRuleList = RuleList(rules: [ ShorthandOperatorRule.self, SortedImportsRule.self, StatementPositionRule.self, + StrictFilePrivateRule.self, SwitchCaseOnNewlineRule.self, SyntacticSugarRule.self, TodoRule.self, diff --git a/Source/SwiftLintFramework/Rules/FilePrivateRule.swift b/Source/SwiftLintFramework/Rules/FilePrivateRule.swift deleted file mode 100644 index f28db8767c..0000000000 --- a/Source/SwiftLintFramework/Rules/FilePrivateRule.swift +++ /dev/null @@ -1,97 +0,0 @@ -// -// FilePrivateRule.swift -// SwiftLint -// -// Created by Jose Cheyo Jimenez on 05/02/17. -// Copyright © 2017 Realm. All rights reserved. -// - -import Foundation -import SourceKittenFramework - -public struct FilePrivateRule: Rule, ConfigurationProviderRule { - public var configuration = FilePrivateConfiguration(strict: false) - - public init() {} - - public static let description = RuleDescription( - identifier: "fileprivate", - name: "FilePrivate", - description: "Prefer private over fileprivate declarations.", - kind: .idiomatic, - nonTriggeringExamples: [ - "extension String {}", - "private extension String {}", - "public \n enum MyEnum {}", - "open extension \n String {}", - "internal extension String {}", - "extension String {\nfileprivate func Something(){}\n}", - "class MyClass {\nfileprivate let myInt = 4\n}", - "class MyClass {\nfileprivate(set) var myInt = 4\n}", - "struct Outter {\nstruct Inter {\nfileprivate struct Inner {}\n}\n}" - ], - triggeringExamples: [ - "fileprivate ↓enum MyEnum {}", - "fileprivate ↓extension String {}", - "fileprivate \n ↓extension String {}", - "fileprivate ↓extension \n String {}", - "fileprivate ↓class MyClass {\nfileprivate(set) var myInt = 4\n}", - "fileprivate ↓extension String {}" - ] - ) - - public func validate(file: File) -> [StyleViolation] { - if configuration.strict { - return strictValidate(file: file) - } else { - return validateTopLevelDeclarations(file: file) - } - } - - private func validateTopLevelDeclarations(file: File) -> [StyleViolation] { - let offsets = file.structure.dictionary.substructure.flatMap { dictionary -> Int? in - guard let offset = dictionary.offset, - dictionary.accessibility.flatMap(AccessControlLevel.init(identifier:)) == .fileprivate else { - return nil - } - - return offset - } + validateTopLevelExtensions(file: file) - - return offsets.map { - StyleViolation(ruleDescription: type(of: self).description, - location: Location(file: file, byteOffset: $0)) - } - } - - private func validateTopLevelExtensions(file: File) -> [Int] { - let syntaxTokens = file.syntaxMap.tokens - let contents = file.contents.bridge() - - return file.structure.dictionary.substructure.flatMap { dictionary -> Int? in - guard dictionary.kind.flatMap(SwiftDeclarationKind.init) == .extension, - let offset = dictionary.offset else { - return nil - } - - let parts = syntaxTokens.prefix { offset > $0.offset } - guard let lastKind = parts.last, - SyntaxKind(rawValue: lastKind.type) == .attributeBuiltin, - let aclName = contents.substringWithByteRange(start:lastKind.offset, length: lastKind.length), - AccessControlLevel(description: aclName) == .fileprivate else { - return nil - } - - return offset - } - } - - private func strictValidate(file: File) -> [StyleViolation] { - // Mark all fileprivate occurences as a violation - return file.match(pattern: "fileprivate", with: [.attributeBuiltin]).map { - StyleViolation(ruleDescription: type(of: self).description, - location: Location(file: file, characterOffset: $0.location), - reason: "fileprivate should be avoided.") - } - } -} diff --git a/Source/SwiftLintFramework/Rules/NoExtensionAccessModifier.swift b/Source/SwiftLintFramework/Rules/NoExtensionAccessModifierRule.swift similarity index 85% rename from Source/SwiftLintFramework/Rules/NoExtensionAccessModifier.swift rename to Source/SwiftLintFramework/Rules/NoExtensionAccessModifierRule.swift index 71d339ccde..4097d29475 100644 --- a/Source/SwiftLintFramework/Rules/NoExtensionAccessModifier.swift +++ b/Source/SwiftLintFramework/Rules/NoExtensionAccessModifierRule.swift @@ -24,11 +24,11 @@ public struct NoExtensionAccessModifierRule: ASTRule, OptInRule, ConfigurationPr "\n\n extension String {}" ], triggeringExamples: [ - "private extension String {}", - "public \n extension String {}", - "open extension String {}", - "internal extension String {}", - "fileprivate extension String {}" + "↓private extension String {}", + "↓public \n extension String {}", + "↓open extension String {}", + "↓internal extension String {}", + "↓fileprivate extension String {}" ] ) @@ -47,7 +47,7 @@ public struct NoExtensionAccessModifierRule: ASTRule, OptInRule, ConfigurationPr return [ StyleViolation(ruleDescription: type(of: self).description, severity: configuration.severity, - location: Location(file: file, byteOffset: offset)) + location: Location(file: file, byteOffset: aclToken.offset)) ] } } diff --git a/Source/SwiftLintFramework/Rules/PrivateOverFilePrivateRule.swift b/Source/SwiftLintFramework/Rules/PrivateOverFilePrivateRule.swift new file mode 100644 index 0000000000..2ee51713cd --- /dev/null +++ b/Source/SwiftLintFramework/Rules/PrivateOverFilePrivateRule.swift @@ -0,0 +1,98 @@ +// +// PrivateOverFilePrivateRule.swift +// SwiftLint +// +// Created by Jose Cheyo Jimenez on 05/02/17. +// Copyright © 2017 Realm. All rights reserved. +// + +import Foundation +import SourceKittenFramework + +public struct PrivateOverFilePrivateRule: Rule, ConfigurationProviderRule, CorrectableRule { + public var configuration = SeverityConfiguration(.warning) + + public init() {} + + public static let description = RuleDescription( + identifier: "private_over_fileprivate", + name: "Private over fileprivate", + description: "Prefer `private` over `fileprivate` declarations.", + kind: .idiomatic, + nonTriggeringExamples: [ + "extension String {}", + "private extension String {}", + "public \n enum MyEnum {}", + "open extension \n String {}", + "internal extension String {}", + "extension String {\nfileprivate func Something(){}\n}", + "class MyClass {\nfileprivate let myInt = 4\n}", + "class MyClass {\nfileprivate(set) var myInt = 4\n}", + "struct Outter {\nstruct Inter {\nfileprivate struct Inner {}\n}\n}" + ], + triggeringExamples: [ + "↓fileprivate enum MyEnum {}", + "↓fileprivate extension String {}", + "↓fileprivate \n extension String {}", + "↓fileprivate extension \n String {}", + "↓fileprivate class MyClass {\nfileprivate(set) var myInt = 4\n}" + ], + corrections: [ + "↓fileprivate enum MyEnum {}": "private enum MyEnum {}", + "↓fileprivate extension String {}": "private extension String {}", + "↓fileprivate \n extension String {}": "private \n extension String {}", + "↓fileprivate extension \n String {}": "private extension \n String {}", + "↓fileprivate class MyClass {\nfileprivate(set) var myInt = 4\n}": + "private class MyClass {\nfileprivate(set) var myInt = 4\n}" + ] + ) + + public func validate(file: File) -> [StyleViolation] { + return violationRanges(in: file).map { + StyleViolation(ruleDescription: type(of: self).description, + location: Location(file: file, characterOffset: $0.location)) + } + } + + private func violationRanges(in file: File) -> [NSRange] { + let syntaxTokens = file.syntaxMap.tokens + let contents = file.contents.bridge() + + return file.structure.dictionary.substructure.flatMap { dictionary -> NSRange? in + guard let offset = dictionary.offset else { + return nil + } + + let parts = syntaxTokens.prefix { offset > $0.offset } + guard let lastKind = parts.last, + SyntaxKind(rawValue: lastKind.type) == .attributeBuiltin, + let aclName = contents.substringWithByteRange(start:lastKind.offset, length: lastKind.length), + AccessControlLevel(description: aclName) == .fileprivate, + let range = contents.byteRangeToNSRange(start: lastKind.offset, length: lastKind.length) else { + return nil + } + + return range + } + } + + public func correct(file: File) -> [Correction] { + let violatingRanges = file.ruleEnabled(violatingRanges: violationRanges(in: file), for: self) + var correctedContents = file.contents + var adjustedLocations = [Int]() + + for violatingRange in violatingRanges.reversed() { + if let indexRange = correctedContents.nsrangeToIndexRange(violatingRange) { + correctedContents = correctedContents.replacingCharacters(in: indexRange, with: "private") + adjustedLocations.insert(violatingRange.location, at: 0) + } + } + + file.write(correctedContents) + + return adjustedLocations.map { + Correction(ruleDescription: type(of: self).description, + location: Location(file: file, characterOffset: $0)) + } + } +} diff --git a/Source/SwiftLintFramework/Rules/RuleConfigurations/FilePrivateConfiguration.swift b/Source/SwiftLintFramework/Rules/RuleConfigurations/FilePrivateConfiguration.swift deleted file mode 100644 index 3c7a96eb5b..0000000000 --- a/Source/SwiftLintFramework/Rules/RuleConfigurations/FilePrivateConfiguration.swift +++ /dev/null @@ -1,40 +0,0 @@ -// -// FilePrivateConfiguration.swift -// SwiftLint -// -// Created by Jose Cheyo Jimenez on 05/02/17. -// Copyright © 2017 Realm. All rights reserved. -// - -public struct FilePrivateConfiguration: RuleConfiguration, Equatable { - private(set) var severityConfiguration = SeverityConfiguration(.warning) - private(set) var strict: Bool - - public var consoleDescription: String { - return severityConfiguration.consoleDescription + ", strict: \(strict)" - } - - public init(strict: Bool) { - self.strict = strict - } - - public mutating func apply(configuration: Any) throws { - guard let configuration = configuration as? [String: Any] else { - throw ConfigurationError.unknownConfiguration - } - - if let strict = configuration["strict"] as? Bool { - self.strict = strict - } - - if let severityString = configuration["severity"] as? String { - try severityConfiguration.apply(configuration: severityString) - } - } - - public static func == (lhs: FilePrivateConfiguration, - rhs: FilePrivateConfiguration) -> Bool { - return lhs.strict == rhs.strict && - lhs.severityConfiguration == rhs.severityConfiguration - } -} diff --git a/Source/SwiftLintFramework/Rules/StrictFilePrivateRule.swift b/Source/SwiftLintFramework/Rules/StrictFilePrivateRule.swift new file mode 100644 index 0000000000..a8266daef5 --- /dev/null +++ b/Source/SwiftLintFramework/Rules/StrictFilePrivateRule.swift @@ -0,0 +1,47 @@ +// +// StrictFilePrivateRule.swift +// SwiftLint +// +// Created by Jose Cheyo Jimenez on 05/02/17. +// Copyright © 2017 Realm. All rights reserved. +// + +import Foundation +import SourceKittenFramework + +public struct StrictFilePrivateRule: OptInRule, ConfigurationProviderRule { + public var configuration = SeverityConfiguration(.warning) + + public init() {} + + public static let description = RuleDescription( + identifier: "strict_fileprivate", + name: "Strict fileprivate", + description: "`fileprivate` should be avoided.", + kind: .idiomatic, + nonTriggeringExamples: [ + "extension String {}", + "private extension String {}", + "public \n extension String {}", + "open extension \n String {}", + "internal extension String {}" + ], + triggeringExamples: [ + "↓fileprivate extension String {}", + "↓fileprivate \n extension String {}", + "↓fileprivate extension \n String {}", + "extension String {\n↓fileprivate func Something(){}\n}", + "class MyClass {\n↓fileprivate let myInt = 4\n}", + "class MyClass {\n↓fileprivate(set) var myInt = 4\n}", + "struct Outter {\nstruct Inter {\n↓fileprivate struct Inner {}\n}\n}" + ] + ) + + public func validate(file: File) -> [StyleViolation] { + // Mark all fileprivate occurences as a violation + return file.match(pattern: "fileprivate", with: [.attributeBuiltin]).map { + StyleViolation(ruleDescription: type(of: self).description, + location: Location(file: file, characterOffset: $0.location)) + } + } +} diff --git a/SwiftLint.xcodeproj/project.pbxproj b/SwiftLint.xcodeproj/project.pbxproj index 5905912135..e2b10998a4 100644 --- a/SwiftLint.xcodeproj/project.pbxproj +++ b/SwiftLint.xcodeproj/project.pbxproj @@ -16,9 +16,8 @@ 02FD8AEF1BFC18D60014BFFB /* ExtendedNSStringTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02FD8AEE1BFC18D60014BFFB /* ExtendedNSStringTests.swift */; }; 094385011D5D2894009168CF /* WeakDelegateRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 094384FF1D5D2382009168CF /* WeakDelegateRule.swift */; }; 094385041D5D4F7C009168CF /* PrivateOutletRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 094385021D5D4F78009168CF /* PrivateOutletRule.swift */; }; - 1E18574B1EADBA51004F89F7 /* NoExtensionAccessModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E18574A1EADBA51004F89F7 /* NoExtensionAccessModifier.swift */; }; - 1E3C2D711EE36C6F00C8386D /* FilePrivateRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E3C2D701EE36C6F00C8386D /* FilePrivateRule.swift */; }; - 1E3C2D731EE36D3500C8386D /* FilePrivateConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E3C2D721EE36D3500C8386D /* FilePrivateConfiguration.swift */; }; + 1E18574B1EADBA51004F89F7 /* NoExtensionAccessModifierRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E18574A1EADBA51004F89F7 /* NoExtensionAccessModifierRule.swift */; }; + 1E3C2D711EE36C6F00C8386D /* PrivateOverFilePrivateRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E3C2D701EE36C6F00C8386D /* PrivateOverFilePrivateRule.swift */; }; 1E82D5591D7775C7009553D7 /* ClosureSpacingRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E82D5581D7775C7009553D7 /* ClosureSpacingRule.swift */; }; 1EC163521D5992D900DD2928 /* VerticalWhitespaceRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EC163511D5992D900DD2928 /* VerticalWhitespaceRule.swift */; }; 1EF115921EB2AD5900E30140 /* ExplicitTopLevelACLRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EF115911EB2AD5900E30140 /* ExplicitTopLevelACLRule.swift */; }; @@ -119,7 +118,7 @@ D4130D991E16CC1300242361 /* TypeNameRuleExamples.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4130D981E16CC1300242361 /* TypeNameRuleExamples.swift */; }; D41B57781ED8CEE0007B0470 /* ExtensionAccessModifierRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D41B57771ED8CEE0007B0470 /* ExtensionAccessModifierRule.swift */; }; D41E7E0B1DF9DABB0065259A /* RedundantStringEnumValueRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D41E7E0A1DF9DABB0065259A /* RedundantStringEnumValueRule.swift */; }; - D42B45D71F0AEF980086B683 /* FilePrivateRuleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D42B45D61F0AEF980086B683 /* FilePrivateRuleTests.swift */; }; + D42B45D91F0AF5E30086B683 /* StrictFilePrivateRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D42B45D81F0AF5E30086B683 /* StrictFilePrivateRule.swift */; }; D42D2B381E09CC0D00CD7A2E /* FirstWhereRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D42D2B371E09CC0D00CD7A2E /* FirstWhereRule.swift */; }; D4348EEA1C46122C007707FB /* FunctionBodyLengthRuleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4348EE91C46122C007707FB /* FunctionBodyLengthRuleTests.swift */; }; D43B04641E0620AB004016AF /* UnusedEnumeratedRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D43B04631E0620AB004016AF /* UnusedEnumeratedRule.swift */; }; @@ -306,9 +305,8 @@ 02FD8AEE1BFC18D60014BFFB /* ExtendedNSStringTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExtendedNSStringTests.swift; sourceTree = ""; }; 094384FF1D5D2382009168CF /* WeakDelegateRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WeakDelegateRule.swift; sourceTree = ""; }; 094385021D5D4F78009168CF /* PrivateOutletRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PrivateOutletRule.swift; sourceTree = ""; }; - 1E18574A1EADBA51004F89F7 /* NoExtensionAccessModifier.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NoExtensionAccessModifier.swift; sourceTree = ""; }; - 1E3C2D701EE36C6F00C8386D /* FilePrivateRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FilePrivateRule.swift; sourceTree = ""; }; - 1E3C2D721EE36D3500C8386D /* FilePrivateConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FilePrivateConfiguration.swift; sourceTree = ""; }; + 1E18574A1EADBA51004F89F7 /* NoExtensionAccessModifierRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NoExtensionAccessModifierRule.swift; sourceTree = ""; }; + 1E3C2D701EE36C6F00C8386D /* PrivateOverFilePrivateRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PrivateOverFilePrivateRule.swift; sourceTree = ""; }; 1E82D5581D7775C7009553D7 /* ClosureSpacingRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClosureSpacingRule.swift; sourceTree = ""; }; 1EC163511D5992D900DD2928 /* VerticalWhitespaceRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VerticalWhitespaceRule.swift; sourceTree = ""; }; 1EF115911EB2AD5900E30140 /* ExplicitTopLevelACLRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExplicitTopLevelACLRule.swift; sourceTree = ""; }; @@ -427,7 +425,7 @@ D4130D981E16CC1300242361 /* TypeNameRuleExamples.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TypeNameRuleExamples.swift; sourceTree = ""; }; D41B57771ED8CEE0007B0470 /* ExtensionAccessModifierRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExtensionAccessModifierRule.swift; sourceTree = ""; }; D41E7E0A1DF9DABB0065259A /* RedundantStringEnumValueRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantStringEnumValueRule.swift; sourceTree = ""; }; - D42B45D61F0AEF980086B683 /* FilePrivateRuleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FilePrivateRuleTests.swift; sourceTree = ""; }; + D42B45D81F0AF5E30086B683 /* StrictFilePrivateRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StrictFilePrivateRule.swift; sourceTree = ""; }; D42D2B371E09CC0D00CD7A2E /* FirstWhereRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FirstWhereRule.swift; sourceTree = ""; }; D4348EE91C46122C007707FB /* FunctionBodyLengthRuleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FunctionBodyLengthRuleTests.swift; sourceTree = ""; }; D43B04631E0620AB004016AF /* UnusedEnumeratedRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnusedEnumeratedRule.swift; sourceTree = ""; }; @@ -616,7 +614,6 @@ D43B04671E07228D004016AF /* ColonConfiguration.swift */, 67EB4DF81E4CC101004E9ACD /* CyclomaticComplexityConfiguration.swift */, D4C4A3511DEFBBB700E0E04C /* FileHeaderConfiguration.swift */, - 1E3C2D721EE36D3500C8386D /* FilePrivateConfiguration.swift */, 47ACC8971E7DC74E0088EEB2 /* ImplicitlyUnwrappedOptionalConfiguration.swift */, 3B034B6C1E0BE544005D49A9 /* LineLengthConfiguration.swift */, 3BCC04D01C4F56D3006073C3 /* NameConfiguration.swift */, @@ -805,7 +802,6 @@ 67EB4DFB1E4CD7F5004E9ACD /* CyclomaticComplexityRuleTests.swift */, 02FD8AEE1BFC18D60014BFFB /* ExtendedNSStringTests.swift */, D4998DE81DF194F20006E05D /* FileHeaderRuleTests.swift */, - D42B45D61F0AEF980086B683 /* FilePrivateRuleTests.swift */, D4348EE91C46122C007707FB /* FunctionBodyLengthRuleTests.swift */, 3B20CD091EB699380069EF2E /* GenericTypeNameRuleTests.swift */, 3B3A9A321EA3DFD90075B121 /* IdentifierNameRuleTests.swift */, @@ -910,7 +906,6 @@ C3DE5DAA1E7DF99B00761483 /* FatalErrorMessageRule.swift */, D4C4A34D1DEA877200E0E04C /* FileHeaderRule.swift */, E88DEA891B0992B300A66CB0 /* FileLengthRule.swift */, - 1E3C2D701EE36C6F00C8386D /* FilePrivateRule.swift */, D42D2B371E09CC0D00CD7A2E /* FirstWhereRule.swift */, E88DEA7F1B09903300A66CB0 /* ForceCastRule.swift */, E816194D1BFBFEAB00946723 /* ForceTryRule.swift */, @@ -937,7 +932,7 @@ 621061BE1ED57E640082D51E /* MultilineParametersRuleExamples.swift */, E88DEA951B099CF200A66CB0 /* NestingRule.swift */, D4DAE8BB1DE14E8F00B0AE7A /* NimbleOperatorRule.swift */, - 1E18574A1EADBA51004F89F7 /* NoExtensionAccessModifier.swift */, + 1E18574A1EADBA51004F89F7 /* NoExtensionAccessModifierRule.swift */, D4DABFD61E2C23B1009617B6 /* NotificationCenterDetachmentRule.swift */, D4DABFD81E2C59BC009617B6 /* NotificationCenterDetachmentRuleExamples.swift */, D46252531DF63FB200BE2CA1 /* NumberSeparatorRule.swift */, @@ -947,6 +942,7 @@ E5A167C81B25A0B000CF2D03 /* OperatorFunctionWhitespaceRule.swift */, D4FBADCF1E00DA0400669C73 /* OperatorUsageWhitespaceRule.swift */, 78F032441D7C877800BE709A /* OverriddenSuperCallRule.swift */, + 1E3C2D701EE36C6F00C8386D /* PrivateOverFilePrivateRule.swift */, 094385021D5D4F78009168CF /* PrivateOutletRule.swift */, B2902A0B1D66815600BFCCF7 /* PrivateUnitTestRule.swift */, 009E09271DFEE4C200B588A7 /* ProhibitedSuperRule.swift */, @@ -960,6 +956,7 @@ D4D5A5FE1E1F3A1C00D15E0C /* ShorthandOperatorRule.swift */, D286EC001E02DA190003CF72 /* SortedImportsRule.swift */, 692B60AB1BD8F2E700C7AA22 /* StatementPositionRule.swift */, + D42B45D81F0AF5E30086B683 /* StrictFilePrivateRule.swift */, D47A510D1DB29EEB00A4CC21 /* SwitchCaseOnNewlineRule.swift */, D44254251DB9C12300492EA4 /* SyntacticSugarRule.swift */, E88DEA811B0990A700A66CB0 /* TodoRule.swift */, @@ -1330,12 +1327,11 @@ E847F0A91BFBBABD00EA9363 /* EmptyCountRule.swift in Sources */, D46252541DF63FB200BE2CA1 /* NumberSeparatorRule.swift in Sources */, E315B83C1DFA4BC500621B44 /* DynamicInlineRule.swift in Sources */, - 1E18574B1EADBA51004F89F7 /* NoExtensionAccessModifier.swift in Sources */, + 1E18574B1EADBA51004F89F7 /* NoExtensionAccessModifierRule.swift in Sources */, D42D2B381E09CC0D00CD7A2E /* FirstWhereRule.swift in Sources */, D4B0226F1E0C75F9007E5297 /* VerticalParameterAlignmentRule.swift in Sources */, E8BDE3FF1EDF91B6002EC12F /* RuleList.swift in Sources */, D44254271DB9C15C00492EA4 /* SyntacticSugarRule.swift in Sources */, - 1E3C2D731EE36D3500C8386D /* FilePrivateConfiguration.swift in Sources */, 006204DC1E1E492F00FFFBE1 /* VerticalWhitespaceConfiguration.swift in Sources */, E88198441BEA93D200333A11 /* ColonRule.swift in Sources */, E809EDA11B8A71DF00399043 /* Configuration.swift in Sources */, @@ -1345,6 +1341,7 @@ 006ECFC41C44E99E00EF6364 /* LegacyConstantRule.swift in Sources */, E88DEA731B0984C400A66CB0 /* String+SwiftLint.swift in Sources */, E88198591BEA95F100333A11 /* LeadingWhitespaceRule.swift in Sources */, + D42B45D91F0AF5E30086B683 /* StrictFilePrivateRule.swift in Sources */, 1EC163521D5992D900DD2928 /* VerticalWhitespaceRule.swift in Sources */, 67EB4DFA1E4CC111004E9ACD /* CyclomaticComplexityConfiguration.swift in Sources */, 57ED827B1CF656E3002B3513 /* JUnitReporter.swift in Sources */, @@ -1404,7 +1401,7 @@ 1EF115921EB2AD5900E30140 /* ExplicitTopLevelACLRule.swift in Sources */, D41E7E0B1DF9DABB0065259A /* RedundantStringEnumValueRule.swift in Sources */, E88DEA711B09847500A66CB0 /* ViolationSeverity.swift in Sources */, - 1E3C2D711EE36C6F00C8386D /* FilePrivateRule.swift in Sources */, + 1E3C2D711EE36C6F00C8386D /* PrivateOverFilePrivateRule.swift in Sources */, B2902A0C1D66815600BFCCF7 /* PrivateUnitTestRule.swift in Sources */, D47A51101DB2DD4800A4CC21 /* AttributesRule.swift in Sources */, CE8178ED1EAC039D0063186E /* UnusedOptionalBindingConfiguration.swift in Sources */, @@ -1461,7 +1458,6 @@ E81ADD741ED6052F000CD451 /* CommandTests.swift in Sources */, 006204DE1E1E4E0A00FFFBE1 /* VerticalWhitespaceRuleTests.swift in Sources */, 02FD8AEF1BFC18D60014BFFB /* ExtendedNSStringTests.swift in Sources */, - D42B45D71F0AEF980086B683 /* FilePrivateRuleTests.swift in Sources */, D4CA758F1E2DEEA500A40E8A /* NumberSeparatorRuleTests.swift in Sources */, D4DB92251E628898005DE9C1 /* TodoRuleTests.swift in Sources */, D4348EEA1C46122C007707FB /* FunctionBodyLengthRuleTests.swift in Sources */, diff --git a/Tests/LinuxMain.swift b/Tests/LinuxMain.swift index bdcbac4941..e0ce59c3fe 100644 --- a/Tests/LinuxMain.swift +++ b/Tests/LinuxMain.swift @@ -126,13 +126,6 @@ extension FileHeaderRuleTests { ] } -extension FilePrivateRuleTests { - static var allTests: [(String, (FilePrivateRuleTests) -> () throws -> Void)] = [ - ("testFilePrivateWithDefaultConfiguration", testFilePrivateWithDefaultConfiguration), - ("testFilePrivateWithStrictConfiguration", testFilePrivateWithStrictConfiguration) - ] -} - extension FunctionBodyLengthRuleTests { static var allTests: [(String, (FunctionBodyLengthRuleTests) -> () throws -> Void)] = [ ("testFunctionBodyLengths", testFunctionBodyLengths), @@ -355,6 +348,7 @@ extension RulesTests { ("testOpeningBrace", testOpeningBrace), ("testOperatorFunctionWhitespace", testOperatorFunctionWhitespace), ("testOperatorUsageWhitespace", testOperatorUsageWhitespace), + ("testPrivateOverFilePrivate", testPrivateOverFilePrivate), ("testPrivateOutlet", testPrivateOutlet), ("testPrivateUnitTest", testPrivateUnitTest), ("testProhibitedSuper", testProhibitedSuper), @@ -369,6 +363,7 @@ extension RulesTests { ("testSortedImports", testSortedImports), ("testStatementPosition", testStatementPosition), ("testStatementPositionUncuddled", testStatementPositionUncuddled), + ("testStrictFilePrivate", testStrictFilePrivate), ("testSwitchCaseOnNewline", testSwitchCaseOnNewline), ("testSyntacticSugar", testSyntacticSugar), ("testTrailingNewline", testTrailingNewline), @@ -455,7 +450,6 @@ XCTMain([ testCase(CyclomaticComplexityRuleTests.allTests), testCase(ExtendedNSStringTests.allTests), testCase(FileHeaderRuleTests.allTests), - testCase(FilePrivateRuleTests.allTests), testCase(FunctionBodyLengthRuleTests.allTests), testCase(GenericTypeNameRuleTests.allTests), testCase(IdentifierNameRuleTests.allTests), diff --git a/Tests/SwiftLintFrameworkTests/FilePrivateRuleTests.swift b/Tests/SwiftLintFrameworkTests/FilePrivateRuleTests.swift deleted file mode 100644 index 5ddcdd3f41..0000000000 --- a/Tests/SwiftLintFrameworkTests/FilePrivateRuleTests.swift +++ /dev/null @@ -1,43 +0,0 @@ -// -// FilePrivateRuleTests.swift -// SwiftLint -// -// Created by Marcelo Fabri on 03/07/17. -// Copyright © 2017 Realm. All rights reserved. -// - -import SwiftLintFramework -import XCTest - -class FilePrivateRuleTests: XCTestCase { - - func testFilePrivateWithDefaultConfiguration() { - verifyRule(FilePrivateRule.description) - } - - func testFilePrivateWithStrictConfiguration() { - let nonTriggeringExamples = [ - "extension String {}", - "private extension String {}", - "public \n extension String {}", - "open extension \n String {}", - "internal extension String {}" - ] - let triggeringExamples = [ - "↓fileprivate extension String {}", - "↓fileprivate extension String {}", - "↓fileprivate \n extension String {}", - "↓fileprivate extension \n String {}", - "↓fileprivate extension String {}", - "extension String {\n↓fileprivate func Something(){}\n}", - "class MyClass {\n↓fileprivate let myInt = 4\n}", - "class MyClass {\n↓fileprivate(set) var myInt = 4\n}", - "struct Outter {\nstruct Inter {\n↓fileprivate struct Inner {}\n}\n}" - ] - let description = FilePrivateRule.description - .with(nonTriggeringExamples: nonTriggeringExamples) - .with(triggeringExamples: triggeringExamples) - - verifyRule(description, ruleConfiguration: ["strict": true]) - } -} diff --git a/Tests/SwiftLintFrameworkTests/RulesTests.swift b/Tests/SwiftLintFrameworkTests/RulesTests.swift index 5d2d00012c..63eb56771f 100644 --- a/Tests/SwiftLintFrameworkTests/RulesTests.swift +++ b/Tests/SwiftLintFrameworkTests/RulesTests.swift @@ -201,6 +201,10 @@ class RulesTests: XCTestCase { verifyRule(OperatorUsageWhitespaceRule.description) } + func testPrivateOverFilePrivate() { + verifyRule(PrivateOverFilePrivateRule.description) + } + func testPrivateOutlet() { verifyRule(PrivateOutletRule.description) @@ -269,6 +273,10 @@ class RulesTests: XCTestCase { verifyRule(StatementPositionRule.uncuddledDescription, ruleConfiguration: configuration) } + func testStrictFilePrivate() { + verifyRule(StrictFilePrivateRule.description) + } + func testSwitchCaseOnNewline() { verifyRule(SwitchCaseOnNewlineRule.description) }