Skip to content

Commit df32252

Browse files
committed
Add autocorrection support to indentation_width rule
1 parent ef026db commit df32252

2 files changed

Lines changed: 102 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,10 @@
7171
The modern replacement is safer, cleaner, Retina-aware and more performant.
7272
[Dimitri Dupuis-Latour](https://github.com/DimDL)
7373
[#6268](https://github.com/realm/SwiftLint/issues/6268)
74+
* Add autocorrection support to `indentation_width` rule to automatically fix
75+
indentation violations using the `--fix` option.
76+
[nadeemnali](https://github.com/nadeemnali)
77+
[#6497](https://github.com/realm/SwiftLint/issues/6497)
7478

7579
### Bug Fixes
7680

Source/SwiftLintBuiltInRules/Rules/Style/IndentationWidthRule.swift

Lines changed: 98 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import SourceKittenFramework
33
import SwiftSyntax
44

55
@DisabledWithoutSourceKit
6-
struct IndentationWidthRule: OptInRule {
6+
struct IndentationWidthRule: OptInRule, CorrectableRule {
77
// MARK: - Subtypes
88
private enum Indentation: Equatable {
99
case tabs(Int)
@@ -279,6 +279,103 @@ struct IndentationWidthRule: OptInRule {
279279
) // Allow unindent if it stays in the grid
280280
)
281281
}
282+
283+
// MARK: - Methods: Correction
284+
func correct(file: SwiftLintFile) -> Int {
285+
var corrections = 0
286+
var previousLineIndentations: [Indentation] = []
287+
var correctedLines = file.lines.map(\.content)
288+
289+
for (lineIndex, line) in file.lines.enumerated() {
290+
corrections += correctLine(
291+
at: lineIndex,
292+
line: line,
293+
file: file,
294+
in: &correctedLines,
295+
trackingIndentations: &previousLineIndentations
296+
)
297+
}
298+
299+
if corrections > 0 {
300+
let correctedContent = correctedLines.joined(separator: "\n")
301+
file.write(correctedContent)
302+
}
303+
304+
return corrections
305+
}
306+
307+
private func correctLine(
308+
at lineIndex: Int,
309+
line: Line,
310+
file: SwiftLintFile,
311+
in correctedLines: inout [String],
312+
trackingIndentations previousLineIndentations: inout [Indentation]
313+
) -> Int {
314+
if ignoreCompilerDirective(line: line, in: file) { return 0 }
315+
let indentationCharacterCount = line.content.countOfLeadingCharacters(in: CharacterSet(charactersIn: " \t"))
316+
if line.content.count == indentationCharacterCount { return 0 }
317+
if ignoreComment(line: line, in: file) || ignoreMultilineStrings(line: line, in: file) { return 0 }
318+
319+
let prefix = String(line.content.prefix(indentationCharacterCount))
320+
let tabCount = prefix.filter { $0 == "\t" }.count
321+
let spaceCount = prefix.filter { $0 == " " }.count
322+
323+
if tabCount != 0, spaceCount != 0 { return 0 }
324+
325+
let indentation: Indentation = tabCount != 0 ? .tabs(tabCount) : .spaces(spaceCount)
326+
327+
guard previousLineIndentations.isNotEmpty else {
328+
previousLineIndentations = [indentation]
329+
if indentation != .spaces(0) {
330+
correctedLines[lineIndex] = String(line.content.dropFirst(indentationCharacterCount))
331+
return 1
332+
}
333+
return 0
334+
}
335+
336+
let linesValidationResult = previousLineIndentations.map {
337+
validate(indentation: indentation, comparingTo: $0)
338+
}
339+
340+
if linesValidationResult.contains(true) {
341+
if linesValidationResult.first == true {
342+
previousLineIndentations = [indentation]
343+
} else {
344+
previousLineIndentations.append(indentation)
345+
}
346+
return 0
347+
}
348+
349+
guard let lastValidIndentation = previousLineIndentations.first else { return 0 }
350+
351+
let correctIndentLevel = lastValidIndentation.spacesEquivalent(indentationWidth: configuration.indentationWidth)
352+
let shouldUseTabs = tabCount > 0
353+
let correctIndent = generateIndentation(spaceCount: correctIndentLevel, usesTabs: shouldUseTabs)
354+
let lineContent = String(line.content.dropFirst(indentationCharacterCount))
355+
correctedLines[lineIndex] = correctIndent + lineContent
356+
357+
let correctedIndentation: Indentation = shouldUseTabs
358+
? .tabs(correctIndent.filter { $0 == "\t" }.count)
359+
: .spaces(correctIndent.filter { $0 == " " }.count)
360+
previousLineIndentations = [correctedIndentation]
361+
362+
return 1
363+
}
364+
365+
/// Generates an indentation string based on the number of spaces and whether tabs should be used.
366+
///
367+
/// - parameter spaceCount: The number of space-equivalents needed.
368+
/// - parameter usesTabs: Whether the indentation should use tabs.
369+
///
370+
/// - returns: The generated indentation string.
371+
private func generateIndentation(spaceCount: Int, usesTabs: Bool) -> String {
372+
if usesTabs {
373+
let tabCount = spaceCount / configuration.indentationWidth
374+
let remainingSpaces = spaceCount % configuration.indentationWidth
375+
return String(repeating: "\t", count: tabCount) + String(repeating: " ", count: remainingSpaces)
376+
}
377+
return String(repeating: " ", count: spaceCount)
378+
}
282379
}
283380

284381
private final class MultilineConditionLineVisitor: SyntaxVisitor {

0 commit comments

Comments
 (0)