Skip to content

Commit af0f3cf

Browse files
authored
Merge pull request swiftlang#6 from allevato/sr-11123
Fix an index-out-of-bounds error in DontRepeatTypeInStaticProperties.
2 parents 8032cf2 + 3f7ec7d commit af0f3cf

File tree

3 files changed

+103
-58
lines changed

3 files changed

+103
-58
lines changed

Sources/SwiftFormatRules/DontRepeatTypeInStaticProperties.swift

Lines changed: 49 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -32,69 +32,87 @@ public struct DontRepeatTypeInStaticProperties: SyntaxLintRule {
3232
}
3333

3434
public func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind {
35-
determinePropertyNameViolations(members: node.members.members, nodeId: node.identifier.text)
35+
diagnoseStaticMembers(node.members.members, endingWith: node.identifier.text)
3636
return .skipChildren
3737
}
3838

3939
public func visit(_ node: EnumDeclSyntax) -> SyntaxVisitorContinueKind {
40-
determinePropertyNameViolations(members: node.members.members, nodeId: node.identifier.text)
40+
diagnoseStaticMembers(node.members.members, endingWith: node.identifier.text)
4141
return .skipChildren
4242
}
4343

4444
public func visit(_ node: ProtocolDeclSyntax) -> SyntaxVisitorContinueKind {
45-
determinePropertyNameViolations(members: node.members.members, nodeId: node.identifier.text)
45+
diagnoseStaticMembers(node.members.members, endingWith: node.identifier.text)
4646
return .skipChildren
4747
}
4848

4949
public func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind {
50-
determinePropertyNameViolations(members: node.members.members, nodeId: node.identifier.text)
50+
diagnoseStaticMembers(node.members.members, endingWith: node.identifier.text)
5151
return .skipChildren
5252
}
5353

5454
public func visit(_ node: ExtensionDeclSyntax) -> SyntaxVisitorContinueKind {
55-
determinePropertyNameViolations(
56-
members: node.members.members,
57-
nodeId: node.extendedType.description)
55+
let members = node.members.members
56+
57+
switch node.extendedType {
58+
case let simpleType as SimpleTypeIdentifierSyntax:
59+
diagnoseStaticMembers(members, endingWith: simpleType.name.text)
60+
case let memberType as MemberTypeIdentifierSyntax:
61+
// We don't need to drill recursively into this structure because types with more than two
62+
// components are constructed left-heavy; that is, `A.B.C.D` is structured as `((A.B).C).D`,
63+
// and the final component of the top type is what we want.
64+
diagnoseStaticMembers(members, endingWith: memberType.name.text)
65+
default:
66+
// Do nothing for non-nominal types. If Swift adds support for extensions on non-nominals,
67+
// we'll need to update this if we need to support some subset of those.
68+
break
69+
}
70+
5871
return .skipChildren
5972
}
6073

61-
func determinePropertyNameViolations(members: MemberDeclListSyntax, nodeId: String) {
74+
/// Iterates over the static/class properties in the given member list and diagnoses any where the
75+
/// name has the containing type name (excluding possible namespace prefixes, like `NS` or `UI`)
76+
/// as a suffix.
77+
private func diagnoseStaticMembers(_ members: MemberDeclListSyntax, endingWith typeName: String) {
6278
for member in members {
63-
guard let decl = member.decl as? VariableDeclSyntax else { continue }
64-
guard let modifiers = decl.modifiers else { continue }
65-
guard modifiers.has(modifier: "static") || modifiers.has(modifier: "class") else { continue }
79+
guard
80+
let varDecl = member.decl as? VariableDeclSyntax,
81+
let modifiers = varDecl.modifiers,
82+
modifiers.has(modifier: "static") || modifiers.has(modifier: "class")
83+
else { continue }
6684

67-
let typeName = withoutPrefix(name: nodeId)
85+
let bareTypeName = removingPossibleNamespacePrefix(from: typeName)
6886

69-
for id in decl.identifiers {
70-
let varName = id.identifier.text
71-
guard varName.contains(typeName) else { continue }
72-
diagnose(.removeTypeFromName(name: varName, type: typeName), on: decl)
87+
for pattern in varDecl.identifiers {
88+
let varName = pattern.identifier.text
89+
if varName.contains(bareTypeName) {
90+
diagnose(.removeTypeFromName(name: varName, type: bareTypeName), on: varDecl)
91+
}
7392
}
7493
}
7594
}
7695

77-
// Returns the given string without capitalized prefix in the beginning
78-
func withoutPrefix(name: String) -> String {
79-
let formattedName = name.trimmingCharacters(in: CharacterSet.whitespaces)
80-
let upperCase = Array(formattedName.uppercased())
81-
let original = Array(formattedName)
82-
guard original[0] == upperCase[0] else { return name }
83-
84-
var prefixEndsAt = 0
85-
var idx = 0
86-
while idx <= name.count - 2 {
87-
if original[idx] == upperCase[idx] && original[idx + 1] != upperCase[idx + 1] {
88-
prefixEndsAt = idx
96+
/// Returns the portion of the given string that excludes a possible Objective-C-style capitalized
97+
/// namespace prefix (a leading sequence of more than one uppercase letter).
98+
///
99+
/// If the name has zero or one leading uppercase letters, the entire name is returned.
100+
private func removingPossibleNamespacePrefix(from name: String) -> Substring {
101+
guard let first = name.first, first.isUppercase else { return name[...] }
102+
103+
for index in name.indices.dropLast() {
104+
let nextIndex = name.index(after: index)
105+
if name[index].isUppercase && !name[nextIndex].isUppercase {
106+
return name[index...]
89107
}
90-
idx += 1
91108
}
92-
return String(formattedName.dropFirst(prefixEndsAt))
109+
110+
return name[...]
93111
}
94112
}
95113

96114
extension Diagnostic.Message {
97-
static func removeTypeFromName(name: String, type: String) -> Diagnostic.Message {
115+
static func removeTypeFromName(name: String, type: Substring) -> Diagnostic.Message {
98116
return .init(.warning, "remove '\(type)' from '\(name)'")
99117
}
100118
}

Tests/SwiftFormatRulesTests/DontRepeatTypeInStaticPropertiesTests.swift

Lines changed: 52 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -7,33 +7,34 @@ import XCTest
77
public class DontRepeatTypeInStaticPropertiesTests: DiagnosingTestCase {
88
public func testRepetitiveProperties() {
99
let input =
10-
"""
11-
public class UIColor {
12-
static let redColor: UIColor
13-
public class var blueColor: UIColor
14-
var yellowColor: UIColor
15-
static let green: UIColor
16-
public class var purple: UIColor
17-
}
18-
enum Sandwich {
19-
static let bolognaSandwich: Sandwich
20-
static var hamSandwich: Sandwich
21-
static var turkey: Sandwich
22-
}
23-
protocol RANDPerson {
24-
var oldPerson: Person
25-
static let youngPerson: Person
26-
}
27-
struct TVGame {
28-
static var basketballGame: TVGame
29-
static var baseballGame: TVGame
30-
static let soccer: TVGame
31-
let hockey: TVGame
32-
}
33-
extension URLSession {
34-
class var sharedSession: URLSession
35-
}
36-
"""
10+
"""
11+
public class UIColor {
12+
static let redColor: UIColor
13+
public class var blueColor: UIColor
14+
var yellowColor: UIColor
15+
static let green: UIColor
16+
public class var purple: UIColor
17+
}
18+
enum Sandwich {
19+
static let bolognaSandwich: Sandwich
20+
static var hamSandwich: Sandwich
21+
static var turkey: Sandwich
22+
}
23+
protocol RANDPerson {
24+
var oldPerson: Person
25+
static let youngPerson: Person
26+
}
27+
struct TVGame {
28+
static var basketballGame: TVGame
29+
static var baseballGame: TVGame
30+
static let soccer: TVGame
31+
let hockey: TVGame
32+
}
33+
extension URLSession {
34+
class var sharedSession: URLSession
35+
}
36+
"""
37+
3738
performLint(DontRepeatTypeInStaticProperties.self, input: input)
3839
XCTAssertDiagnosed(.removeTypeFromName(name: "redColor", type: "Color"))
3940
XCTAssertDiagnosed(.removeTypeFromName(name: "blueColor", type: "Color"))
@@ -55,4 +56,28 @@ public class DontRepeatTypeInStaticPropertiesTests: DiagnosingTestCase {
5556

5657
XCTAssertDiagnosed(.removeTypeFromName(name: "sharedSession", type: "Session"))
5758
}
59+
60+
public func testSR11123() {
61+
let input =
62+
"""
63+
extension A {
64+
static let b = C()
65+
}
66+
"""
67+
68+
performLint(DontRepeatTypeInStaticProperties.self, input: input)
69+
XCTAssertNotDiagnosed(.removeTypeFromName(name: "b", type: "A"))
70+
}
71+
72+
public func testDottedExtendedType() {
73+
let input =
74+
"""
75+
extension Dotted.Thing {
76+
static let defaultThing: Dotted.Thing
77+
}
78+
"""
79+
80+
performLint(DontRepeatTypeInStaticProperties.self, input: input)
81+
XCTAssertDiagnosed(.removeTypeFromName(name: "defaultThing", type: "Thing"))
82+
}
5883
}

Tests/SwiftFormatRulesTests/XCTestManifests.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,9 @@ extension DontRepeatTypeInStaticPropertiesTests {
7676
// `swift test --generate-linuxmain`
7777
// to regenerate.
7878
static let __allTests__DontRepeatTypeInStaticPropertiesTests = [
79+
("testDottedExtendedType", testDottedExtendedType),
7980
("testRepetitiveProperties", testRepetitiveProperties),
81+
("testSR11123", testSR11123),
8082
]
8183
}
8284

0 commit comments

Comments
 (0)