From df322525ead5c9b476f7cadfbaec807ae282d45d Mon Sep 17 00:00:00 2001 From: Nadeem Ali Date: Mon, 20 Apr 2026 20:50:04 +0100 Subject: [PATCH 1/3] Add autocorrection support to indentation_width rule --- CHANGELOG.md | 4 + .../Rules/Style/IndentationWidthRule.swift | 99 ++++++++++++++++++- 2 files changed, 102 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index df3ac1957c..a1a18dae94 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -71,6 +71,10 @@ The modern replacement is safer, cleaner, Retina-aware and more performant. [Dimitri Dupuis-Latour](https://github.com/DimDL) [#6268](https://github.com/realm/SwiftLint/issues/6268) +* Add autocorrection support to `indentation_width` rule to automatically fix + indentation violations using the `--fix` option. + [nadeemnali](https://github.com/nadeemnali) + [#6497](https://github.com/realm/SwiftLint/issues/6497) ### Bug Fixes diff --git a/Source/SwiftLintBuiltInRules/Rules/Style/IndentationWidthRule.swift b/Source/SwiftLintBuiltInRules/Rules/Style/IndentationWidthRule.swift index bc8b11ace0..d1239f70c7 100644 --- a/Source/SwiftLintBuiltInRules/Rules/Style/IndentationWidthRule.swift +++ b/Source/SwiftLintBuiltInRules/Rules/Style/IndentationWidthRule.swift @@ -3,7 +3,7 @@ import SourceKittenFramework import SwiftSyntax @DisabledWithoutSourceKit -struct IndentationWidthRule: OptInRule { +struct IndentationWidthRule: OptInRule, CorrectableRule { // MARK: - Subtypes private enum Indentation: Equatable { case tabs(Int) @@ -279,6 +279,103 @@ struct IndentationWidthRule: OptInRule { ) // Allow unindent if it stays in the grid ) } + + // MARK: - Methods: Correction + func correct(file: SwiftLintFile) -> Int { + var corrections = 0 + var previousLineIndentations: [Indentation] = [] + var correctedLines = file.lines.map(\.content) + + for (lineIndex, line) in file.lines.enumerated() { + corrections += correctLine( + at: lineIndex, + line: line, + file: file, + in: &correctedLines, + trackingIndentations: &previousLineIndentations + ) + } + + if corrections > 0 { + let correctedContent = correctedLines.joined(separator: "\n") + file.write(correctedContent) + } + + return corrections + } + + private func correctLine( + at lineIndex: Int, + line: Line, + file: SwiftLintFile, + in correctedLines: inout [String], + trackingIndentations previousLineIndentations: inout [Indentation] + ) -> Int { + if ignoreCompilerDirective(line: line, in: file) { return 0 } + let indentationCharacterCount = line.content.countOfLeadingCharacters(in: CharacterSet(charactersIn: " \t")) + if line.content.count == indentationCharacterCount { return 0 } + if ignoreComment(line: line, in: file) || ignoreMultilineStrings(line: line, in: file) { return 0 } + + let prefix = String(line.content.prefix(indentationCharacterCount)) + let tabCount = prefix.filter { $0 == "\t" }.count + let spaceCount = prefix.filter { $0 == " " }.count + + if tabCount != 0, spaceCount != 0 { return 0 } + + let indentation: Indentation = tabCount != 0 ? .tabs(tabCount) : .spaces(spaceCount) + + guard previousLineIndentations.isNotEmpty else { + previousLineIndentations = [indentation] + if indentation != .spaces(0) { + correctedLines[lineIndex] = String(line.content.dropFirst(indentationCharacterCount)) + return 1 + } + return 0 + } + + let linesValidationResult = previousLineIndentations.map { + validate(indentation: indentation, comparingTo: $0) + } + + if linesValidationResult.contains(true) { + if linesValidationResult.first == true { + previousLineIndentations = [indentation] + } else { + previousLineIndentations.append(indentation) + } + return 0 + } + + guard let lastValidIndentation = previousLineIndentations.first else { return 0 } + + let correctIndentLevel = lastValidIndentation.spacesEquivalent(indentationWidth: configuration.indentationWidth) + let shouldUseTabs = tabCount > 0 + let correctIndent = generateIndentation(spaceCount: correctIndentLevel, usesTabs: shouldUseTabs) + let lineContent = String(line.content.dropFirst(indentationCharacterCount)) + correctedLines[lineIndex] = correctIndent + lineContent + + let correctedIndentation: Indentation = shouldUseTabs + ? .tabs(correctIndent.filter { $0 == "\t" }.count) + : .spaces(correctIndent.filter { $0 == " " }.count) + previousLineIndentations = [correctedIndentation] + + return 1 + } + + /// Generates an indentation string based on the number of spaces and whether tabs should be used. + /// + /// - parameter spaceCount: The number of space-equivalents needed. + /// - parameter usesTabs: Whether the indentation should use tabs. + /// + /// - returns: The generated indentation string. + private func generateIndentation(spaceCount: Int, usesTabs: Bool) -> String { + if usesTabs { + let tabCount = spaceCount / configuration.indentationWidth + let remainingSpaces = spaceCount % configuration.indentationWidth + return String(repeating: "\t", count: tabCount) + String(repeating: " ", count: remainingSpaces) + } + return String(repeating: " ", count: spaceCount) + } } private final class MultilineConditionLineVisitor: SyntaxVisitor { From 1eaebbe2b51a7c03c9f1a5a9276635ab6c757cc5 Mon Sep 17 00:00:00 2001 From: Nadeem Ali Date: Mon, 20 Apr 2026 20:50:04 +0100 Subject: [PATCH 2/3] Add autocorrection support to indentation_width rule --- CHANGELOG.md | 4 + .../Rules/Style/IndentationWidthRule.swift | 99 ++++++++++++++++++- 2 files changed, 102 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index be786e3de4..bca92d4651 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -71,6 +71,10 @@ The modern replacement is safer, cleaner, Retina-aware and more performant. [Dimitri Dupuis-Latour](https://github.com/DimDL) [#6268](https://github.com/realm/SwiftLint/issues/6268) +* Add autocorrection support to `indentation_width` rule to automatically fix + indentation violations using the `--fix` option. + [nadeemnali](https://github.com/nadeemnali) + [#6497](https://github.com/realm/SwiftLint/issues/6497) * Support access level modifiers on imports in `unused_imports` rule. [SimplyDanny](https://github.com/SimplyDanny) diff --git a/Source/SwiftLintBuiltInRules/Rules/Style/IndentationWidthRule.swift b/Source/SwiftLintBuiltInRules/Rules/Style/IndentationWidthRule.swift index bc8b11ace0..d1239f70c7 100644 --- a/Source/SwiftLintBuiltInRules/Rules/Style/IndentationWidthRule.swift +++ b/Source/SwiftLintBuiltInRules/Rules/Style/IndentationWidthRule.swift @@ -3,7 +3,7 @@ import SourceKittenFramework import SwiftSyntax @DisabledWithoutSourceKit -struct IndentationWidthRule: OptInRule { +struct IndentationWidthRule: OptInRule, CorrectableRule { // MARK: - Subtypes private enum Indentation: Equatable { case tabs(Int) @@ -279,6 +279,103 @@ struct IndentationWidthRule: OptInRule { ) // Allow unindent if it stays in the grid ) } + + // MARK: - Methods: Correction + func correct(file: SwiftLintFile) -> Int { + var corrections = 0 + var previousLineIndentations: [Indentation] = [] + var correctedLines = file.lines.map(\.content) + + for (lineIndex, line) in file.lines.enumerated() { + corrections += correctLine( + at: lineIndex, + line: line, + file: file, + in: &correctedLines, + trackingIndentations: &previousLineIndentations + ) + } + + if corrections > 0 { + let correctedContent = correctedLines.joined(separator: "\n") + file.write(correctedContent) + } + + return corrections + } + + private func correctLine( + at lineIndex: Int, + line: Line, + file: SwiftLintFile, + in correctedLines: inout [String], + trackingIndentations previousLineIndentations: inout [Indentation] + ) -> Int { + if ignoreCompilerDirective(line: line, in: file) { return 0 } + let indentationCharacterCount = line.content.countOfLeadingCharacters(in: CharacterSet(charactersIn: " \t")) + if line.content.count == indentationCharacterCount { return 0 } + if ignoreComment(line: line, in: file) || ignoreMultilineStrings(line: line, in: file) { return 0 } + + let prefix = String(line.content.prefix(indentationCharacterCount)) + let tabCount = prefix.filter { $0 == "\t" }.count + let spaceCount = prefix.filter { $0 == " " }.count + + if tabCount != 0, spaceCount != 0 { return 0 } + + let indentation: Indentation = tabCount != 0 ? .tabs(tabCount) : .spaces(spaceCount) + + guard previousLineIndentations.isNotEmpty else { + previousLineIndentations = [indentation] + if indentation != .spaces(0) { + correctedLines[lineIndex] = String(line.content.dropFirst(indentationCharacterCount)) + return 1 + } + return 0 + } + + let linesValidationResult = previousLineIndentations.map { + validate(indentation: indentation, comparingTo: $0) + } + + if linesValidationResult.contains(true) { + if linesValidationResult.first == true { + previousLineIndentations = [indentation] + } else { + previousLineIndentations.append(indentation) + } + return 0 + } + + guard let lastValidIndentation = previousLineIndentations.first else { return 0 } + + let correctIndentLevel = lastValidIndentation.spacesEquivalent(indentationWidth: configuration.indentationWidth) + let shouldUseTabs = tabCount > 0 + let correctIndent = generateIndentation(spaceCount: correctIndentLevel, usesTabs: shouldUseTabs) + let lineContent = String(line.content.dropFirst(indentationCharacterCount)) + correctedLines[lineIndex] = correctIndent + lineContent + + let correctedIndentation: Indentation = shouldUseTabs + ? .tabs(correctIndent.filter { $0 == "\t" }.count) + : .spaces(correctIndent.filter { $0 == " " }.count) + previousLineIndentations = [correctedIndentation] + + return 1 + } + + /// Generates an indentation string based on the number of spaces and whether tabs should be used. + /// + /// - parameter spaceCount: The number of space-equivalents needed. + /// - parameter usesTabs: Whether the indentation should use tabs. + /// + /// - returns: The generated indentation string. + private func generateIndentation(spaceCount: Int, usesTabs: Bool) -> String { + if usesTabs { + let tabCount = spaceCount / configuration.indentationWidth + let remainingSpaces = spaceCount % configuration.indentationWidth + return String(repeating: "\t", count: tabCount) + String(repeating: " ", count: remainingSpaces) + } + return String(repeating: " ", count: spaceCount) + } } private final class MultilineConditionLineVisitor: SyntaxVisitor { From fd5eac95dbf38af82f26666c9c33b9ec52f211d9 Mon Sep 17 00:00:00 2001 From: Nadeem Ali Date: Sun, 17 May 2026 15:56:59 +0100 Subject: [PATCH 3/3] Fixed line length --- .../Rules/Style/IndentationWidthRule.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Source/SwiftLintBuiltInRules/Rules/Style/IndentationWidthRule.swift b/Source/SwiftLintBuiltInRules/Rules/Style/IndentationWidthRule.swift index a5c9db5eb8..562beb77ed 100644 --- a/Source/SwiftLintBuiltInRules/Rules/Style/IndentationWidthRule.swift +++ b/Source/SwiftLintBuiltInRules/Rules/Style/IndentationWidthRule.swift @@ -319,7 +319,8 @@ struct IndentationWidthRule: OptInRule, CorrectableRule { // Indentation is wrong, fix it let lastValidIndentation = previousLineIndentations[0] - let correctIndentLevel = lastValidIndentation.spacesEquivalent(indentationWidth: configuration.indentationWidth) + let correctIndentLevel = lastValidIndentation.spacesEquivalent( + indentationWidth: configuration.indentationWidth) let shouldUseTabs = prefix.tabCount > 0 let correctIndent = generateIndentation(spaceCount: correctIndentLevel, usesTabs: shouldUseTabs) let lineContent = String(line.content.dropFirst(indentationCharacterCount))