Skip to content

Commit 783ba1d

Browse files
committed
Add autocorrection support to indentation_width rule
1 parent a7878fc commit 783ba1d

2 files changed

Lines changed: 103 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,11 @@
5252
[kapitoshka438](https://github.com/kapitoshka438)
5353
[#6045](https://github.com/realm/SwiftLint/issues/6045)
5454

55+
* Add autocorrection support to `indentation_width` rule to automatically fix
56+
indentation violations using the `--fix` option.
57+
[nadeemnali](https://github.com/nadeemnali)
58+
[#6497](https://github.com/realm/SwiftLint/issues/6497)
59+
5560
### Bug Fixes
5661

5762
* Detect and autocorrect missing whitespace before `else` in `guard`

Source/SwiftLintBuiltInRules/Rules/Style/IndentationWidthRule.swift

Lines changed: 98 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import Foundation
22
import SourceKittenFramework
33

44
@DisabledWithoutSourceKit
5-
struct IndentationWidthRule: OptInRule {
5+
struct IndentationWidthRule: OptInRule, CorrectableRule {
66
// MARK: - Subtypes
77
private enum Indentation: Equatable {
88
case tabs(Int)
@@ -202,4 +202,101 @@ struct IndentationWidthRule: OptInRule {
202202
) // Allow unindent if it stays in the grid
203203
)
204204
}
205+
206+
// MARK: - Methods: Correction
207+
func correct(file: SwiftLintFile) -> Int {
208+
var corrections = 0
209+
var previousLineIndentations: [Indentation] = []
210+
var correctedLines = file.lines.map(\.content)
211+
212+
for (lineIndex, line) in file.lines.enumerated() {
213+
corrections += correctLine(
214+
at: lineIndex,
215+
line: line,
216+
file: file,
217+
in: &correctedLines,
218+
trackingIndentations: &previousLineIndentations
219+
)
220+
}
221+
222+
if corrections > 0 {
223+
let correctedContent = correctedLines.joined(separator: "\n")
224+
file.write(correctedContent)
225+
}
226+
227+
return corrections
228+
}
229+
230+
private func correctLine(
231+
at lineIndex: Int,
232+
line: Line,
233+
file: SwiftLintFile,
234+
in correctedLines: inout [String],
235+
trackingIndentations previousLineIndentations: inout [Indentation]
236+
) -> Int {
237+
if ignoreCompilerDirective(line: line, in: file) { return 0 }
238+
let indentationCharacterCount = line.content.countOfLeadingCharacters(in: CharacterSet(charactersIn: " \t"))
239+
if line.content.count == indentationCharacterCount { return 0 }
240+
if ignoreComment(line: line, in: file) || ignoreMultilineStrings(line: line, in: file) { return 0 }
241+
242+
let prefix = String(line.content.prefix(indentationCharacterCount))
243+
let tabCount = prefix.filter { $0 == "\t" }.count
244+
let spaceCount = prefix.filter { $0 == " " }.count
245+
246+
if tabCount != 0, spaceCount != 0 { return 0 }
247+
248+
let indentation: Indentation = tabCount != 0 ? .tabs(tabCount) : .spaces(spaceCount)
249+
250+
guard previousLineIndentations.isNotEmpty else {
251+
previousLineIndentations = [indentation]
252+
if indentation != .spaces(0) {
253+
correctedLines[lineIndex] = String(line.content.dropFirst(indentationCharacterCount))
254+
return 1
255+
}
256+
return 0
257+
}
258+
259+
let linesValidationResult = previousLineIndentations.map {
260+
validate(indentation: indentation, comparingTo: $0)
261+
}
262+
263+
if linesValidationResult.contains(true) {
264+
if linesValidationResult.first == true {
265+
previousLineIndentations = [indentation]
266+
} else {
267+
previousLineIndentations.append(indentation)
268+
}
269+
return 0
270+
}
271+
272+
guard let lastValidIndentation = previousLineIndentations.first else { return 0 }
273+
274+
let correctIndentLevel = lastValidIndentation.spacesEquivalent(indentationWidth: configuration.indentationWidth)
275+
let shouldUseTabs = tabCount > 0
276+
let correctIndent = generateIndentation(spaceCount: correctIndentLevel, usesTabs: shouldUseTabs)
277+
let lineContent = String(line.content.dropFirst(indentationCharacterCount))
278+
correctedLines[lineIndex] = correctIndent + lineContent
279+
280+
let correctedIndentation: Indentation = shouldUseTabs
281+
? .tabs(correctIndent.filter { $0 == "\t" }.count)
282+
: .spaces(correctIndent.filter { $0 == " " }.count)
283+
previousLineIndentations = [correctedIndentation]
284+
285+
return 1
286+
}
287+
288+
/// Generates an indentation string based on the number of spaces and whether tabs should be used.
289+
///
290+
/// - parameter spaceCount: The number of space-equivalents needed.
291+
/// - parameter usesTabs: Whether the indentation should use tabs.
292+
///
293+
/// - returns: The generated indentation string.
294+
private func generateIndentation(spaceCount: Int, usesTabs: Bool) -> String {
295+
if usesTabs {
296+
let tabCount = spaceCount / configuration.indentationWidth
297+
let remainingSpaces = spaceCount % configuration.indentationWidth
298+
return String(repeating: "\t", count: tabCount) + String(repeating: " ", count: remainingSpaces)
299+
}
300+
return String(repeating: " ", count: spaceCount)
301+
}
205302
}

0 commit comments

Comments
 (0)