forked from realm/SwiftLint
-
Notifications
You must be signed in to change notification settings - Fork 0
/
CyclomaticComplexityRule.swift
118 lines (96 loc) · 4.39 KB
/
CyclomaticComplexityRule.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
//
// CyclomaticComplexityRule.swift
// SwiftLint
//
// Created by Denis Lebedev on 24/1/16.
// Copyright © 2016 Realm. All rights reserved.
//
import Foundation
import SourceKittenFramework
public struct CyclomaticComplexityRule: ASTRule, ConfigurationProviderRule {
public var configuration = SeverityLevelsConfiguration(warning: 10, error: 20)
public init() {}
public static let description = RuleDescription(
identifier: "cyclomatic_complexity",
name: "Cyclomatic Complexity",
description: "Complexity of function bodies should be limited.",
nonTriggeringExamples: [
"func f1() {\nif true {\nfor _ in 1..5 { } }\nif false { }\n}",
"func f(code: Int) -> Int {" +
"switch code {\n case 0: fallthrough\ncase 0: return 1\ncase 0: return 1\n" +
"case 0: return 1\ncase 0: return 1\ncase 0: return 1\ncase 0: return 1\n" +
"case 0: return 1\ncase 0: return 1\ndefault: return 1}}",
"func f1() {" +
"if true {}; if true {}; if true {}; if true {}; if true {}; if true {}\n" +
"func f2() {\n" +
"if true {}; if true {}; if true {}; if true {}; if true {}\n" +
"}}"
],
triggeringExamples: [
"↓func f1() {\n if true {\n if true {\n if false {}\n }\n" +
" }\n if false {}\n let i = 0\n\n switch i {\n case 1: break\n" +
" case 2: break\n case 3: break\n case 4: break\n default: break\n }\n" +
" for _ in 1...5 {\n guard true else {\n return\n }\n }\n}\n"
]
)
public func validate(file: File, kind: SwiftDeclarationKind,
dictionary: [String: SourceKitRepresentable]) -> [StyleViolation] {
if !SwiftDeclarationKind.functionKinds().contains(kind) {
return []
}
let complexity = measureComplexity(in: file, dictionary: dictionary)
for parameter in configuration.params where complexity > parameter.value {
let offset = dictionary.offset ?? 0
return [StyleViolation(ruleDescription: type(of: self).description,
severity: parameter.severity,
location: Location(file: file, byteOffset: offset),
reason: "Function should have complexity \(configuration.warning) or less: " +
"currently complexity equals \(complexity)")]
}
return []
}
private func measureComplexity(in file: File, dictionary: [String: SourceKitRepresentable]) -> Int {
var hasSwitchStatements = false
let complexity = dictionary.substructure.reduce(0) { complexity, subDict in
guard let kind = subDict.kind else {
return complexity
}
if let declarationKind = SwiftDeclarationKind(rawValue: kind),
SwiftDeclarationKind.functionKinds().contains(declarationKind) {
return complexity
}
guard let statementKind = StatementKind(rawValue: kind) else {
return complexity + measureComplexity(in: file, dictionary: subDict)
}
if statementKind == .switch {
hasSwitchStatements = true
}
return complexity +
(complexityStatements.contains(statementKind) ? 1 : 0) +
measureComplexity(in: file, dictionary: subDict)
}
if hasSwitchStatements {
return reduceSwitchComplexity(initialComplexity: complexity, file: file, dictionary: dictionary)
}
return complexity
}
// Switch complexity is reduced by `fallthrough` cases
private func reduceSwitchComplexity(initialComplexity complexity: Int, file: File,
dictionary: [String: SourceKitRepresentable]) -> Int {
let bodyOffset = dictionary.bodyOffset ?? 0
let bodyLength = dictionary.bodyLength ?? 0
let c = file.contents.bridge()
.substringWithByteRange(start: bodyOffset, length: bodyLength) ?? ""
let fallthroughCount = c.components(separatedBy: "fallthrough").count - 1
return complexity - fallthroughCount
}
private let complexityStatements: [StatementKind] = [
.forEach,
.if,
.case,
.guard,
.for,
.repeatWhile,
.while
]
}