Files
SwiftLint/Source/SwiftLintFramework/Rules/QuickDiscouragedCallRule.swift
T
JP Simard b83e0991b9 Remove all file headers
The MIT license doesn't require that all files be prepended with this
licensing or copyright information. Realm confirmed that they're ok with this
change. This will enable some companies to contribute to SwiftLint and the
date & authorship information will remain accessible via git source control.
2018-05-04 13:42:02 -07:00

128 lines
4.4 KiB
Swift

import Foundation
import SourceKittenFramework
public struct QuickDiscouragedCallRule: OptInRule, ConfigurationProviderRule {
public var configuration = SeverityConfiguration(.warning)
public init() {}
public static let description = RuleDescription(
identifier: "quick_discouraged_call",
name: "Quick Discouraged Call",
description: "Discouraged call inside 'describe' and/or 'context' block.",
kind: .lint,
nonTriggeringExamples: QuickDiscouragedCallRuleExamples.nonTriggeringExamples,
triggeringExamples: QuickDiscouragedCallRuleExamples.triggeringExamples
)
public func validate(file: File) -> [StyleViolation] {
let testClasses = file.structure.dictionary.substructure.filter {
return $0.inheritedTypes.contains("QuickSpec") &&
$0.kind.flatMap(SwiftDeclarationKind.init) == .class
}
let specDeclarations = testClasses.flatMap { classDict in
return classDict.substructure.filter {
return $0.name == "spec()" && $0.enclosedVarParameters.isEmpty &&
$0.kind.flatMap(SwiftDeclarationKind.init) == .functionMethodInstance &&
$0.enclosedSwiftAttributes.contains(.override)
}
}
return specDeclarations.flatMap {
validate(file: file, dictionary: $0)
}
}
private func validate(file: File, dictionary: [String: SourceKitRepresentable]) -> [StyleViolation] {
return dictionary.substructure.flatMap { subDict -> [StyleViolation] in
var violations = validate(file: file, dictionary: subDict)
if let kindString = subDict.kind,
let kind = SwiftExpressionKind(rawValue: kindString) {
violations += validate(file: file, kind: kind, dictionary: subDict)
}
return violations
}
}
private func validate(file: File,
kind: SwiftExpressionKind,
dictionary: [String: SourceKitRepresentable]) -> [StyleViolation] {
// is it a call to a restricted method?
guard
kind == .call,
let name = dictionary.name,
let kindName = QuickCallKind(rawValue: name),
QuickCallKind.restrictiveKinds.contains(kindName)
else { return [] }
return violationOffsets(in: dictionary.enclosedArguments).map {
StyleViolation(ruleDescription: type(of: self).description,
severity: configuration.severity,
location: Location(file: file, byteOffset: $0),
reason: "Discouraged call inside a '\(name)' block.")
}
}
private func violationOffsets(in substructure: [[String: SourceKitRepresentable]]) -> [Int] {
return substructure.flatMap { dictionary -> [Int] in
return dictionary.substructure.flatMap(toViolationOffsets)
}
}
private func toViolationOffsets(dictionary: [String: SourceKitRepresentable]) -> [Int] {
guard
let kind = dictionary.kind,
let offset = dictionary.offset
else { return [] }
if SwiftExpressionKind(rawValue: kind) == .call,
let name = dictionary.name, QuickCallKind(rawValue: name) == nil {
return [offset]
}
guard SwiftExpressionKind(rawValue: kind) != .call else { return [] }
return dictionary.substructure.compactMap(toViolationOffset)
}
private func toViolationOffset(dictionary: [String: SourceKitRepresentable]) -> Int? {
guard
let name = dictionary.name,
let offset = dictionary.offset,
let kind = dictionary.kind,
SwiftExpressionKind(rawValue: kind) == .call,
QuickCallKind(rawValue: name) == nil
else { return nil }
return offset
}
}
private enum QuickCallKind: String {
case describe
case context
case sharedExamples
case itBehavesLike
case beforeEach
case beforeSuite
case afterEach
case afterSuite
case it // swiftlint:disable:this identifier_name
case pending
case xdescribe
case xcontext
case xit
case xitBehavesLike
case fdescribe
case fcontext
case fit
case fitBehavesLike
static let restrictiveKinds: Set<QuickCallKind> = [
.describe, .fdescribe, .xdescribe, .context, .fcontext, .xcontext, .sharedExamples
]
}