added fileprivateRule

This commit is contained in:
J Cheyo Jimenez
2017-06-03 18:16:56 -07:00
committed by Marcelo Fabri
parent 85cdcbeb5b
commit e72d09bc31
10 changed files with 173 additions and 5 deletions
+5
View File
@@ -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.
@@ -35,6 +35,7 @@ public let masterRuleList = RuleList(rules: [
FatalErrorMessageRule.self,
FileHeaderRule.self,
FileLengthRule.self,
FileprivateRule.self,
FirstWhereRule.self,
ForWhereRule.self,
ForceCastRule.self,
@@ -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))
})
}
}
}
@@ -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)
@@ -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
}
}
+8
View File
@@ -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 = "<group>"; };
094385021D5D4F78009168CF /* PrivateOutletRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PrivateOutletRule.swift; sourceTree = "<group>"; };
1E18574A1EADBA51004F89F7 /* NoExtensionAccessModifier.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NoExtensionAccessModifier.swift; sourceTree = "<group>"; };
1E3C2D701EE36C6F00C8386D /* FileprivateRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileprivateRule.swift; sourceTree = "<group>"; };
1E3C2D721EE36D3500C8386D /* FileprivateConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileprivateConfiguration.swift; sourceTree = "<group>"; };
1E82D5581D7775C7009553D7 /* ClosureSpacingRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClosureSpacingRule.swift; sourceTree = "<group>"; };
1EC163511D5992D900DD2928 /* VerticalWhitespaceRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VerticalWhitespaceRule.swift; sourceTree = "<group>"; };
1EF115911EB2AD5900E30140 /* ExplicitTopLevelACLRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExplicitTopLevelACLRule.swift; sourceTree = "<group>"; };
@@ -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 */,
+2
View File
@@ -323,6 +323,8 @@ extension RulesTests {
("testExtensionAccessModifier", testExtensionAccessModifier),
("testFatalErrorMessage", testFatalErrorMessage),
("testFileLength", testFileLength),
("testFileprivateRule", testFileprivateRule),
("testFileprivateRuleWithConfig", testFileprivateRuleWithConfig),
("testFirstWhere", testFirstWhere),
("testForceCast", testForceCast),
("testForceTry", testForceTry),
@@ -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")
@@ -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 []
}
@@ -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)
}