mirror of
https://github.com/realm/SwiftLint.git
synced 2026-05-07 20:12:49 +00:00
Separated nesting level counting for types and functions in nesting rule (fixes #1151). Enhanced nesting rule to search for nested types and functions within closures and statements. Enhanced nesting rule to allow for one nested type within a function even if breaking a maximum type level nesting (fixes #1151).
This commit is contained in:
@@ -17,6 +17,19 @@
|
||||
[JP Simard](https://github.com/jpsim)
|
||||
[#3412](https://github.com/realm/SwiftLint/issues/3412)
|
||||
|
||||
* Renamed `statement_level` to `function_level` in `nesting` rule configuration.
|
||||
[Skoti](https://github.com/Skoti)
|
||||
|
||||
* Separated `type_level` and `function_level` counting in `nesting` rule.
|
||||
[Skoti](https://github.com/Skoti)
|
||||
[#1151](https://github.com/realm/SwiftLint/issues/1151)
|
||||
|
||||
* `function_level` in `nesting` rule defaults to 2 levels.
|
||||
[Skoti](https://github.com/Skoti)
|
||||
|
||||
* Added `check_nesting_in_closures_and_statements` in `nesting` rule to search for nested types and functions within closures and statements. Defaults to `true`.
|
||||
[Skoti](https://github.com/Skoti)
|
||||
|
||||
#### Experimental
|
||||
|
||||
* None.
|
||||
@@ -27,6 +40,9 @@
|
||||
`Never`.
|
||||
[Artem Garmash](https://github.com/agarmash)
|
||||
[#3286](https://github.com/realm/SwiftLint/issues/3286)
|
||||
* Added `always_allow_one_type_in_functions` option in `nesting` rule configuration. Defaults to `false`. This allows to nest one type within a function even if breaking the maximum `type_level`.
|
||||
[Skoti](https://github.com/Skoti)
|
||||
[#1151](https://github.com/realm/SwiftLint/issues/1151)
|
||||
|
||||
#### Bug Fixes
|
||||
|
||||
|
||||
@@ -34,4 +34,12 @@ extension SwiftDeclarationKind {
|
||||
.associatedtype,
|
||||
.enum
|
||||
]
|
||||
|
||||
internal static let extensionKinds: Set<SwiftDeclarationKind> = [
|
||||
.extension,
|
||||
.extensionClass,
|
||||
.extensionEnum,
|
||||
.extensionProtocol,
|
||||
.extensionStruct
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import SourceKittenFramework
|
||||
|
||||
public struct NestingRule: ASTRule, ConfigurationProviderRule, AutomaticTestableRule {
|
||||
public struct NestingRule: ConfigurationProviderRule {
|
||||
public var configuration = NestingConfiguration(typeLevelWarning: 1,
|
||||
typeLevelError: nil,
|
||||
statementLevelWarning: 5,
|
||||
statementLevelError: nil)
|
||||
functionLevelWarning: 2,
|
||||
functionLevelError: nil)
|
||||
|
||||
public init() {}
|
||||
|
||||
@@ -12,79 +12,151 @@ public struct NestingRule: ASTRule, ConfigurationProviderRule, AutomaticTestable
|
||||
identifier: "nesting",
|
||||
name: "Nesting",
|
||||
description: "Types should be nested at most 1 level deep, " +
|
||||
"and statements should be nested at most 5 levels deep.",
|
||||
kind: .metrics,
|
||||
nonTriggeringExamples: ["class", "struct", "enum"].flatMap { kind -> [Example] in
|
||||
[
|
||||
Example("\(kind) Class0 { \(kind) Class1 {} }\n"),
|
||||
Example("""
|
||||
func func0() {
|
||||
func func1() {
|
||||
func func2() {
|
||||
func func3() {
|
||||
func func4() {
|
||||
func func5() {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
""")
|
||||
]
|
||||
} + [Example("enum Enum0 { enum Enum1 { case Case } }")],
|
||||
triggeringExamples: ["class", "struct", "enum"].map { kind -> Example in
|
||||
return Example("\(kind) A { \(kind) B { ↓\(kind) C {} } }\n")
|
||||
} + [
|
||||
Example("""
|
||||
func func0() {
|
||||
func func1() {
|
||||
func func2() {
|
||||
func func3() {
|
||||
func func4() {
|
||||
func func5() {
|
||||
↓func func6() {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
""")
|
||||
]
|
||||
"and functions should be nested at most 2 levels deep.",
|
||||
kind: .metrics,
|
||||
nonTriggeringExamples: NestingRuleExamples.nonTriggeringExamples,
|
||||
triggeringExamples: NestingRuleExamples.triggeringExamples
|
||||
)
|
||||
|
||||
public func validate(file: SwiftLintFile, kind: SwiftDeclarationKind,
|
||||
dictionary: SourceKittenDictionary) -> [StyleViolation] {
|
||||
return validate(file: file, kind: kind, dictionary: dictionary, level: 0)
|
||||
private let omittedStructureKinds: [SwiftStructureKind] =
|
||||
[.declaration(.enumcase), .declaration(.enumelement)]
|
||||
+ SwiftDeclarationKind.variableKinds.map { .declaration($0) }
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
private func validate(file: SwiftLintFile, kind: SwiftDeclarationKind, dictionary: SourceKittenDictionary,
|
||||
level: Int) -> [StyleViolation] {
|
||||
var violations = [StyleViolation]()
|
||||
let typeKinds = SwiftDeclarationKind.typeKinds
|
||||
if let offset = dictionary.offset {
|
||||
let (targetName, targetLevel) = typeKinds.contains(kind)
|
||||
? ("Types", configuration.typeLevel) : ("Statements", configuration.statementLevel)
|
||||
if let severity = configuration.severity(with: targetLevel, for: level) {
|
||||
let threshold = configuration.threshold(with: targetLevel, for: severity)
|
||||
let pluralSuffix = threshold > 1 ? "s" : ""
|
||||
violations.append(StyleViolation(
|
||||
ruleDescription: Self.description,
|
||||
severity: severity,
|
||||
location: Location(file: file, byteOffset: offset),
|
||||
reason: "\(targetName) should be nested at most \(threshold) level\(pluralSuffix) deep"))
|
||||
public 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))
|
||||
}
|
||||
}
|
||||
violations.append(contentsOf: dictionary.substructure.compactMap { subDict in
|
||||
if let kind = subDict.declarationKind {
|
||||
return (kind, subDict)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}.flatMap { kind, subDict in
|
||||
return validate(file: file, kind: kind, dictionary: subDict, level: level + 1)
|
||||
})
|
||||
return violations
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
static func == (lhs: SwiftStructureKind, rhs: SwiftStructureKind) -> Bool {
|
||||
switch (lhs, rhs) {
|
||||
case let (.declaration(lhsKind), .declaration(rhsKind)):
|
||||
return lhsKind == rhsKind
|
||||
case let (.expression(lhsKind), .expression(rhsKind)):
|
||||
return lhsKind == rhsKind
|
||||
case let (.statement(lhsKind), .statement(rhsKind)):
|
||||
return lhsKind == rhsKind
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,415 @@
|
||||
// swiftlint:disable file_length type_body_length
|
||||
internal struct NestingRuleExamples {
|
||||
static let nonTriggeringExamples = nonTriggeringTypeExamples
|
||||
+ nonTriggeringFunctionExamples
|
||||
+ nonTriggeringClosureAndStatementExamples
|
||||
+ nonTriggeringMixedExamples
|
||||
|
||||
private static let nonTriggeringTypeExamples =
|
||||
["class", "struct", "enum"].flatMap { type -> [Example] in
|
||||
[
|
||||
// default maximum type nesting level
|
||||
.init("""
|
||||
\(type) Example_0 {
|
||||
\(type) Example_1 {}
|
||||
}
|
||||
"""),
|
||||
|
||||
/*
|
||||
all variableKinds of SwiftDeclarationKind (except .varParameter which is a function parameter)
|
||||
are flattend in a file structure so limits do not change
|
||||
*/
|
||||
.init("""
|
||||
var example: Int {
|
||||
\(type) Example_0 {
|
||||
\(type) Example_1 {}
|
||||
}
|
||||
return 5
|
||||
}
|
||||
"""),
|
||||
|
||||
// didSet is not present in file structure although there is such a swift declaration kind
|
||||
.init("""
|
||||
var example: Int = 5 {
|
||||
didSet {
|
||||
\(type) Example_0 {
|
||||
\(type) Example_1 {}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""),
|
||||
|
||||
// extensions are counted as a type level
|
||||
.init("""
|
||||
extension Example_0 {
|
||||
\(type) Example_1 {}
|
||||
}
|
||||
""")
|
||||
]
|
||||
}
|
||||
|
||||
private static let nonTriggeringFunctionExamples: [Example] = [
|
||||
// default maximum function nesting level
|
||||
.init("""
|
||||
func f_0() {
|
||||
func f_1() {
|
||||
func f_2() {}
|
||||
}
|
||||
}
|
||||
"""),
|
||||
|
||||
/*
|
||||
all variableKinds of SwiftDeclarationKind (except .varParameter which is a function parameter)
|
||||
are flattend in a file structure so level limits do not change
|
||||
*/
|
||||
.init("""
|
||||
var example: Int {
|
||||
func f_0() {
|
||||
func f_1() {
|
||||
func f_2() {}
|
||||
}
|
||||
}
|
||||
return 5
|
||||
}
|
||||
"""),
|
||||
|
||||
// didSet is not present in file structure although there is such a swift declaration kind
|
||||
.init("""
|
||||
var example: Int = 5 {
|
||||
didSet {
|
||||
func f_0() {
|
||||
func f_1() {
|
||||
func f_2() {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""),
|
||||
|
||||
// extensions are counted as a type level
|
||||
.init("""
|
||||
extension Example_0 {
|
||||
func f_0() {
|
||||
func f_1() {
|
||||
func f_2() {}
|
||||
}
|
||||
}
|
||||
}
|
||||
""")
|
||||
]
|
||||
|
||||
private static let nonTriggeringClosureAndStatementExamples =
|
||||
["class", "struct", "enum"].flatMap { type -> [Example] in
|
||||
[
|
||||
// swich statement example
|
||||
.init("""
|
||||
switch example {
|
||||
case .exampleCase:
|
||||
\(type) Example_0 {
|
||||
\(type) Example_1 {}
|
||||
}
|
||||
default:
|
||||
func f_0() {
|
||||
func f_1() {
|
||||
func f_2() {}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""),
|
||||
|
||||
// closure var example
|
||||
.init("""
|
||||
var exampleClosure: () -> Void = {
|
||||
\(type) Example_0 {
|
||||
\(type) Example_1 {}
|
||||
}
|
||||
func f_0() {
|
||||
func f_1() {
|
||||
func f_2() {}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""),
|
||||
|
||||
// function closure parameter example
|
||||
.init("""
|
||||
exampleFunc(closure: {
|
||||
\(type) Example_0 {
|
||||
\(type) Example_1 {}
|
||||
}
|
||||
func f_0() {
|
||||
func f_1() {
|
||||
func f_2() {}
|
||||
}
|
||||
}
|
||||
})
|
||||
""")
|
||||
]
|
||||
}
|
||||
|
||||
private static let nonTriggeringMixedExamples =
|
||||
["class", "struct", "enum"].flatMap { type -> [Example] in
|
||||
[
|
||||
// default maximum nesting level for both type and function (nesting order is arbitrary)
|
||||
.init("""
|
||||
\(type) Example_0 {
|
||||
func f_0() {
|
||||
\(type) Example_1 {
|
||||
func f_1() {
|
||||
func f_2() {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""),
|
||||
|
||||
// default maximum nesting level for both type and function within closures and statements
|
||||
.init("""
|
||||
\(type) Example_0 {
|
||||
func f_0() {
|
||||
switch example {
|
||||
case .exampleCase:
|
||||
\(type) Example_1 {
|
||||
func f_1() {
|
||||
func f_2() {}
|
||||
}
|
||||
}
|
||||
default:
|
||||
exampleFunc(closure: {
|
||||
\(type) Example_1 {
|
||||
func f_1() {
|
||||
func f_2() {}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
""")
|
||||
]
|
||||
}
|
||||
|
||||
static let triggeringExamples = triggeringTypeExamples
|
||||
+ triggeringFunctionExamples
|
||||
+ triggeringClosureAndStatementExamples
|
||||
+ triggeringMixedExamples
|
||||
|
||||
private static let triggeringTypeExamples =
|
||||
["class", "struct", "enum"].flatMap { type -> [Example] in
|
||||
[
|
||||
// violation of default maximum type nesting level
|
||||
.init("""
|
||||
\(type) Example_0 {
|
||||
\(type) Example_1 {
|
||||
↓\(type) Example_2 {}
|
||||
}
|
||||
}
|
||||
"""),
|
||||
|
||||
/*
|
||||
all variableKinds of SwiftDeclarationKind (except .varParameter which is a function parameter)
|
||||
are flattend in a file structure so limits do not change
|
||||
*/
|
||||
.init("""
|
||||
var example: Int {
|
||||
\(type) Example_0 {
|
||||
\(type) Example_1 {
|
||||
↓\(type) Example_2 {}
|
||||
}
|
||||
}
|
||||
return 5
|
||||
}
|
||||
"""),
|
||||
|
||||
// didSet is not present in file structure although there is such a swift declaration kind
|
||||
.init("""
|
||||
var example: Int = 5 {
|
||||
didSet {
|
||||
\(type) Example_0 {
|
||||
\(type) Example_1 {
|
||||
↓\(type) Example_2 {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""),
|
||||
|
||||
// extensions are counted as a type level, violation of default maximum type nesting level
|
||||
.init("""
|
||||
extension Example_0 {
|
||||
\(type) Example_1 {
|
||||
↓\(type) Example_2 {}
|
||||
}
|
||||
}
|
||||
""")
|
||||
]
|
||||
}
|
||||
|
||||
private static let triggeringFunctionExamples: [Example] = [
|
||||
// violation of default maximum function nesting level
|
||||
.init("""
|
||||
func f_0() {
|
||||
func f_1() {
|
||||
func f_2() {
|
||||
↓func f_3() {}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""),
|
||||
|
||||
/*
|
||||
all variableKinds of SwiftDeclarationKind (except .varParameter which is a function parameter)
|
||||
are flattend in a file structure so level limits do not change
|
||||
*/
|
||||
.init("""
|
||||
var example: Int {
|
||||
func f_0() {
|
||||
func f_1() {
|
||||
func f_2() {
|
||||
↓func f_3() {}
|
||||
}
|
||||
}
|
||||
}
|
||||
return 5
|
||||
}
|
||||
"""),
|
||||
|
||||
// didSet is not present in file structure although there is such a swift declaration kind
|
||||
.init("""
|
||||
var example: Int = 5 {
|
||||
didSet {
|
||||
func f_0() {
|
||||
func f_1() {
|
||||
func f_2() {
|
||||
↓func f_3() {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""),
|
||||
|
||||
// extensions are counted as a type level, violation of default maximum function nesting level
|
||||
.init("""
|
||||
extension Example_0 {
|
||||
func f_0() {
|
||||
func f_1() {
|
||||
func f_2() {
|
||||
↓func f_3() {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
""")
|
||||
]
|
||||
|
||||
private static let triggeringClosureAndStatementExamples =
|
||||
["class", "struct", "enum"].flatMap { type -> [Example] in
|
||||
[
|
||||
// swich statement example
|
||||
.init("""
|
||||
switch example {
|
||||
case .exampleCase:
|
||||
\(type) Example_0 {
|
||||
\(type) Example_1 {
|
||||
↓\(type) Example_2 {}
|
||||
}
|
||||
}
|
||||
default:
|
||||
func f_0() {
|
||||
func f_1() {
|
||||
func f_2() {
|
||||
↓func f_3() {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""),
|
||||
|
||||
// closure var example
|
||||
.init("""
|
||||
var exampleClosure: () -> Void = {
|
||||
\(type) Example_0 {
|
||||
\(type) Example_1 {
|
||||
↓\(type) Example_2 {}
|
||||
}
|
||||
}
|
||||
func f_0() {
|
||||
func f_1() {
|
||||
func f_2() {
|
||||
↓func f_3() {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""),
|
||||
|
||||
// function closure parameter example
|
||||
.init("""
|
||||
exampleFunc(closure: {
|
||||
\(type) Example_0 {
|
||||
\(type) Example_1 {}
|
||||
}
|
||||
func f_0() {
|
||||
func f_1() {
|
||||
func f_2() {
|
||||
↓func f_3() {}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
""")
|
||||
]
|
||||
}
|
||||
|
||||
private static let triggeringMixedExamples =
|
||||
["class", "struct", "enum"].flatMap { type -> [Example] in
|
||||
[
|
||||
// violation of default maximum nesting level for both type and function (nesting order is arbitrary)
|
||||
.init("""
|
||||
\(type) Example_0 {
|
||||
func f_0() {
|
||||
\(type) Example_1 {
|
||||
func f_1() {
|
||||
func f_2() {
|
||||
↓\(type) Example_2 {}
|
||||
↓func f_3() {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""),
|
||||
|
||||
// violation of default maximum nesting level for both type and function within closures and statements
|
||||
.init("""
|
||||
\(type) Example_0 {
|
||||
func f_0() {
|
||||
switch example {
|
||||
case .exampleCase:
|
||||
\(type) Example_1 {
|
||||
func f_1() {
|
||||
func f_2() {
|
||||
↓\(type) Example_2 {}
|
||||
↓func f_3() {}
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
exampleFunc(closure: {
|
||||
\(type) Example_1 {
|
||||
func f_1() {
|
||||
func f_2() {
|
||||
↓\(type) Example_2 {}
|
||||
↓func f_3() {}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
""")
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,26 @@
|
||||
public struct NestingConfiguration: RuleConfiguration, Equatable {
|
||||
public var consoleDescription: String {
|
||||
return "(type_level) \(typeLevel.shortConsoleDescription), " +
|
||||
"(statement_level) \(statementLevel.shortConsoleDescription)"
|
||||
return "(type_level) \(typeLevel.shortConsoleDescription)"
|
||||
+ ", (function_level) \(functionLevel.shortConsoleDescription)"
|
||||
+ ", (check_nesting_in_closures_and_statements) \(checkNestingInClosuresAndStatements)"
|
||||
+ ", (always_allow_one_type_in_functions) \(alwaysAllowOneTypeInFunctions)"
|
||||
}
|
||||
|
||||
var typeLevel: SeverityLevelsConfiguration
|
||||
var statementLevel: SeverityLevelsConfiguration
|
||||
var functionLevel: SeverityLevelsConfiguration
|
||||
var checkNestingInClosuresAndStatements: Bool
|
||||
var alwaysAllowOneTypeInFunctions: Bool
|
||||
|
||||
public init(typeLevelWarning: Int,
|
||||
typeLevelError: Int?,
|
||||
statementLevelWarning: Int,
|
||||
statementLevelError: Int?) {
|
||||
typeLevel = SeverityLevelsConfiguration(warning: typeLevelWarning, error: typeLevelError)
|
||||
statementLevel = SeverityLevelsConfiguration(warning: statementLevelWarning, error: statementLevelError)
|
||||
functionLevelWarning: Int,
|
||||
functionLevelError: Int?,
|
||||
checkNestingInClosuresAndStatements: Bool = true,
|
||||
alwaysAllowOneTypeInFunctions: Bool = false) {
|
||||
self.typeLevel = SeverityLevelsConfiguration(warning: typeLevelWarning, error: typeLevelError)
|
||||
self.functionLevel = SeverityLevelsConfiguration(warning: functionLevelWarning, error: functionLevelError)
|
||||
self.checkNestingInClosuresAndStatements = checkNestingInClosuresAndStatements
|
||||
self.alwaysAllowOneTypeInFunctions = alwaysAllowOneTypeInFunctions
|
||||
}
|
||||
|
||||
public mutating func apply(configuration: Any) throws {
|
||||
@@ -23,9 +31,12 @@ public struct NestingConfiguration: RuleConfiguration, Equatable {
|
||||
if let typeLevelConfiguration = configurationDict["type_level"] {
|
||||
try typeLevel.apply(configuration: typeLevelConfiguration)
|
||||
}
|
||||
if let statementLevelConfiguration = configurationDict["statement_level"] {
|
||||
try statementLevel.apply(configuration: statementLevelConfiguration)
|
||||
if let functionLevelConfiguration = configurationDict["function_level"] {
|
||||
try functionLevel.apply(configuration: functionLevelConfiguration)
|
||||
}
|
||||
// swiftlint:disable:next line_length
|
||||
checkNestingInClosuresAndStatements = configurationDict["check_nesting_in_closures_and_statements"] as? Bool ?? true
|
||||
alwaysAllowOneTypeInFunctions = configurationDict["always_allow_one_type_in_functions"] as? Bool ?? false
|
||||
}
|
||||
|
||||
func severity(with config: SeverityLevelsConfiguration, for level: Int) -> ViolationSeverity? {
|
||||
|
||||
@@ -1011,7 +1011,9 @@ extension NSObjectPreferIsEqualRuleTests {
|
||||
|
||||
extension NestingRuleTests {
|
||||
static var allTests: [(String, (NestingRuleTests) -> () throws -> Void)] = [
|
||||
("testWithDefaultConfiguration", testWithDefaultConfiguration)
|
||||
("testNestingWithDefaultConfiguration", testNestingWithDefaultConfiguration),
|
||||
("testNestingWithAlwaysAllowOneTypeInFunctions", testNestingWithAlwaysAllowOneTypeInFunctions),
|
||||
("testNestingWithoutCheckNestingInClosuresAndStatements", testNestingWithoutCheckNestingInClosuresAndStatements)
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@@ -432,12 +432,6 @@ class NSObjectPreferIsEqualRuleTests: XCTestCase {
|
||||
}
|
||||
}
|
||||
|
||||
class NestingRuleTests: XCTestCase {
|
||||
func testWithDefaultConfiguration() {
|
||||
verifyRule(NestingRule.description)
|
||||
}
|
||||
}
|
||||
|
||||
class NimbleOperatorRuleTests: XCTestCase {
|
||||
func testWithDefaultConfiguration() {
|
||||
verifyRule(NimbleOperatorRule.description)
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
@testable import SwiftLintFramework
|
||||
import XCTest
|
||||
|
||||
// swiftlint:disable nesting
|
||||
|
||||
class CollectingRuleTests: XCTestCase {
|
||||
func testCollectsIntoStorage() {
|
||||
struct Spec: MockCollectingRule {
|
||||
|
||||
@@ -0,0 +1,377 @@
|
||||
import SwiftLintFramework
|
||||
import XCTest
|
||||
|
||||
// swiftlint:disable:next type_body_length
|
||||
class NestingRuleTests: XCTestCase {
|
||||
func testNestingWithDefaultConfiguration() {
|
||||
verifyRule(NestingRule.description)
|
||||
}
|
||||
|
||||
// swiftlint:disable:next function_body_length
|
||||
func testNestingWithAlwaysAllowOneTypeInFunctions() {
|
||||
var nonTriggeringExamples = NestingRule.description.nonTriggeringExamples
|
||||
nonTriggeringExamples.append(contentsOf: ["class", "struct", "enum"].flatMap { type -> [Example] in
|
||||
[
|
||||
.init("""
|
||||
\(type) Example_0 {
|
||||
\(type) Example_1 {
|
||||
func f_0() {
|
||||
\(type) Example_2 {}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""),
|
||||
|
||||
.init("""
|
||||
\(type) Example_0 {
|
||||
\(type) Example_1 {
|
||||
func f_0() {
|
||||
\(type) Example_2 {
|
||||
func f_1() {
|
||||
\(type) Example_3 {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""),
|
||||
|
||||
.init("""
|
||||
func f_0() {
|
||||
\(type) Example_0 {
|
||||
\(type) Example_1 {}
|
||||
}
|
||||
}
|
||||
""")
|
||||
]
|
||||
})
|
||||
nonTriggeringExamples.append(contentsOf: ["class", "struct", "enum"].flatMap { type -> [Example] in
|
||||
[
|
||||
.init("""
|
||||
exampleFunc(closure: {
|
||||
\(type) Example_0 {
|
||||
\(type) Example_1 {
|
||||
func f_0() {
|
||||
\(type) Example_2 {}
|
||||
}
|
||||
}
|
||||
}
|
||||
func f_0() {
|
||||
\(type) Example_0 {
|
||||
func f_1() {
|
||||
\(type) Example_1 {
|
||||
func f_2() {
|
||||
\(type) Example_2 {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
"""),
|
||||
|
||||
.init("""
|
||||
switch example {
|
||||
case .exampleCase:
|
||||
\(type) Example_0 {
|
||||
\(type) Example_1 {
|
||||
func f_0() {
|
||||
\(type) Example_2 {}
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
func f_0() {
|
||||
\(type) Example_0 {
|
||||
func f_1() {
|
||||
\(type) Example_1 {
|
||||
func f_2() {
|
||||
\(type) Example_2 {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
""")
|
||||
]
|
||||
})
|
||||
|
||||
var triggeringExamples = ["class", "struct", "enum"].flatMap { type -> [Example] in
|
||||
[
|
||||
.init("""
|
||||
\(type) Example_0 {
|
||||
\(type) Example_1 {
|
||||
func f_0() {
|
||||
\(type) Example_2 {
|
||||
↓\(type) Example_3 {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""),
|
||||
|
||||
.init("""
|
||||
\(type) Example_0 {
|
||||
\(type) Example_1 {
|
||||
func f_0() {
|
||||
\(type) Example_2 {
|
||||
func f_1() {
|
||||
\(type) Example_3 {
|
||||
↓\(type) Example_4 {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""),
|
||||
|
||||
.init("""
|
||||
func f_0() {
|
||||
\(type) Example_0 {
|
||||
\(type) Example_1 {
|
||||
↓\(type) Example_2 {}
|
||||
}
|
||||
}
|
||||
}
|
||||
""")
|
||||
]
|
||||
}
|
||||
|
||||
triggeringExamples.append(contentsOf: ["class", "struct", "enum"].flatMap { type -> [Example] in
|
||||
[
|
||||
.init("""
|
||||
exampleFunc(closure: {
|
||||
\(type) Example_0 {
|
||||
\(type) Example_1 {
|
||||
func f_0() {
|
||||
\(type) Example_2 {
|
||||
↓\(type) Example_3 {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
func f_0() {
|
||||
\(type) Example_0 {
|
||||
func f_1() {
|
||||
\(type) Example_1 {
|
||||
func f_2() {
|
||||
\(type) Example_2 {
|
||||
↓\(type) Example_3 {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
"""),
|
||||
|
||||
.init("""
|
||||
switch example {
|
||||
case .exampleCase:
|
||||
\(type) Example_0 {
|
||||
\(type) Example_1 {
|
||||
func f_0() {
|
||||
\(type) Example_2 {
|
||||
↓\(type) Example_3 {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
func f_0() {
|
||||
\(type) Example_0 {
|
||||
func f_1() {
|
||||
\(type) Example_1 {
|
||||
func f_2() {
|
||||
\(type) Example_2 {
|
||||
↓\(type) Example_3 {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
""")
|
||||
]
|
||||
})
|
||||
|
||||
let description = RuleDescription(
|
||||
identifier: NestingRule.description.identifier,
|
||||
name: NestingRule.description.name,
|
||||
description: NestingRule.description.description,
|
||||
kind: .metrics,
|
||||
nonTriggeringExamples: nonTriggeringExamples,
|
||||
triggeringExamples: triggeringExamples
|
||||
)
|
||||
|
||||
verifyRule(description, ruleConfiguration: ["always_allow_one_type_in_functions": true])
|
||||
}
|
||||
|
||||
// swiftlint:disable:next function_body_length
|
||||
func testNestingWithoutCheckNestingInClosuresAndStatements() {
|
||||
var nonTriggeringExamples = NestingRule.description.nonTriggeringExamples
|
||||
nonTriggeringExamples.append(contentsOf: ["class", "struct", "enum"].flatMap { type -> [Example] in
|
||||
[
|
||||
.init("""
|
||||
exampleFunc(closure: {
|
||||
\(type) Example_0 {
|
||||
\(type) Example_1 {
|
||||
\(type) Example_2 {}
|
||||
}
|
||||
}
|
||||
func f_0() {
|
||||
func f_1() {
|
||||
func f_2() {
|
||||
func f_3() {}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
"""),
|
||||
|
||||
.init("""
|
||||
switch example {
|
||||
case .exampleCase:
|
||||
\(type) Example_0 {
|
||||
\(type) Example_1 {
|
||||
\(type) Example 2 {}
|
||||
}
|
||||
}
|
||||
default:
|
||||
func f_0() {
|
||||
func f_1() {
|
||||
func f_2() {
|
||||
func f_3() {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
""")
|
||||
]
|
||||
})
|
||||
|
||||
var triggeringExamples = ["class", "struct", "enum"].flatMap { type -> [Example] in
|
||||
[
|
||||
.init("""
|
||||
\(type) Example_0 {
|
||||
\(type) Example_1 {
|
||||
↓\(type) Example_2 {}
|
||||
}
|
||||
}
|
||||
"""),
|
||||
|
||||
.init("""
|
||||
var example: Int {
|
||||
\(type) Example_0 {
|
||||
\(type) Example_1 {
|
||||
↓\(type) Example_2 {}
|
||||
}
|
||||
}
|
||||
return 5
|
||||
}
|
||||
"""),
|
||||
|
||||
.init("""
|
||||
var example: Int = 5 {
|
||||
didSet {
|
||||
\(type) Example_0 {
|
||||
\(type) Example_1 {
|
||||
↓\(type) Example_2 {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""),
|
||||
|
||||
.init("""
|
||||
extension Example_0 {
|
||||
\(type) Example_1 {
|
||||
↓\(type) Example_2 {}
|
||||
}
|
||||
}
|
||||
"""),
|
||||
|
||||
.init("""
|
||||
\(type) Example_0 {
|
||||
func f_0() {
|
||||
\(type) Example_1 {
|
||||
func f_1() {
|
||||
func f_2() {
|
||||
↓\(type) Example_2 {}
|
||||
↓func f_3() {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
""")
|
||||
]
|
||||
}
|
||||
|
||||
triggeringExamples.append(contentsOf: [
|
||||
.init("""
|
||||
func f_0() {
|
||||
func f_1() {
|
||||
func f_2() {
|
||||
↓func f_3() {}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""),
|
||||
|
||||
.init("""
|
||||
var example: Int {
|
||||
func f_0() {
|
||||
func f_1() {
|
||||
func f_2() {
|
||||
↓func f_3() {}
|
||||
}
|
||||
}
|
||||
}
|
||||
return 5
|
||||
}
|
||||
"""),
|
||||
|
||||
.init("""
|
||||
var example: Int = 5 {
|
||||
didSet {
|
||||
func f_0() {
|
||||
func f_1() {
|
||||
func f_2() {
|
||||
↓func f_3() {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""),
|
||||
|
||||
.init("""
|
||||
extension Example_0 {
|
||||
func f_0() {
|
||||
func f_1() {
|
||||
func f_2() {
|
||||
↓func f_3() {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
""")
|
||||
])
|
||||
|
||||
let description = RuleDescription(
|
||||
identifier: NestingRule.description.identifier,
|
||||
name: NestingRule.description.name,
|
||||
description: NestingRule.description.description,
|
||||
kind: .metrics,
|
||||
nonTriggeringExamples: nonTriggeringExamples,
|
||||
triggeringExamples: triggeringExamples
|
||||
)
|
||||
|
||||
verifyRule(description, ruleConfiguration: ["check_nesting_in_closures_and_statements": false])
|
||||
}
|
||||
}
|
||||
@@ -70,20 +70,24 @@ class RuleConfigurationTests: XCTestCase {
|
||||
"type_level": [
|
||||
"warning": 7, "error": 17
|
||||
],
|
||||
"statement_level": [
|
||||
"function_level": [
|
||||
"warning": 8, "error": 18
|
||||
]
|
||||
],
|
||||
"check_nesting_in_closures_and_statements": false,
|
||||
"always_allow_one_type_in_functions": true
|
||||
] as [String: Any]
|
||||
var nestingConfig = NestingConfiguration(typeLevelWarning: 0,
|
||||
typeLevelError: nil,
|
||||
statementLevelWarning: 0,
|
||||
statementLevelError: nil)
|
||||
functionLevelWarning: 0,
|
||||
functionLevelError: nil)
|
||||
do {
|
||||
try nestingConfig.apply(configuration: config)
|
||||
XCTAssertEqual(nestingConfig.typeLevel.warning, 7)
|
||||
XCTAssertEqual(nestingConfig.statementLevel.warning, 8)
|
||||
XCTAssertEqual(nestingConfig.functionLevel.warning, 8)
|
||||
XCTAssertEqual(nestingConfig.typeLevel.error, 17)
|
||||
XCTAssertEqual(nestingConfig.statementLevel.error, 18)
|
||||
XCTAssertEqual(nestingConfig.functionLevel.error, 18)
|
||||
XCTAssert(nestingConfig.alwaysAllowOneTypeInFunctions)
|
||||
XCTAssert(!nestingConfig.checkNestingInClosuresAndStatements)
|
||||
} catch {
|
||||
XCTFail("Failed to configure nested configurations")
|
||||
}
|
||||
@@ -93,8 +97,8 @@ class RuleConfigurationTests: XCTestCase {
|
||||
let config = 17
|
||||
var nestingConfig = NestingConfiguration(typeLevelWarning: 0,
|
||||
typeLevelError: nil,
|
||||
statementLevelWarning: 0,
|
||||
statementLevelError: nil)
|
||||
functionLevelWarning: 0,
|
||||
functionLevelError: nil)
|
||||
checkError(ConfigurationError.unknownConfiguration) {
|
||||
try nestingConfig.apply(configuration: config)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user