90 lines
3.8 KiB
Swift
90 lines
3.8 KiB
Swift
import SourceKittenFramework
|
|
|
|
public struct LowerACLThanParentRule: OptInRule, ConfigurationProviderRule, AutomaticTestableRule {
|
|
public var configuration = SeverityConfiguration(.warning)
|
|
|
|
public init() {}
|
|
|
|
public static let description = RuleDescription(
|
|
identifier: "lower_acl_than_parent",
|
|
name: "Lower ACL than parent",
|
|
description: "Ensure definitions have a lower access control level than their enclosing parent",
|
|
kind: .lint,
|
|
nonTriggeringExamples: [
|
|
"public struct Foo { public func bar() {} }",
|
|
"internal struct Foo { func bar() {} }",
|
|
"struct Foo { func bar() {} }",
|
|
"open class Foo { public func bar() {} }",
|
|
"open class Foo { open func bar() {} }",
|
|
"fileprivate struct Foo { private func bar() {} }",
|
|
"private struct Foo { private func bar(id: String) }",
|
|
"extension Foo { public func bar() {} }",
|
|
"private struct Foo { fileprivate func bar() {} }",
|
|
"private func foo(id: String) {}"
|
|
],
|
|
triggeringExamples: [
|
|
"struct Foo { public func bar() {} }",
|
|
"enum Foo { public func bar() {} }",
|
|
"public class Foo { open func bar() }",
|
|
"class Foo { public private(set) var bar: String? }"
|
|
]
|
|
)
|
|
|
|
public func validate(file: File) -> [StyleViolation] {
|
|
return validateACL(isHigherThan: .open, in: file.structure.dictionary).map {
|
|
StyleViolation(ruleDescription: type(of: self).description,
|
|
severity: configuration.severity,
|
|
location: Location(file: file, byteOffset: $0))
|
|
}
|
|
}
|
|
|
|
private func validateACL(isHigherThan parentAccessibility: AccessControlLevel,
|
|
in substructure: [String: SourceKitRepresentable]) -> [Int] {
|
|
return substructure.substructure.flatMap { element -> [Int] in
|
|
guard let elementKind = element.kind.flatMap(SwiftDeclarationKind.init(rawValue:)),
|
|
elementKind.isRelevantDeclaration else {
|
|
return []
|
|
}
|
|
|
|
var violationOffset: Int?
|
|
let accessibility = element.accessibility.flatMap(AccessControlLevel.init(identifier:))
|
|
?? .`internal`
|
|
if accessibility.priority > parentAccessibility.priority {
|
|
violationOffset = element.offset
|
|
}
|
|
|
|
return [violationOffset].compactMap { $0 } + self.validateACL(isHigherThan: accessibility, in: element)
|
|
}
|
|
}
|
|
}
|
|
|
|
private extension SwiftDeclarationKind {
|
|
var isRelevantDeclaration: Bool {
|
|
switch self {
|
|
case .`associatedtype`, .enumcase, .enumelement, .`extension`, .`extensionClass`, .`extensionEnum`,
|
|
.extensionProtocol, .extensionStruct, .functionAccessorAddress, .functionAccessorDidset,
|
|
.functionAccessorGetter, .functionAccessorMutableaddress, .functionAccessorSetter,
|
|
.functionAccessorWillset, .functionDestructor, .genericTypeParam, .module, .precedenceGroup, .varLocal,
|
|
.varParameter:
|
|
return false
|
|
case .`class`, .`enum`, .functionConstructor, .functionFree, .functionMethodClass, .functionMethodInstance,
|
|
.functionMethodStatic, .functionOperator, .functionOperatorInfix, .functionOperatorPostfix,
|
|
.functionOperatorPrefix, .functionSubscript, .`protocol`, .`struct`, .`typealias`, .varClass, .varGlobal,
|
|
.varInstance, .varStatic:
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
|
|
private extension AccessControlLevel {
|
|
var priority: Int {
|
|
switch self {
|
|
case .private: return 1
|
|
case .fileprivate: return 1
|
|
case .internal: return 2
|
|
case .public: return 3
|
|
case .open: return 4
|
|
}
|
|
}
|
|
}
|