SwiftLint/Source/SwiftLintBuiltInRules/Rules/Metrics/NestingRule.swift

145 lines
6.3 KiB
Swift

import SourceKittenFramework
struct NestingRule: ConfigurationProviderRule {
var configuration = NestingConfiguration()
static let description = RuleDescription(
identifier: "nesting",
name: "Nesting",
description:
"Types should be nested at most 1 level deep, and functions should be nested at most 2 levels deep.",
kind: .metrics,
nonTriggeringExamples: NestingRuleExamples.nonTriggeringExamples,
triggeringExamples: NestingRuleExamples.triggeringExamples
)
private let omittedStructureKinds = SwiftDeclarationKind.variableKinds
.union([.enumcase, .enumelement])
.map(SwiftStructureKind.declaration)
private struct ValidationArgs {
var typeLevel: Int = -1
var functionLevel: Int = -1
var previousKind: SwiftStructureKind?
var violations: [StyleViolation] = []
func with(previousKind: SwiftStructureKind?) -> ValidationArgs {
var args = self
args.previousKind = previousKind
return args
}
}
func validate(file: SwiftLintFile) -> [StyleViolation] {
return validate(file: file, substructure: file.structureDictionary.substructure, args: ValidationArgs())
}
private func validate(file: SwiftLintFile, substructure: [SourceKittenDictionary],
args: ValidationArgs) -> [StyleViolation] {
return args.violations + substructure.flatMap { dictionary -> [StyleViolation] in
guard let kindString = dictionary.kind, let structureKind = SwiftStructureKind(kindString) else {
return validate(file: file, substructure: dictionary.substructure, args: args.with(previousKind: nil))
}
guard !omittedStructureKinds.contains(structureKind) else {
return args.violations
}
switch structureKind {
case let .declaration(declarationKind):
return validate(file: file, structureKind: structureKind,
declarationKind: declarationKind, dictionary: dictionary, args: args)
case .expression, .statement:
guard configuration.checkNestingInClosuresAndStatements else {
return args.violations
}
return validate(file: file, substructure: dictionary.substructure,
args: args.with(previousKind: structureKind))
}
}
}
private func validate(file: SwiftLintFile, structureKind: SwiftStructureKind, declarationKind: SwiftDeclarationKind,
dictionary: SourceKittenDictionary, args: ValidationArgs) -> [StyleViolation] {
let isTypeOrExtension = SwiftDeclarationKind.typeKinds.contains(declarationKind)
|| SwiftDeclarationKind.extensionKinds.contains(declarationKind)
let isFunction = SwiftDeclarationKind.functionKinds.contains(declarationKind)
guard isTypeOrExtension || isFunction else {
return validate(file: file, substructure: dictionary.substructure,
args: args.with(previousKind: structureKind))
}
let currentTypeLevel = isTypeOrExtension ? args.typeLevel + 1 : args.typeLevel
let currentFunctionLevel = isFunction ? args.functionLevel + 1 : args.functionLevel
var violations = args.violations
if let violation = levelViolation(file: file, dictionary: dictionary,
previousKind: args.previousKind,
level: isFunction ? currentFunctionLevel : currentTypeLevel,
forFunction: isFunction) {
violations.append(violation)
}
return validate(file: file, substructure: dictionary.substructure,
args: ValidationArgs(
typeLevel: currentTypeLevel,
functionLevel: currentFunctionLevel,
previousKind: structureKind,
violations: violations
)
)
}
private func levelViolation(file: SwiftLintFile, dictionary: SourceKittenDictionary,
previousKind: SwiftStructureKind?, level: Int, forFunction: Bool) -> StyleViolation? {
guard let offset = dictionary.offset else {
return nil
}
let targetLevel = forFunction ? configuration.functionLevel : configuration.typeLevel
var violatingSeverity: ViolationSeverity?
if configuration.alwaysAllowOneTypeInFunctions,
case let .declaration(previousDeclarationKind)? = previousKind,
!SwiftDeclarationKind.functionKinds.contains(previousDeclarationKind) {
violatingSeverity = configuration.severity(with: targetLevel, for: level)
} else if forFunction || !configuration.alwaysAllowOneTypeInFunctions || previousKind == nil {
violatingSeverity = configuration.severity(with: targetLevel, for: level)
} else {
violatingSeverity = nil
}
guard let severity = violatingSeverity else {
return nil
}
let targetName = forFunction ? "Functions" : "Types"
let threshold = configuration.threshold(with: targetLevel, for: severity)
let pluralSuffix = threshold > 1 ? "s" : ""
return StyleViolation(
ruleDescription: Self.description,
severity: severity,
location: Location(file: file, byteOffset: offset),
reason: "\(targetName) should be nested at most \(threshold) level\(pluralSuffix) deep"
)
}
}
private enum SwiftStructureKind: Equatable {
case declaration(SwiftDeclarationKind)
case expression(SwiftExpressionKind)
case statement(StatementKind)
init?(_ structureKind: String) {
if let declarationKind = SwiftDeclarationKind(rawValue: structureKind) {
self = .declaration(declarationKind)
} else if let expressionKind = SwiftExpressionKind(rawValue: structureKind) {
self = .expression(expressionKind)
} else if let statementKind = StatementKind(rawValue: structureKind) {
self = .statement(statementKind)
} else {
return nil
}
}
}