diff --git a/CHANGELOG.md b/CHANGELOG.md index 26eb82b36..3ed87348f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,9 @@ [SimplyDanny](https://github.com/SimplyDanny) [#4860](https://github.com/realm/SwiftLint/issues/4860) +* Fix false positives in `indentation_width` rule. + [Sven Münnich](https://github.com/svenmuennich) + ## 0.51.0: bzllint #### Breaking diff --git a/Source/SwiftLintFramework/Rules/Style/IndentationWidthRule.swift b/Source/SwiftLintFramework/Rules/Style/IndentationWidthRule.swift index 937554376..ccf656d67 100644 --- a/Source/SwiftLintFramework/Rules/Style/IndentationWidthRule.swift +++ b/Source/SwiftLintFramework/Rules/Style/IndentationWidthRule.swift @@ -172,10 +172,21 @@ struct IndentationWidthRule: ConfigurationProviderRule, OptInRule { if configuration.includeMultilineStrings { return false } - if file.syntaxMap.tokens(inByteRange: line.byteRange).kinds == [.string] { - return true + + // A multiline string content line is characterized by beginning with a token of kind string whose range's lower + // bound is smaller than that of the line itself. + let tokensInLine = file.syntaxMap.tokens(inByteRange: line.byteRange) + guard + let firstToken = tokensInLine.first, + firstToken.kind == .string, + firstToken.range.lowerBound < line.byteRange.lowerBound else { + return false } - return false + + // Closing delimiters of a multiline string should follow the defined indentation. The Swift compiler requires + // those delimiters to be on their own line so we need to consider the number of tokens as well as the upper + // bounds. + return tokensInLine.count > 1 || line.byteRange.upperBound < firstToken.range.upperBound } /// Validates whether the indentation of a specific line is valid based on the indentation of the previous line. diff --git a/Tests/SwiftLintFrameworkTests/IndentationWidthRuleTests.swift b/Tests/SwiftLintFrameworkTests/IndentationWidthRuleTests.swift index 129c5b009..8af4a65ab 100644 --- a/Tests/SwiftLintFrameworkTests/IndentationWidthRuleTests.swift +++ b/Tests/SwiftLintFrameworkTests/IndentationWidthRuleTests.swift @@ -196,18 +196,57 @@ class IndentationWidthRuleTests: XCTestCase { """, includeCompilerDirectives: true) } - func testIgnoredMultilineStrings() { - assertNoViolation( - in: "let x = \"\"\"\nstring1\n string2\n string3\n\"\"\"\n", - includeMultilineStrings: false - ) - assert1Violation( - in: "let x = \"\"\"\nstring1\n string2\n string3\n\"\"\"\n" - ) - assertViolations( - in: "let x = \"\"\"\nstring1\n string2\n string3\n string4\n\"\"\"\n", - equals: 2 - ) + func testIncludeMultilineStrings() { + let example0 = #""" + let x = """ + string1 + string2 + string3 + """ + """# + assertNoViolation(in: example0, includeMultilineStrings: false) + assert1Violation(in: example0, includeMultilineStrings: true) + + let example1 = #""" + let x = """ + string1 + string2 + string3 + string4 + """ + """# + assertNoViolation(in: example1, includeMultilineStrings: false) + assertViolations(in: example1, equals: 2, includeMultilineStrings: true) + + let example2 = ##""" + let x = #""" + string1 + """# + """## + assert1Violation(in: example2, includeMultilineStrings: false) + assert1Violation(in: example2, includeMultilineStrings: true) + + let example3 = """ + let x = [ + "key": [ + ["nestedKey": "string"], + ], + ] + """ + assertNoViolation(in: example3, includeMultilineStrings: false) + assertNoViolation(in: example3, includeMultilineStrings: true) + + let example4 = #""" + func test() -> String { + """ + ▿ Type: + - property: \(123) + \(456) + \(true) + """ + } + """# + assertNoViolation(in: example4, includeMultilineStrings: false) + assert1Violation(in: example4, includeMultilineStrings: true) } // MARK: Helpers