Skip to content

Commit 9a1f74f

Browse files
committed
[Lint] Add a rule to detect that type declarations are not capitalized
1 parent 36372a7 commit 9a1f74f

File tree

5 files changed

+99
-14
lines changed

5 files changed

+99
-14
lines changed

Sources/SwiftFormat/Pipelines+Generated.swift

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ class LintPipeline: SyntaxVisitor {
5151
visitIfEnabled(BeginDocumentationCommentWithOneLineSummary.visit, for: node)
5252
visitIfEnabled(DontRepeatTypeInStaticProperties.visit, for: node)
5353
visitIfEnabled(NoLeadingUnderscores.visit, for: node)
54+
visitIfEnabled(TypeNamesShouldBeCapitalized.visit, for: node)
5455
visitIfEnabled(UseTripleSlashForDocumentationComments.visit, for: node)
5556
return .visitChildren
5657
}
@@ -108,6 +109,7 @@ class LintPipeline: SyntaxVisitor {
108109
visitIfEnabled(FullyIndirectEnum.visit, for: node)
109110
visitIfEnabled(NoLeadingUnderscores.visit, for: node)
110111
visitIfEnabled(OneCasePerLine.visit, for: node)
112+
visitIfEnabled(TypeNamesShouldBeCapitalized.visit, for: node)
111113
visitIfEnabled(UseTripleSlashForDocumentationComments.visit, for: node)
112114
return .visitChildren
113115
}
@@ -165,12 +167,22 @@ class LintPipeline: SyntaxVisitor {
165167
return .visitChildren
166168
}
167169

170+
override func visit(_ node: GenericSpecializationExprSyntax) -> SyntaxVisitorContinueKind {
171+
visitIfEnabled(UseShorthandTypeNames.visit, for: node)
172+
return .visitChildren
173+
}
174+
168175
override func visit(_ node: IdentifierPatternSyntax) -> SyntaxVisitorContinueKind {
169176
visitIfEnabled(IdentifiersMustBeASCII.visit, for: node)
170177
visitIfEnabled(NoLeadingUnderscores.visit, for: node)
171178
return .visitChildren
172179
}
173180

181+
override func visit(_ node: IdentifierTypeSyntax) -> SyntaxVisitorContinueKind {
182+
visitIfEnabled(UseShorthandTypeNames.visit, for: node)
183+
return .visitChildren
184+
}
185+
174186
override func visit(_ node: IfExprSyntax) -> SyntaxVisitorContinueKind {
175187
visitIfEnabled(NoParensAroundConditions.visit, for: node)
176188
return .visitChildren
@@ -194,13 +206,13 @@ class LintPipeline: SyntaxVisitor {
194206
return .visitChildren
195207
}
196208

197-
override func visit(_ node: MemberBlockSyntax) -> SyntaxVisitorContinueKind {
198-
visitIfEnabled(AmbiguousTrailingClosureOverload.visit, for: node)
209+
override func visit(_ node: MemberBlockItemListSyntax) -> SyntaxVisitorContinueKind {
210+
visitIfEnabled(DoNotUseSemicolons.visit, for: node)
199211
return .visitChildren
200212
}
201213

202-
override func visit(_ node: MemberBlockItemListSyntax) -> SyntaxVisitorContinueKind {
203-
visitIfEnabled(DoNotUseSemicolons.visit, for: node)
214+
override func visit(_ node: MemberBlockSyntax) -> SyntaxVisitorContinueKind {
215+
visitIfEnabled(AmbiguousTrailingClosureOverload.visit, for: node)
204216
return .visitChildren
205217
}
206218

@@ -224,6 +236,7 @@ class LintPipeline: SyntaxVisitor {
224236
visitIfEnabled(BeginDocumentationCommentWithOneLineSummary.visit, for: node)
225237
visitIfEnabled(DontRepeatTypeInStaticProperties.visit, for: node)
226238
visitIfEnabled(NoLeadingUnderscores.visit, for: node)
239+
visitIfEnabled(TypeNamesShouldBeCapitalized.visit, for: node)
227240
visitIfEnabled(UseTripleSlashForDocumentationComments.visit, for: node)
228241
return .visitChildren
229242
}
@@ -233,11 +246,6 @@ class LintPipeline: SyntaxVisitor {
233246
return .visitChildren
234247
}
235248

236-
override func visit(_ node: IdentifierTypeSyntax) -> SyntaxVisitorContinueKind {
237-
visitIfEnabled(UseShorthandTypeNames.visit, for: node)
238-
return .visitChildren
239-
}
240-
241249
override func visit(_ node: SourceFileSyntax) -> SyntaxVisitorContinueKind {
242250
visitIfEnabled(AlwaysUseLowerCamelCase.visit, for: node)
243251
visitIfEnabled(AmbiguousTrailingClosureOverload.visit, for: node)
@@ -249,16 +257,12 @@ class LintPipeline: SyntaxVisitor {
249257
return .visitChildren
250258
}
251259

252-
override func visit(_ node: GenericSpecializationExprSyntax) -> SyntaxVisitorContinueKind {
253-
visitIfEnabled(UseShorthandTypeNames.visit, for: node)
254-
return .visitChildren
255-
}
256-
257260
override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind {
258261
visitIfEnabled(AllPublicDeclarationsHaveDocumentation.visit, for: node)
259262
visitIfEnabled(BeginDocumentationCommentWithOneLineSummary.visit, for: node)
260263
visitIfEnabled(DontRepeatTypeInStaticProperties.visit, for: node)
261264
visitIfEnabled(NoLeadingUnderscores.visit, for: node)
265+
visitIfEnabled(TypeNamesShouldBeCapitalized.visit, for: node)
262266
visitIfEnabled(UseSynthesizedInitializer.visit, for: node)
263267
visitIfEnabled(UseTripleSlashForDocumentationComments.visit, for: node)
264268
return .visitChildren

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: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
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+
private func diagnoseNameConventionMismatch<T: DeclSyntaxProtocol>(_ type: T, name: TokenSyntax) {
41+
if let firstChar = name.text.first, !firstChar.isUppercase {
42+
diagnose(.capitalizeTypeName(name: name.text), on: type, severity: .convention)
43+
}
44+
}
45+
}
46+
47+
extension Finding.Message {
48+
public static func capitalizeTypeName(name: String) -> Finding.Message {
49+
let capitalized = name.prefix(1).uppercased() + name.dropFirst()
50+
return "type names should be capitalized: \(name) -> \(capitalized)"
51+
}
52+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
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+
}

0 commit comments

Comments
 (0)