Skip to content

Commit f0d1167

Browse files
authored
Merge pull request #777 from shawnhyam/upgrade-dont-repeat-type-in-static-properties
Upgrade the DontRepeatTypeInStaticProperties rule.
2 parents 992d2c6 + 3155cb5 commit f0d1167

9 files changed

+66
-69
lines changed

Documentation/RuleDocumentation.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
Use the rules below in the `rules` block of your `.swift-format`
66
configuration file, as described in
7-
[Configuration](Configuration.md). All of these rules can be
7+
[Configuration](Documentation/Configuration.md). All of these rules can be
88
applied in the linter, but only some of them can format your source code
99
automatically.
1010

Sources/SwiftFormat/Core/Pipelines+Generated.swift

+2-10
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,6 @@ class LintPipeline: SyntaxVisitor {
7676
visitIfEnabled(AllPublicDeclarationsHaveDocumentation.visit, for: node)
7777
visitIfEnabled(AlwaysUseLowerCamelCase.visit, for: node)
7878
visitIfEnabled(BeginDocumentationCommentWithOneLineSummary.visit, for: node)
79-
visitIfEnabled(DontRepeatTypeInStaticProperties.visit, for: node)
8079
visitIfEnabled(NoLeadingUnderscores.visit, for: node)
8180
visitIfEnabled(TypeNamesShouldBeCapitalized.visit, for: node)
8281
visitIfEnabled(UseTripleSlashForDocumentationComments.visit, for: node)
@@ -86,7 +85,6 @@ class LintPipeline: SyntaxVisitor {
8685
onVisitPost(rule: AllPublicDeclarationsHaveDocumentation.self, for: node)
8786
onVisitPost(rule: AlwaysUseLowerCamelCase.self, for: node)
8887
onVisitPost(rule: BeginDocumentationCommentWithOneLineSummary.self, for: node)
89-
onVisitPost(rule: DontRepeatTypeInStaticProperties.self, for: node)
9088
onVisitPost(rule: NoLeadingUnderscores.self, for: node)
9189
onVisitPost(rule: TypeNamesShouldBeCapitalized.self, for: node)
9290
onVisitPost(rule: UseTripleSlashForDocumentationComments.self, for: node)
@@ -182,7 +180,6 @@ class LintPipeline: SyntaxVisitor {
182180

183181
override func visit(_ node: EnumDeclSyntax) -> SyntaxVisitorContinueKind {
184182
visitIfEnabled(BeginDocumentationCommentWithOneLineSummary.visit, for: node)
185-
visitIfEnabled(DontRepeatTypeInStaticProperties.visit, for: node)
186183
visitIfEnabled(FullyIndirectEnum.visit, for: node)
187184
visitIfEnabled(NoLeadingUnderscores.visit, for: node)
188185
visitIfEnabled(OneCasePerLine.visit, for: node)
@@ -192,7 +189,6 @@ class LintPipeline: SyntaxVisitor {
192189
}
193190
override func visitPost(_ node: EnumDeclSyntax) {
194191
onVisitPost(rule: BeginDocumentationCommentWithOneLineSummary.self, for: node)
195-
onVisitPost(rule: DontRepeatTypeInStaticProperties.self, for: node)
196192
onVisitPost(rule: FullyIndirectEnum.self, for: node)
197193
onVisitPost(rule: NoLeadingUnderscores.self, for: node)
198194
onVisitPost(rule: OneCasePerLine.self, for: node)
@@ -202,14 +198,12 @@ class LintPipeline: SyntaxVisitor {
202198

203199
override func visit(_ node: ExtensionDeclSyntax) -> SyntaxVisitorContinueKind {
204200
visitIfEnabled(AvoidRetroactiveConformances.visit, for: node)
205-
visitIfEnabled(DontRepeatTypeInStaticProperties.visit, for: node)
206201
visitIfEnabled(NoAccessLevelOnExtensionDeclaration.visit, for: node)
207202
visitIfEnabled(UseTripleSlashForDocumentationComments.visit, for: node)
208203
return .visitChildren
209204
}
210205
override func visitPost(_ node: ExtensionDeclSyntax) {
211206
onVisitPost(rule: AvoidRetroactiveConformances.self, for: node)
212-
onVisitPost(rule: DontRepeatTypeInStaticProperties.self, for: node)
213207
onVisitPost(rule: NoAccessLevelOnExtensionDeclaration.self, for: node)
214208
onVisitPost(rule: UseTripleSlashForDocumentationComments.self, for: node)
215209
}
@@ -423,7 +417,6 @@ class LintPipeline: SyntaxVisitor {
423417
override func visit(_ node: ProtocolDeclSyntax) -> SyntaxVisitorContinueKind {
424418
visitIfEnabled(AllPublicDeclarationsHaveDocumentation.visit, for: node)
425419
visitIfEnabled(BeginDocumentationCommentWithOneLineSummary.visit, for: node)
426-
visitIfEnabled(DontRepeatTypeInStaticProperties.visit, for: node)
427420
visitIfEnabled(NoLeadingUnderscores.visit, for: node)
428421
visitIfEnabled(TypeNamesShouldBeCapitalized.visit, for: node)
429422
visitIfEnabled(UseTripleSlashForDocumentationComments.visit, for: node)
@@ -432,7 +425,6 @@ class LintPipeline: SyntaxVisitor {
432425
override func visitPost(_ node: ProtocolDeclSyntax) {
433426
onVisitPost(rule: AllPublicDeclarationsHaveDocumentation.self, for: node)
434427
onVisitPost(rule: BeginDocumentationCommentWithOneLineSummary.self, for: node)
435-
onVisitPost(rule: DontRepeatTypeInStaticProperties.self, for: node)
436428
onVisitPost(rule: NoLeadingUnderscores.self, for: node)
437429
onVisitPost(rule: TypeNamesShouldBeCapitalized.self, for: node)
438430
onVisitPost(rule: UseTripleSlashForDocumentationComments.self, for: node)
@@ -469,7 +461,6 @@ class LintPipeline: SyntaxVisitor {
469461
override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind {
470462
visitIfEnabled(AllPublicDeclarationsHaveDocumentation.visit, for: node)
471463
visitIfEnabled(BeginDocumentationCommentWithOneLineSummary.visit, for: node)
472-
visitIfEnabled(DontRepeatTypeInStaticProperties.visit, for: node)
473464
visitIfEnabled(NoLeadingUnderscores.visit, for: node)
474465
visitIfEnabled(TypeNamesShouldBeCapitalized.visit, for: node)
475466
visitIfEnabled(UseSynthesizedInitializer.visit, for: node)
@@ -479,7 +470,6 @@ class LintPipeline: SyntaxVisitor {
479470
override func visitPost(_ node: StructDeclSyntax) {
480471
onVisitPost(rule: AllPublicDeclarationsHaveDocumentation.self, for: node)
481472
onVisitPost(rule: BeginDocumentationCommentWithOneLineSummary.self, for: node)
482-
onVisitPost(rule: DontRepeatTypeInStaticProperties.self, for: node)
483473
onVisitPost(rule: NoLeadingUnderscores.self, for: node)
484474
onVisitPost(rule: TypeNamesShouldBeCapitalized.self, for: node)
485475
onVisitPost(rule: UseSynthesizedInitializer.self, for: node)
@@ -568,6 +558,7 @@ class LintPipeline: SyntaxVisitor {
568558
visitIfEnabled(AllPublicDeclarationsHaveDocumentation.visit, for: node)
569559
visitIfEnabled(AlwaysUseLowerCamelCase.visit, for: node)
570560
visitIfEnabled(BeginDocumentationCommentWithOneLineSummary.visit, for: node)
561+
visitIfEnabled(DontRepeatTypeInStaticProperties.visit, for: node)
571562
visitIfEnabled(NeverUseImplicitlyUnwrappedOptionals.visit, for: node)
572563
visitIfEnabled(UseTripleSlashForDocumentationComments.visit, for: node)
573564
return .visitChildren
@@ -576,6 +567,7 @@ class LintPipeline: SyntaxVisitor {
576567
onVisitPost(rule: AllPublicDeclarationsHaveDocumentation.self, for: node)
577568
onVisitPost(rule: AlwaysUseLowerCamelCase.self, for: node)
578569
onVisitPost(rule: BeginDocumentationCommentWithOneLineSummary.self, for: node)
570+
onVisitPost(rule: DontRepeatTypeInStaticProperties.self, for: node)
579571
onVisitPost(rule: NeverUseImplicitlyUnwrappedOptionals.self, for: node)
580572
onVisitPost(rule: UseTripleSlashForDocumentationComments.self, for: node)
581573
}

Sources/SwiftFormat/Rules/AlwaysUseLowerCamelCase.swift

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import SwiftSyntax
1717
///
1818
/// This rule does not apply to test code, defined as code which:
1919
/// * Contains the line `import XCTest`
20+
/// * The function is marked with `@Test` attribute
2021
///
2122
/// Lint: If an identifier contains underscores or begins with a capital letter, a lint error is
2223
/// raised.

Sources/SwiftFormat/Rules/DontRepeatTypeInStaticProperties.swift

+54-58
Original file line numberDiff line numberDiff line change
@@ -22,69 +22,28 @@ import SwiftSyntax
2222
@_spi(Rules)
2323
public final class DontRepeatTypeInStaticProperties: SyntaxLintRule {
2424

25-
public override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind {
26-
diagnoseStaticMembers(node.memberBlock.members, endingWith: node.name.text)
27-
return .skipChildren
28-
}
29-
30-
public override func visit(_ node: EnumDeclSyntax) -> SyntaxVisitorContinueKind {
31-
diagnoseStaticMembers(node.memberBlock.members, endingWith: node.name.text)
32-
return .skipChildren
33-
}
34-
35-
public override func visit(_ node: ProtocolDeclSyntax) -> SyntaxVisitorContinueKind {
36-
diagnoseStaticMembers(node.memberBlock.members, endingWith: node.name.text)
37-
return .skipChildren
38-
}
39-
40-
public override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind {
41-
diagnoseStaticMembers(node.memberBlock.members, endingWith: node.name.text)
42-
return .skipChildren
43-
}
44-
45-
public override func visit(_ node: ExtensionDeclSyntax) -> SyntaxVisitorContinueKind {
46-
let members = node.memberBlock.members
47-
48-
switch Syntax(node.extendedType).as(SyntaxEnum.self) {
49-
case .identifierType(let simpleType):
50-
diagnoseStaticMembers(members, endingWith: simpleType.name.text)
51-
case .memberType(let memberType):
52-
// We don't need to drill recursively into this structure because types with more than two
53-
// components are constructed left-heavy; that is, `A.B.C.D` is structured as `((A.B).C).D`,
54-
// and the final component of the top type is what we want.
55-
diagnoseStaticMembers(members, endingWith: memberType.name.text)
56-
default:
57-
// Do nothing for non-nominal types. If Swift adds support for extensions on non-nominals,
58-
// we'll need to update this if we need to support some subset of those.
59-
break
25+
/// Visits the static/class properties and diagnoses any where the name has the containing
26+
/// type name (excluding possible namespace prefixes, like `NS` or `UI`) as a suffix.
27+
public override func visit(_ node: VariableDeclSyntax) -> SyntaxVisitorContinueKind {
28+
guard node.modifiers.contains(anyOf: [.class, .static]),
29+
let typeName = Syntax(node).containingDeclName
30+
else {
31+
return .visitChildren
6032
}
6133

62-
return .skipChildren
63-
}
64-
65-
/// Iterates over the static/class properties in the given member list and diagnoses any where the
66-
/// name has the containing type name (excluding possible namespace prefixes, like `NS` or `UI`)
67-
/// as a suffix.
68-
private func diagnoseStaticMembers(_ members: MemberBlockItemListSyntax, endingWith typeName: String) {
69-
for member in members {
70-
guard
71-
let varDecl = member.decl.as(VariableDeclSyntax.self),
72-
varDecl.modifiers.contains(anyOf: [.class, .static])
73-
else { continue }
74-
75-
let bareTypeName = removingPossibleNamespacePrefix(from: typeName)
76-
77-
for binding in varDecl.bindings {
78-
guard let identifierPattern = binding.pattern.as(IdentifierPatternSyntax.self) else {
79-
continue
80-
}
34+
let bareTypeName = removingPossibleNamespacePrefix(from: typeName)
35+
for binding in node.bindings {
36+
guard let identifierPattern = binding.pattern.as(IdentifierPatternSyntax.self) else {
37+
continue
38+
}
8139

82-
let varName = identifierPattern.identifier.text
83-
if varName.contains(bareTypeName) {
84-
diagnose(.removeTypeFromName(name: varName, type: bareTypeName), on: identifierPattern)
85-
}
40+
let varName = identifierPattern.identifier.text
41+
if varName.contains(bareTypeName) {
42+
diagnose(.removeTypeFromName(name: varName, type: bareTypeName), on: identifierPattern)
8643
}
8744
}
45+
46+
return .visitChildren
8847
}
8948

9049
/// Returns the portion of the given string that excludes a possible Objective-C-style capitalized
@@ -110,3 +69,40 @@ extension Finding.Message {
11069
"remove the suffix '\(type)' from the name of the variable '\(name)'"
11170
}
11271
}
72+
73+
extension Syntax {
74+
/// Returns the name of the immediately enclosing type of this decl if there is one,
75+
/// otherwise nil.
76+
fileprivate var containingDeclName: String? {
77+
switch Syntax(self).as(SyntaxEnum.self) {
78+
case .actorDecl(let node):
79+
return node.name.text
80+
case .classDecl(let node):
81+
return node.name.text
82+
case .enumDecl(let node):
83+
return node.name.text
84+
case .protocolDecl(let node):
85+
return node.name.text
86+
case .structDecl(let node):
87+
return node.name.text
88+
case .extensionDecl(let node):
89+
switch Syntax(node.extendedType).as(SyntaxEnum.self) {
90+
case .identifierType(let simpleType):
91+
return simpleType.name.text
92+
case .memberType(let memberType):
93+
// the final component of the top type `A.B.C.D` is what we want `D`.
94+
return memberType.name.text
95+
default:
96+
// Do nothing for non-nominal types. If Swift adds support for extensions on non-nominals,
97+
// we'll need to update this if we need to support some subset of those.
98+
return nil
99+
}
100+
default:
101+
if let parent = self.parent {
102+
return parent.containingDeclName
103+
}
104+
105+
return nil
106+
}
107+
}
108+
}

Sources/SwiftFormat/Rules/NeverForceUnwrap.swift

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import SwiftSyntax
1616
///
1717
/// This rule does not apply to test code, defined as code which:
1818
/// * Contains the line `import XCTest`
19+
/// * The function is marked with `@Test` attribute
1920
///
2021
/// Lint: If a force unwrap is used, a lint warning is raised.
2122
@_spi(Rules)

Sources/SwiftFormat/Rules/NeverUseForceTry.swift

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import SwiftSyntax
1616
///
1717
/// This rule does not apply to test code, defined as code which:
1818
/// * Contains the line `import XCTest`
19+
/// * The function is marked with `@Test` attribute
1920
///
2021
/// Lint: Using `try!` results in a lint error.
2122
///

Sources/SwiftFormat/Rules/NeverUseImplicitlyUnwrappedOptionals.swift

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import SwiftSyntax
1818
///
1919
/// This rule does not apply to test code, defined as code which:
2020
/// * Contains the line `import XCTest`
21+
/// * The function is marked with `@Test` attribute
2122
///
2223
/// TODO: Create exceptions for other UI elements (ex: viewDidLoad)
2324
///

Sources/generate-swift-format/PipelineGenerator.swift

+1
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ final class PipelineGenerator: FileGenerator {
9393
handle.write(
9494
"""
9595
override func visitPost(_ node: \(nodeType)) {
96+
9697
"""
9798
)
9899
for ruleName in lintRules.sorted() {

Tests/SwiftFormatTests/Rules/DontRepeatTypeInStaticPropertiesTests.swift

+4
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ final class DontRepeatTypeInStaticPropertiesTests: LintOrFormatRuleTestCase {
3232
extension URLSession {
3333
class var 8️⃣sharedSession: URLSession
3434
}
35+
public actor Cookie {
36+
static let 9️⃣chocolateChipCookie: Cookie
37+
}
3538
""",
3639
findings: [
3740
FindingSpec("1️⃣", message: "remove the suffix 'Color' from the name of the variable 'redColor'"),
@@ -42,6 +45,7 @@ final class DontRepeatTypeInStaticPropertiesTests: LintOrFormatRuleTestCase {
4245
FindingSpec("6️⃣", message: "remove the suffix 'Game' from the name of the variable 'basketballGame'"),
4346
FindingSpec("7️⃣", message: "remove the suffix 'Game' from the name of the variable 'baseballGame'"),
4447
FindingSpec("8️⃣", message: "remove the suffix 'Session' from the name of the variable 'sharedSession'"),
48+
FindingSpec("9️⃣", message: "remove the suffix 'Cookie' from the name of the variable 'chocolateChipCookie'"),
4549
]
4650
)
4751
}

0 commit comments

Comments
 (0)