SwiftLint/Source/SwiftLintFramework/Rules/Lint/DynamicInlineRule.swift

72 lines
3.1 KiB
Swift

import Foundation
import SourceKittenFramework
public struct DynamicInlineRule: ASTRule, ConfigurationProviderRule, AutomaticTestableRule {
public var configuration = SeverityConfiguration(.error)
public init() {}
public static let description = RuleDescription(
identifier: "dynamic_inline",
name: "Dynamic Inline",
description: "Avoid using 'dynamic' and '@inline(__always)' together.",
kind: .lint,
nonTriggeringExamples: [
"class C {\ndynamic func f() {}}",
"class C {\n@inline(__always) func f() {}}",
"class C {\n@inline(never) dynamic func f() {}}"
],
triggeringExamples: [
"class C {\n@inline(__always) dynamic ↓func f() {}\n}",
"class C {\n@inline(__always) public dynamic ↓func f() {}\n}",
"class C {\n@inline(__always) dynamic internal ↓func f() {}\n}",
"class C {\n@inline(__always)\ndynamic ↓func f() {}\n}",
"class C {\n@inline(__always)\ndynamic\n↓func f() {}\n}"
]
)
public func validate(file: SwiftLintFile, kind: SwiftDeclarationKind,
dictionary: SourceKittenDictionary) -> [StyleViolation] {
// Look for functions with both "inline" and "dynamic". For each of these, we can get offset
// of the "func" keyword. We can assume that the nearest "@inline" before this offset is
// the attribute we are interested in.
guard functionKinds.contains(kind),
case let attributes = dictionary.enclosedSwiftAttributes,
attributes.contains(.dynamic),
attributes.contains(.inline),
let funcOffset = dictionary.offset.flatMap(file.stringView.location),
case let inlinePattern = regex("@inline"),
case let range = NSRange(location: 0, length: funcOffset),
let inlineMatch = inlinePattern.matches(in: file.contents, options: [], range: range)
.last,
inlineMatch.range.location != NSNotFound,
case let attributeRange = NSRange(location: inlineMatch.range.location,
length: funcOffset - inlineMatch.range.location),
case let alwaysInlinePattern = regex("@inline\\(\\s*__always\\s*\\)"),
alwaysInlinePattern.firstMatch(in: file.contents, options: [], range: attributeRange) != nil
else {
return []
}
return [StyleViolation(ruleDescription: type(of: self).description,
severity: configuration.severity,
location: Location(file: file, characterOffset: funcOffset))]
}
fileprivate let functionKinds: [SwiftDeclarationKind] = [
.functionAccessorAddress,
.functionAccessorDidset,
.functionAccessorGetter,
.functionAccessorMutableaddress,
.functionAccessorSetter,
.functionAccessorWillset,
.functionConstructor,
.functionDestructor,
.functionFree,
.functionMethodClass,
.functionMethodInstance,
.functionMethodStatic,
.functionOperator,
.functionSubscript
]
}