Skip to content

Commit

Permalink
Merge pull request #1966 from joseprl89/feature/explicit_acl_rule
Browse files Browse the repository at this point in the history
Add explicit acl rule to satisfy Issue #1649
  • Loading branch information
jpsim authored Dec 22, 2017
2 parents b7b9fa0 + f9d7cca commit 1541f51
Show file tree
Hide file tree
Showing 6 changed files with 267 additions and 0 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,10 @@
[JP Simard](https://github.com/jpsim)
[#1822](https://github.com/realm/SwiftLint/issues/1822)

* Added `explicit_acl` which enforces explicit access control levels.
[Josep Rodriguez](https://github.com/joseprl89)
[#1822](https://github.com/realm/SwiftLint/issues/1649)

##### Bug Fixes

* Extend `first_where` and `contains_over_first_not_nil` rules to also detect
Expand Down
100 changes: 100 additions & 0 deletions Rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
* [Empty Enum Arguments](#empty-enum-arguments)
* [Empty Parameters](#empty-parameters)
* [Empty Parentheses with Trailing Closure](#empty-parentheses-with-trailing-closure)
* [Explicit ACL](#explicit-acl)
* [Explicit Enum Raw Value](#explicit-enum-raw-value)
* [Explicit Init](#explicit-init)
* [Explicit Top Level ACL](#explicit-top-level-acl)
Expand Down Expand Up @@ -2436,6 +2437,105 @@ UIView.animateWithDuration(0.3, animations: {



## Explicit ACL

Identifier | Enabled by default | Supports autocorrection | Kind
--- | --- | --- | ---
`explicit_acl` | Disabled | No | idiomatic

All declarations should specify Access Control Level keywords explicitly.

### Examples

<details>
<summary>Non Triggering Examples</summary>

```swift
internal enum A {}

```

```swift
public final class B {}

```

```swift
private struct C {}

```

```swift
internal enum A {
internal enum B {}
}
```

```swift
internal final class Foo {}
```

```swift
internal
class Foo { private let bar = 5 }
```

```swift
internal func a() { let a = }

```

```swift
private func a() { func innerFunction() { } }
```

```swift
private enum Foo { enum Bar { } }
```

```swift
private struct C { let d = 5 }
```

</details>
<details>
<summary>Triggering Examples</summary>

```swift
enum A {}

```

```swift
final class B {}

```

```swift
internal struct C { let d = 5 }

```

```swift
public struct C { let d = 5 }

```

```swift
func a() {}

```

```swift
internal let a = 0
func b() {}

```

</details>



## Explicit Enum Raw Value

Identifier | Enabled by default | Supports autocorrection | Kind
Expand Down
1 change: 1 addition & 0 deletions Source/SwiftLintFramework/Models/MasterRuleList.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public let masterRuleList = RuleList(rules: [
EmptyEnumArgumentsRule.self,
EmptyParametersRule.self,
EmptyParenthesesWithTrailingClosureRule.self,
ExplicitACLRule.self,
ExplicitEnumRawValueRule.self,
ExplicitInitRule.self,
ExplicitTopLevelACLRule.self,
Expand Down
154 changes: 154 additions & 0 deletions Source/SwiftLintFramework/Rules/ExplicitACLRule.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
//
// ExplicitACLRule.swift
// SwiftLint
//
// Created by Josep Rodriguez on 02/12/17.
// Copyright © 2017 Realm. All rights reserved.
//

import Foundation
import SourceKittenFramework

private typealias SourceKittenElement = [String: SourceKitRepresentable]

public struct ExplicitACLRule: OptInRule, ConfigurationProviderRule {

public var configuration = SeverityConfiguration(.warning)

public init() {}

public static let description = RuleDescription(
identifier: "explicit_acl",
name: "Explicit ACL",
description: "All declarations should specify Access Control Level keywords explicitly.",
kind: .idiomatic,
nonTriggeringExamples: [
"internal enum A {}\n",
"public final class B {}\n",
"private struct C {}\n",
"internal enum A {\n internal enum B {}\n}",
"internal final class Foo {}",
"internal\nclass Foo { private let bar = 5 }",
"internal func a() { let a = }\n",
"private func a() { func innerFunction() { } }",
"private enum Foo { enum Bar { } }",
"private struct C { let d = 5 }"
],
triggeringExamples: [
"enum A {}\n",
"final class B {}\n",
"internal struct C { let d = 5 }\n",
"public struct C { let d = 5 }\n",
"func a() {}\n",
"internal let a = 0\nfunc b() {}\n"
]
)

private func findAllExplicitInternalTokens(in file: File) -> [NSRange] {
let contents = file.contents.bridge()
return file.match(pattern: "internal", with: [.attributeBuiltin]).flatMap {
contents.NSRangeToByteRange(start: $0.location, length: $0.length)
}
}

private func offsetOfElements(from elements: [SourceKittenElement], in file: File,
thatAreNotInRanges ranges: [NSRange]) -> [Int] {
return elements.flatMap { element in
guard let typeOffset = element.offset else {
return nil
}

// find the last "internal" token before the type
guard let previousInternalByteRange = lastInternalByteRange(before: typeOffset,
in: ranges) else {
return typeOffset
}

// the "internal" token correspond to the type if there're only
// attributeBuiltin (`final` for example) tokens between them
let length = typeOffset - previousInternalByteRange.location
let range = NSRange(location: previousInternalByteRange.location, length: length)
let internalDoesntBelongToType = Set(file.syntaxMap.kinds(inByteRange: range)) != [.attributeBuiltin]

return internalDoesntBelongToType ? typeOffset : nil
}
}

public func validate(file: File) -> [StyleViolation] {
let implicitAndExplicitInternalElements = internalTypeElements(in: file.structure.dictionary)

guard !implicitAndExplicitInternalElements.isEmpty else {
return []
}

let explicitInternalRanges = findAllExplicitInternalTokens(in: file)

let violations = offsetOfElements(from: implicitAndExplicitInternalElements, in: file,
thatAreNotInRanges: explicitInternalRanges)

return violations.map {
StyleViolation(ruleDescription: type(of: self).description,
severity: configuration.severity,
location: Location(file: file, byteOffset: $0))
}
}

private func lastInternalByteRange(before typeOffset: Int, in ranges: [NSRange]) -> NSRange? {
let firstPartition = ranges.prefix(while: { typeOffset > $0.location })
return firstPartition.last
}

private func internalTypeElements(in element: SourceKittenElement) -> [SourceKittenElement] {
return element.substructure.flatMap { element -> [SourceKittenElement] in
guard let elementKind = SwiftDeclarationKind(rawValue: element.kind ?? "") else {
return []
}

let internalTypeElementsInSubstructure = elementKind.childsAreExemptFromACL ? [] :
internalTypeElements(in: element)

if element.accessibility.flatMap(AccessControlLevel.init(identifier:)) == .internal {
return internalTypeElementsInSubstructure + [element]
}

return internalTypeElementsInSubstructure
}
}
}

private extension SwiftDeclarationKind {

var childsAreExemptFromACL: Bool {
switch self {
case .`associatedtype`, .enumcase, .enumelement, .functionAccessorAddress,
.functionAccessorDidset, .functionAccessorGetter, .functionAccessorMutableaddress,
.functionAccessorSetter, .functionAccessorWillset, .genericTypeParam, .module,
.precedenceGroup, .varLocal, .varParameter, .varClass,
.varGlobal, .varInstance, .varStatic, .`typealias`, .functionConstructor, .functionDestructor,
.functionFree, .functionMethodClass, .functionMethodInstance, .functionMethodStatic,
.functionOperator, .functionOperatorInfix, .functionOperatorPostfix, .functionOperatorPrefix,
.functionSubscript:
return true
case .`class`, .`enum`, .`extension`, .`extensionClass`, .`extensionEnum`,
.extensionProtocol, .extensionStruct, .`protocol`, .`struct`:
return false
}
}

var shouldContainExplicitACL: Bool {
switch self {
case .`associatedtype`, .enumcase, .enumelement, .functionAccessorAddress,
.functionAccessorDidset, .functionAccessorGetter, .functionAccessorMutableaddress,
.functionAccessorSetter, .functionAccessorWillset, .genericTypeParam, .module,
.precedenceGroup, .varLocal, .varParameter:
return false
case .`class`, .`enum`, .`extension`, .`extensionClass`, .`extensionEnum`,
.extensionProtocol, .extensionStruct, .functionConstructor, .functionDestructor,
.functionFree, .functionMethodClass, .functionMethodInstance, .functionMethodStatic,
.functionOperator, .functionOperatorInfix, .functionOperatorPostfix, .functionOperatorPrefix,
.functionSubscript, .`protocol`, .`struct`, .`typealias`, .varClass,
.varGlobal, .varInstance, .varStatic:
return true
}
}
}
4 changes: 4 additions & 0 deletions SwiftLint.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@
6CCFCF2E1CFEF73A003239EB /* SWXMLHash.framework in Embed Frameworks into SwiftLintFramework.framework */ = {isa = PBXBuildFile; fileRef = E8C0DFCC1AD349DB007EE3D4 /* SWXMLHash.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
6CCFCF2F1CFEF73E003239EB /* SwiftyTextTable.framework in Embed Frameworks into SwiftLintFramework.framework */ = {isa = PBXBuildFile; fileRef = 3BBF2F9C1C640A0F006CD775 /* SwiftyTextTable.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
7250948A1D0859260039B353 /* StatementPositionConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 725094881D0855760039B353 /* StatementPositionConfiguration.swift */; };
72EA17B61FD31F10009D5CE6 /* ExplicitACLRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72EA17B51FD31F10009D5CE6 /* ExplicitACLRule.swift */; };
78F032461D7C877E00BE709A /* OverriddenSuperCallRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78F032441D7C877800BE709A /* OverriddenSuperCallRule.swift */; };
78F032481D7D614300BE709A /* OverridenSuperCallConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78F032471D7D614300BE709A /* OverridenSuperCallConfiguration.swift */; };
7C0C2E7A1D2866CB0076435A /* ExplicitInitRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C0C2E791D2866CB0076435A /* ExplicitInitRule.swift */; };
Expand Down Expand Up @@ -437,6 +438,7 @@
6CC4259A1C77046200AEA885 /* SyntaxMap+SwiftLint.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SyntaxMap+SwiftLint.swift"; sourceTree = "<group>"; };
6CC898A61EA0E1EF003DC0E2 /* CannedEmojiReporterOutputNonObjC.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = CannedEmojiReporterOutputNonObjC.txt; sourceTree = "<group>"; };
725094881D0855760039B353 /* StatementPositionConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatementPositionConfiguration.swift; sourceTree = "<group>"; };
72EA17B51FD31F10009D5CE6 /* ExplicitACLRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExplicitACLRule.swift; sourceTree = "<group>"; };
78F032441D7C877800BE709A /* OverriddenSuperCallRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OverriddenSuperCallRule.swift; sourceTree = "<group>"; };
78F032471D7D614300BE709A /* OverridenSuperCallConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OverridenSuperCallConfiguration.swift; sourceTree = "<group>"; };
7C0C2E791D2866CB0076435A /* ExplicitInitRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExplicitInitRule.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1020,6 +1022,7 @@
D4470D581EB6B4D1008A1B2E /* EmptyEnumArgumentsRule.swift */,
D47079AC1DFE2FA700027086 /* EmptyParametersRule.swift */,
D47079A61DFCEB2D00027086 /* EmptyParenthesesWithTrailingClosureRule.swift */,
72EA17B51FD31F10009D5CE6 /* ExplicitACLRule.swift */,
827169B21F488181003FB9AF /* ExplicitEnumRawValueRule.swift */,
7C0C2E791D2866CB0076435A /* ExplicitInitRule.swift */,
1EF115911EB2AD5900E30140 /* ExplicitTopLevelACLRule.swift */,
Expand Down Expand Up @@ -1608,6 +1611,7 @@
E88DEA711B09847500A66CB0 /* ViolationSeverity.swift in Sources */,
187290721FC37CA50016BEA2 /* YodaConditionRule.swift in Sources */,
1E3C2D711EE36C6F00C8386D /* PrivateOverFilePrivateRule.swift in Sources */,
72EA17B61FD31F10009D5CE6 /* ExplicitACLRule.swift in Sources */,
B2902A0C1D66815600BFCCF7 /* PrivateUnitTestRule.swift in Sources */,
D47A51101DB2DD4800A4CC21 /* AttributesRule.swift in Sources */,
CE8178ED1EAC039D0063186E /* UnusedOptionalBindingConfiguration.swift in Sources */,
Expand Down
4 changes: 4 additions & 0 deletions Tests/SwiftLintFrameworkTests/RulesTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,10 @@ class RulesTests: XCTestCase {
verifyRule(EmptyParenthesesWithTrailingClosureRule.description)
}

func testExplicitACL() {
verifyRule(ExplicitACLRule.description)
}

func testExplicitEnumRawValue() {
verifyRule(ExplicitEnumRawValueRule.description)
}
Expand Down

0 comments on commit 1541f51

Please sign in to comment.