Skip to content

Commit a27f05e

Browse files
authored
Merge pull request #587 from xedin/upstream-lint-type-capitalization-rule
[Lint] Add a rule to detect that type declarations are not capitalized
2 parents ea5f38d + be4493c commit a27f05e

File tree

5 files changed

+227
-14
lines changed

5 files changed

+227
-14
lines changed

Sources/SwiftFormat/Pipelines+Generated.swift

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@ class LintPipeline: SyntaxVisitor {
3434
super.init(viewMode: .sourceAccurate)
3535
}
3636

37+
override func visit(_ node: ActorDeclSyntax) -> SyntaxVisitorContinueKind {
38+
visitIfEnabled(TypeNamesShouldBeCapitalized.visit, for: node)
39+
return .visitChildren
40+
}
41+
3742
override func visit(_ node: AsExprSyntax) -> SyntaxVisitorContinueKind {
3843
visitIfEnabled(NeverForceUnwrap.visit, for: node)
3944
return .visitChildren
@@ -42,6 +47,7 @@ class LintPipeline: SyntaxVisitor {
4247
override func visit(_ node: AssociatedTypeDeclSyntax) -> SyntaxVisitorContinueKind {
4348
visitIfEnabled(BeginDocumentationCommentWithOneLineSummary.visit, for: node)
4449
visitIfEnabled(NoLeadingUnderscores.visit, for: node)
50+
visitIfEnabled(TypeNamesShouldBeCapitalized.visit, for: node)
4551
return .visitChildren
4652
}
4753

@@ -51,6 +57,7 @@ class LintPipeline: SyntaxVisitor {
5157
visitIfEnabled(BeginDocumentationCommentWithOneLineSummary.visit, for: node)
5258
visitIfEnabled(DontRepeatTypeInStaticProperties.visit, for: node)
5359
visitIfEnabled(NoLeadingUnderscores.visit, for: node)
60+
visitIfEnabled(TypeNamesShouldBeCapitalized.visit, for: node)
5461
visitIfEnabled(UseTripleSlashForDocumentationComments.visit, for: node)
5562
return .visitChildren
5663
}
@@ -108,6 +115,7 @@ class LintPipeline: SyntaxVisitor {
108115
visitIfEnabled(FullyIndirectEnum.visit, for: node)
109116
visitIfEnabled(NoLeadingUnderscores.visit, for: node)
110117
visitIfEnabled(OneCasePerLine.visit, for: node)
118+
visitIfEnabled(TypeNamesShouldBeCapitalized.visit, for: node)
111119
visitIfEnabled(UseTripleSlashForDocumentationComments.visit, for: node)
112120
return .visitChildren
113121
}
@@ -165,12 +173,22 @@ class LintPipeline: SyntaxVisitor {
165173
return .visitChildren
166174
}
167175

176+
override func visit(_ node: GenericSpecializationExprSyntax) -> SyntaxVisitorContinueKind {
177+
visitIfEnabled(UseShorthandTypeNames.visit, for: node)
178+
return .visitChildren
179+
}
180+
168181
override func visit(_ node: IdentifierPatternSyntax) -> SyntaxVisitorContinueKind {
169182
visitIfEnabled(IdentifiersMustBeASCII.visit, for: node)
170183
visitIfEnabled(NoLeadingUnderscores.visit, for: node)
171184
return .visitChildren
172185
}
173186

187+
override func visit(_ node: IdentifierTypeSyntax) -> SyntaxVisitorContinueKind {
188+
visitIfEnabled(UseShorthandTypeNames.visit, for: node)
189+
return .visitChildren
190+
}
191+
174192
override func visit(_ node: IfExprSyntax) -> SyntaxVisitorContinueKind {
175193
visitIfEnabled(NoParensAroundConditions.visit, for: node)
176194
return .visitChildren
@@ -194,13 +212,13 @@ class LintPipeline: SyntaxVisitor {
194212
return .visitChildren
195213
}
196214

197-
override func visit(_ node: MemberBlockSyntax) -> SyntaxVisitorContinueKind {
198-
visitIfEnabled(AmbiguousTrailingClosureOverload.visit, for: node)
215+
override func visit(_ node: MemberBlockItemListSyntax) -> SyntaxVisitorContinueKind {
216+
visitIfEnabled(DoNotUseSemicolons.visit, for: node)
199217
return .visitChildren
200218
}
201219

202-
override func visit(_ node: MemberBlockItemListSyntax) -> SyntaxVisitorContinueKind {
203-
visitIfEnabled(DoNotUseSemicolons.visit, for: node)
220+
override func visit(_ node: MemberBlockSyntax) -> SyntaxVisitorContinueKind {
221+
visitIfEnabled(AmbiguousTrailingClosureOverload.visit, for: node)
204222
return .visitChildren
205223
}
206224

@@ -224,6 +242,7 @@ class LintPipeline: SyntaxVisitor {
224242
visitIfEnabled(BeginDocumentationCommentWithOneLineSummary.visit, for: node)
225243
visitIfEnabled(DontRepeatTypeInStaticProperties.visit, for: node)
226244
visitIfEnabled(NoLeadingUnderscores.visit, for: node)
245+
visitIfEnabled(TypeNamesShouldBeCapitalized.visit, for: node)
227246
visitIfEnabled(UseTripleSlashForDocumentationComments.visit, for: node)
228247
return .visitChildren
229248
}
@@ -233,11 +252,6 @@ class LintPipeline: SyntaxVisitor {
233252
return .visitChildren
234253
}
235254

236-
override func visit(_ node: IdentifierTypeSyntax) -> SyntaxVisitorContinueKind {
237-
visitIfEnabled(UseShorthandTypeNames.visit, for: node)
238-
return .visitChildren
239-
}
240-
241255
override func visit(_ node: SourceFileSyntax) -> SyntaxVisitorContinueKind {
242256
visitIfEnabled(AlwaysUseLowerCamelCase.visit, for: node)
243257
visitIfEnabled(AmbiguousTrailingClosureOverload.visit, for: node)
@@ -249,16 +263,12 @@ class LintPipeline: SyntaxVisitor {
249263
return .visitChildren
250264
}
251265

252-
override func visit(_ node: GenericSpecializationExprSyntax) -> SyntaxVisitorContinueKind {
253-
visitIfEnabled(UseShorthandTypeNames.visit, for: node)
254-
return .visitChildren
255-
}
256-
257266
override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind {
258267
visitIfEnabled(AllPublicDeclarationsHaveDocumentation.visit, for: node)
259268
visitIfEnabled(BeginDocumentationCommentWithOneLineSummary.visit, for: node)
260269
visitIfEnabled(DontRepeatTypeInStaticProperties.visit, for: node)
261270
visitIfEnabled(NoLeadingUnderscores.visit, for: node)
271+
visitIfEnabled(TypeNamesShouldBeCapitalized.visit, for: node)
262272
visitIfEnabled(UseSynthesizedInitializer.visit, for: node)
263273
visitIfEnabled(UseTripleSlashForDocumentationComments.visit, for: node)
264274
return .visitChildren
@@ -300,6 +310,7 @@ class LintPipeline: SyntaxVisitor {
300310
visitIfEnabled(AllPublicDeclarationsHaveDocumentation.visit, for: node)
301311
visitIfEnabled(BeginDocumentationCommentWithOneLineSummary.visit, for: node)
302312
visitIfEnabled(NoLeadingUnderscores.visit, for: node)
313+
visitIfEnabled(TypeNamesShouldBeCapitalized.visit, for: node)
303314
visitIfEnabled(UseTripleSlashForDocumentationComments.visit, for: node)
304315
return .visitChildren
305316
}

Sources/SwiftFormatConfiguration/RuleRegistry+Generated.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ enum RuleRegistry {
4141
"OnlyOneTrailingClosureArgument": true,
4242
"OrderedImports": true,
4343
"ReturnVoidInsteadOfEmptyTuple": true,
44+
"TypeNamesShouldBeCapitalized": true,
4445
"UseEarlyExits": false,
4546
"UseLetInEveryBoundCaseVariable": true,
4647
"UseShorthandTypeNames": true,

Sources/SwiftFormatRules/RuleNameCache+Generated.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ public let ruleNameCache: [ObjectIdentifier: String] = [
4141
ObjectIdentifier(OnlyOneTrailingClosureArgument.self): "OnlyOneTrailingClosureArgument",
4242
ObjectIdentifier(OrderedImports.self): "OrderedImports",
4343
ObjectIdentifier(ReturnVoidInsteadOfEmptyTuple.self): "ReturnVoidInsteadOfEmptyTuple",
44+
ObjectIdentifier(TypeNamesShouldBeCapitalized.self): "TypeNamesShouldBeCapitalized",
4445
ObjectIdentifier(UseEarlyExits.self): "UseEarlyExits",
4546
ObjectIdentifier(UseLetInEveryBoundCaseVariable.self): "UseLetInEveryBoundCaseVariable",
4647
ObjectIdentifier(UseShorthandTypeNames.self): "UseShorthandTypeNames",
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import SwiftFormatCore
14+
import SwiftSyntax
15+
16+
/// `struct`, `class`, `enum` and `protocol` declarations should have a capitalized name.
17+
///
18+
/// Lint: Types with un-capitalized names will yield a lint error.
19+
public final class TypeNamesShouldBeCapitalized : SyntaxLintRule {
20+
public override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind {
21+
diagnoseNameConventionMismatch(node, name: node.name)
22+
return .visitChildren
23+
}
24+
25+
public override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind {
26+
diagnoseNameConventionMismatch(node, name: node.name)
27+
return .visitChildren
28+
}
29+
30+
public override func visit(_ node: EnumDeclSyntax) -> SyntaxVisitorContinueKind {
31+
diagnoseNameConventionMismatch(node, name: node.name)
32+
return .visitChildren
33+
}
34+
35+
public override func visit(_ node: ProtocolDeclSyntax) -> SyntaxVisitorContinueKind {
36+
diagnoseNameConventionMismatch(node, name: node.name)
37+
return .visitChildren
38+
}
39+
40+
public override func visit(_ node: ActorDeclSyntax) -> SyntaxVisitorContinueKind {
41+
diagnoseNameConventionMismatch(node, name: node.name)
42+
return .visitChildren
43+
}
44+
45+
public override func visit(_ node: AssociatedTypeDeclSyntax) -> SyntaxVisitorContinueKind {
46+
diagnoseNameConventionMismatch(node, name: node.name)
47+
return .visitChildren
48+
}
49+
50+
public override func visit(_ node: TypeAliasDeclSyntax) -> SyntaxVisitorContinueKind {
51+
diagnoseNameConventionMismatch(node, name: node.name)
52+
return .visitChildren
53+
}
54+
55+
private func diagnoseNameConventionMismatch<T: DeclSyntaxProtocol>(_ type: T, name: TokenSyntax) {
56+
let leadingUnderscores = name.text.prefix { $0 == "_" }
57+
if let firstChar = name.text[leadingUnderscores.endIndex...].first,
58+
firstChar.uppercased() != String(firstChar) {
59+
diagnose(.capitalizeTypeName(name: name.text), on: type, severity: .convention)
60+
}
61+
}
62+
}
63+
64+
extension Finding.Message {
65+
public static func capitalizeTypeName(name: String) -> Finding.Message {
66+
var capitalized = name
67+
let leadingUnderscores = capitalized.prefix { $0 == "_" }
68+
let charAt = leadingUnderscores.endIndex
69+
capitalized.replaceSubrange(charAt...charAt, with: capitalized[charAt].uppercased())
70+
return "type names should be capitalized: \(name) -> \(capitalized)"
71+
}
72+
}
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
import SwiftFormatRules
2+
3+
final class TypeNamesShouldBeCapitalizedTests: LintOrFormatRuleTestCase {
4+
func testConstruction() {
5+
let input =
6+
"""
7+
struct a {}
8+
class klassName {
9+
struct subType {}
10+
}
11+
protocol myProtocol {}
12+
13+
extension myType {
14+
struct innerType {}
15+
}
16+
"""
17+
18+
performLint(TypeNamesShouldBeCapitalized.self, input: input)
19+
20+
XCTAssertDiagnosed(.capitalizeTypeName(name: "a"))
21+
XCTAssertDiagnosed(.capitalizeTypeName(name: "klassName"))
22+
XCTAssertDiagnosed(.capitalizeTypeName(name: "subType"))
23+
XCTAssertDiagnosed(.capitalizeTypeName(name: "myProtocol"))
24+
XCTAssertNotDiagnosed(.capitalizeTypeName(name: "myType"))
25+
XCTAssertDiagnosed(.capitalizeTypeName(name: "innerType"))
26+
}
27+
28+
func testActors() {
29+
let input =
30+
"""
31+
actor myActor {}
32+
actor OtherActor {}
33+
distributed actor greeter {}
34+
distributed actor DistGreeter {}
35+
"""
36+
37+
performLint(TypeNamesShouldBeCapitalized.self, input: input)
38+
39+
XCTAssertDiagnosed(.capitalizeTypeName(name: "myActor"))
40+
XCTAssertNotDiagnosed(.capitalizeTypeName(name: "OtherActor"))
41+
XCTAssertDiagnosed(.capitalizeTypeName(name: "greeter"))
42+
XCTAssertNotDiagnosed(.capitalizeTypeName(name: "DistGreeter"))
43+
}
44+
45+
func testAssociatedTypeandTypeAlias() {
46+
let input =
47+
"""
48+
protocol P {
49+
associatedtype kind
50+
associatedtype OtherKind
51+
}
52+
53+
typealias x = Int
54+
typealias Y = String
55+
56+
struct MyType {
57+
typealias data<T> = Y
58+
59+
func test() {
60+
typealias Value<T> = Y
61+
}
62+
}
63+
"""
64+
65+
performLint(TypeNamesShouldBeCapitalized.self, input: input)
66+
67+
XCTAssertDiagnosed(.capitalizeTypeName(name: "kind"))
68+
XCTAssertNotDiagnosed(.capitalizeTypeName(name: "OtherKind"))
69+
XCTAssertDiagnosed(.capitalizeTypeName(name: "x"))
70+
XCTAssertNotDiagnosed(.capitalizeTypeName(name: "Y"))
71+
XCTAssertDiagnosed(.capitalizeTypeName(name: "data"))
72+
XCTAssertNotDiagnosed(.capitalizeTypeName(name: "Value"))
73+
}
74+
75+
func testThatUnderscoredNamesAreDiagnosed() {
76+
let input =
77+
"""
78+
protocol _p {
79+
associatedtype _value
80+
associatedtype __Value
81+
}
82+
83+
protocol ___Q {
84+
}
85+
86+
struct _data {
87+
typealias _x = Int
88+
}
89+
90+
struct _Data {}
91+
92+
actor _internalActor {}
93+
94+
enum __e {
95+
}
96+
97+
enum _OtherE {
98+
}
99+
100+
func test() {
101+
class _myClass {}
102+
do {
103+
class _MyClass {}
104+
}
105+
}
106+
107+
distributed actor __greeter {}
108+
distributed actor __InternalGreeter {}
109+
"""
110+
111+
performLint(TypeNamesShouldBeCapitalized.self, input: input)
112+
113+
XCTAssertDiagnosed(.capitalizeTypeName(name: "_p"))
114+
XCTAssertNotDiagnosed(.capitalizeTypeName(name: "___Q"))
115+
XCTAssertDiagnosed(.capitalizeTypeName(name: "_value"))
116+
XCTAssertNotDiagnosed(.capitalizeTypeName(name: "__Value"))
117+
XCTAssertDiagnosed(.capitalizeTypeName(name: "_data"))
118+
XCTAssertNotDiagnosed(.capitalizeTypeName(name: "_Data"))
119+
XCTAssertDiagnosed(.capitalizeTypeName(name: "_x"))
120+
XCTAssertDiagnosed(.capitalizeTypeName(name: "_internalActor"))
121+
XCTAssertDiagnosed(.capitalizeTypeName(name: "__e"))
122+
XCTAssertNotDiagnosed(.capitalizeTypeName(name: "__OtherE"))
123+
XCTAssertDiagnosed(.capitalizeTypeName(name: "_myClass"))
124+
XCTAssertNotDiagnosed(.capitalizeTypeName(name: "_MyClass"))
125+
XCTAssertDiagnosed(.capitalizeTypeName(name: "__greeter"))
126+
XCTAssertNotDiagnosed(.capitalizeTypeName(name: "__InternalGreeter"))
127+
}
128+
}

0 commit comments

Comments
 (0)