Skip to content
Open
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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@

### Bug Fixes

* Ensure that `indentation_width` flags under-indented closing braces while
still avoiding duplicate warnings on the next outer closing brace.
[theamodhshetty](https://github.com/theamodhshetty)
[#6498](https://github.com/realm/SwiftLint/issues/6498)

* Add an `ignore_attributes` option to `implicit_optional_initialization` so
wrappers/attributes that require explicit `= nil` can be excluded from
style checks for both `style: always` and `style: never`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ struct IndentationWidthRule: OptInRule {
func validate(file: SwiftLintFile) -> [StyleViolation] { // swiftlint:disable:this function_body_length
var violations: [StyleViolation] = []
var previousLineIndentations: [Indentation] = []
var previousLineStartedWithClosingDelimiter = false
var previousLineWasInvalid = false

for line in file.lines {
if ignoreCompilerDirective(line: line, in: file) { continue }
Expand Down Expand Up @@ -85,6 +87,8 @@ struct IndentationWidthRule: OptInRule {
// Catch indented first line
guard previousLineIndentations.isNotEmpty else {
previousLineIndentations = [indentation]
previousLineStartedWithClosingDelimiter = startsWithClosingDelimiter(in: line.content)
previousLineWasInvalid = false

if indentation != .spaces(0) {
// There's an indentation although this is the first line!
Expand All @@ -101,12 +105,18 @@ struct IndentationWidthRule: OptInRule {
continue
}

let linesValidationResult = previousLineIndentations.map {
validate(indentation: indentation, comparingTo: $0)
}
let startsWithClosingDelimiter = startsWithClosingDelimiter(in: line.content)
let linesValidationResult = validationResults(
for: indentation,
previousLineIndentations: previousLineIndentations,
startsWithClosingDelimiter: startsWithClosingDelimiter,
previousLineStartedWithClosingDelimiter: previousLineStartedWithClosingDelimiter,
previousLineWasInvalid: previousLineWasInvalid
)

// Catch wrong indentation or wrong unindentation
if !linesValidationResult.contains(true) {
let isValidLine = linesValidationResult.contains(true)
if !isValidLine {
let isIndentation = previousLineIndentations.last.map {
indentation.spacesEquivalent(indentationWidth: configuration.indentationWidth) >=
$0.spacesEquivalent(indentationWidth: configuration.indentationWidth)
Expand Down Expand Up @@ -136,6 +146,9 @@ struct IndentationWidthRule: OptInRule {
// This mechanism avoids duplicate warnings.
previousLineIndentations.append(indentation)
}

previousLineStartedWithClosingDelimiter = startsWithClosingDelimiter
previousLineWasInvalid = !isValidLine
}

return violations
Expand Down Expand Up @@ -202,4 +215,58 @@ struct IndentationWidthRule: OptInRule {
) // Allow unindent if it stays in the grid
)
}

private func validateClosingDelimiterLine(
indentation: Indentation,
previousIndentation: Indentation,
previousLineStartedWithClosingDelimiter: Bool,
previousLineWasInvalid: Bool
) -> Bool {
let currentSpaceEquivalent = indentation.spacesEquivalent(indentationWidth: configuration.indentationWidth)
let previousSpaceEquivalent = previousIndentation.spacesEquivalent(
indentationWidth: configuration.indentationWidth
)

return (
currentSpaceEquivalent == previousSpaceEquivalent - configuration.indentationWidth ||
(
previousLineStartedWithClosingDelimiter &&
previousLineWasInvalid &&
currentSpaceEquivalent == previousSpaceEquivalent
)
)
}

private func validationResults(
for indentation: Indentation,
previousLineIndentations: [Indentation],
startsWithClosingDelimiter: Bool,
previousLineStartedWithClosingDelimiter: Bool,
previousLineWasInvalid: Bool
) -> [Bool] {
if
startsWithClosingDelimiter,
let previousIndentation = previousLineIndentations.last {
return [
validateClosingDelimiterLine(
indentation: indentation,
previousIndentation: previousIndentation,
previousLineStartedWithClosingDelimiter: previousLineStartedWithClosingDelimiter,
previousLineWasInvalid: previousLineWasInvalid
),
]
}

return previousLineIndentations.map {
validate(indentation: indentation, comparingTo: $0)
}
}

private func startsWithClosingDelimiter(in lineContent: String) -> Bool {
guard let firstCharacter = lineContent.trimmingCharacters(in: .whitespacesAndNewlines).first else {
return false
}

return firstCharacter == "}" || firstCharacter == "]" || firstCharacter == ")"
}
}
36 changes: 36 additions & 0 deletions Tests/BuiltInRulesTests/IndentationWidthRuleTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,42 @@ final class IndentationWidthRuleTests: SwiftLintTestCase {
assertNoViolation(in: "firstLine\n\tsecondLine\n\t\tthirdLine\n\t\t\tfourthLine\nfifthLine")
}

func testClosingBraceIndentation() {
assert1Violation(in: """
import SwiftUI

struct TestView: View {
var body: some View {
VStack {
Text("Hello")
}
.onTapGesture {
if true {
print("inside if")
}
}
}
}
""")

assertNoViolation(in: """
import SwiftUI

struct TestView: View {
var body: some View {
VStack {
Text("Hello")
}
.onTapGesture {
if true {
print("inside if")
}
}
}
}
""")
}

/// It's okay to have empty lines between iff the following indentations obey the rules.
func testEmptyLinesBetween() {
assertNoViolation(in: "firstLine\n\tsecondLine\n\n\tfourthLine")
Expand Down