Compare commits
2 Commits
main
...
marcelo/ex
Author | SHA1 | Date |
---|---|---|
![]() |
ce212e745e | |
![]() |
f81e28c574 |
|
@ -96,6 +96,7 @@
|
|||
- `empty_parentheses_with_trailing_closure`
|
||||
- `empty_string`
|
||||
- `enum_case_associated_values_count`
|
||||
- `explicit_acl`
|
||||
- `explicit_enum_raw_value`
|
||||
- `explicit_init`
|
||||
- `explicit_top_level_acl`
|
||||
|
|
|
@ -148,6 +148,20 @@ extension ModifierListSyntax? {
|
|||
}
|
||||
}
|
||||
|
||||
extension DeclModifierSyntax {
|
||||
var isACLModifier: Bool {
|
||||
let aclModifiers: Set<TokenKind> = [
|
||||
.privateKeyword,
|
||||
.fileprivateKeyword,
|
||||
.internalKeyword,
|
||||
.publicKeyword,
|
||||
.contextualKeyword("open")
|
||||
]
|
||||
|
||||
return detail == nil && aclModifiers.contains(name.tokenKind)
|
||||
}
|
||||
}
|
||||
|
||||
extension VariableDeclSyntax {
|
||||
var isIBOutlet: Bool {
|
||||
attributes?.contains { attr in
|
||||
|
|
|
@ -152,6 +152,14 @@ open class ViolationsSyntaxVisitor: SyntaxVisitor {
|
|||
skippableDeclarations.contains { $0 == VariableDeclSyntax.self } ? .skipChildren : .visitChildren
|
||||
}
|
||||
|
||||
override open func visit(_ node: SubscriptDeclSyntax) -> SyntaxVisitorContinueKind {
|
||||
skippableDeclarations.contains { $0 == SubscriptDeclSyntax.self } ? .skipChildren : .visitChildren
|
||||
}
|
||||
|
||||
override open func visit(_ node: InitializerDeclSyntax) -> SyntaxVisitorContinueKind {
|
||||
skippableDeclarations.contains { $0 == InitializerDeclSyntax.self } ? .skipChildren : .visitChildren
|
||||
}
|
||||
|
||||
override open func visit(_ node: ProtocolDeclSyntax) -> SyntaxVisitorContinueKind {
|
||||
skippableDeclarations.contains { $0 == ProtocolDeclSyntax.self } ? .skipChildren : .visitChildren
|
||||
}
|
||||
|
@ -170,7 +178,9 @@ extension Array where Element == DeclSyntaxProtocol.Type {
|
|||
ExtensionDeclSyntax.self,
|
||||
ProtocolDeclSyntax.self,
|
||||
StructDeclSyntax.self,
|
||||
VariableDeclSyntax.self
|
||||
VariableDeclSyntax.self,
|
||||
SubscriptDeclSyntax.self,
|
||||
InitializerDeclSyntax.self
|
||||
]
|
||||
|
||||
/// Useful for class-specific checks since extensions and protocols do not allow nested classes.
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
import Foundation
|
||||
import SourceKittenFramework
|
||||
import SwiftSyntax
|
||||
|
||||
private typealias SourceKittenElement = SourceKittenDictionary
|
||||
|
||||
public struct ExplicitACLRule: OptInRule, ConfigurationProviderRule {
|
||||
public struct ExplicitACLRule: SwiftSyntaxRule, OptInRule, ConfigurationProviderRule {
|
||||
public var configuration = SeverityConfiguration(.warning)
|
||||
|
||||
public init() {}
|
||||
|
@ -71,13 +68,22 @@ public struct ExplicitACLRule: OptInRule, ConfigurationProviderRule {
|
|||
}
|
||||
}
|
||||
}
|
||||
"""),
|
||||
Example("""
|
||||
private extension Foo {
|
||||
var isValid: Bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
""")
|
||||
],
|
||||
triggeringExamples: [
|
||||
Example("↓enum A {}\n"),
|
||||
Example("final ↓class B {}\n"),
|
||||
Example("internal struct C { ↓let d = 5 }\n"),
|
||||
Example("internal struct C { ↓static let d = 5 }\n"),
|
||||
Example("public struct C { ↓let d = 5 }\n"),
|
||||
Example("public struct C { ↓init() }\n"),
|
||||
Example("func a() {}\n"),
|
||||
Example("internal let a = 0\n↓func b() {}\n"),
|
||||
Example("""
|
||||
|
@ -88,109 +94,128 @@ public struct ExplicitACLRule: OptInRule, ConfigurationProviderRule {
|
|||
]
|
||||
)
|
||||
|
||||
private func findAllExplicitInternalTokens(in file: SwiftLintFile) -> [ByteRange] {
|
||||
let contents = file.stringView
|
||||
return file.match(pattern: "internal", with: [.attributeBuiltin]).compactMap {
|
||||
contents.NSRangeToByteRange(start: $0.location, length: $0.length)
|
||||
}
|
||||
public func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor {
|
||||
Visitor(viewMode: .sourceAccurate)
|
||||
}
|
||||
}
|
||||
|
||||
private func offsetOfElements(from elements: [SourceKittenElement], in file: SwiftLintFile,
|
||||
thatAreNotInRanges ranges: [ByteRange]) -> [ByteCount] {
|
||||
return elements.compactMap { element in
|
||||
guard let typeOffset = element.offset else {
|
||||
return nil
|
||||
}
|
||||
|
||||
guard let kind = element.declarationKind,
|
||||
!SwiftDeclarationKind.extensionKinds.contains(kind) 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 = ByteRange(location: previousInternalByteRange.location, length: length)
|
||||
let internalDoesntBelongToType = Set(file.syntaxMap.kinds(inByteRange: range)) != [.attributeBuiltin]
|
||||
|
||||
return internalDoesntBelongToType ? typeOffset : nil
|
||||
}
|
||||
}
|
||||
|
||||
public func validate(file: SwiftLintFile) -> [StyleViolation] {
|
||||
let implicitAndExplicitInternalElements = internalTypeElements(in: file.structureDictionary)
|
||||
|
||||
guard implicitAndExplicitInternalElements.isNotEmpty else {
|
||||
return []
|
||||
private extension ExplicitACLRule {
|
||||
final class Visitor: ViolationsSyntaxVisitor {
|
||||
override var skippableDeclarations: [DeclSyntaxProtocol.Type] {
|
||||
[
|
||||
FunctionDeclSyntax.self,
|
||||
SubscriptDeclSyntax.self,
|
||||
VariableDeclSyntax.self,
|
||||
ProtocolDeclSyntax.self,
|
||||
InitializerDeclSyntax.self
|
||||
]
|
||||
}
|
||||
|
||||
let explicitInternalRanges = findAllExplicitInternalTokens(in: file)
|
||||
override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind {
|
||||
if hasViolation(modifiers: node.modifiers) {
|
||||
violations.append(node.classKeyword.positionAfterSkippingLeadingTrivia)
|
||||
}
|
||||
|
||||
let violations = offsetOfElements(from: implicitAndExplicitInternalElements, in: file,
|
||||
thatAreNotInRanges: explicitInternalRanges)
|
||||
|
||||
return violations.map {
|
||||
StyleViolation(ruleDescription: Self.description,
|
||||
severity: configuration.severity,
|
||||
location: Location(file: file, byteOffset: $0))
|
||||
return node.modifiers.isPrivateOrFileprivate ? .skipChildren : .visitChildren
|
||||
}
|
||||
}
|
||||
|
||||
private func lastInternalByteRange(before typeOffset: ByteCount, in ranges: [ByteRange]) -> ByteRange? {
|
||||
let firstPartition = ranges.prefix(while: { typeOffset > $0.location })
|
||||
return firstPartition.last
|
||||
}
|
||||
|
||||
private func internalTypeElements(in parent: SourceKittenElement) -> [SourceKittenElement] {
|
||||
return parent.substructure.flatMap { element -> [SourceKittenElement] in
|
||||
guard let elementKind = element.declarationKind,
|
||||
elementKind != .varLocal, elementKind != .varParameter else {
|
||||
return []
|
||||
override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind {
|
||||
if hasViolation(modifiers: node.modifiers) {
|
||||
violations.append(node.structKeyword.positionAfterSkippingLeadingTrivia)
|
||||
}
|
||||
|
||||
let isDeinit = elementKind == .functionMethodInstance && element.name == "deinit"
|
||||
guard !isDeinit else {
|
||||
return []
|
||||
return node.modifiers.isPrivateOrFileprivate ? .skipChildren : .visitChildren
|
||||
}
|
||||
|
||||
override func visit(_ node: EnumDeclSyntax) -> SyntaxVisitorContinueKind {
|
||||
if hasViolation(modifiers: node.modifiers) {
|
||||
violations.append(node.enumKeyword.positionAfterSkippingLeadingTrivia)
|
||||
}
|
||||
|
||||
let isPrivate = element.accessibility?.isPrivate ?? false
|
||||
let internalTypeElementsInSubstructure = elementKind.childsAreExemptFromACL || isPrivate ? [] :
|
||||
internalTypeElements(in: element)
|
||||
return node.modifiers.isPrivateOrFileprivate ? .skipChildren : .visitChildren
|
||||
}
|
||||
|
||||
var isInExtension = false
|
||||
if let kind = parent.declarationKind {
|
||||
isInExtension = SwiftDeclarationKind.extensionKinds.contains(kind)
|
||||
override func visit(_ node: ActorDeclSyntax) -> SyntaxVisitorContinueKind {
|
||||
if hasViolation(modifiers: node.modifiers) {
|
||||
violations.append(node.actorKeyword.positionAfterSkippingLeadingTrivia)
|
||||
}
|
||||
|
||||
if element.accessibility == .internal || (element.accessibility == nil && isInExtension) {
|
||||
return internalTypeElementsInSubstructure + [element]
|
||||
return node.modifiers.isPrivateOrFileprivate ? .skipChildren : .visitChildren
|
||||
}
|
||||
|
||||
override func visitPost(_ node: ProtocolDeclSyntax) {
|
||||
if hasViolation(modifiers: node.modifiers) {
|
||||
violations.append(node.protocolKeyword.positionAfterSkippingLeadingTrivia)
|
||||
}
|
||||
}
|
||||
|
||||
override func visitPost(_ node: TypealiasDeclSyntax) {
|
||||
if hasViolation(modifiers: node.modifiers) {
|
||||
violations.append(node.typealiasKeyword.positionAfterSkippingLeadingTrivia)
|
||||
}
|
||||
}
|
||||
|
||||
override func visitPost(_ node: FunctionDeclSyntax) {
|
||||
if hasViolation(modifiers: node.modifiers) {
|
||||
let position = node.modifiers.staticOrClassPosition ??
|
||||
node.funcKeyword.positionAfterSkippingLeadingTrivia
|
||||
violations.append(position)
|
||||
}
|
||||
}
|
||||
|
||||
override func visitPost(_ node: SubscriptDeclSyntax) {
|
||||
if hasViolation(modifiers: node.modifiers) {
|
||||
let position = node.modifiers.staticOrClassPosition ??
|
||||
node.subscriptKeyword.positionAfterSkippingLeadingTrivia
|
||||
violations.append(position)
|
||||
}
|
||||
}
|
||||
|
||||
override func visitPost(_ node: InitializerDeclSyntax) {
|
||||
if hasViolation(modifiers: node.modifiers) {
|
||||
violations.append(node.initKeyword.positionAfterSkippingLeadingTrivia)
|
||||
}
|
||||
}
|
||||
|
||||
override func visitPost(_ node: VariableDeclSyntax) {
|
||||
if hasViolation(modifiers: node.modifiers) {
|
||||
let position = node.modifiers.staticOrClassPosition ??
|
||||
node.letOrVarKeyword.positionAfterSkippingLeadingTrivia
|
||||
violations.append(position)
|
||||
}
|
||||
}
|
||||
|
||||
override func visit(_ node: ExtensionDeclSyntax) -> SyntaxVisitorContinueKind {
|
||||
guard let modifiers = node.modifiers,
|
||||
modifiers.contains(where: \.isACLModifier) else {
|
||||
return .visitChildren
|
||||
}
|
||||
|
||||
return internalTypeElementsInSubstructure
|
||||
return .skipChildren
|
||||
}
|
||||
|
||||
override func visit(_ node: CodeBlockSyntax) -> SyntaxVisitorContinueKind {
|
||||
.skipChildren
|
||||
}
|
||||
|
||||
override func visit(_ node: ClosureExprSyntax) -> SyntaxVisitorContinueKind {
|
||||
.skipChildren
|
||||
}
|
||||
|
||||
private func hasViolation(modifiers: ModifierListSyntax?) -> Bool {
|
||||
guard let modifiers = modifiers else {
|
||||
return true
|
||||
}
|
||||
|
||||
return !modifiers.contains(where: \.isACLModifier)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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, .functionAccessorModify, .functionAccessorRead,
|
||||
.functionConstructor, .functionDestructor, .functionFree, .functionMethodClass,
|
||||
.functionMethodInstance, .functionMethodStatic, .functionOperator, .functionOperatorInfix,
|
||||
.functionOperatorPostfix, .functionOperatorPrefix, .functionSubscript, .protocol, .opaqueType:
|
||||
return true
|
||||
case .class, .enum, .extension, .extensionClass, .extensionEnum,
|
||||
.extensionProtocol, .extensionStruct, .struct:
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
private extension ModifierListSyntax? {
|
||||
var staticOrClassPosition: AbsolutePosition? {
|
||||
self?.first { modifier in
|
||||
modifier.name.tokenKind == .staticKeyword || modifier.name.tokenKind == .classKeyword
|
||||
}?.positionAfterSkippingLeadingTrivia
|
||||
}
|
||||
}
|
||||
|
|
|
@ -104,17 +104,3 @@ private extension ExplicitTopLevelACLRule {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension DeclModifierSyntax {
|
||||
var isACLModifier: Bool {
|
||||
let aclModifiers: Set<TokenKind> = [
|
||||
.privateKeyword,
|
||||
.fileprivateKeyword,
|
||||
.internalKeyword,
|
||||
.publicKeyword,
|
||||
.contextualKeyword("open")
|
||||
]
|
||||
|
||||
return detail == nil && aclModifiers.contains(name.tokenKind)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue