forked from realm/SwiftLint
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Prevent concatenation of SwiftUI text elements (#3)
We want to prevent developers from doing things that break translations such as concatenating two `Text` elements in order to apply different styling to each. The proper way to do it is to use `AttributedString` and apply different styling that way. This adds a rule to look for binary `+` operators with `Text.init` calls on both sides.
- Loading branch information
Showing
3 changed files
with
88 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
81 changes: 81 additions & 0 deletions
81
Source/SwiftLintBuiltInRules/Rules/Whatnot/TextConcatenationRule.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
import SwiftLintCore | ||
import SwiftSyntax | ||
|
||
@SwiftSyntaxRule(foldExpressions: true) | ||
struct TextConcatenationRule: Rule { | ||
var configuration = SeverityConfiguration<Self>(.warning) | ||
|
||
static let description = RuleDescription( | ||
identifier: "text_concatenation", | ||
name: "SwiftUI.Text Concatenation", | ||
description: "Avoid concatenating SwiftUI.Text instances", | ||
kind: .lint, | ||
nonTriggeringExamples: [ | ||
Example(""" | ||
Text(string) | ||
"""), | ||
Example(#"Text("wow \(wowee)")"#), | ||
Example(""" | ||
HStack { | ||
Text("foo") | ||
Text("bar") | ||
} | ||
""") | ||
], | ||
triggeringExamples: [ | ||
Example(""" | ||
Text("bar") ↓+ Text("foo") | ||
"""), | ||
Example(""" | ||
Text("wow") | ||
.foregroundColor(.blue) | ||
.font(.heavy) | ||
↓+ | ||
Text("wow2") | ||
.foregroundColor(.black) | ||
""") | ||
] | ||
) | ||
} | ||
|
||
private extension TextConcatenationRule { | ||
final class Visitor: ViolationsSyntaxVisitor<ConfigurationType> { | ||
override func visitPost(_ node: InfixOperatorExprSyntax) { | ||
guard node.operator.as(BinaryOperatorExprSyntax.self)?.operator.text == "+" else { return } | ||
|
||
if recursivelySearchForTextInitializerCall(node.leftOperand) != nil, | ||
recursivelySearchForTextInitializerCall(node.rightOperand) != nil { | ||
violations.append(reason(position: node.operator.positionAfterSkippingLeadingTrivia)) | ||
} | ||
} | ||
|
||
func recursivelySearchForTextInitializerCall(_ node: any ExprSyntaxProtocol) -> FunctionCallExprSyntax? { | ||
if let funcCall = node.as(FunctionCallExprSyntax.self) { | ||
let isTextInit = funcCall.calledExpression.as(DeclReferenceExprSyntax.self)?.baseName.text == "Text" | ||
|
||
if isTextInit { | ||
return funcCall | ||
} else { | ||
return recursivelySearchForTextInitializerCall(funcCall.calledExpression) | ||
} | ||
} else if let memberAccess = node.as(MemberAccessExprSyntax.self), let base = memberAccess.base { | ||
return recursivelySearchForTextInitializerCall(base) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func reason(position: AbsolutePosition) -> ReasonedRuleViolation { | ||
.init( | ||
position: position, | ||
reason: """ | ||
Avoid concatenating Swift.Text elements with '+' because it breaks translations. \ | ||
Use AttributedString.init if you need to apply multiple styles inside a single string | ||
""", | ||
severity: .warning | ||
) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters