Skip to content

Commit

Permalink
Rewrite object_literal rule with SwiftSyntax (realm#4525)
Browse files Browse the repository at this point in the history
  • Loading branch information
marcelofabri authored Nov 7, 2022
1 parent 6c7e210 commit eaf7db1
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 48 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@
- `nslocalizedstring_require_bundle`
- `nsobject_prefer_isequal`
- `number_separator`
- `object_literal`
- `operator_whitespace`
- `optional_enum_case_matching`
- `orphaned_doc_comment`
Expand Down
116 changes: 68 additions & 48 deletions Source/SwiftLintFramework/Rules/Idiomatic/ObjectLiteralRule.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import SourceKittenFramework
import SwiftSyntax

public struct ObjectLiteralRule: ASTRule, ConfigurationProviderRule, OptInRule {
public struct ObjectLiteralRule: SwiftSyntaxRule, ConfigurationProviderRule, OptInRule {
public var configuration = ObjectLiteralConfiguration()

public init() {}
Expand Down Expand Up @@ -33,67 +33,87 @@ public struct ObjectLiteralRule: ASTRule, ConfigurationProviderRule, OptInRule {
}
)

public func validate(file: SwiftLintFile, kind: SwiftExpressionKind,
dictionary: SourceKittenDictionary) -> [StyleViolation] {
guard kind == .call,
let offset = dictionary.offset,
(configuration.imageLiteral && isImageNamedInit(dictionary: dictionary, file: file)) ||
(configuration.colorLiteral && isColorInit(dictionary: dictionary, file: file)) else {
return []
public func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor {
Visitor(validateImageLiteral: configuration.imageLiteral, validateColorLiteral: configuration.colorLiteral)
}
}

private extension ObjectLiteralRule {
final class Visitor: ViolationsSyntaxVisitor {
private let validateImageLiteral: Bool
private let validateColorLiteral: Bool

init(validateImageLiteral: Bool, validateColorLiteral: Bool) {
self.validateImageLiteral = validateImageLiteral
self.validateColorLiteral = validateColorLiteral
super.init(viewMode: .sourceAccurate)
}

return [
StyleViolation(ruleDescription: Self.description,
severity: configuration.severityConfiguration.severity,
location: Location(file: file, byteOffset: offset))
]
}
override func visitPost(_ node: FunctionCallExprSyntax) {
guard validateColorLiteral || validateImageLiteral else {
return
}

private func isImageNamedInit(dictionary: SourceKittenDictionary, file: SwiftLintFile) -> Bool {
guard let name = dictionary.name,
inits(forClasses: ["UIImage", "NSImage"]).contains(name),
case let arguments = dictionary.enclosedArguments,
arguments.compactMap({ $0.name }) == ["named"],
let argument = arguments.first,
case let kinds = kinds(forArgument: argument, file: file),
kinds == [.string] else {
let name = node.calledExpression.withoutTrivia().description
if validateImageLiteral, isImageNamedInit(node: node, name: name) {
violations.append(node.positionAfterSkippingLeadingTrivia)
} else if validateColorLiteral, isColorInit(node: node, name: name) {
violations.append(node.positionAfterSkippingLeadingTrivia)
}
}

private func isImageNamedInit(node: FunctionCallExprSyntax, name: String) -> Bool {
guard inits(forClasses: ["UIImage", "NSImage"]).contains(name),
node.argumentList.compactMap(\.label?.text) == ["named"],
let argument = node.argumentList.first?.expression.as(StringLiteralExprSyntax.self),
argument.isConstantString else {
return false
}

return true
}

return true
}
private func isColorInit(node: FunctionCallExprSyntax, name: String) -> Bool {
guard inits(forClasses: ["UIColor", "NSColor"]).contains(name),
case let argumentsNames = node.argumentList.compactMap(\.label?.text),
argumentsNames == ["red", "green", "blue", "alpha"] || argumentsNames == ["white", "alpha"] else {
return false
}

private func isColorInit(dictionary: SourceKittenDictionary, file: SwiftLintFile) -> Bool {
guard let name = dictionary.name,
inits(forClasses: ["UIColor", "NSColor"]).contains(name),
case let arguments = dictionary.enclosedArguments,
case let argumentsNames = arguments.compactMap({ $0.name }),
argumentsNames == ["red", "green", "blue", "alpha"] || argumentsNames == ["white", "alpha"],
validateColorKinds(arguments: arguments, file: file) else {
return false
return node.argumentList.allSatisfy { elem in
elem.expression.canBeExpressedAsColorLiteralParams
}
}

return true
private func inits(forClasses names: [String]) -> [String] {
return names.flatMap { name in
[
name,
name + ".init"
]
}
}
}
}

private func inits(forClasses names: [String]) -> [String] {
return names.flatMap { name in
[
name,
name + ".init"
]
}
private extension StringLiteralExprSyntax {
var isConstantString: Bool {
segments.allSatisfy { $0.is(StringSegmentSyntax.self) }
}
}

private func validateColorKinds(arguments: [SourceKittenDictionary], file: SwiftLintFile) -> Bool {
for dictionary in arguments where kinds(forArgument: dictionary, file: file) != [.number] {
return false
private extension ExprSyntax {
var canBeExpressedAsColorLiteralParams: Bool {
if self.is(FloatLiteralExprSyntax.self) ||
self.is(IntegerLiteralExprSyntax.self) ||
self.is(BinaryOperatorExprSyntax.self) {
return true
}

return true
}
if let expr = self.as(SequenceExprSyntax.self) {
return expr.elements.allSatisfy(\.canBeExpressedAsColorLiteralParams)
}

private func kinds(forArgument argument: SourceKittenDictionary, file: SwiftLintFile) -> Set<SyntaxKind> {
return argument.bodyByteRange.map { Set(file.syntaxMap.kinds(inByteRange: $0)) } ?? []
return false
}
}

0 comments on commit eaf7db1

Please sign in to comment.