Update SwiftSyntax to latest development snapshot (#4759)

* Merge `spacedBinaryOperator` and `unspacedBinaryOperator`

* New contextual keyword enums

* Update for removed `UnavailabilityConditionSyntax`

* Handle how attributes are now defined

This partially reverts commit 325d0ee1e4.

* Handle removal of `TokenListSyntax`

* Update `Package.swift`

* Extract some SwiftSyntax helpers

* Update to `0.50900.0-swift-DEVELOPMENT-SNAPSHOT-2023-02-06-a`

* Skip attributes with keypath arguments in `attributes` rule

To preserve the rule's existing behavior.

* Limit unowned_variable_capture violations to capture lists

* Add changelog entries
This commit is contained in:
JP Simard 2023-02-20 10:51:31 -05:00 committed by GitHub
parent 393318d903
commit 2f0e537f9b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
96 changed files with 452 additions and 475 deletions

View File

@ -73,6 +73,16 @@
[#3399](https://github.com/realm/SwiftLint/issues/3399) [#3399](https://github.com/realm/SwiftLint/issues/3399)
[#3605](https://github.com/realm/SwiftLint/issues/3605) [#3605](https://github.com/realm/SwiftLint/issues/3605)
* Speed up linting by up to 6% updating to use a newer version of
`SwiftSyntax`.
[JP Simard](https://github.com/jpsim)
* Catch more valid `legacy_multiple` violations.
[JP Simard](https://github.com/jpsim)
* Catch more valid `no_magic_numbers` violations.
[JP Simard](https://github.com/jpsim)
#### Bug Fixes #### Bug Fixes
* Report violations in all `<scope>_length` rules when the error threshold is * Report violations in all `<scope>_length` rules when the error threshold is
@ -125,6 +135,10 @@
[Mathias Schreck](https://github.com/lo1tuma) [Mathias Schreck](https://github.com/lo1tuma)
[#4772](https://github.com/realm/SwiftLint/issues/4772) [#4772](https://github.com/realm/SwiftLint/issues/4772)
* Fix false positives in `attributes` rule when using property wrappers
with keypath arguments.
[JP Simard](https://github.com/jpsim)
## 0.50.3: Bundle of Towels ## 0.50.3: Bundle of Towels
#### Breaking #### Breaking

View File

@ -32,8 +32,8 @@
"kind" : "remoteSourceControl", "kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-syntax.git", "location" : "https://github.com/apple/swift-syntax.git",
"state" : { "state" : {
"revision" : "edd2d0cdb988ac45e2515e0dd0624e4a6de54a94", "revision" : "013a48e2312e57b7b355db25bd3ea75282ebf274",
"version" : "0.50800.0-SNAPSHOT-2022-12-29-a" "version" : "0.50900.0-swift-DEVELOPMENT-SNAPSHOT-2023-02-06-a"
} }
}, },
{ {

View File

@ -30,7 +30,7 @@ let package = Package(
], ],
dependencies: [ dependencies: [
.package(url: "https://github.com/apple/swift-argument-parser.git", .upToNextMinor(from: "1.2.1")), .package(url: "https://github.com/apple/swift-argument-parser.git", .upToNextMinor(from: "1.2.1")),
.package(url: "https://github.com/apple/swift-syntax.git", exact: "0.50800.0-SNAPSHOT-2022-12-29-a"), .package(url: "https://github.com/apple/swift-syntax.git", exact: "0.50900.0-swift-DEVELOPMENT-SNAPSHOT-2023-02-06-a"),
.package(url: "https://github.com/jpsim/SourceKitten.git", .upToNextMinor(from: "0.34.0")), .package(url: "https://github.com/jpsim/SourceKitten.git", .upToNextMinor(from: "0.34.0")),
.package(url: "https://github.com/jpsim/Yams.git", from: "5.0.4"), .package(url: "https://github.com/jpsim/Yams.git", from: "5.0.4"),
.package(url: "https://github.com/scottrhoyt/SwiftyTextTable.git", from: "0.9.0"), .package(url: "https://github.com/scottrhoyt/SwiftyTextTable.git", from: "0.9.0"),

View File

@ -44,7 +44,9 @@ extension SwiftLintFile {
.tokens(viewMode: .sourceAccurate) .tokens(viewMode: .sourceAccurate)
.reduce(into: []) { linesWithTokens, token in .reduce(into: []) { linesWithTokens, token in
if case .stringSegment = token.tokenKind { if case .stringSegment = token.tokenKind {
let sourceRange = token.withoutTrivia().sourceRange(converter: locationConverter) let sourceRange = token
.trimmed
.sourceRange(converter: locationConverter)
let startLine = sourceRange.start.line! let startLine = sourceRange.start.line!
let endLine = sourceRange.end.line! let endLine = sourceRange.end.line!
linesWithTokens.formUnion(startLine...endLine) linesWithTokens.formUnion(startLine...endLine)

View File

@ -8,7 +8,7 @@ extension SyntaxVisitor: SwiftLintSyntaxVisitor {}
extension SwiftLintSyntaxVisitor { extension SwiftLintSyntaxVisitor {
func walk<T, SyntaxType: SyntaxProtocol>(tree: SyntaxType, handler: (Self) -> T) -> T { func walk<T, SyntaxType: SyntaxProtocol>(tree: SyntaxType, handler: (Self) -> T) -> T {
#if DEBUG #if DEBUG
// workaround for stack overflow when running in debug // workaround for stack overflow when running in debug
// https://bugs.swift.org/browse/SR-11170 // https://bugs.swift.org/browse/SR-11170
let lock = NSLock() let lock = NSLock()
@ -31,10 +31,10 @@ extension SwiftLintSyntaxVisitor {
} }
return handler(self) return handler(self)
#else #else
walk(tree) walk(tree)
return handler(self) return handler(self)
#endif #endif
} }
func walk<T>(file: SwiftLintFile, handler: (Self) -> [T]) -> [T] { func walk<T>(file: SwiftLintFile, handler: (Self) -> [T]) -> [T] {
@ -107,19 +107,17 @@ extension StringLiteralExprSyntax {
extension TokenKind { extension TokenKind {
var isEqualityComparison: Bool { var isEqualityComparison: Bool {
self == .spacedBinaryOperator("==") || self == .binaryOperator("==") || self == .binaryOperator("!=")
self == .spacedBinaryOperator("!=") ||
self == .unspacedBinaryOperator("==")
} }
} }
extension ModifierListSyntax? { extension ModifierListSyntax? {
var containsLazy: Bool { var containsLazy: Bool {
contains(tokenKind: .contextualKeyword("lazy")) contains(tokenKind: .keyword(.lazy))
} }
var containsOverride: Bool { var containsOverride: Bool {
contains(tokenKind: .contextualKeyword("override")) contains(tokenKind: .keyword(.override))
} }
var containsStaticOrClass: Bool { var containsStaticOrClass: Bool {
@ -127,15 +125,15 @@ extension ModifierListSyntax? {
} }
var isStatic: Bool { var isStatic: Bool {
contains(tokenKind: .staticKeyword) contains(tokenKind: .keyword(.static))
} }
var isClass: Bool { var isClass: Bool {
contains(tokenKind: .classKeyword) contains(tokenKind: .keyword(.class))
} }
var isFileprivate: Bool { var isFileprivate: Bool {
contains(tokenKind: .fileprivateKeyword) contains(tokenKind: .keyword(.fileprivate))
} }
var isPrivateOrFileprivate: Bool { var isPrivateOrFileprivate: Bool {
@ -144,13 +142,13 @@ extension ModifierListSyntax? {
} }
return modifiers.contains { elem in return modifiers.contains { elem in
(elem.name.tokenKind == .privateKeyword || elem.name.tokenKind == .fileprivateKeyword) && (elem.name.tokenKind == .keyword(.private) || elem.name.tokenKind == .keyword(.fileprivate)) &&
elem.detail == nil elem.detail == nil
} }
} }
var isFinal: Bool { var isFinal: Bool {
contains(tokenKind: .contextualKeyword("final")) contains(tokenKind: .keyword(.final))
} }
private func contains(tokenKind: TokenKind) -> Bool { private func contains(tokenKind: TokenKind) -> Bool {
@ -162,17 +160,34 @@ extension ModifierListSyntax? {
} }
} }
extension AttributeSyntax {
var attributeNameText: String {
attributeName.as(SimpleTypeIdentifierSyntax.self)?.name.text ??
attributeName.description
}
}
extension AttributeListSyntax? {
func contains(attributeNamed attributeName: String) -> Bool {
self?.contains { $0.as(AttributeSyntax.self)?.attributeNameText == attributeName } == true
}
}
extension TokenKind {
var isUnavailableKeyword: Bool {
self == .keyword(.unavailable) || self == .identifier("unavailable")
}
}
extension VariableDeclSyntax { extension VariableDeclSyntax {
var isIBOutlet: Bool { var isIBOutlet: Bool {
attributes?.contains { attr in attributes.contains(attributeNamed: "IBOutlet")
attr.as(AttributeSyntax.self)?.attributeName.tokenKind == .identifier("IBOutlet")
} ?? false
} }
var weakOrUnownedModifier: DeclModifierSyntax? { var weakOrUnownedModifier: DeclModifierSyntax? {
modifiers?.first { decl in modifiers?.first { decl in
decl.name.tokenKind == .contextualKeyword("weak") || decl.name.tokenKind == .keyword(.weak) ||
decl.name.tokenKind == .contextualKeyword("unowned") decl.name.tokenKind == .keyword(.unowned)
} }
} }
@ -207,9 +222,7 @@ public extension EnumDeclSyntax {
extension FunctionDeclSyntax { extension FunctionDeclSyntax {
var isIBAction: Bool { var isIBAction: Bool {
attributes?.contains { attr in attributes.contains(attributeNamed: "IBAction")
attr.as(AttributeSyntax.self)?.attributeName.tokenKind == .identifier("IBAction")
} ?? false
} }
/// Returns the signature including arguments, e.g "setEditing(_:animated:)" /// Returns the signature including arguments, e.g "setEditing(_:animated:)"
@ -241,13 +254,13 @@ extension FunctionDeclSyntax {
extension AccessorBlockSyntax { extension AccessorBlockSyntax {
var getAccessor: AccessorDeclSyntax? { var getAccessor: AccessorDeclSyntax? {
accessors.first { accessor in accessors.first { accessor in
accessor.accessorKind.tokenKind == .contextualKeyword("get") accessor.accessorKind.tokenKind == .keyword(.get)
} }
} }
var setAccessor: AccessorDeclSyntax? { var setAccessor: AccessorDeclSyntax? {
accessors.first { accessor in accessors.first { accessor in
accessor.accessorKind.tokenKind == .contextualKeyword("set") accessor.accessorKind.tokenKind == .keyword(.set)
} }
} }

View File

@ -62,7 +62,7 @@ enum LegacyFunctionRuleHelper {
correctionPositions.append(node.positionAfterSkippingLeadingTrivia) correctionPositions.append(node.positionAfterSkippingLeadingTrivia)
let trimmedArguments = node.argumentList.map { $0.trimmed() } let trimmedArguments = node.argumentList.map { $0.trimmingTrailingComma() }
let rewriteStrategy = legacyFunctions[funcName] let rewriteStrategy = legacyFunctions[funcName]
let expr: ExprSyntax let expr: ExprSyntax
@ -82,8 +82,8 @@ enum LegacyFunctionRuleHelper {
} }
return expr return expr
.withLeadingTrivia(node.leadingTrivia ?? .zero) .with(\.leadingTrivia, node.leadingTrivia ?? .zero)
.withTrailingTrivia(node.trailingTrivia ?? .zero) .with(\.trailingTrivia, node.trailingTrivia ?? .zero)
} }
} }
} }
@ -103,10 +103,7 @@ private extension FunctionCallExprSyntax {
} }
private extension TupleExprElementSyntax { private extension TupleExprElementSyntax {
func trimmed() -> TupleExprElementSyntax { func trimmingTrailingComma() -> TupleExprElementSyntax {
self self.trimmed.with(\.trailingComma, nil).trimmed
.withoutTrivia()
.withTrailingComma(nil)
.withoutTrivia()
} }
} }

View File

@ -53,7 +53,7 @@ private extension BlockBasedKVORule {
} }
let types = parameterList let types = parameterList
.compactMap { $0.type?.withoutTrivia().description.replacingOccurrences(of: " ", with: "") } .compactMap { $0.type?.trimmedDescription.replacingOccurrences(of: " ", with: "") }
let firstTypes = ["String?", "Any?", "[NSKeyValueChangeKey:Any]?", "UnsafeMutableRawPointer?"] let firstTypes = ["String?", "Any?", "[NSKeyValueChangeKey:Any]?", "UnsafeMutableRawPointer?"]
let secondTypes = ["String?", "Any?", "Dictionary<NSKeyValueChangeKey,Any>?", "UnsafeMutableRawPointer?"] let secondTypes = ["String?", "Any?", "Dictionary<NSKeyValueChangeKey,Any>?", "UnsafeMutableRawPointer?"]
if types == firstTypes || types == secondTypes { if types == firstTypes || types == secondTypes {

View File

@ -198,15 +198,11 @@ private extension TypeInheritanceClauseSyntax? {
private extension AttributeListSyntax? { private extension AttributeListSyntax? {
var containsObjcMembers: Bool { var containsObjcMembers: Bool {
self?.contains { elem in contains(attributeNamed: "objcMembers")
elem.as(AttributeSyntax.self)?.attributeName.tokenKind == .identifier("objcMembers")
} ?? false
} }
var containsObjc: Bool { var containsObjc: Bool {
self?.contains { elem in contains(attributeNamed: "objc")
elem.as(AttributeSyntax.self)?.attributeName.tokenKind == .contextualKeyword("objc")
} ?? false
} }
} }
@ -218,12 +214,12 @@ private extension AttributeListSyntax? {
return attrs.contains { elem in return attrs.contains { elem in
guard let attr = elem.as(AttributeSyntax.self), guard let attr = elem.as(AttributeSyntax.self),
let arguments = attr.argument?.as(AvailabilitySpecListSyntax.self) else { let arguments = attr.argument?.as(AvailabilitySpecListSyntax.self) else {
return false return false
} }
return attr.attributeName.tokenKind == .contextualKeyword("available") && arguments.contains { arg in return attr.attributeNameText == "available" && arguments.contains { arg in
arg.entry.as(TokenSyntax.self)?.tokenKind == .contextualKeyword("unavailable") arg.entry.as(TokenSyntax.self)?.tokenKind.isUnavailableKeyword == true
} }
} }
} }

View File

@ -39,11 +39,11 @@ struct DiscouragedAssertRule: SwiftSyntaxRule, OptInRule, ConfigurationProviderR
private extension DiscouragedAssertRule { private extension DiscouragedAssertRule {
final class Visitor: ViolationsSyntaxVisitor { final class Visitor: ViolationsSyntaxVisitor {
override func visitPost(_ node: FunctionCallExprSyntax) { override func visitPost(_ node: FunctionCallExprSyntax) {
guard node.calledExpression.as(IdentifierExprSyntax.self)?.identifier.withoutTrivia().text == "assert", guard node.calledExpression.as(IdentifierExprSyntax.self)?.identifier.text == "assert",
let firstArg = node.argumentList.first, let firstArg = node.argumentList.first,
firstArg.label == nil, firstArg.label == nil,
let boolExpr = firstArg.expression.as(BooleanLiteralExprSyntax.self), let boolExpr = firstArg.expression.as(BooleanLiteralExprSyntax.self),
boolExpr.booleanLiteral.tokenKind == .falseKeyword else { boolExpr.booleanLiteral.tokenKind == .keyword(.false) else {
return return
} }

View File

@ -218,7 +218,8 @@ private extension ExplicitInitRule {
} }
correctionPositions.append(violationPosition) correctionPositions.append(violationPosition)
return super.visit(node.withCalledExpression("\(calledBase.withoutTrivia())")) let newNode = node.with(\.calledExpression, calledBase.trimmed)
return super.visit(newNode)
} }
} }
} }

View File

@ -108,11 +108,11 @@ private extension ExplicitTopLevelACLRule {
private extension DeclModifierSyntax { private extension DeclModifierSyntax {
var isACLModifier: Bool { var isACLModifier: Bool {
let aclModifiers: Set<TokenKind> = [ let aclModifiers: Set<TokenKind> = [
.privateKeyword, .keyword(.private),
.fileprivateKeyword, .keyword(.fileprivate),
.internalKeyword, .keyword(.internal),
.publicKeyword, .keyword(.public),
.contextualKeyword("open") .keyword(.open)
] ]
return detail == nil && aclModifiers.contains(name.tokenKind) return detail == nil && aclModifiers.contains(name.tokenKind)

View File

@ -132,7 +132,7 @@ private extension InitializerClauseSyntax {
} }
var isTypeReference: Bool { var isTypeReference: Bool {
value.as(MemberAccessExprSyntax.self)?.name.tokenKind == .selfKeyword value.as(MemberAccessExprSyntax.self)?.name.tokenKind == .keyword(.self)
} }
} }

View File

@ -45,7 +45,7 @@ private extension FatalErrorMessageRule {
final class Visitor: ViolationsSyntaxVisitor { final class Visitor: ViolationsSyntaxVisitor {
override func visitPost(_ node: FunctionCallExprSyntax) { override func visitPost(_ node: FunctionCallExprSyntax) {
guard let expression = node.calledExpression.as(IdentifierExprSyntax.self), guard let expression = node.calledExpression.as(IdentifierExprSyntax.self),
expression.identifier.withoutTrivia().text == "fatalError", expression.identifier.text == "fatalError",
node.argumentList.isEmptyOrEmptyString else { node.argumentList.isEmptyOrEmptyString else {
return return
} }

View File

@ -87,6 +87,6 @@ private class TypeNameCollectingVisitor: SyntaxVisitor {
} }
override func visitPost(_ node: ExtensionDeclSyntax) { override func visitPost(_ node: ExtensionDeclSyntax) {
names.insert(node.extendedType.withoutTrivia().description) names.insert(node.extendedType.trimmedDescription)
} }
} }

View File

@ -134,26 +134,26 @@ private extension ForWhereRule {
override func visitPost(_ node: ForInStmtSyntax) { override func visitPost(_ node: ForInStmtSyntax) {
guard node.whereClause == nil, guard node.whereClause == nil,
case let statements = node.body.statements, let onlyExprStmt = node.body.statements.onlyElement?.item.as(ExpressionStmtSyntax.self),
let ifStatement = statements.onlyElement?.item.as(IfStmtSyntax.self), let ifExpr = onlyExprStmt.expression.as(IfExprSyntax.self),
ifStatement.elseBody == nil, ifExpr.elseBody == nil,
!ifStatement.containsOptionalBinding, !ifExpr.containsOptionalBinding,
!ifStatement.containsPatternCondition, !ifExpr.containsPatternCondition,
let condition = ifStatement.conditions.onlyElement, let condition = ifExpr.conditions.onlyElement,
!condition.containsMultipleConditions else { !condition.containsMultipleConditions else {
return return
} }
if allowForAsFilter, ifStatement.containsReturnStatement { if allowForAsFilter, ifExpr.containsReturnStatement {
return return
} }
violations.append(ifStatement.positionAfterSkippingLeadingTrivia) violations.append(ifExpr.positionAfterSkippingLeadingTrivia)
} }
} }
} }
private extension IfStmtSyntax { private extension IfExprSyntax {
var containsOptionalBinding: Bool { var containsOptionalBinding: Bool {
conditions.contains { element in conditions.contains { element in
element.condition.is(OptionalBindingConditionSyntax.self) element.condition.is(OptionalBindingConditionSyntax.self)

View File

@ -129,8 +129,6 @@ private extension FunctionParameterSyntax {
return false return false
} }
return attrType.attributes?.contains { attr in return attrType.attributes.contains(attributeNamed: "escaping")
attr.as(AttributeSyntax.self)?.attributeName.tokenKind == .identifier("escaping")
} ?? false
} }
} }

View File

@ -76,7 +76,7 @@ private extension JoinedDefaultParameterRule {
} }
correctionPositions.append(violationPosition) correctionPositions.append(violationPosition)
let newNode = node.withArgumentList(nil) let newNode = node.with(\.argumentList, [])
return super.visit(newNode) return super.visit(newNode)
} }
} }

View File

@ -63,8 +63,8 @@ private extension LegacyConstantRule {
correctionPositions.append(node.positionAfterSkippingLeadingTrivia) correctionPositions.append(node.positionAfterSkippingLeadingTrivia)
return ("\(raw: correction)" as ExprSyntax) return ("\(raw: correction)" as ExprSyntax)
.withLeadingTrivia(node.leadingTrivia ?? .zero) .with(\.leadingTrivia, node.leadingTrivia ?? .zero)
.withTrailingTrivia(node.trailingTrivia ?? .zero) .with(\.trailingTrivia, node.trailingTrivia ?? .zero)
} }
override func visit(_ node: FunctionCallExprSyntax) -> ExprSyntax { override func visit(_ node: FunctionCallExprSyntax) -> ExprSyntax {
@ -78,8 +78,8 @@ private extension LegacyConstantRule {
correctionPositions.append(node.positionAfterSkippingLeadingTrivia) correctionPositions.append(node.positionAfterSkippingLeadingTrivia)
return ("\(raw: calledExpression.identifier.text).pi" as ExprSyntax) return ("\(raw: calledExpression.identifier.text).pi" as ExprSyntax)
.withLeadingTrivia(node.leadingTrivia ?? .zero) .with(\.leadingTrivia, node.leadingTrivia ?? .zero)
.withTrailingTrivia(node.trailingTrivia ?? .zero) .with(\.trailingTrivia, node.trailingTrivia ?? .zero)
} }
} }
} }

View File

@ -140,7 +140,7 @@ private extension LegacyConstructorRule {
final class Visitor: ViolationsSyntaxVisitor { final class Visitor: ViolationsSyntaxVisitor {
override func visitPost(_ node: FunctionCallExprSyntax) { override func visitPost(_ node: FunctionCallExprSyntax) {
if let identifierExpr = node.calledExpression.as(IdentifierExprSyntax.self), if let identifierExpr = node.calledExpression.as(IdentifierExprSyntax.self),
constructorsToCorrectedNames[identifierExpr.identifier.withoutTrivia().text] != nil { constructorsToCorrectedNames[identifierExpr.identifier.text] != nil {
violations.append(node.positionAfterSkippingLeadingTrivia) violations.append(node.positionAfterSkippingLeadingTrivia)
} }
} }
@ -158,7 +158,7 @@ private extension LegacyConstructorRule {
override func visit(_ node: FunctionCallExprSyntax) -> ExprSyntax { override func visit(_ node: FunctionCallExprSyntax) -> ExprSyntax {
guard let identifierExpr = node.calledExpression.as(IdentifierExprSyntax.self), guard let identifierExpr = node.calledExpression.as(IdentifierExprSyntax.self),
case let identifier = identifierExpr.identifier.withoutTrivia().text, case let identifier = identifierExpr.identifier.text,
let correctedName = constructorsToCorrectedNames[identifier], let correctedName = constructorsToCorrectedNames[identifier],
let args = constructorsToArguments[identifier], let args = constructorsToArguments[identifier],
!node.isContainedIn(regions: disabledRegions, locationConverter: locationConverter) else { !node.isContainedIn(regions: disabledRegions, locationConverter: locationConverter) else {
@ -169,10 +169,11 @@ private extension LegacyConstructorRule {
let arguments = TupleExprElementListSyntax(node.argumentList.map { elem in let arguments = TupleExprElementListSyntax(node.argumentList.map { elem in
elem elem
.withLabel(.identifier(args[elem.indexInParent])) .with(\.label, .identifier(args[elem.indexInParent]))
.withColon(.colonToken(trailingTrivia: .space)) .with(\.colon, .colonToken(trailingTrivia: .space))
}) })
let newExpression = identifierExpr.withIdentifier( let newExpression = identifierExpr.with(
\.identifier,
.identifier( .identifier(
correctedName, correctedName,
leadingTrivia: identifierExpr.identifier.leadingTrivia, leadingTrivia: identifierExpr.identifier.leadingTrivia,
@ -180,8 +181,8 @@ private extension LegacyConstructorRule {
) )
) )
let newNode = node let newNode = node
.withCalledExpression(ExprSyntax(newExpression)) .with(\.calledExpression, ExprSyntax(newExpression))
.withArgumentList(arguments) .with(\.argumentList, arguments)
return super.visit(newNode) return super.visit(newNode)
} }
} }

View File

@ -85,7 +85,7 @@ extension LegacyHashingRule {
override func visitPost(_ node: VariableDeclSyntax) { override func visitPost(_ node: VariableDeclSyntax) {
guard guard
node.parent?.is(MemberDeclListItemSyntax.self) == true, node.parent?.is(MemberDeclListItemSyntax.self) == true,
node.letOrVarKeyword.tokenKind == .varKeyword, node.letOrVarKeyword.tokenKind == .keyword(.var),
let binding = node.bindings.onlyElement, let binding = node.bindings.onlyElement,
let identifier = binding.pattern.as(IdentifierPatternSyntax.self), let identifier = binding.pattern.as(IdentifierPatternSyntax.self),
identifier.identifier.text == "hashValue", identifier.identifier.text == "hashValue",

View File

@ -52,7 +52,7 @@ private extension LegacyMultipleRule {
final class Visitor: ViolationsSyntaxVisitor { final class Visitor: ViolationsSyntaxVisitor {
override func visitPost(_ node: InfixOperatorExprSyntax) { override func visitPost(_ node: InfixOperatorExprSyntax) {
guard let operatorNode = node.operatorOperand.as(BinaryOperatorExprSyntax.self), guard let operatorNode = node.operatorOperand.as(BinaryOperatorExprSyntax.self),
operatorNode.operatorToken.tokenKind == .spacedBinaryOperator("%"), operatorNode.operatorToken.tokenKind == .binaryOperator("%"),
let parent = node.parent?.as(InfixOperatorExprSyntax.self), let parent = node.parent?.as(InfixOperatorExprSyntax.self),
let parentOperatorNode = parent.operatorOperand.as(BinaryOperatorExprSyntax.self), let parentOperatorNode = parent.operatorOperand.as(BinaryOperatorExprSyntax.self),
parentOperatorNode.isEqualityOrInequalityOperator else { parentOperatorNode.isEqualityOrInequalityOperator else {
@ -80,8 +80,7 @@ private extension LegacyMultipleRule {
private extension BinaryOperatorExprSyntax { private extension BinaryOperatorExprSyntax {
var isEqualityOrInequalityOperator: Bool { var isEqualityOrInequalityOperator: Bool {
operatorToken.tokenKind == .spacedBinaryOperator("==") || operatorToken.tokenKind == .binaryOperator("==") ||
operatorToken.tokenKind == .unspacedBinaryOperator("==") || operatorToken.tokenKind == .binaryOperator("!=")
operatorToken.tokenKind == .spacedBinaryOperator("!=")
} }
} }

View File

@ -36,7 +36,7 @@ private extension LegacyRandomRule {
] ]
override func visitPost(_ node: FunctionCallExprSyntax) { override func visitPost(_ node: FunctionCallExprSyntax) {
if let function = node.calledExpression.as(IdentifierExprSyntax.self)?.identifier.withoutTrivia().text, if let function = node.calledExpression.as(IdentifierExprSyntax.self)?.identifier.text,
Self.legacyRandomFunctions.contains(function) { Self.legacyRandomFunctions.contains(function) {
violations.append(node.positionAfterSkippingLeadingTrivia) violations.append(node.positionAfterSkippingLeadingTrivia)
} }

View File

@ -54,7 +54,7 @@ private extension ObjectLiteralRule {
return return
} }
let name = node.calledExpression.withoutTrivia().description let name = node.calledExpression.trimmedDescription
if validateImageLiteral, isImageNamedInit(node: node, name: name) { if validateImageLiteral, isImageNamedInit(node: node, name: name) {
violations.append(node.positionAfterSkippingLeadingTrivia) violations.append(node.positionAfterSkippingLeadingTrivia)
} else if validateColorLiteral, isColorInit(node: node, name: name) { } else if validateColorLiteral, isColorInit(node: node, name: name) {

View File

@ -76,11 +76,12 @@ private extension PreferZeroOverExplicitInitRule {
correctionPositions.append(node.positionAfterSkippingLeadingTrivia) correctionPositions.append(node.positionAfterSkippingLeadingTrivia)
let newNode: MemberAccessExprSyntax = "\(raw: name).zero" let newNode = MemberAccessExprSyntax(name: "zero")
.with(\.base, "\(raw: name)")
return super.visit( return super.visit(
newNode newNode
.withLeadingTrivia(node.leadingTrivia ?? .zero) .with(\.leadingTrivia, node.leadingTrivia ?? .zero)
.withTrailingTrivia(node.trailingTrivia ?? .zero) .with(\.trailingTrivia, node.trailingTrivia ?? .zero)
) )
} }
} }

View File

@ -155,7 +155,7 @@ private extension PrivateOverFilePrivateRule {
} }
correctionPositions.append(modifier.positionAfterSkippingLeadingTrivia) correctionPositions.append(modifier.positionAfterSkippingLeadingTrivia)
let newNode = node.withModifiers(node.modifiers?.replacing(fileprivateModifier: modifier)) let newNode = node.with(\.modifiers, node.modifiers?.replacing(fileprivateModifier: modifier))
return DeclSyntax(newNode) return DeclSyntax(newNode)
} }
@ -166,7 +166,7 @@ private extension PrivateOverFilePrivateRule {
} }
correctionPositions.append(modifier.positionAfterSkippingLeadingTrivia) correctionPositions.append(modifier.positionAfterSkippingLeadingTrivia)
let newNode = node.withModifiers(node.modifiers?.replacing(fileprivateModifier: modifier)) let newNode = node.with(\.modifiers, node.modifiers?.replacing(fileprivateModifier: modifier))
return DeclSyntax(newNode) return DeclSyntax(newNode)
} }
@ -177,7 +177,7 @@ private extension PrivateOverFilePrivateRule {
} }
correctionPositions.append(modifier.positionAfterSkippingLeadingTrivia) correctionPositions.append(modifier.positionAfterSkippingLeadingTrivia)
let newNode = node.withModifiers(node.modifiers?.replacing(fileprivateModifier: modifier)) let newNode = node.with(\.modifiers, node.modifiers?.replacing(fileprivateModifier: modifier))
return DeclSyntax(newNode) return DeclSyntax(newNode)
} }
@ -188,7 +188,7 @@ private extension PrivateOverFilePrivateRule {
} }
correctionPositions.append(modifier.positionAfterSkippingLeadingTrivia) correctionPositions.append(modifier.positionAfterSkippingLeadingTrivia)
let newNode = node.withModifiers(node.modifiers?.replacing(fileprivateModifier: modifier)) let newNode = node.with(\.modifiers, node.modifiers?.replacing(fileprivateModifier: modifier))
return DeclSyntax(newNode) return DeclSyntax(newNode)
} }
@ -199,7 +199,7 @@ private extension PrivateOverFilePrivateRule {
} }
correctionPositions.append(modifier.positionAfterSkippingLeadingTrivia) correctionPositions.append(modifier.positionAfterSkippingLeadingTrivia)
let newNode = node.withModifiers(node.modifiers?.replacing(fileprivateModifier: modifier)) let newNode = node.with(\.modifiers, node.modifiers?.replacing(fileprivateModifier: modifier))
return DeclSyntax(newNode) return DeclSyntax(newNode)
} }
@ -210,7 +210,7 @@ private extension PrivateOverFilePrivateRule {
} }
correctionPositions.append(modifier.positionAfterSkippingLeadingTrivia) correctionPositions.append(modifier.positionAfterSkippingLeadingTrivia)
let newNode = node.withModifiers(node.modifiers?.replacing(fileprivateModifier: modifier)) let newNode = node.with(\.modifiers, node.modifiers?.replacing(fileprivateModifier: modifier))
return DeclSyntax(newNode) return DeclSyntax(newNode)
} }
@ -221,7 +221,7 @@ private extension PrivateOverFilePrivateRule {
} }
correctionPositions.append(modifier.positionAfterSkippingLeadingTrivia) correctionPositions.append(modifier.positionAfterSkippingLeadingTrivia)
let newNode = node.withModifiers(node.modifiers?.replacing(fileprivateModifier: modifier)) let newNode = node.with(\.modifiers, node.modifiers?.replacing(fileprivateModifier: modifier))
return DeclSyntax(newNode) return DeclSyntax(newNode)
} }
@ -232,7 +232,7 @@ private extension PrivateOverFilePrivateRule {
} }
correctionPositions.append(modifier.positionAfterSkippingLeadingTrivia) correctionPositions.append(modifier.positionAfterSkippingLeadingTrivia)
let newNode = node.withModifiers(node.modifiers?.replacing(fileprivateModifier: modifier)) let newNode = node.with(\.modifiers, node.modifiers?.replacing(fileprivateModifier: modifier))
return DeclSyntax(newNode) return DeclSyntax(newNode)
} }
} }
@ -240,7 +240,7 @@ private extension PrivateOverFilePrivateRule {
private extension ModifierListSyntax? { private extension ModifierListSyntax? {
var fileprivateModifier: DeclModifierSyntax? { var fileprivateModifier: DeclModifierSyntax? {
self?.first { $0.name.tokenKind == .fileprivateKeyword } self?.first { $0.name.tokenKind == .keyword(.fileprivate) }
} }
} }
@ -248,8 +248,10 @@ private extension ModifierListSyntax {
func replacing(fileprivateModifier: DeclModifierSyntax) -> ModifierListSyntax? { func replacing(fileprivateModifier: DeclModifierSyntax) -> ModifierListSyntax? {
replacing( replacing(
childAt: fileprivateModifier.indexInParent, childAt: fileprivateModifier.indexInParent,
with: fileprivateModifier.withName( with: fileprivateModifier.with(
.privateKeyword( \.name,
.keyword(
.private,
leadingTrivia: fileprivateModifier.leadingTrivia ?? .zero, leadingTrivia: fileprivateModifier.leadingTrivia ?? .zero,
trailingTrivia: fileprivateModifier.trailingTrivia ?? .zero trailingTrivia: fileprivateModifier.trailingTrivia ?? .zero
) )

View File

@ -38,7 +38,7 @@ struct RedundantNilCoalescingRule: OptInRule, SwiftSyntaxCorrectableRule, Config
private extension RedundantNilCoalescingRule { private extension RedundantNilCoalescingRule {
final class Visitor: ViolationsSyntaxVisitor { final class Visitor: ViolationsSyntaxVisitor {
override func visitPost(_ node: TokenSyntax) { override func visitPost(_ node: TokenSyntax) {
if node.tokenKind.isNilCoalescingOperator && node.nextToken?.tokenKind == .nilKeyword { if node.tokenKind.isNilCoalescingOperator && node.nextToken?.tokenKind == .keyword(.nil) {
violations.append(node.position) violations.append(node.position)
} }
} }
@ -66,7 +66,7 @@ private extension RedundantNilCoalescingRule {
return super.visit(node) return super.visit(node)
} }
let newNode = node.removingLast().removingLast().withoutTrailingTrivia() let newNode = node.removingLast().removingLast().with(\.trailingTrivia, [])
correctionPositions.append(newNode.endPosition) correctionPositions.append(newNode.endPosition)
return super.visit(newNode) return super.visit(newNode)
} }
@ -75,6 +75,6 @@ private extension RedundantNilCoalescingRule {
private extension TokenKind { private extension TokenKind {
var isNilCoalescingOperator: Bool { var isNilCoalescingOperator: Bool {
self == .spacedBinaryOperator("??") || self == .unspacedBinaryOperator("??") self == .binaryOperator("??")
} }
} }

View File

@ -42,22 +42,15 @@ struct RedundantObjcAttributeRule: SwiftSyntaxRule, SubstitutionCorrectableRule,
} }
private extension AttributeListSyntax { private extension AttributeListSyntax {
var hasObjCMembers: Bool {
contains { $0.as(AttributeSyntax.self)?.attributeName.tokenKind == .identifier("objcMembers") }
}
var objCAttribute: AttributeSyntax? { var objCAttribute: AttributeSyntax? {
lazy lazy
.compactMap { $0.as(AttributeSyntax.self) } .compactMap { $0.as(AttributeSyntax.self) }
.first { attribute in .first { $0.attributeNameText == "objc" && $0.argument == nil }
attribute.attributeName.tokenKind == .contextualKeyword("objc") &&
attribute.argument == nil
}
} }
var hasAttributeImplyingObjC: Bool { var hasAttributeImplyingObjC: Bool {
contains { element in contains { element in
guard case let .identifier(attributeName) = element.as(AttributeSyntax.self)?.attributeName.tokenKind else { guard let attributeName = element.as(AttributeSyntax.self)?.attributeNameText else {
return false return false
} }
@ -89,7 +82,7 @@ private extension AttributeListSyntax {
return objcAttribute return objcAttribute
} else if parent?.isFunctionOrStoredProperty == true, } else if parent?.isFunctionOrStoredProperty == true,
let parentClassDecl = parent?.parent?.parent?.parent?.parent?.as(ClassDeclSyntax.self), let parentClassDecl = parent?.parent?.parent?.parent?.parent?.as(ClassDeclSyntax.self),
parentClassDecl.attributes?.hasObjCMembers == true { parentClassDecl.attributes.contains(attributeNamed: "objcMembers") {
return objcAttribute return objcAttribute
} else if let parentExtensionDecl = parent?.parent?.parent?.parent?.parent?.as(ExtensionDeclSyntax.self), } else if let parentExtensionDecl = parent?.parent?.parent?.parent?.parent?.as(ExtensionDeclSyntax.self),
parentExtensionDecl.attributes?.objCAttribute != nil { parentExtensionDecl.attributes?.objCAttribute != nil {

View File

@ -121,7 +121,7 @@ struct RedundantOptionalInitializationRule: SwiftSyntaxCorrectableRule, Configur
private extension RedundantOptionalInitializationRule { private extension RedundantOptionalInitializationRule {
final class Visitor: ViolationsSyntaxVisitor { final class Visitor: ViolationsSyntaxVisitor {
override func visitPost(_ node: VariableDeclSyntax) { override func visitPost(_ node: VariableDeclSyntax) {
guard node.letOrVarKeyword.tokenKind == .varKeyword, guard node.letOrVarKeyword.tokenKind == .keyword(.var),
!node.modifiers.containsLazy else { !node.modifiers.containsLazy else {
return return
} }
@ -141,7 +141,7 @@ private extension RedundantOptionalInitializationRule {
} }
override func visit(_ node: VariableDeclSyntax) -> DeclSyntax { override func visit(_ node: VariableDeclSyntax) -> DeclSyntax {
guard node.letOrVarKeyword.tokenKind == .varKeyword, guard node.letOrVarKeyword.tokenKind == .keyword(.var),
!node.modifiers.containsLazy else { !node.modifiers.containsLazy else {
return super.visit(node) return super.visit(node)
} }
@ -166,16 +166,16 @@ private extension RedundantOptionalInitializationRule {
return binding return binding
} }
let newBinding = binding.withInitializer(nil) let newBinding = binding.with(\.initializer, nil)
if newBinding.accessor == nil { if newBinding.accessor == nil {
return newBinding.withTrailingTrivia(binding.initializer?.trailingTrivia ?? .zero) return newBinding.with(\.trailingTrivia, binding.initializer?.trailingTrivia ?? .zero)
} else { } else {
return newBinding return newBinding
} }
}) })
return super.visit(node.withBindings(newBindings)) return super.visit(node.with(\.bindings, newBindings))
} }
} }
} }

View File

@ -82,11 +82,11 @@ private extension RedundantSetAccessControlRule {
return return
} }
if setAccessor.name.tokenKind == .fileprivateKeyword, if setAccessor.name.tokenKind == .keyword(.fileprivate),
modifiers.getAccessor == nil, modifiers.getAccessor == nil,
let closestDeclModifiers = node.closestDecl()?.modifiers { let closestDeclModifiers = node.closestDecl()?.modifiers {
let closestDeclIsFilePrivate = closestDeclModifiers.contains { let closestDeclIsFilePrivate = closestDeclModifiers.contains {
$0.name.tokenKind == .fileprivateKeyword $0.name.tokenKind == .keyword(.fileprivate)
} }
if closestDeclIsFilePrivate { if closestDeclIsFilePrivate {
@ -95,12 +95,12 @@ private extension RedundantSetAccessControlRule {
} }
} }
if setAccessor.name.tokenKind == .internalKeyword, if setAccessor.name.tokenKind == .keyword(.internal),
modifiers.getAccessor == nil, modifiers.getAccessor == nil,
let closesDecl = node.closestDecl(), let closesDecl = node.closestDecl(),
let closestDeclModifiers = closesDecl.modifiers { let closestDeclModifiers = closesDecl.modifiers {
let closestDeclIsInternal = closestDeclModifiers.isEmpty || closestDeclModifiers.contains { let closestDeclIsInternal = closestDeclModifiers.isEmpty || closestDeclModifiers.contains {
$0.name.tokenKind == .internalKeyword $0.name.tokenKind == .keyword(.internal)
} }
if closestDeclIsInternal { if closestDeclIsInternal {
@ -144,7 +144,7 @@ private extension DeclSyntax {
private extension ModifierListSyntax { private extension ModifierListSyntax {
var setAccessor: DeclModifierSyntax? { var setAccessor: DeclModifierSyntax? {
first { $0.detail?.detail.tokenKind == .contextualKeyword("set") } first { $0.detail?.detail.tokenKind == .keyword(.set) }
} }
var getAccessor: DeclModifierSyntax? { var getAccessor: DeclModifierSyntax? {

View File

@ -121,8 +121,8 @@ private class Rewriter: SyntaxRewriter, ViolationsSyntaxRewriter {
correctionPositions.append(node.positionAfterSkippingLeadingTrivia) correctionPositions.append(node.positionAfterSkippingLeadingTrivia)
let newNode = node let newNode = node
.withInitializer(nil) .with(\.initializer, nil)
.withPattern(node.pattern.withTrailingTrivia(node.trailingTrivia ?? .zero)) .with(\.pattern, node.pattern.with(\.trailingTrivia, node.trailingTrivia ?? .zero))
return super.visit(newNode) return super.visit(newNode)
} }
} }

View File

@ -102,7 +102,7 @@ private extension FunctionDeclSyntax {
var isOperator: Bool { var isOperator: Bool {
switch identifier.tokenKind { switch identifier.tokenKind {
case .spacedBinaryOperator: case .binaryOperator:
return true return true
default: default:
return false return false

View File

@ -11,7 +11,10 @@ struct StrictFilePrivateRule: OptInRule, ConfigurationProviderRule, SwiftSyntaxR
description: "`fileprivate` should be avoided", description: "`fileprivate` should be avoided",
kind: .idiomatic, kind: .idiomatic,
nonTriggeringExamples: [ nonTriggeringExamples: [
Example("extension String {}"),
Example("private extension String {}"),
Example(""" Example("""
public
extension String { extension String {
var i: Int { 1 } var i: Int { 1 }
} }
@ -180,7 +183,7 @@ private extension StrictFilePrivateRule {
}() }()
override func visitPost(_ node: DeclModifierSyntax) { override func visitPost(_ node: DeclModifierSyntax) {
guard node.name.tokenKind == .fileprivateKeyword, let grandparent = node.parent?.parent else { guard node.name.tokenKind == .keyword(.fileprivate), let grandparent = node.parent?.parent else {
return return
} }
guard grandparent.is(FunctionDeclSyntax.self) || grandparent.is(VariableDeclSyntax.self) else { guard grandparent.is(FunctionDeclSyntax.self) || grandparent.is(VariableDeclSyntax.self) else {
@ -193,7 +196,7 @@ private extension StrictFilePrivateRule {
return return
} }
if let varDecl = grandparent.as(VariableDeclSyntax.self) { if let varDecl = grandparent.as(VariableDeclSyntax.self) {
let isSpecificForSetter = node.detail?.detail.tokenKind == .contextualKeyword("set") let isSpecificForSetter = node.detail?.detail.tokenKind == .keyword(.set)
let firstImplementingProtocol = varDecl.bindings let firstImplementingProtocol = varDecl.bindings
.flatMap { binding in .flatMap { binding in
let pattern = binding.pattern let pattern = binding.pattern

View File

@ -166,11 +166,11 @@ private final class SyntacticSugarRuleVisitor: SyntaxVisitor {
// Skip checks for 'self' or \T Dictionary<Key, Value>.self // Skip checks for 'self' or \T Dictionary<Key, Value>.self
if let parent = node.parent?.as(MemberAccessExprSyntax.self), if let parent = node.parent?.as(MemberAccessExprSyntax.self),
let lastToken = Array(parent.tokens(viewMode: .sourceAccurate)).last?.tokenKind, let lastToken = Array(parent.tokens(viewMode: .sourceAccurate)).last?.tokenKind,
[.selfKeyword, .identifier("Type"), .identifier("none"), .identifier("Index")].contains(lastToken) { [.keyword(.self), .identifier("Type"), .identifier("none"), .identifier("Index")].contains(lastToken) {
return return
} }
let typeName = node.expression.withoutTrivia().description let typeName = node.expression.trimmedDescription
if SugaredType(typeName: typeName) != nil { if SugaredType(typeName: typeName) != nil {
if let violation = violation(from: node) { if let violation = violation(from: node) {

View File

@ -73,11 +73,14 @@ private extension ToggleBoolRule {
correctionPositions.append(node.positionAfterSkippingLeadingTrivia) correctionPositions.append(node.positionAfterSkippingLeadingTrivia)
let newNode = node let newNode = node
.replacing(childAt: 0, with: "\(node.first!.withoutTrivia()).toggle()") .replacing(
childAt: 0,
with: "\(node.first!.trimmed).toggle()"
)
.removingLast() .removingLast()
.removingLast() .removingLast()
.withLeadingTrivia(node.leadingTrivia ?? .zero) .with(\.leadingTrivia, node.leadingTrivia ?? .zero)
.withTrailingTrivia(node.trailingTrivia ?? .zero) .with(\.trailingTrivia, node.trailingTrivia ?? .zero)
return super.visit(newNode) return super.visit(newNode)
} }
@ -90,8 +93,8 @@ private extension ExprListSyntax {
count == 3, count == 3,
dropFirst().first?.is(AssignmentExprSyntax.self) == true, dropFirst().first?.is(AssignmentExprSyntax.self) == true,
last?.is(PrefixOperatorExprSyntax.self) == true, last?.is(PrefixOperatorExprSyntax.self) == true,
let lhs = first?.withoutTrivia().description, let lhs = first?.trimmedDescription,
let rhs = last?.withoutTrivia().description, let rhs = last?.trimmedDescription,
rhs == "!\(lhs)" rhs == "!\(lhs)"
else { else {
return false return false

View File

@ -77,8 +77,8 @@ struct UnavailableConditionRule: ConfigurationProviderRule, SwiftSyntaxRule {
} }
private final class UnavailableConditionRuleVisitor: ViolationsSyntaxVisitor { private final class UnavailableConditionRuleVisitor: ViolationsSyntaxVisitor {
override func visitPost(_ node: IfStmtSyntax) { override func visitPost(_ node: IfExprSyntax) {
guard node.body.statements.withoutTrivia().isEmpty else { guard node.body.statements.isEmpty else {
return return
} }
@ -101,13 +101,13 @@ private final class UnavailableConditionRuleVisitor: ViolationsSyntaxVisitor {
) )
} }
private func asAvailabilityCondition(_ condition: ConditionElementSyntax.Condition) -> SyntaxProtocol? { private func asAvailabilityCondition(_ condition: ConditionElementSyntax.Condition)
condition.as(AvailabilityConditionSyntax.self) ?? -> AvailabilityConditionSyntax? {
condition.as(UnavailabilityConditionSyntax.self) condition.as(AvailabilityConditionSyntax.self)
} }
private func otherAvailabilityCheckInvolved(ifStmt: IfStmtSyntax) -> Bool { private func otherAvailabilityCheckInvolved(ifStmt: IfExprSyntax) -> Bool {
if let elseBody = ifStmt.elseBody, let nestedIfStatement = elseBody.as(IfStmtSyntax.self) { if let elseBody = ifStmt.elseBody, let nestedIfStatement = elseBody.as(IfExprSyntax.self) {
if nestedIfStatement.conditions.map(\.condition).compactMap(asAvailabilityCondition).isNotEmpty { if nestedIfStatement.conditions.map(\.condition).compactMap(asAvailabilityCondition).isNotEmpty {
return true return true
} }
@ -116,11 +116,11 @@ private final class UnavailableConditionRuleVisitor: ViolationsSyntaxVisitor {
return false return false
} }
private func reason(for check: SyntaxProtocol) -> String { private func reason(for condition: AvailabilityConditionSyntax) -> String {
switch check { switch condition.availabilityKeyword.tokenKind {
case is AvailabilityConditionSyntax: case .poundAvailableKeyword:
return "Use #unavailable instead of #available with an empty body" return "Use #unavailable instead of #available with an empty body"
case is UnavailabilityConditionSyntax: case .poundUnavailableKeyword:
return "Use #available instead of #unavailable with an empty body" return "Use #available instead of #unavailable with an empty body"
default: default:
queuedFatalError("Unknown availability check type.") queuedFatalError("Unknown availability check type.")

View File

@ -105,7 +105,7 @@ private extension UnavailableFunctionRule {
private extension FunctionDeclSyntax { private extension FunctionDeclSyntax {
var returnsNever: Bool { var returnsNever: Bool {
if let expr = signature.output?.returnType.as(SimpleTypeIdentifierSyntax.self) { if let expr = signature.output?.returnType.as(SimpleTypeIdentifierSyntax.self) {
return expr.name.withoutTrivia().text == "Never" return expr.name.text == "Never"
} }
return false return false
} }
@ -123,8 +123,9 @@ private extension AttributeListSyntax? {
return false return false
} }
return attr.attributeName.tokenKind == .contextualKeyword("available") && arguments.contains { arg in let attributeName = attr.attributeNameText
arg.entry.as(TokenSyntax.self)?.tokenKind == .contextualKeyword("unavailable") return attributeName == "available" && arguments.contains { arg in
arg.entry.as(TokenSyntax.self)?.tokenKind.isUnavailableKeyword == true
} }
} }
} }
@ -148,7 +149,7 @@ private extension CodeBlockSyntax? {
return false return false
} }
return terminatingFunctions.contains(identifierExpr.identifier.withoutTrivia().text) return terminatingFunctions.contains(identifierExpr.identifier.text)
} }
} }

View File

@ -154,8 +154,8 @@ private final class UntypedErrorInCatchRuleRewriter: SyntaxRewriter, ViolationsS
correctionPositions.append(node.catchKeyword.positionAfterSkippingLeadingTrivia) correctionPositions.append(node.catchKeyword.positionAfterSkippingLeadingTrivia)
return super.visit( return super.visit(
node node
.withCatchKeyword(node.catchKeyword.withTrailingTrivia(.spaces(1))) .with(\.catchKeyword, node.catchKeyword.with(\.trailingTrivia, .spaces(1)))
.withCatchItems(CatchItemListSyntax([])) .with(\.catchItems, CatchItemListSyntax([]))
) )
} }
} }

View File

@ -68,7 +68,7 @@ private extension FunctionCallExprSyntax {
var isEnumerated: Bool { var isEnumerated: Bool {
guard let memberAccess = calledExpression.as(MemberAccessExprSyntax.self), guard let memberAccess = calledExpression.as(MemberAccessExprSyntax.self),
memberAccess.base != nil, memberAccess.base != nil,
memberAccess.name.withoutTrivia().text == "enumerated", memberAccess.name.text == "enumerated",
hasNoArguments else { hasNoArguments else {
return false return false
} }

View File

@ -44,7 +44,7 @@ private extension XCTSpecificMatcherRule {
*/ */
let arguments = node.argumentList let arguments = node.argumentList
.prefix(2) .prefix(2)
.map { $0.expression.withoutTrivia().description } .map { $0.expression.trimmedDescription }
.sorted { arg1, _ -> Bool in .sorted { arg1, _ -> Bool in
return Self.protectedArguments.contains(arg1) return Self.protectedArguments.contains(arg1)
} }

View File

@ -83,11 +83,12 @@ private extension AnyObjectProtocolRule {
correctionPositions.append(node.positionAfterSkippingLeadingTrivia) correctionPositions.append(node.positionAfterSkippingLeadingTrivia)
return super.visit( return super.visit(
node.withTypeName( node.with(
\.typeName,
TypeSyntax( TypeSyntax(
SimpleTypeIdentifierSyntax(name: .identifier("AnyObject"), genericArgumentClause: nil) SimpleTypeIdentifierSyntax(name: .identifier("AnyObject"), genericArgumentClause: nil)
.withLeadingTrivia(typeName.leadingTrivia ?? .zero) .with(\.leadingTrivia, typeName.leadingTrivia ?? .zero)
.withTrailingTrivia(typeName.trailingTrivia ?? .zero) .with(\.trailingTrivia, typeName.trailingTrivia ?? .zero)
) )
) )
) )

View File

@ -55,7 +55,7 @@ private extension ClassDelegateProtocolRule {
private extension ProtocolDeclSyntax { private extension ProtocolDeclSyntax {
func hasObjCAttribute() -> Bool { func hasObjCAttribute() -> Bool {
attributes?.contains { $0.as(AttributeSyntax.self)?.attributeName.text == "objc" } == true attributes.contains(attributeNamed: "objc")
} }
func isClassRestricted() -> Bool { func isClassRestricted() -> Bool {

View File

@ -83,36 +83,28 @@ private extension DeploymentTargetRule {
} }
} }
override func visitPost(_ node: UnavailabilityConditionSyntax) {
for elem in node.availabilitySpec {
guard let restriction = elem.entry.as(AvailabilityVersionRestrictionSyntax.self),
let versionString = restriction.version?.description,
let reason = reason(platform: restriction.platform, version: versionString,
violationType: .negativeCondition) else {
continue
}
violations.append(
ReasonedRuleViolation(
position: node.poundUnavailableKeyword.positionAfterSkippingLeadingTrivia,
reason: reason
)
)
}
}
override func visitPost(_ node: AvailabilityConditionSyntax) { override func visitPost(_ node: AvailabilityConditionSyntax) {
let violationType: AvailabilityType
switch node.availabilityKeyword.tokenKind {
case .poundUnavailableKeyword:
violationType = .negativeCondition
case .poundAvailableKeyword:
violationType = .condition
default:
queuedFatalError("Unknown availability check type.")
}
for elem in node.availabilitySpec { for elem in node.availabilitySpec {
guard let restriction = elem.entry.as(AvailabilityVersionRestrictionSyntax.self), guard let restriction = elem.entry.as(AvailabilityVersionRestrictionSyntax.self),
let versionString = restriction.version?.description, let versionString = restriction.version?.description,
let reason = reason(platform: restriction.platform, version: versionString, let reason = reason(platform: restriction.platform, version: versionString,
violationType: .condition) else { violationType: violationType) else {
continue continue
} }
violations.append( violations.append(
ReasonedRuleViolation( ReasonedRuleViolation(
position: node.poundAvailableKeyword.positionAfterSkippingLeadingTrivia, position: node.availabilityKeyword.positionAfterSkippingLeadingTrivia,
reason: reason reason: reason
) )
) )

View File

@ -73,7 +73,7 @@ private extension DiscardedNotificationCenterObserverRule {
let thirdParent = secondParent.parent?.as(CodeBlockItemListSyntax.self), let thirdParent = secondParent.parent?.as(CodeBlockItemListSyntax.self),
let fourthParent = thirdParent.parent?.as(CodeBlockSyntax.self), let fourthParent = thirdParent.parent?.as(CodeBlockSyntax.self),
let fifthParent = fourthParent.parent?.as(FunctionDeclSyntax.self), let fifthParent = fourthParent.parent?.as(FunctionDeclSyntax.self),
fifthParent.attributes?.hasDiscardableResultAttribute != true !fifthParent.attributes.hasDiscardableResultAttribute
{ {
return // result is returned from a function return // result is returned from a function
} else if node.parent?.is(TupleExprElementSyntax.self) == true { } else if node.parent?.is(TupleExprElementSyntax.self) == true {
@ -83,7 +83,7 @@ private extension DiscardedNotificationCenterObserverRule {
} else if } else if
let previousToken = node.previousToken, let previousToken = node.previousToken,
case .equal = previousToken.tokenKind, case .equal = previousToken.tokenKind,
previousToken.previousToken?.tokenKind != .wildcardKeyword previousToken.previousToken?.tokenKind != .wildcard
{ {
return // result is assigned to something other than the wildcard keyword (`_`) return // result is assigned to something other than the wildcard keyword (`_`)
} }
@ -93,8 +93,8 @@ private extension DiscardedNotificationCenterObserverRule {
} }
} }
private extension AttributeListSyntax { private extension AttributeListSyntax? {
var hasDiscardableResultAttribute: Bool { var hasDiscardableResultAttribute: Bool {
contains { $0.as(AttributeSyntax.self)?.attributeName.tokenKind == .identifier("discardableResult") } == true contains(attributeNamed: "discardableResult")
} }
} }

View File

@ -53,7 +53,7 @@ private extension DiscouragedDirectInitRule {
override func visitPost(_ node: FunctionCallExprSyntax) { override func visitPost(_ node: FunctionCallExprSyntax) {
guard node.argumentList.isEmpty, node.trailingClosure == nil, guard node.argumentList.isEmpty, node.trailingClosure == nil,
discouragedInits.contains(node.calledExpression.withoutTrivia().description) else { discouragedInits.contains(node.calledExpression.trimmedDescription) else {
return return
} }

View File

@ -47,7 +47,7 @@ private extension DynamicInlineRule {
private extension AttributeSyntax { private extension AttributeSyntax {
var isInlineAlways: Bool { var isInlineAlways: Bool {
attributeName.text == "inline" && attributeNameText == "inline" &&
argument?.firstToken?.tokenKind == .identifier("__always") argument?.firstToken?.tokenKind == .identifier("__always")
} }
} }

View File

@ -38,7 +38,7 @@ private extension IBInspectableInExtensionRule {
} }
override func visitPost(_ node: AttributeSyntax) { override func visitPost(_ node: AttributeSyntax) {
if node.attributeName.text == "IBInspectable" { if node.attributeNameText == "IBInspectable" {
violations.append(node.positionAfterSkippingLeadingTrivia) violations.append(node.positionAfterSkippingLeadingTrivia)
} }
} }

View File

@ -85,7 +85,7 @@ private extension IdenticalOperandsRule {
final class Visitor: ViolationsSyntaxVisitor { final class Visitor: ViolationsSyntaxVisitor {
override func visitPost(_ node: InfixOperatorExprSyntax) { override func visitPost(_ node: InfixOperatorExprSyntax) {
guard let operatorNode = node.operatorOperand.as(BinaryOperatorExprSyntax.self), guard let operatorNode = node.operatorOperand.as(BinaryOperatorExprSyntax.self),
IdenticalOperandsRule.operators.contains(operatorNode.operatorToken.withoutTrivia().text) else { IdenticalOperandsRule.operators.contains(operatorNode.operatorToken.text) else {
return return
} }

View File

@ -112,18 +112,17 @@ private extension LowerACLThanParentRule {
} }
correctionPositions.append(node.positionAfterSkippingLeadingTrivia) correctionPositions.append(node.positionAfterSkippingLeadingTrivia)
let keyword: String let newNode: DeclModifierSyntax
let trailingTrivia: Trivia if node.name.tokenKind == .keyword(.open) {
if node.name.tokenKind == .contextualKeyword("open") { newNode = DeclModifierSyntax(
keyword = "public" leadingTrivia: node.leadingTrivia ?? .zero,
trailingTrivia = .space name: .keyword(.public),
trailingTrivia: .space
)
} else { } else {
keyword = "" newNode = DeclModifierSyntax(name: .keyword(.internal, presence: .missing))
trailingTrivia = .zero
} }
let newNode = node.withName(
.contextualKeyword(keyword, leadingTrivia: node.leadingTrivia ?? .zero, trailingTrivia: trailingTrivia)
)
return super.visit(newNode) return super.visit(newNode)
} }
} }
@ -136,29 +135,29 @@ private extension DeclModifierSyntax {
} }
switch name.tokenKind { switch name.tokenKind {
case .internalKeyword case .keyword(.internal)
where nearestNominalParent.modifiers.isPrivate || where nearestNominalParent.modifiers.isPrivate ||
nearestNominalParent.modifiers.isFileprivate: nearestNominalParent.modifiers.isFileprivate:
return true return true
case .internalKeyword case .keyword(.internal)
where !nearestNominalParent.modifiers.containsACLModifier: where !nearestNominalParent.modifiers.containsACLModifier:
guard let nominalExtension = nearestNominalParent.nearestNominalExtensionDeclParent() else { guard let nominalExtension = nearestNominalParent.nearestNominalExtensionDeclParent() else {
return false return false
} }
return nominalExtension.modifiers.isPrivate || return nominalExtension.modifiers.isPrivate ||
nominalExtension.modifiers.isFileprivate nominalExtension.modifiers.isFileprivate
case .publicKeyword case .keyword(.public)
where nearestNominalParent.modifiers.isPrivate || where nearestNominalParent.modifiers.isPrivate ||
nearestNominalParent.modifiers.isFileprivate || nearestNominalParent.modifiers.isFileprivate ||
nearestNominalParent.modifiers.isInternal: nearestNominalParent.modifiers.isInternal:
return true return true
case .publicKeyword case .keyword(.public)
where !nearestNominalParent.modifiers.containsACLModifier: where !nearestNominalParent.modifiers.containsACLModifier:
guard let nominalExtension = nearestNominalParent.nearestNominalExtensionDeclParent() else { guard let nominalExtension = nearestNominalParent.nearestNominalExtensionDeclParent() else {
return true return true
} }
return !nominalExtension.modifiers.isPublic return !nominalExtension.modifiers.isPublic
case .contextualKeyword("open") where !nearestNominalParent.modifiers.isOpen: case .keyword(.open) where !nearestNominalParent.modifiers.isOpen:
return true return true
default: default:
return false return false
@ -215,32 +214,33 @@ private extension Syntax {
private extension ModifierListSyntax? { private extension ModifierListSyntax? {
var isPrivate: Bool { var isPrivate: Bool {
self?.contains(where: { $0.name.tokenKind == .privateKeyword }) == true self?.contains(where: { $0.name.tokenKind == .keyword(.private) }) == true
} }
var isInternal: Bool { var isInternal: Bool {
self?.contains(where: { $0.name.tokenKind == .internalKeyword }) == true self?.contains(where: { $0.name.tokenKind == .keyword(.internal) }) == true
} }
var isPublic: Bool { var isPublic: Bool {
self?.contains(where: { $0.name.tokenKind == .publicKeyword }) == true self?.contains(where: { $0.name.tokenKind == .keyword(.public) }) == true
} }
var isOpen: Bool { var isOpen: Bool {
self?.contains(where: { $0.name.tokenKind == .contextualKeyword("open") }) == true self?.contains(where: { $0.name.tokenKind == .keyword(.open) }) == true
} }
var containsACLModifier: Bool { var containsACLModifier: Bool {
guard self?.isEmpty == false else { guard self?.isEmpty == false else {
return false return false
} }
let aclTokens: [TokenKind] = [ let aclTokens: Set<TokenKind> = [
.fileprivateKeyword, .keyword(.private),
.privateKeyword, .keyword(.fileprivate),
.internalKeyword, .keyword(.internal),
.publicKeyword, .keyword(.public),
.contextualKeyword("open") .keyword(.open)
] ]
return self?.contains(where: { return self?.contains(where: {
aclTokens.contains($0.name.tokenKind) aclTokens.contains($0.name.tokenKind)
}) == true }) == true

View File

@ -33,7 +33,7 @@ private extension NSObjectPreferIsEqualRule {
private extension ClassDeclSyntax { private extension ClassDeclSyntax {
var isObjC: Bool { var isObjC: Bool {
if attributes?.isObjc == true { if attributes.isObjc {
return true return true
} }
@ -58,8 +58,8 @@ private extension FunctionDeclSyntax {
let rhs = parameterList.last, let rhs = parameterList.last,
lhs.firstName?.text == "lhs", lhs.firstName?.text == "lhs",
rhs.firstName?.text == "rhs", rhs.firstName?.text == "rhs",
let lhsTypeDescription = lhs.type?.withoutTrivia().description, let lhsTypeDescription = lhs.type?.trimmedDescription,
let rhsTypeDescription = rhs.type?.withoutTrivia().description, let rhsTypeDescription = rhs.type?.trimmedDescription,
lhsTypeDescription == rhsTypeDescription lhsTypeDescription == rhsTypeDescription
else { else {
return false return false
@ -85,8 +85,8 @@ private extension SyntaxProtocol {
} }
} }
private extension AttributeListSyntax { private extension AttributeListSyntax? {
var isObjc: Bool { var isObjc: Bool {
contains { ["objc", "objcMembers"].contains($0.as(AttributeSyntax.self)?.attributeName.text) } contains(attributeNamed: "objc") || contains(attributeNamed: "objcMembers")
} }
} }

View File

@ -26,7 +26,7 @@ private extension NotificationCenterDetachmentRule {
let arg = node.argumentList.first, let arg = node.argumentList.first,
arg.label == nil, arg.label == nil,
let expr = arg.expression.as(IdentifierExprSyntax.self), let expr = arg.expression.as(IdentifierExprSyntax.self),
expr.identifier.tokenKind == .selfKeyword else { expr.identifier.tokenKind == .keyword(.self) else {
return return
} }

View File

@ -95,7 +95,7 @@ private extension PrivateOutletRule {
override func visitPost(_ node: MemberDeclListItemSyntax) { override func visitPost(_ node: MemberDeclListItemSyntax) {
guard guard
let decl = node.decl.as(VariableDeclSyntax.self), let decl = node.decl.as(VariableDeclSyntax.self),
decl.attributes?.hasIBOutlet == true, decl.attributes.contains(attributeNamed: "IBOutlet"),
decl.modifiers?.isPrivateOrFilePrivate != true decl.modifiers?.isPrivateOrFilePrivate != true
else { else {
return return
@ -110,12 +110,6 @@ private extension PrivateOutletRule {
} }
} }
private extension AttributeListSyntax {
var hasIBOutlet: Bool {
contains { $0.as(AttributeSyntax.self)?.attributeName.text == "IBOutlet" }
}
}
private extension ModifierListSyntax { private extension ModifierListSyntax {
var isPrivateOrFilePrivate: Bool { var isPrivateOrFilePrivate: Bool {
contains(where: \.isPrivateOrFilePrivate) contains(where: \.isPrivateOrFilePrivate)

View File

@ -201,7 +201,7 @@ private class Rewriter: SyntaxRewriter, ViolationsSyntaxRewriter {
correctionPositions.append(node.positionAfterSkippingLeadingTrivia) correctionPositions.append(node.positionAfterSkippingLeadingTrivia)
let (modifiers, declKeyword) = withoutPrivate(modifiers: node.modifiers, declKeyword: node.classKeyword) let (modifiers, declKeyword) = withoutPrivate(modifiers: node.modifiers, declKeyword: node.classKeyword)
return super.visit(node.withModifiers(modifiers).withClassKeyword(declKeyword)) return super.visit(node.with(\.modifiers, modifiers).with(\.classKeyword, declKeyword))
} }
override func visit(_ node: FunctionDeclSyntax) -> DeclSyntax { override func visit(_ node: FunctionDeclSyntax) -> DeclSyntax {
@ -215,7 +215,7 @@ private class Rewriter: SyntaxRewriter, ViolationsSyntaxRewriter {
correctionPositions.append(node.positionAfterSkippingLeadingTrivia) correctionPositions.append(node.positionAfterSkippingLeadingTrivia)
let (modifiers, declKeyword) = withoutPrivate(modifiers: node.modifiers, declKeyword: node.funcKeyword) let (modifiers, declKeyword) = withoutPrivate(modifiers: node.modifiers, declKeyword: node.funcKeyword)
return super.visit(node.withModifiers(modifiers).withFuncKeyword(declKeyword)) return super.visit(node.with(\.modifiers, modifiers).with(\.funcKeyword, declKeyword))
} }
private func withoutPrivate(modifiers: ModifierListSyntax?, private func withoutPrivate(modifiers: ModifierListSyntax?,
@ -227,14 +227,14 @@ private class Rewriter: SyntaxRewriter, ViolationsSyntaxRewriter {
var leadingTrivia = Trivia.zero var leadingTrivia = Trivia.zero
for modifier in modifiers { for modifier in modifiers {
let accumulatedLeadingTrivia = leadingTrivia + (modifier.leadingTrivia ?? .zero) let accumulatedLeadingTrivia = leadingTrivia + (modifier.leadingTrivia ?? .zero)
if modifier.name.tokenKind == .privateKeyword { if modifier.name.tokenKind == .keyword(.private) {
leadingTrivia = accumulatedLeadingTrivia leadingTrivia = accumulatedLeadingTrivia
} else { } else {
filteredModifiers.append(modifier.withLeadingTrivia(accumulatedLeadingTrivia)) filteredModifiers.append(modifier.with(\.leadingTrivia, accumulatedLeadingTrivia))
leadingTrivia = .zero leadingTrivia = .zero
} }
} }
let declKeyword = declKeyword.withLeadingTrivia(leadingTrivia + (declKeyword.leadingTrivia ?? .zero)) let declKeyword = declKeyword.with(\.leadingTrivia, leadingTrivia + (declKeyword.leadingTrivia ?? .zero))
return (ModifierListSyntax(filteredModifiers), declKeyword) return (ModifierListSyntax(filteredModifiers), declKeyword)
} }
} }
@ -269,11 +269,11 @@ private extension FunctionDeclSyntax {
private extension ModifierListSyntax { private extension ModifierListSyntax {
var hasPrivate: Bool { var hasPrivate: Bool {
contains { $0.name.tokenKind == .privateKeyword } contains { $0.name.tokenKind == .keyword(.private) }
} }
var hasStatic: Bool { var hasStatic: Bool {
contains { $0.name.tokenKind == .staticKeyword } contains { $0.name.tokenKind == .keyword(.static) }
} }
} }
@ -281,8 +281,6 @@ private func resultInPrivateProperty(modifiers: ModifierListSyntax?, attributes:
guard let modifiers, modifiers.hasPrivate else { guard let modifiers, modifiers.hasPrivate else {
return false return false
} }
guard let attributes else {
return true return !attributes.contains(attributeNamed: "objc")
}
return !attributes.contains { $0.as(AttributeSyntax.self)?.attributeName.tokenKind == .contextualKeyword("objc") }
} }

View File

@ -25,7 +25,7 @@ private extension QuickDiscouragedFocusedTestRule {
override func visitPost(_ node: FunctionCallExprSyntax) { override func visitPost(_ node: FunctionCallExprSyntax) {
if let identifierExpr = node.calledExpression.as(IdentifierExprSyntax.self), if let identifierExpr = node.calledExpression.as(IdentifierExprSyntax.self),
case let name = identifierExpr.identifier.withoutTrivia().text, case let name = identifierExpr.identifier.text,
QuickFocusedCallKind(rawValue: name) != nil { QuickFocusedCallKind(rawValue: name) != nil {
violations.append(node.positionAfterSkippingLeadingTrivia) violations.append(node.positionAfterSkippingLeadingTrivia)
} }

View File

@ -25,7 +25,7 @@ private extension QuickDiscouragedPendingTestRule {
override func visitPost(_ node: FunctionCallExprSyntax) { override func visitPost(_ node: FunctionCallExprSyntax) {
if let identifierExpr = node.calledExpression.as(IdentifierExprSyntax.self), if let identifierExpr = node.calledExpression.as(IdentifierExprSyntax.self),
case let name = identifierExpr.identifier.withoutTrivia().text, case let name = identifierExpr.identifier.text,
QuickPendingCallKind(rawValue: name) != nil { QuickPendingCallKind(rawValue: name) != nil {
violations.append(node.positionAfterSkippingLeadingTrivia) violations.append(node.positionAfterSkippingLeadingTrivia)
} }

View File

@ -107,7 +107,7 @@ private extension SelfInPropertyInitializationRule {
return return
} }
let visitor = IdentifierUsageVisitor(identifier: .selfKeyword) let visitor = IdentifierUsageVisitor(identifier: .keyword(.self))
for binding in node.bindings { for binding in node.bindings {
guard let initializer = binding.initializer, guard let initializer = binding.initializer,
visitor.walk(tree: initializer.value, handler: \.isTokenUsed) else { visitor.walk(tree: initializer.value, handler: \.isTokenUsed) else {

View File

@ -70,7 +70,7 @@ private extension StrongIBOutletRule {
} }
let newModifiers = ModifierListSyntax(modifiers.filter { $0 != weakOrUnownedModifier }) let newModifiers = ModifierListSyntax(modifiers.filter { $0 != weakOrUnownedModifier })
let newNode = node.withModifiers(newModifiers) let newNode = node.with(\.modifiers, newModifiers)
correctionPositions.append(violationPosition) correctionPositions.append(violationPosition)
return super.visit(newNode) return super.visit(newNode)
} }

View File

@ -16,7 +16,16 @@ struct UnownedVariableCaptureRule: SwiftSyntaxRule, OptInRule, ConfigurationProv
Example("foo { [weak bar] in _ }"), Example("foo { [weak bar] in _ }"),
Example("foo { [weak bar] param in _ }"), Example("foo { [weak bar] param in _ }"),
Example("foo { bar in _ }"), Example("foo { bar in _ }"),
Example("foo { $0 }") Example("foo { $0 }"),
Example("""
final class First {}
final class Second {
unowned var value: First
init(value: First) {
self.value = value
}
}
""")
], ],
triggeringExamples: [ triggeringExamples: [
Example("foo { [↓unowned self] in _ }"), Example("foo { [↓unowned self] in _ }"),
@ -31,23 +40,9 @@ struct UnownedVariableCaptureRule: SwiftSyntaxRule, OptInRule, ConfigurationProv
} }
private final class UnownedVariableCaptureRuleVisitor: ViolationsSyntaxVisitor { private final class UnownedVariableCaptureRuleVisitor: ViolationsSyntaxVisitor {
override func visitPost(_ node: ClosureCaptureItemSyntax) { override func visitPost(_ node: TokenSyntax) {
if let token = node.unownedToken { if case .keyword(.unowned) = node.tokenKind, node.parent?.is(ClosureCaptureItemSpecifierSyntax.self) == true {
violations.append(token.positionAfterSkippingLeadingTrivia)
}
}
override func visitPost(_ node: TokenListSyntax) {
if case .contextualKeyword("unowned") = node.first?.tokenKind {
violations.append(node.positionAfterSkippingLeadingTrivia) violations.append(node.positionAfterSkippingLeadingTrivia)
} }
} }
} }
private extension ClosureCaptureItemSyntax {
var unownedToken: TokenSyntax? {
specifier?.first { token in
token.tokenKind == .identifier("unowned")
}
}
}

View File

@ -142,7 +142,7 @@ private extension UnusedCaptureListRule {
final class Visitor: ViolationsSyntaxVisitor { final class Visitor: ViolationsSyntaxVisitor {
override func visitPost(_ node: ClosureExprSyntax) { override func visitPost(_ node: ClosureExprSyntax) {
guard let captureItems = node.signature?.capture?.items, guard let captureItems = node.signature?.capture?.items,
captureItems.isNotEmpty else { captureItems.isNotEmpty else {
return return
} }
@ -152,12 +152,14 @@ private extension UnusedCaptureListRule {
return (name.text, item) return (name.text, item)
} else if let expr = item.expression.as(IdentifierExprSyntax.self) { } else if let expr = item.expression.as(IdentifierExprSyntax.self) {
// allow "[unowned self]" // allow "[unowned self]"
if expr.identifier.tokenKind == .selfKeyword && item.specifier.containsUnowned { if expr.identifier.tokenKind == .keyword(.self),
item.specifier?.specifier.tokenKind == .keyword(.unowned) {
return nil return nil
} }
// allow "[self]" capture (SE-0269) // allow "[self]" capture (SE-0269)
if expr.identifier.tokenKind == .selfKeyword && item.specifier.isNilOrEmpty { if expr.identifier.tokenKind == .keyword(.self),
item.specifier == nil {
return nil return nil
} }
@ -203,15 +205,3 @@ private final class IdentifierReferenceVisitor: SyntaxVisitor {
} }
} }
} }
private extension TokenListSyntax? {
var containsUnowned: Bool {
self?.contains { token in
token.tokenKind == .contextualKeyword("unowned")
} ?? false
}
var isNilOrEmpty: Bool {
self?.isEmpty ?? true
}
}

View File

@ -79,27 +79,26 @@ private extension UnusedClosureParameterRule {
continue continue
} }
if name.tokenKind == .wildcardKeyword { if name.tokenKind == .wildcard {
continue continue
} else if referencedIdentifiers.contains(name.text.removingDollarsAndBackticks) { } else if referencedIdentifiers.contains(name.text.removingDollarsAndBackticks) {
continue continue
} }
correctionPositions.append(name.positionAfterSkippingLeadingTrivia) correctionPositions.append(name.positionAfterSkippingLeadingTrivia)
newParams = newParams.withParameterList( let newParameterList = newParams.parameterList.replacing(
newParams.parameterList.replacing( childAt: index,
childAt: index, with: param.with(\.firstName, name.withKind(.wildcard))
with: param.withFirstName(name.withKind(.wildcardKeyword))
)
) )
newParams = newParams.with(\.parameterList, newParameterList)
} }
let newNode = node.withSignature(signature.withInput(.init(newParams))) let newNode = node.with(\.signature, signature.with(\.input, .init(newParams)))
return super.visit(newNode) return super.visit(newNode)
} }
var newParams = params var newParams = params
for (index, param) in params.enumerated() { for (index, param) in params.enumerated() {
if param.name.tokenKind == .wildcardKeyword { if param.name.tokenKind == .wildcard {
continue continue
} else if referencedIdentifiers.contains(param.name.text.removingDollarsAndBackticks) { } else if referencedIdentifiers.contains(param.name.text.removingDollarsAndBackticks) {
continue continue
@ -108,10 +107,10 @@ private extension UnusedClosureParameterRule {
correctionPositions.append(param.name.positionAfterSkippingLeadingTrivia) correctionPositions.append(param.name.positionAfterSkippingLeadingTrivia)
newParams = newParams.replacing( newParams = newParams.replacing(
childAt: index, childAt: index,
with: param.withName(param.name.withKind(.wildcardKeyword)) with: param.with(\.name, param.name.withKind(.wildcard))
) )
} }
let newNode = node.withSignature(signature.withInput(.init(newParams))) let newNode = node.with(\.signature, signature.with(\.input, .init(newParams)))
return super.visit(newNode) return super.visit(newNode)
} }
} }
@ -141,7 +140,7 @@ private extension ClosureExprSyntax {
var namedParameters: [ClosureParam] { var namedParameters: [ClosureParam] {
if let params = signature?.input?.as(ClosureParamListSyntax.self) { if let params = signature?.input?.as(ClosureParamListSyntax.self) {
return params.compactMap { param in return params.compactMap { param in
if param.name.tokenKind == .wildcardKeyword { if param.name.tokenKind == .wildcard {
return nil return nil
} }
return ClosureParam( return ClosureParam(
@ -151,7 +150,7 @@ private extension ClosureExprSyntax {
} }
} else if let params = signature?.input?.as(ParameterClauseSyntax.self)?.parameterList { } else if let params = signature?.input?.as(ParameterClauseSyntax.self)?.parameterList {
return params.compactMap { param in return params.compactMap { param in
if param.firstName?.tokenKind == .wildcardKeyword { if param.firstName?.tokenKind == .wildcard {
return nil return nil
} }
return param.firstName.map { name in return param.firstName.map { name in

View File

@ -122,7 +122,7 @@ private extension UnusedControlFlowLabelRule {
return super.visit(node) return super.visit(node)
} }
let newNode = node.statement.withLeadingTrivia(node.leadingTrivia ?? .zero) let newNode = node.statement.with(\.leadingTrivia, node.leadingTrivia ?? .zero)
correctionPositions.append(violationPosition) correctionPositions.append(violationPosition)
return visit(newNode).as(StmtSyntax.self) ?? newNode return visit(newNode).as(StmtSyntax.self) ?? newNode
} }
@ -133,7 +133,7 @@ private extension LabeledStmtSyntax {
var violationPosition: AbsolutePosition? { var violationPosition: AbsolutePosition? {
let visitor = BreakAndContinueLabelCollector(viewMode: .sourceAccurate) let visitor = BreakAndContinueLabelCollector(viewMode: .sourceAccurate)
let labels = visitor.walk(tree: self, handler: \.labels) let labels = visitor.walk(tree: self, handler: \.labels)
guard !labels.contains(labelName.withoutTrivia().text) else { guard !labels.contains(labelName.text) else {
return nil return nil
} }
@ -145,13 +145,13 @@ private class BreakAndContinueLabelCollector: SyntaxVisitor {
private(set) var labels: Set<String> = [] private(set) var labels: Set<String> = []
override func visitPost(_ node: BreakStmtSyntax) { override func visitPost(_ node: BreakStmtSyntax) {
if let label = node.label?.withoutTrivia().text { if let label = node.label?.text {
labels.insert(label) labels.insert(label)
} }
} }
override func visitPost(_ node: ContinueStmtSyntax) { override func visitPost(_ node: ContinueStmtSyntax) {
if let label = node.label?.withoutTrivia().text { if let label = node.label?.text {
labels.insert(label) labels.insert(label)
} }
} }

View File

@ -139,11 +139,11 @@ private extension UnusedSetterValueRule {
override var skippableDeclarations: [DeclSyntaxProtocol.Type] { [ProtocolDeclSyntax.self] } override var skippableDeclarations: [DeclSyntaxProtocol.Type] { [ProtocolDeclSyntax.self] }
override func visitPost(_ node: AccessorDeclSyntax) { override func visitPost(_ node: AccessorDeclSyntax) {
guard node.accessorKind.tokenKind == .contextualKeyword("set") else { guard node.accessorKind.tokenKind == .keyword(.set) else {
return return
} }
let variableName = node.parameter?.name.withoutTrivia().text ?? "newValue" let variableName = node.parameter?.name.text ?? "newValue"
let visitor = NewValueUsageVisitor(variableName: variableName) let visitor = NewValueUsageVisitor(variableName: variableName)
if !visitor.walk(tree: node, handler: \.isVariableUsed) { if !visitor.walk(tree: node, handler: \.isVariableUsed) {
if (Syntax(node).closestVariableOrSubscript()?.modifiers).containsOverride, if (Syntax(node).closestVariableOrSubscript()?.modifiers).containsOverride,
@ -166,7 +166,7 @@ private extension UnusedSetterValueRule {
} }
override func visitPost(_ node: IdentifierExprSyntax) { override func visitPost(_ node: IdentifierExprSyntax) {
if node.identifier.withoutTrivia().text == variableName { if node.identifier.text == variableName {
isVariableUsed = true isVariableUsed = true
} }
} }

View File

@ -170,9 +170,7 @@ private extension ValidIBInspectableRule {
private extension VariableDeclSyntax { private extension VariableDeclSyntax {
var isIBInspectable: Bool { var isIBInspectable: Bool {
attributes?.contains { attr in attributes.contains(attributeNamed: "IBInspectable")
attr.as(AttributeSyntax.self)?.attributeName.text == "IBInspectable"
} ?? false
} }
var hasViolation: Bool { var hasViolation: Bool {
@ -180,7 +178,7 @@ private extension VariableDeclSyntax {
} }
var isReadOnlyProperty: Bool { var isReadOnlyProperty: Bool {
if letOrVarKeyword.tokenKind == .letKeyword { if letOrVarKeyword.tokenKind == .keyword(.let) {
return true return true
} }
@ -212,7 +210,7 @@ private extension VariableDeclSyntax {
return false return false
} }
return ValidIBInspectableRule.supportedTypes.contains(type.type.withoutTrivia().description) return ValidIBInspectableRule.supportedTypes.contains(type.type.trimmedDescription)
} }
} }
} }

View File

@ -114,7 +114,7 @@ private extension VariableDeclSyntax {
return false return false
} }
return pattern.identifier.withoutTrivia().text.lowercased().hasSuffix("delegate") return pattern.identifier.text.lowercased().hasSuffix("delegate")
} }
} }
@ -142,12 +142,12 @@ private extension VariableDeclSyntax {
] ]
return attributes?.contains { attr in return attributes?.contains { attr in
guard let customAttr = attr.as(CustomAttributeSyntax.self), guard case let .attribute(customAttr) = attr,
let typeIdentifier = customAttr.attributeName.as(SimpleTypeIdentifierSyntax.self) else { let typeIdentifier = customAttr.attributeName.as(SimpleTypeIdentifierSyntax.self) else {
return false return false
} }
return ignoredAttributes.contains(typeIdentifier.name.withoutTrivia().text) return ignoredAttributes.contains(typeIdentifier.name.text)
} ?? false } ?? false
} }
} }

View File

@ -42,7 +42,7 @@ struct YodaConditionRule: OptInRule, ConfigurationProviderRule, SwiftSyntaxRule
} }
private final class YodaConditionRuleVisitor: ViolationsSyntaxVisitor { private final class YodaConditionRuleVisitor: ViolationsSyntaxVisitor {
override func visitPost(_ node: IfStmtSyntax) { override func visitPost(_ node: IfExprSyntax) {
visit(conditions: node.conditions) visit(conditions: node.conditions)
} }

View File

@ -67,7 +67,7 @@ private extension EnumCaseAssociatedValuesLengthRule {
violationSeverity = .warning violationSeverity = .warning
} }
let reason = "Enum case \(node.identifier.withoutTrivia().text) should contain " let reason = "Enum case \(node.identifier.text) should contain "
+ "less than \(configuration.warning) associated values: " + "less than \(configuration.warning) associated values: "
+ "currently contains \(enumCaseAssociatedValueCount)" + "currently contains \(enumCaseAssociatedValueCount)"
violations.append( violations.append(

View File

@ -60,10 +60,8 @@ private extension ContainsOverFilterCountRule {
private extension TokenKind { private extension TokenKind {
var isZeroComparison: Bool { var isZeroComparison: Bool {
self == .spacedBinaryOperator("==") || self == .binaryOperator("==") ||
self == .spacedBinaryOperator("!=") || self == .binaryOperator("!=") ||
self == .unspacedBinaryOperator("==") || self == .binaryOperator(">")
self == .spacedBinaryOperator(">") ||
self == .unspacedBinaryOperator(">")
} }
} }

View File

@ -98,7 +98,7 @@ private extension ExprSyntax {
private extension TokenSyntax { private extension TokenSyntax {
var binaryOperator: String? { var binaryOperator: String? {
switch tokenKind { switch tokenKind {
case .spacedBinaryOperator(let str), .unspacedBinaryOperator(let str): case .binaryOperator(let str):
return str return str
default: default:
return nil return nil

View File

@ -43,7 +43,7 @@ private extension ReduceBooleanRule {
return return
} }
let suggestedFunction = bool.booleanLiteral.tokenKind == .trueKeyword ? "allSatisfy" : "contains" let suggestedFunction = bool.booleanLiteral.tokenKind == .keyword(.true) ? "allSatisfy" : "contains"
violations.append( violations.append(
ReasonedRuleViolation( ReasonedRuleViolation(
position: calledExpression.name.positionAfterSkippingLeadingTrivia, position: calledExpression.name.positionAfterSkippingLeadingTrivia,

View File

@ -121,7 +121,7 @@ private struct RuleHelper {
func hasViolation( func hasViolation(
locationConverter: SourceLocationConverter, locationConverter: SourceLocationConverter,
attributesAndPlacements: [(SyntaxProtocol, AttributePlacement)] attributesAndPlacements: [(AttributeSyntax, AttributePlacement)]
) -> Bool { ) -> Bool {
var linesWithAttributes: Set<Int> = [keywordLine] var linesWithAttributes: Set<Int> = [keywordLine]
for (attribute, placement) in attributesAndPlacements { for (attribute, placement) in attributesAndPlacements {
@ -147,75 +147,40 @@ private struct RuleHelper {
} }
} }
private enum Attribute {
case builtIn(AttributeSyntax)
case custom(CustomAttributeSyntax)
static func from(syntax: SyntaxProtocol) -> Self? {
if let attribute = syntax.as(AttributeSyntax.self) {
return .builtIn(attribute)
}
if let attribute = syntax.as(CustomAttributeSyntax.self) {
return .custom(attribute)
}
return nil
}
var hasArguments: Bool {
switch self {
case let .builtIn(attribute):
return attribute.argument != nil
case let .custom(attribute):
return attribute.argumentList != nil
}
}
var name: String? {
switch self {
case let .builtIn(attribute):
return attribute.attributeName.text
case let .custom(attribute):
return attribute.attributeName.as(SimpleTypeIdentifierSyntax.self)?.typeName
}
}
var syntaxNode: SyntaxProtocol {
switch self {
case let .builtIn(attribute):
return attribute
case let .custom(attribute):
return attribute
}
}
}
private extension AttributeListSyntax { private extension AttributeListSyntax {
func attributesAndPlacements(configuration: AttributesConfiguration, shouldBeOnSameLine: Bool) func attributesAndPlacements(configuration: AttributesConfiguration, shouldBeOnSameLine: Bool)
-> [(SyntaxProtocol, AttributePlacement)] { -> [(AttributeSyntax, AttributePlacement)] {
self.children(viewMode: .sourceAccurate) self
.compactMap { Attribute.from(syntax: $0) } .children(viewMode: .sourceAccurate)
.compactMap { attribute in .compactMap { $0.as(AttributeSyntax.self) }
guard let attributeName = attribute.name else { .map { attribute in
return nil let atPrefixedName = "@\(attribute.attributeNameText)"
}
let atPrefixedName = "@\(attributeName)"
if configuration.alwaysOnSameLine.contains(atPrefixedName) { if configuration.alwaysOnSameLine.contains(atPrefixedName) {
return (attribute.syntaxNode, .sameLineAsDeclaration) return (attribute, .sameLineAsDeclaration)
} else if configuration.alwaysOnNewLine.contains(atPrefixedName) { } else if configuration.alwaysOnNewLine.contains(atPrefixedName) {
return (attribute.syntaxNode, .dedicatedLine) return (attribute, .dedicatedLine)
} else if attribute.hasArguments { } else if attribute.argument != nil {
return (attribute.syntaxNode, .dedicatedLine) return (attribute, .dedicatedLine)
} }
return shouldBeOnSameLine return shouldBeOnSameLine ? (attribute, .sameLineAsDeclaration) : (attribute, .dedicatedLine)
? (attribute.syntaxNode, .sameLineAsDeclaration)
: (attribute.syntaxNode, .dedicatedLine)
} }
} }
var hasAttributeWithKeypathArgument: Bool {
contains { element in
switch element {
case .attribute(let attribute):
return attribute.hasKeypathArgument
case .ifConfigDecl:
return false
}
}
}
// swiftlint:disable:next cyclomatic_complexity // swiftlint:disable:next cyclomatic_complexity
func makeHelper(locationConverter: SourceLocationConverter) -> RuleHelper? { func makeHelper(locationConverter: SourceLocationConverter) -> RuleHelper? {
guard let parent else { guard let parent, !hasAttributeWithKeypathArgument else {
return nil return nil
} }
@ -263,3 +228,9 @@ private extension AttributeListSyntax {
) )
} }
} }
private extension AttributeSyntax {
var hasKeypathArgument: Bool {
argument?.as(TupleExprElementListSyntax.self)?.first?.expression.is(KeyPathExprSyntax.self) == true
}
}

View File

@ -80,7 +80,17 @@ internal struct AttributesRuleExamples {
@NSApplicationMain @NSApplicationMain
@MainActor @MainActor
final class AppDelegate: NSAppDelegate {} final class AppDelegate: NSAppDelegate {}
""") """),
Example(#"""
final class MyView: View {
@SwiftUI.Environment(\.colorScheme) var colorScheme: ColorScheme
}
"""#),
Example(#"""
final class MyView: View {
@Environment(\.colorScheme) var colorScheme: ColorScheme
}
"""#)
] ]
static let triggeringExamples = [ static let triggeringExamples = [

View File

@ -63,7 +63,7 @@ private extension ClosingBraceRule {
} }
correctionPositions.append(node.positionAfterSkippingLeadingTrivia) correctionPositions.append(node.positionAfterSkippingLeadingTrivia)
return super.visit(node.withTrailingTrivia(.zero)) return super.visit(node.with(\.trailingTrivia, .zero))
} }
} }
} }

View File

@ -103,16 +103,16 @@ private final class ClosureSpacingRuleRewriter: SyntaxRewriter, ViolationsSyntax
let violations = node.violations let violations = node.violations
if violations.leftBraceLeftSpace { if violations.leftBraceLeftSpace {
node.leftBrace = node.leftBrace.withLeadingTrivia(.spaces(1)) node.leftBrace = node.leftBrace.with(\.leadingTrivia, .spaces(1))
} }
if violations.leftBraceRightSpace { if violations.leftBraceRightSpace {
node.leftBrace = node.leftBrace.withTrailingTrivia(.spaces(1)) node.leftBrace = node.leftBrace.with(\.trailingTrivia, .spaces(1))
} }
if violations.rightBraceLeftSpace { if violations.rightBraceLeftSpace {
node.rightBrace = node.rightBrace.withLeadingTrivia(.spaces(1)) node.rightBrace = node.rightBrace.with(\.leadingTrivia, .spaces(1))
} }
if violations.rightBraceRightSpace { if violations.rightBraceRightSpace {
node.rightBrace = node.rightBrace.withTrailingTrivia(.spaces(1)) node.rightBrace = node.rightBrace.with(\.trailingTrivia, .spaces(1))
} }
if violations.hasViolations { if violations.hasViolations {
correctionPositions.append(node.positionAfterSkippingLeadingTrivia) correctionPositions.append(node.positionAfterSkippingLeadingTrivia)

View File

@ -67,11 +67,11 @@ private extension AccessorBlockSyntax {
} }
let tokens = accessors.map(\.accessorKind.tokenKind) let tokens = accessors.map(\.accessorKind.tokenKind)
if tokens == [.contextualKeyword("get"), .contextualKeyword("set")] { if tokens == [.keyword(.get), .keyword(.set)] {
return .getSet return .getSet
} }
if tokens == [.contextualKeyword("set"), .contextualKeyword("get")] { if tokens == [.keyword(.set), .keyword(.get)] {
return .setGet return .setGet
} }

View File

@ -53,7 +53,7 @@ private extension ConditionalReturnsOnNewlineRule {
super.init(viewMode: .sourceAccurate) super.init(viewMode: .sourceAccurate)
} }
override func visitPost(_ node: IfStmtSyntax) { override func visitPost(_ node: IfExprSyntax) {
if isReturn(node.body.statements.lastReturn, onTheSameLineAs: node.ifKeyword) { if isReturn(node.body.statements.lastReturn, onTheSameLineAs: node.ifKeyword) {
violations.append(node.ifKeyword.positionAfterSkippingLeadingTrivia) violations.append(node.ifKeyword.positionAfterSkippingLeadingTrivia)
return return

View File

@ -210,22 +210,30 @@ private class Rewriter: SyntaxRewriter, ViolationsSyntaxRewriter {
.enumerated() .enumerated()
.map { index, item in .map { index, item in
if index == bindingList.count - 2 { if index == bindingList.count - 2 {
return item.withTrailingComma(false) return item.with(\.trailingComma, nil)
} }
return item return item
} }
if newBindingList.isNotEmpty { if newBindingList.isNotEmpty {
newStmtList.append(CodeBlockItemSyntax( newStmtList.append(CodeBlockItemSyntax(
item: .decl(DeclSyntax(varDecl.withBindings(PatternBindingListSyntax(newBindingList)))) item: .decl(DeclSyntax(varDecl.with(\.bindings, PatternBindingListSyntax(newBindingList))))
)) ))
newStmtList.append(CodeBlockItemSyntax( newStmtList.append(CodeBlockItemSyntax(
item: .stmt(StmtSyntax(returnStmt.withExpression(initExpression))) item: .stmt(StmtSyntax(returnStmt.with(\.expression, initExpression)))
)) ))
} else { } else {
let leadingTrivia = (binding.trailingTrivia ?? .zero) + (returnStmt.leadingTrivia ?? .zero) let leadingTrivia = (binding.trailingTrivia ?? .zero) + (returnStmt.leadingTrivia ?? .zero)
newStmtList.append(CodeBlockItemSyntax( newStmtList.append(
item: .stmt(StmtSyntax(returnStmt.withExpression(initExpression).withLeadingTrivia(leadingTrivia))) CodeBlockItemSyntax(
)) item: .stmt(
StmtSyntax(
returnStmt
.with(\.expression, initExpression)
.with(\.leadingTrivia, leadingTrivia)
)
)
)
)
} }
return super.visit(CodeBlockItemListSyntax(newStmtList)) return super.visit(CodeBlockItemListSyntax(newStmtList))
} }

View File

@ -159,7 +159,7 @@ private extension EmptyEnumArgumentsRule {
} }
correctionPositions.append(violationPosition) correctionPositions.append(violationPosition)
return super.visit(node.withPattern(newPattern)) return super.visit(node.with(\.pattern, newPattern))
} }
override func visit(_ node: MatchingPatternConditionSyntax) -> MatchingPatternConditionSyntax { override func visit(_ node: MatchingPatternConditionSyntax) -> MatchingPatternConditionSyntax {
@ -171,7 +171,7 @@ private extension EmptyEnumArgumentsRule {
} }
correctionPositions.append(violationPosition) correctionPositions.append(violationPosition)
return super.visit(node.withPattern(newPattern)) return super.visit(node.with(\.pattern, newPattern))
} }
} }
} }
@ -200,7 +200,7 @@ private extension PatternSyntax {
private extension FunctionCallExprSyntax { private extension FunctionCallExprSyntax {
var argumentsHasViolation: Bool { var argumentsHasViolation: Bool {
!calledExpression.is(IdentifierExprSyntax.self) && !calledExpression.is(IdentifierExprSyntax.self) &&
calledExpression.as(MemberAccessExprSyntax.self)?.lastToken?.tokenKind != .initKeyword && calledExpression.as(MemberAccessExprSyntax.self)?.lastToken?.tokenKind != .keyword(.`init`) &&
argumentList.allSatisfy(\.expression.isDiscardAssignmentOrFunction) argumentList.allSatisfy(\.expression.isDiscardAssignmentOrFunction)
} }
@ -222,19 +222,19 @@ private extension FunctionCallExprSyntax {
if argumentList.allSatisfy({ $0.expression.is(DiscardAssignmentExprSyntax.self) }) { if argumentList.allSatisfy({ $0.expression.is(DiscardAssignmentExprSyntax.self) }) {
let newCalledExpression = calledExpression let newCalledExpression = calledExpression
.withTrailingTrivia(rightParen?.trailingTrivia ?? .zero) .with(\.trailingTrivia, rightParen?.trailingTrivia ?? .zero)
let newExpression = self let newExpression = self
.withCalledExpression(ExprSyntax(newCalledExpression)) .with(\.calledExpression, ExprSyntax(newCalledExpression))
.withLeftParen(nil) .with(\.leftParen, nil)
.withArgumentList(nil) .with(\.argumentList, [])
.withRightParen(nil) .with(\.rightParen, nil)
return ExprSyntax(newExpression) return ExprSyntax(newExpression)
} }
var copy = self var copy = self
for (index, arg) in argumentList.enumerated() { for (index, arg) in argumentList.enumerated() {
if let newArgExpr = arg.expression.as(FunctionCallExprSyntax.self) { if let newArgExpr = arg.expression.as(FunctionCallExprSyntax.self) {
let newArg = arg.withExpression(newArgExpr.removingInnermostDiscardArguments) let newArg = arg.with(\.expression, newArgExpr.removingInnermostDiscardArguments)
copy.argumentList = copy.argumentList.replacing(childAt: index, with: newArg) copy.argumentList = copy.argumentList.replacing(childAt: index, with: newArg)
} }
} }

View File

@ -75,7 +75,7 @@ private extension EmptyParametersRule {
} }
correctionPositions.append(violationPosition) correctionPositions.append(violationPosition)
return super.visit(node.withArguments(TupleTypeElementListSyntax([]))) return super.visit(node.with(\.arguments, TupleTypeElementListSyntax([])))
} }
} }
} }

View File

@ -88,9 +88,9 @@ private extension EmptyParenthesesWithTrailingClosureRule {
} }
let newNode = node let newNode = node
.withLeftParen(nil) .with(\.leftParen, nil)
.withRightParen(nil) .with(\.rightParen, nil)
.withTrailingClosure(node.trailingClosure?.withLeadingTrivia(.spaces(1))) .with(\.trailingClosure, node.trailingClosure?.with(\.leadingTrivia, .spaces(1)))
correctionPositions.append(violationPosition) correctionPositions.append(violationPosition)
return super.visit(newNode) return super.visit(newNode)
} }

View File

@ -36,8 +36,7 @@ private final class ImplicitGetterRuleVisitor: ViolationsSyntaxVisitor {
override func visitPost(_ node: AccessorBlockSyntax) { override func visitPost(_ node: AccessorBlockSyntax) {
guard let getAccessor = node.getAccessor, guard let getAccessor = node.getAccessor,
node.setAccessor == nil, node.setAccessor == nil,
getAccessor.asyncKeyword == nil, getAccessor.effectSpecifiers == nil,
getAccessor.throwsKeyword == nil,
getAccessor.modifier == nil, getAccessor.modifier == nil,
(getAccessor.attributes == nil || getAccessor.attributes?.isEmpty == true), (getAccessor.attributes == nil || getAccessor.attributes?.isEmpty == true),
getAccessor.body != nil else { getAccessor.body != nil else {

View File

@ -89,7 +89,7 @@ private extension NoSpaceInMethodCallRule {
correctionPositions.append(node.calledExpression.endPositionBeforeTrailingTrivia) correctionPositions.append(node.calledExpression.endPositionBeforeTrailingTrivia)
let newNode = node let newNode = node
.withCalledExpression(node.calledExpression.withoutTrailingTrivia()) .with(\.calledExpression, node.calledExpression.with(\.trailingTrivia, []))
return super.visit(newNode) return super.visit(newNode)
} }

View File

@ -88,7 +88,8 @@ private extension NumberSeparatorRule {
return super.visit(node) return super.visit(node)
} }
let newNode = node.withFloatingDigits(node.floatingDigits.withKind(.floatingLiteral(violation.correction))) let newNode = node.with(\.floatingDigits,
node.floatingDigits.withKind(.floatingLiteral(violation.correction)))
correctionPositions.append(violation.position) correctionPositions.append(violation.position)
return super.visit(newNode) return super.visit(newNode)
} }
@ -101,7 +102,7 @@ private extension NumberSeparatorRule {
return super.visit(node) return super.visit(node)
} }
let newNode = node.withDigits(node.digits.withKind(.integerLiteral(violation.correction))) let newNode = node.with(\.digits, node.digits.withKind(.integerLiteral(violation.correction)))
correctionPositions.append(violation.position) correctionPositions.append(violation.position)
return super.visit(newNode) return super.visit(newNode)
} }
@ -140,7 +141,7 @@ private enum NumberSeparatorViolation {
private extension NumberSeparatorValidator { private extension NumberSeparatorValidator {
func violation(token: TokenSyntax) -> NumberSeparatorViolation? { func violation(token: TokenSyntax) -> NumberSeparatorViolation? {
let content = token.withoutTrivia().text let content = token.text
guard isDecimal(number: content), guard isDecimal(number: content),
!isInValidRanges(number: content) !isInValidRanges(number: content)
else { else {

View File

@ -45,7 +45,7 @@ private extension OperatorFunctionWhitespaceRule {
private extension FunctionDeclSyntax { private extension FunctionDeclSyntax {
var isOperatorDeclaration: Bool { var isOperatorDeclaration: Bool {
switch identifier.tokenKind { switch identifier.tokenKind {
case .spacedBinaryOperator, .unspacedBinaryOperator: case .binaryOperator:
return true return true
default: default:
return false return false

View File

@ -183,7 +183,7 @@ private class OperatorUsageWhitespaceVisitor: SyntaxVisitor {
let noSpacingAfter = operatorToken.trailingTrivia.isEmpty && nextToken.leadingTrivia.isEmpty let noSpacingAfter = operatorToken.trailingTrivia.isEmpty && nextToken.leadingTrivia.isEmpty
let noSpacing = noSpacingBefore || noSpacingAfter let noSpacing = noSpacingBefore || noSpacingAfter
let operatorText = operatorToken.withoutTrivia().text let operatorText = operatorToken.text
if noSpacing && allowedNoSpaceOperators.contains(operatorText) { if noSpacing && allowedNoSpaceOperators.contains(operatorText) {
return nil return nil
} }
@ -230,7 +230,7 @@ private extension Trivia {
switch element { switch element {
case .blockComment, .docLineComment, .docBlockComment, .lineComment: case .blockComment, .docLineComment, .docBlockComment, .lineComment:
return true return true
case .carriageReturnLineFeeds, .carriageReturns, .formfeeds, .newlines, case .backslashes, .carriageReturnLineFeeds, .carriageReturns, .formfeeds, .newlines, .pounds,
.shebang, .spaces, .tabs, .unexpectedText, .verticalTabs: .shebang, .spaces, .tabs, .unexpectedText, .verticalTabs:
return false return false
} }

View File

@ -209,11 +209,11 @@ private extension OptionalEnumCaseMatchingRule {
!expression.expression.isDiscardAssignmentOrBoolLiteral { !expression.expression.isDiscardAssignmentOrBoolLiteral {
let violationPosition = expression.questionMark.positionAfterSkippingLeadingTrivia let violationPosition = expression.questionMark.positionAfterSkippingLeadingTrivia
correctionPositions.append(violationPosition) correctionPositions.append(violationPosition)
let newExpression = ExprSyntax(expression.withQuestionMark(nil)) let newPattern = PatternSyntax(pattern.with(\.expression, expression.expression))
let newPattern = PatternSyntax(pattern.withExpression(newExpression))
let newNode = node let newNode = node
.withPattern(newPattern) .with(\.pattern, newPattern)
.withWhereClause(node.whereClause?.withLeadingTrivia(expression.questionMark.trailingTrivia)) .with(\.whereClause,
node.whereClause?.with(\.leadingTrivia, expression.questionMark.trailingTrivia))
return super.visit(newNode) return super.visit(newNode)
} else if let expression = pattern.expression.as(TupleExprSyntax.self) { } else if let expression = pattern.expression.as(TupleExprSyntax.self) {
var newExpression = expression var newExpression = expression
@ -228,14 +228,13 @@ private extension OptionalEnumCaseMatchingRule {
let violationPosition = optionalChainingExpression.questionMark.positionAfterSkippingLeadingTrivia let violationPosition = optionalChainingExpression.questionMark.positionAfterSkippingLeadingTrivia
correctionPositions.append(violationPosition) correctionPositions.append(violationPosition)
let newElementExpression = ExprSyntax(optionalChainingExpression.withQuestionMark(nil)) let newElement = element.with(\.expression, optionalChainingExpression.expression)
let newElement = element.withExpression(newElementExpression)
newExpression.elementList = newExpression.elementList newExpression.elementList = newExpression.elementList
.replacing(childAt: index, with: newElement) .replacing(childAt: index, with: newElement)
} }
let newPattern = PatternSyntax(pattern.withExpression(ExprSyntax(newExpression))) let newPattern = PatternSyntax(pattern.with(\.expression, ExprSyntax(newExpression)))
let newNode = node.withPattern(newPattern) let newNode = node.with(\.pattern, newPattern)
return super.visit(newNode) return super.visit(newNode)
} }

View File

@ -296,7 +296,7 @@ private class Visitor: ViolationsSyntaxVisitor {
override func visit(_ node: MemberAccessExprSyntax) -> SyntaxVisitorContinueKind { override func visit(_ node: MemberAccessExprSyntax) -> SyntaxVisitorContinueKind {
if case .likeClass = parentDeclScopes.last { if case .likeClass = parentDeclScopes.last {
if node.name.tokenKind == .selfKeyword { if node.name.tokenKind == .keyword(.self) {
return .skipChildren return .skipChildren
} }
} }

View File

@ -147,11 +147,12 @@ private extension PreferSelfTypeOverTypeOfSelfRule {
correctionPositions.append(function.positionAfterSkippingLeadingTrivia) correctionPositions.append(function.positionAfterSkippingLeadingTrivia)
let base: IdentifierExprSyntax = "Self" // swiftlint:disable:next rule_id
let base = IdentifierExprSyntax(identifier: "Self")
let baseWithTrivia = base let baseWithTrivia = base
.withLeadingTrivia(function.leadingTrivia ?? .zero) .with(\.leadingTrivia, function.leadingTrivia ?? .zero)
.withTrailingTrivia(function.trailingTrivia ?? .zero) .with(\.trailingTrivia, function.trailingTrivia ?? .zero)
return super.visit(node.withBase(ExprSyntax(baseWithTrivia))) return super.visit(node.with(\.base, ExprSyntax(baseWithTrivia)))
} }
} }
} }
@ -160,7 +161,7 @@ private extension FunctionCallExprSyntax {
var hasViolation: Bool { var hasViolation: Bool {
return isTypeOfSelfCall && return isTypeOfSelfCall &&
argumentList.map(\.label?.text) == ["of"] && argumentList.map(\.label?.text) == ["of"] &&
argumentList.first?.expression.as(IdentifierExprSyntax.self)?.identifier.tokenKind == .selfKeyword argumentList.first?.expression.as(IdentifierExprSyntax.self)?.identifier.tokenKind == .keyword(.self)
} }
var isTypeOfSelfCall: Bool { var isTypeOfSelfCall: Bool {

View File

@ -87,7 +87,7 @@ private extension PrefixedTopLevelConstantRule {
override var skippableDeclarations: [DeclSyntaxProtocol.Type] { .all } override var skippableDeclarations: [DeclSyntaxProtocol.Type] { .all }
override func visitPost(_ node: VariableDeclSyntax) { override func visitPost(_ node: VariableDeclSyntax) {
guard node.letOrVarKeyword.tokenKind == .letKeyword else { guard node.letOrVarKeyword.tokenKind == .keyword(.let) else {
return return
} }

View File

@ -73,7 +73,7 @@ private extension ProtocolPropertyAccessorsOrderRule {
let reversedAccessors = AccessorListSyntax(Array(node.accessors.reversed())) let reversedAccessors = AccessorListSyntax(Array(node.accessors.reversed()))
return super.visit( return super.visit(
node.withAccessors(reversedAccessors) node.with(\.accessors, reversedAccessors)
) )
} }
} }
@ -83,7 +83,7 @@ private extension AccessorBlockSyntax {
var hasViolation: Bool { var hasViolation: Bool {
guard accessors.count == 2, guard accessors.count == 2,
accessors.allSatisfy({ $0.body == nil }), accessors.allSatisfy({ $0.body == nil }),
accessors.first?.accessorKind.tokenKind == .contextualKeyword("set") else { accessors.first?.accessorKind.tokenKind == .keyword(.set) else {
return false return false
} }

View File

@ -69,8 +69,8 @@ private extension RedundantDiscardableLetRule {
correctionPositions.append(node.positionAfterSkippingLeadingTrivia) correctionPositions.append(node.positionAfterSkippingLeadingTrivia)
let newNode = node let newNode = node
.withLetOrVarKeyword(nil) .with(\.letOrVarKeyword, .keyword(.let, presence: .missing))
.withBindings(node.bindings.withLeadingTrivia(node.letOrVarKeyword.leadingTrivia)) .with(\.bindings, node.bindings.with(\.leadingTrivia, node.letOrVarKeyword.leadingTrivia))
return super.visit(newNode) return super.visit(newNode)
} }
} }
@ -78,7 +78,7 @@ private extension RedundantDiscardableLetRule {
private extension VariableDeclSyntax { private extension VariableDeclSyntax {
var hasRedundantDiscardableLetViolation: Bool { var hasRedundantDiscardableLetViolation: Bool {
letOrVarKeyword.tokenKind == .letKeyword && letOrVarKeyword.tokenKind == .keyword(.let) &&
bindings.count == 1 && bindings.count == 1 &&
bindings.first!.pattern.is(WildcardPatternSyntax.self) && bindings.first!.pattern.is(WildcardPatternSyntax.self) &&
bindings.first!.typeAnnotation == nil && bindings.first!.typeAnnotation == nil &&

View File

@ -93,7 +93,7 @@ private extension ReturnArrowWhitespaceRule {
private(set) var corrections: [ArrowViolation] = [] private(set) var corrections: [ArrowViolation] = []
override func visitPost(_ node: FunctionTypeSyntax) { override func visitPost(_ node: FunctionTypeSyntax) {
guard let violation = node.arrow.arrowViolation else { guard let violation = node.output.arrow.arrowViolation else {
return return
} }

View File

@ -120,36 +120,36 @@ private final class SelfBindingRuleRewriter: SyntaxRewriter, ViolationsSyntaxRew
initializerIdentifier.identifier.text == "self" { initializerIdentifier.identifier.text == "self" {
correctionPositions.append(identifierPattern.positionAfterSkippingLeadingTrivia) correctionPositions.append(identifierPattern.positionAfterSkippingLeadingTrivia)
return super.visit( let newPattern = PatternSyntax(
node.withPattern( identifierPattern
PatternSyntax( .with(\.identifier,
identifierPattern.withIdentifier( identifierPattern.identifier.withKind(.identifier(bindIdentifier))
identifierPattern.identifier.withKind(.identifier(bindIdentifier))
)
)
) )
) )
return super.visit(node.with(\.pattern, newPattern))
} else if node.initializer == nil, identifierPattern.identifier.text == "self", bindIdentifier != "self" { } else if node.initializer == nil, identifierPattern.identifier.text == "self", bindIdentifier != "self" {
correctionPositions.append(identifierPattern.positionAfterSkippingLeadingTrivia) correctionPositions.append(identifierPattern.positionAfterSkippingLeadingTrivia)
let newPattern = PatternSyntax(
identifierPattern
.with(\.identifier,
identifierPattern.identifier.withKind(.identifier(bindIdentifier)))
)
let newInitializer = InitializerClauseSyntax(
value: IdentifierExprSyntax(
identifier: .keyword(
.`self`,
leadingTrivia: .space,
trailingTrivia: identifierPattern.trailingTrivia ?? .space
)
)
)
let newNode = node let newNode = node
.withPattern( .with(\.pattern, newPattern)
PatternSyntax( .with(\.initializer, newInitializer)
identifierPattern.withIdentifier(
identifierPattern.identifier.withKind(.identifier(bindIdentifier))
)
)
)
.withInitializer(
InitializerClauseSyntax(
value: IdentifierExprSyntax(
identifier: .selfKeyword(
leadingTrivia: .space,
trailingTrivia: identifierPattern.trailingTrivia ?? .space
)
)
)
)
return super.visit(newNode) return super.visit(newNode)
} else { } else {
return super.visit(node) return super.visit(node)

View File

@ -62,8 +62,8 @@ private extension ShorthandOperatorRule {
guard node.operatorOperand.is(AssignmentExprSyntax.self), guard node.operatorOperand.is(AssignmentExprSyntax.self),
let rightExpr = node.rightOperand.as(InfixOperatorExprSyntax.self), let rightExpr = node.rightOperand.as(InfixOperatorExprSyntax.self),
let binaryOperatorExpr = rightExpr.operatorOperand.as(BinaryOperatorExprSyntax.self), let binaryOperatorExpr = rightExpr.operatorOperand.as(BinaryOperatorExprSyntax.self),
ShorthandOperatorRule.allOperators.contains(binaryOperatorExpr.operatorToken.withoutTrivia().text), ShorthandOperatorRule.allOperators.contains(binaryOperatorExpr.operatorToken.text),
node.leftOperand.withoutTrivia().description == rightExpr.leftOperand.withoutTrivia().description node.leftOperand.trimmedDescription == rightExpr.leftOperand.trimmedDescription
else { else {
return return
} }
@ -86,7 +86,7 @@ private extension ShorthandOperatorRule {
private extension TokenSyntax { private extension TokenSyntax {
var binaryOperator: String? { var binaryOperator: String? {
switch tokenKind { switch tokenKind {
case .spacedBinaryOperator(let str), .unspacedBinaryOperator(let str): case .binaryOperator(let str):
return str return str
default: default:
return nil return nil

View File

@ -56,7 +56,7 @@ extension SwitchCaseAlignmentRule {
super.init(viewMode: .sourceAccurate) super.init(viewMode: .sourceAccurate)
} }
override func visitPost(_ node: SwitchStmtSyntax) { override func visitPost(_ node: SwitchExprSyntax) {
let switchPosition = node.switchKeyword.positionAfterSkippingLeadingTrivia let switchPosition = node.switchKeyword.positionAfterSkippingLeadingTrivia
guard guard
let switchColumn = locationConverter.location(for: switchPosition).column, let switchColumn = locationConverter.location(for: switchPosition).column,

View File

@ -135,16 +135,15 @@ private extension TrailingCommaRule {
switch (lastElement.trailingComma, mandatoryComma) { switch (lastElement.trailingComma, mandatoryComma) {
case (let commaToken?, false): case (let commaToken?, false):
correctionPositions.append(commaToken.positionAfterSkippingLeadingTrivia) correctionPositions.append(commaToken.positionAfterSkippingLeadingTrivia)
let newTrailingTrivia = (lastElement.valueExpression.trailingTrivia ?? .zero)
.appending(trivia: commaToken.leadingTrivia)
.appending(trivia: commaToken.trailingTrivia)
let newNode = node let newNode = node
.replacing( .replacing(
childAt: lastElement.indexInParent, childAt: lastElement.indexInParent,
with: lastElement with: lastElement
.withTrailingComma(nil) .with(\.trailingComma, nil)
.withTrailingTrivia( .with(\.trailingTrivia, newTrailingTrivia)
(lastElement.valueExpression.trailingTrivia ?? .zero)
.appending(trivia: commaToken.leadingTrivia)
.appending(trivia: commaToken.trailingTrivia)
)
) )
return super.visit(newNode) return super.visit(newNode)
case (nil, true) where !locationConverter.isSingleLine(node: node): case (nil, true) where !locationConverter.isSingleLine(node: node):
@ -153,9 +152,9 @@ private extension TrailingCommaRule {
.replacing( .replacing(
childAt: lastElement.indexInParent, childAt: lastElement.indexInParent,
with: lastElement with: lastElement
.withoutTrailingTrivia() .with(\.trailingTrivia, [])
.withTrailingComma(.commaToken()) .with(\.trailingComma, .commaToken())
.withTrailingTrivia(lastElement.trailingTrivia ?? .zero) .with(\.trailingTrivia, lastElement.trailingTrivia ?? .zero)
) )
return super.visit(newNode) return super.visit(newNode)
case (_, true), (nil, false): case (_, true), (nil, false):
@ -176,11 +175,11 @@ private extension TrailingCommaRule {
.replacing( .replacing(
childAt: lastElement.indexInParent, childAt: lastElement.indexInParent,
with: lastElement with: lastElement
.withTrailingComma(nil) .with(\.trailingComma, nil)
.withTrailingTrivia( .with(\.trailingTrivia,
(lastElement.expression.trailingTrivia ?? .zero) (lastElement.expression.trailingTrivia ?? .zero)
.appending(trivia: commaToken.leadingTrivia) .appending(trivia: commaToken.leadingTrivia)
.appending(trivia: commaToken.trailingTrivia) .appending(trivia: commaToken.trailingTrivia)
) )
) )
return super.visit(newNode) return super.visit(newNode)
@ -189,9 +188,9 @@ private extension TrailingCommaRule {
let newNode = node.replacing( let newNode = node.replacing(
childAt: lastElement.indexInParent, childAt: lastElement.indexInParent,
with: lastElement with: lastElement
.withExpression(lastElement.expression.withoutTrailingTrivia()) .with(\.expression, lastElement.expression.with(\.trailingTrivia, []))
.withTrailingComma(.commaToken()) .with(\.trailingComma, .commaToken())
.withTrailingTrivia(lastElement.expression.trailingTrivia ?? .zero) .with(\.trailingTrivia, lastElement.expression.trailingTrivia ?? .zero)
) )
return super.visit(newNode) return super.visit(newNode)
case (_, true), (nil, false): case (_, true), (nil, false):

View File

@ -133,7 +133,7 @@ private final class Rewriter: SyntaxRewriter, ViolationsSyntaxRewriter {
correctionPositions.append(clause.positionAfterSkippingLeadingTrivia) correctionPositions.append(clause.positionAfterSkippingLeadingTrivia)
let paramList = ClosureParamListSyntax(items).withTrailingTrivia(.spaces(1)) let paramList = ClosureParamListSyntax(items).with(\.trailingTrivia, .spaces(1))
return super.visit(node.withInput(.init(paramList))) return super.visit(node.with(\.input, .init(paramList)))
} }
} }

View File

@ -20,10 +20,10 @@ def swiftlint_repos(bzlmod = False):
http_archive( http_archive(
name = "com_github_apple_swift_syntax", name = "com_github_apple_swift_syntax",
sha256 = "2024415299a487fcb3b2d7500d2e7c149b4bc4f3b94408edd601457ee27f6b11", # SwiftSyntax sha256 sha256 = "ef9701634ad34e2dd08a2cd85bb5437af17512175bf6b4623c7d2d28068b6786", # SwiftSyntax sha256
build_file = "@SwiftLint//bazel:SwiftSyntax.BUILD", build_file = "@SwiftLint//bazel:SwiftSyntax.BUILD",
strip_prefix = "swift-syntax-0.50800.0-SNAPSHOT-2022-12-29-a", strip_prefix = "swift-syntax-0.50900.0-swift-DEVELOPMENT-SNAPSHOT-2023-02-06-a",
url = "https://github.com/apple/swift-syntax/archive/refs/tags/0.50800.0-SNAPSHOT-2022-12-29-a.tar.gz", url = "https://github.com/apple/swift-syntax/archive/refs/tags/0.50900.0-swift-DEVELOPMENT-SNAPSHOT-2023-02-06-a.tar.gz",
) )
http_archive( http_archive(