Files
SwiftLint/Source/SwiftLintFramework/Rules/Metrics/NestingRuleExamples.swift
T
JP Simard fa6bf50a22 Rethink body line count calculation (#4369)
A long-standing limitation with SourceKit's "editor open" request is
that we weren't able to get certain tokens, such as braces, brackets and
parentheses.

This meant that this code block would be counted as two lines:

```swift
print(
  "hi"
)
```

because the trailing `)` would be treated as a whitespace line.

This meant that our "body length" family of rules that measure the
effective line count of declarations like functions, types or closures
would often significantly under-count the number of content lines in a
body.

Now with SwiftSyntax, we can get all tokens, including the ones
SourceKit was previously ignoring, so we can get much more accurate line
counts when ignoring whitespace and comments.

In addition, we weren't very thorough in how we measured body length.

As an exercise, how many lines long would you say the body of this
function is?

```swift
func hello() {
  print("hello")
}
```

Does the body span one line or three lines?

I propose that we consistently ignore the left and right brace lines
when calculating the body line count of these scopes so that we measure
body line counts like this:

```swift
// 1 line
{ print("foo") }
// 1 line
{
}
// 1 line
{
  print("foo")
}
// 2 lines
{
  let sum = 1 + 2
  print(sum)
}
```

Now with those changes in place, in order to keep the default
configuration thresholds to similar levels as before, we need to adjust
them slightly. Here's what I'm suggesting:

|Rule|Before|After|
|-|-|-|
|closure_body_length|20/100|30/100|
|function_body_length|40/100|50/100|
|type_body_length|200/350|250/350|

This is a pretty significant breaking change and I suspect we'll hear
from users who are surprised that some of their declarations now exceed
the rule limits, but I believe this new approach to calculating body
lines is more correct and intuitive compared to what we've had until
now.

OSSCheck is also going to report a bazillion changes with this, which is
expected given the scope of this change.
2022-10-14 03:16:26 -04:00

416 lines
14 KiB
Swift

// swiftlint:disable file_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() {}
}
}
}
})
}
}
}
""")
]
}
}