Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Measure complexity of nested functions separately #469

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,15 @@
[Denis Lebedev](https://github.com/garnett)
[#415](https://github.com/realm/SwiftLint/issues/415)

* Measure complexity of nested functions separately in `CyclomaticComplexityRule`.
[Denis Lebedev](https://github.com/garnett)
[#424](https://github.com/realm/SwiftLint/issues/424)

##### Bug Fixes

* None.
* Fix complexity measurement for switch statements in `CyclomaticComplexityRule`.
[Denis Lebedev](https://github.com/garnett)
[#461](https://github.com/realm/SwiftLint/issues/461)

## 0.7.2: Appliance Manual

Expand Down
65 changes: 52 additions & 13 deletions Source/SwiftLintFramework/Rules/CyclomaticComplexityRule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import Foundation
import SourceKittenFramework


public struct CyclomaticComplexityRule: ASTRule, ConfigProviderRule {
public var config = SeverityLevelsConfig(warning: 10, error: 20)

Expand All @@ -20,12 +21,20 @@ public struct CyclomaticComplexityRule: ASTRule, ConfigProviderRule {
description: "Complexity of function bodies should be limited.",
nonTriggeringExamples: [
"func f1() {\nif true {\nfor _ in 1..5 { } }\nif false { }\n}",
"func f3() {while true {}}",
"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 default: break\n }\n\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"
]
)
Expand All @@ -36,8 +45,7 @@ public struct CyclomaticComplexityRule: ASTRule, ConfigProviderRule {
return []
}

let substructure = dictionary["key.substructure"] as? [SourceKitRepresentable] ?? []
let complexity = measureComplexity(substructure) + 1
let complexity = measureComplexity(file, dictionary: dictionary)

for parameter in config.params where complexity > parameter.value {
let offset = Int(dictionary["key.offset"] as? Int64 ?? 0)
Expand All @@ -51,25 +59,56 @@ public struct CyclomaticComplexityRule: ASTRule, ConfigProviderRule {
return []
}

private func measureComplexity(substructure: [SourceKitRepresentable]) -> Int {
return substructure.reduce(0) { complexity, subItem in
private func measureComplexity(file: File,
dictionary: [String: SourceKitRepresentable]) -> Int {
var hasSwitchStatements = false

let substructure = dictionary["key.substructure"] as? [SourceKitRepresentable] ?? []

let complexity = substructure.reduce(0) { complexity, subItem in
guard let subDict = subItem as? [String: SourceKitRepresentable],
key = subDict["key.kind"] as? String else {
kind = subDict["key.kind"] as? String else {
return complexity
}
if let subSubItem = subDict["key.substructure"] as? [SourceKitRepresentable] {
return complexity +
Int(complexityStatements.contains(key)) +
measureComplexity(subSubItem)

if let declarationKid = SwiftDeclarationKind(rawValue: kind)
where functionKinds.contains(declarationKid) {
return complexity
}

if kind == "source.lang.swift.stmt.switch" {
hasSwitchStatements = true
}
return complexity + Int(complexityStatements.contains(key))

return complexity +
Int(complexityStatements.contains(kind)) +
measureComplexity(file, dictionary: subDict)
}

if hasSwitchStatements {
return reduceSwitchComplexity(complexity, file: file, dictionary: dictionary)
}

return complexity
}

// Switch complexity is reduced by `fallthrough` cases

private func reduceSwitchComplexity(complexity: Int, file: File,
dictionary: [String: SourceKitRepresentable]) -> Int {
let bodyOffset = Int(dictionary["key.bodyoffset"] as? Int64 ?? 0)
let bodyLength = Int(dictionary["key.bodylength"] as? Int64 ?? 0)

let c = (file.contents as NSString)
.substringWithByteRange(start: bodyOffset, length: bodyLength) ?? ""

let fallthroughCount = c.componentsSeparatedByString("fallthrough").count - 1
return complexity - fallthroughCount
}

private let complexityStatements = [
"source.lang.swift.stmt.foreach",
"source.lang.swift.stmt.if",
"source.lang.swift.stmt.switch",
"source.lang.swift.stmt.case",
"source.lang.swift.stmt.guard",
"source.lang.swift.stmt.for",
Expand Down