Skip to content

Commit f8a7f31

Browse files
authored
Fix redundant public accessibility for initializers & subscripts. (#308)
1 parent b7073d2 commit f8a7f31

File tree

11 files changed

+194
-63
lines changed

11 files changed

+194
-63
lines changed

Sources/PeripheryKit/Indexer/Declaration.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,14 @@ public final class Declaration {
5050
rawValue.hasPrefix("function")
5151
}
5252

53+
var isAccessibilityModifiableFunctionKind: Bool {
54+
rawValue.hasPrefix("function.method")
55+
|| rawValue.hasPrefix("function.operator")
56+
|| rawValue == "function.subscript"
57+
|| rawValue == "function.free"
58+
|| rawValue == "function.constructor"
59+
}
60+
5361
static var variableKinds: Set<Kind> {
5462
Set(Kind.allCases.filter { $0.isVariableKind })
5563
}
@@ -58,6 +66,10 @@ public final class Declaration {
5866
rawValue.hasPrefix("var")
5967
}
6068

69+
var isAccessibilityModifiableVariableKind: Bool {
70+
isVariableKind
71+
}
72+
6173
static var globalKinds: Set<Kind> = [
6274
.class,
6375
.protocol,

Sources/PeripheryKit/Indexer/SwiftIndexer.swift

Lines changed: 14 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -214,16 +214,16 @@ public final class SwiftIndexer {
214214

215215
file.importStatements = importVisitor.importStatements
216216
let propertyByLocation = propertyVisitor.resultsByLocation
217-
let propertyByTypeLocation = propertyVisitor.resultsByTypeLocation
218217
let functionByLocation = functionVisitor.resultsByLocation
219218
let conformableByLocation = conformableVisitor.resultsByLocation
220-
let propertyDeclarations = decls.filter { !$0.isImplicit && $0.kind.isVariableKind }
221-
let functionDeclarations = decls.filter { !$0.isImplicit && $0.kind.isFunctionKind }
222-
let conformableDeclarations = decls.filter { !$0.isImplicit && $0.kind.isDiscreteConformableKind }
219+
let explicitDeclarations = decls.filter { !$0.isImplicit }
220+
let propertyDeclarations = explicitDeclarations.filter { $0.kind.isAccessibilityModifiableVariableKind }
221+
let functionDeclarations = explicitDeclarations.filter { $0.kind.isAccessibilityModifiableFunctionKind }
222+
let conformableDeclarations = explicitDeclarations.filter { $0.kind.isDiscreteConformableKind }
223223

224224
establishDeclarationHierarchy()
225225
makeProtocolPropertyAccessorsImplicit(for: decls)
226-
associateDanglingReferences(for: decls, using: propertyByTypeLocation)
226+
associateDanglingReferences(for: explicitDeclarations)
227227
identifyDeclaredPropertyTypes(for: propertyDeclarations, using: propertyByLocation)
228228
identifyPropertyReferenceRoles(for: propertyDeclarations, using: propertyByLocation)
229229
identifyFunctionReferenceRoles(for: functionDeclarations, using: functionByLocation)
@@ -297,34 +297,20 @@ public final class SwiftIndexer {
297297
// Workaround for https://bugs.swift.org/browse/SR-13766
298298
// Swift does not associate some type references with the containing declaration, resulting in references
299299
// with no clear parent.
300-
private func associateDanglingReferences(for decls: [Declaration], using propertyByTypeLocation: [SourceLocation: [PropertyVisitor.Result]]) {
300+
private func associateDanglingReferences(for declarations: [Declaration]) {
301301
guard !danglingReferences.isEmpty else { return }
302302

303-
let explicitDecls = decls.filter { !$0.isImplicit }
304-
let declsByLocation = explicitDecls
305-
.lazy
303+
let declsByLocation = declarations
306304
.reduce(into: [SourceLocation: [Declaration]]()) { (result, decl) in
307305
result[decl.location, default: []].append(decl)
308306
}
309-
let declsByLine = explicitDecls
310-
.lazy
307+
let declsByLine = declarations
311308
.reduce(into: [Int64: [Declaration]]()) { (result, decl) in
312309
result[decl.location.line, default: []].append(decl)
313310
}
314311

315312
for ref in danglingReferences {
316-
var propertyDecls: [Declaration]?
317-
318-
// First attempt to identify property declarations that may be associated with this reference.
319-
if let propertyMetadatas = propertyByTypeLocation[ref.location] {
320-
for propertyMetadata in propertyMetadatas {
321-
if let decls = declsByLocation[propertyMetadata.location] {
322-
propertyDecls = decls
323-
}
324-
}
325-
}
326-
327-
guard let candidateDecls = propertyDecls ??
313+
guard let candidateDecls =
328314
declsByLocation[ref.location] ??
329315
declsByLine[ref.location.line] else { continue }
330316

@@ -333,7 +319,11 @@ public final class SwiftIndexer {
333319
// a decl without a parent, as the reference may be a related type of a class/struct/etc.
334320
if let decl = candidateDecls.first(where: { $0.parent == nil }) {
335321
associate(ref, with: decl)
336-
} else if let decl = candidateDecls.first { // Fallback to using the first decl.
322+
} else if let decl = candidateDecls.sorted().first {
323+
// Fallback to using the first declaration.
324+
// Sorting the declarations helps in the situation where the candidate declarations includes a
325+
// property/subscript, and a getter on the same line. The property/subscript is more likely to be
326+
// the declaration that should hold the references.
337327
associate(ref, with: decl)
338328
}
339329
}

Sources/PeripheryKit/Syntax/FunctionVisitor.swift

Lines changed: 59 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -32,46 +32,72 @@ final class FunctionVisitor: PeripherySyntaxVisitor {
3232
}
3333

3434
func visit(_ node: FunctionDeclSyntax) {
35-
let location = sourceLocationBuilder.location(at: node.identifier.positionAfterSkippingLeadingTrivia)
36-
var returnTypeLocations: Set<SourceLocation> = []
37-
var parameterTypeLocations: Set<SourceLocation> = []
38-
var genericParameterLocations: Set<SourceLocation> = []
39-
var genericConformanceRequirementLocations: Set<SourceLocation> = []
40-
41-
if let returnTypeSyntax = node.signature.output?.returnType {
42-
if let someReturnType = returnTypeSyntax.as(SomeTypeSyntax.self) {
43-
returnTypeLocations = typeSyntaxInspector.typeLocations(for: someReturnType.baseType)
44-
} else {
45-
returnTypeLocations = typeSyntaxInspector.typeLocations(for: returnTypeSyntax)
46-
}
47-
}
35+
results.append((sourceLocationBuilder.location(at: node.identifier.positionAfterSkippingLeadingTrivia),
36+
typeLocations(for: node.signature.input),
37+
typeLocations(for: node.signature.output),
38+
typeLocations(for: node.genericParameterClause),
39+
typeLocations(for: node.genericWhereClause)))
40+
}
41+
42+
func visit(_ node: InitializerDeclSyntax) {
43+
results.append((sourceLocationBuilder.location(at: node.initKeyword.positionAfterSkippingLeadingTrivia),
44+
typeLocations(for: node.parameters),
45+
[],
46+
typeLocations(for: node.genericParameterClause),
47+
typeLocations(for: node.genericWhereClause)))
48+
}
4849

49-
for functionParameterSyntax in node.signature.input.parameterList {
50-
if let typeSyntax = functionParameterSyntax.type {
51-
parameterTypeLocations.formUnion(typeSyntaxInspector.typeLocations(for: typeSyntax))
50+
func visit(_ node: SubscriptDeclSyntax) {
51+
results.append((sourceLocationBuilder.location(at: node.subscriptKeyword.positionAfterSkippingLeadingTrivia),
52+
typeLocations(for: node.indices),
53+
typeLocations(for: node.result),
54+
typeLocations(for: node.genericParameterClause),
55+
typeLocations(for: node.genericWhereClause)))
56+
}
57+
58+
// MARK: - Private
59+
60+
private func typeLocations(for parameterClause: ParameterClauseSyntax) -> Set<SourceLocation> {
61+
return Set(parameterClause.parameterList.flatMap { parameter -> Set<SourceLocation> in
62+
if let typeSyntax = parameter.type {
63+
return typeSyntaxInspector.typeLocations(for: typeSyntax)
5264
}
65+
66+
return []
67+
})
68+
}
69+
70+
private func typeLocations(for returnClause: ReturnClauseSyntax?) -> Set<SourceLocation> {
71+
guard let returnTypeSyntax = returnClause?.returnType else { return [] }
72+
73+
if let someReturnType = returnTypeSyntax.as(SomeTypeSyntax.self) {
74+
return typeSyntaxInspector.typeLocations(for: someReturnType.baseType)
5375
}
5476

55-
if let genericParameterList = node.genericParameterClause?.genericParameterList {
56-
for param in genericParameterList {
57-
if let inheritedType = param.inheritedType {
58-
genericParameterLocations.formUnion(typeSyntaxInspector.typeLocations(for: inheritedType))
59-
}
77+
return typeSyntaxInspector.typeLocations(for: returnTypeSyntax)
78+
}
79+
80+
private func typeLocations(for genericParameterClause: GenericParameterClauseSyntax?) -> Set<SourceLocation> {
81+
guard let genericParameterClause = genericParameterClause else { return [] }
82+
83+
return Set(genericParameterClause.genericParameterList.flatMap { parameter -> Set<SourceLocation> in
84+
if let inheritedType = parameter.inheritedType {
85+
return typeSyntaxInspector.typeLocations(for: inheritedType)
6086
}
61-
}
6287

63-
if let requirementList = node.genericWhereClause?.requirementList {
64-
for requirement in requirementList {
65-
if let conformanceRequirementType = requirement.body.as(ConformanceRequirementSyntax.self) {
66-
genericConformanceRequirementLocations.formUnion(typeSyntaxInspector.typeLocations(for: conformanceRequirementType.rightTypeIdentifier))
67-
}
88+
return []
89+
})
90+
}
91+
92+
private func typeLocations(for genericWhereClause: GenericWhereClauseSyntax?) -> Set<SourceLocation> {
93+
guard let genericWhereClause = genericWhereClause else { return [] }
94+
95+
return Set(genericWhereClause.requirementList.flatMap { requirement -> Set<SourceLocation> in
96+
if let conformanceRequirementType = requirement.body.as(ConformanceRequirementSyntax.self) {
97+
return typeSyntaxInspector.typeLocations(for: conformanceRequirementType.rightTypeIdentifier)
6898
}
69-
}
7099

71-
results.append((location,
72-
parameterTypeLocations,
73-
returnTypeLocations,
74-
genericParameterLocations,
75-
genericConformanceRequirementLocations))
100+
return []
101+
})
76102
}
77103
}

Sources/PeripheryKit/Syntax/PropertyVisitor.swift

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,6 @@ final class PropertyVisitor: PeripherySyntaxVisitor {
1919
private let logger: Logger
2020
private(set) var results: [Result] = []
2121

22-
var resultsByTypeLocation: [SourceLocation: [Result]] {
23-
results.reduce(into: [SourceLocation: [Result]]()) { (dict, result) in
24-
result.typeLocations.forEach { dict[$0, default: []].append(result) }
25-
}
26-
}
27-
2822
var resultsByLocation: [SourceLocation: Result] {
2923
results.reduce(into: [SourceLocation: Result]()) { (dict, result) in
3024
dict[result.location] = result

Tests/AccessibilityTests/AccessibilityProject/Sources/MainTarget/main.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ _ = PublicTypeUsedAsPublicPropertyTypeRetainer().retain1
1010
_ = PublicTypeUsedAsPublicPropertyTypeRetainer().retain2
1111
_ = PublicTypeUsedAsPublicPropertyTypeRetainer().retain3
1212

13+
_ = PublicTypeUsedAsPublicInitializerParameterTypeRetainer()
14+
15+
_ = PublicTypeUsedAsPublicSubscriptParameterTypeRetainer()[]
16+
_ = PublicTypeUsedAsPublicSubscriptReturnTypeRetainer()[]
17+
1318
PublicTypeUsedAsPublicFunctionParameterTypeRetainer().retain1()
1419
PublicTypeUsedAsPublicFunctionParameterTypeRetainer().retain2()
1520
PublicTypeUsedAsPublicFunctionParameterTypeRetainer().retain3()
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import Foundation
2+
3+
public class PublicTypeUsedAsPublicInitializerParameterType {}
4+
5+
public class PublicTypeUsedAsPublicInitializerParameterTypeRetainer {
6+
public init(_ cls: PublicTypeUsedAsPublicInitializerParameterType? = nil) {}
7+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import Foundation
2+
3+
public class PublicTypeUsedAsPublicSubscriptParameterType {}
4+
5+
public class PublicTypeUsedAsPublicSubscriptParameterTypeRetainer {
6+
public init() {}
7+
public subscript(_ type: PublicTypeUsedAsPublicSubscriptParameterType? = nil) -> Int { 0 }
8+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import Foundation
2+
3+
public class PublicTypeUsedAsPublicSubscriptReturnType {}
4+
5+
public class PublicTypeUsedAsPublicSubscriptReturnTypeRetainer {
6+
public init() {}
7+
public subscript(_ idx: Int = 0) -> PublicTypeUsedAsPublicSubscriptReturnType { .init() }
8+
}

Tests/AccessibilityTests/RedundantPublicAccessabilityTest.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,10 @@ class RedundantPublicAccessabilityTest: SourceGraphTestCase {
5959
XCTAssertNotRedundantPublicAccessibility((.class, "PublicTypeUsedAsPublicPropertyArrayType"))
6060
}
6161

62+
func testPublicTypeUsedAsPublicInitializerParameterType() {
63+
XCTAssertNotRedundantPublicAccessibility((.class, "PublicTypeUsedAsPublicInitializerParameterType"))
64+
}
65+
6266
func testPublicTypeUsedAsPublicFunctionParameterType() {
6367
XCTAssertNotRedundantPublicAccessibility((.class, "PublicTypeUsedAsPublicFunctionParameterType"))
6468
XCTAssertNotRedundantPublicAccessibility((.class, "PublicTypeUsedAsPublicFunctionParameterTypeClosureArgument"))
@@ -71,6 +75,14 @@ class RedundantPublicAccessabilityTest: SourceGraphTestCase {
7175
XCTAssertNotRedundantPublicAccessibility((.class, "PublicTypeUsedAsPublicFunctionReturnTypeClosureReturnType"))
7276
}
7377

78+
func testPublicTypeUsedAsPublicSubscriptParameterType() {
79+
XCTAssertNotRedundantPublicAccessibility((.class, "PublicTypeUsedAsPublicSubscriptParameterType"))
80+
}
81+
82+
func testPublicTypeUsedAsPublicSubscriptReturnType() {
83+
XCTAssertNotRedundantPublicAccessibility((.class, "PublicTypeUsedAsPublicSubscriptReturnType"))
84+
}
85+
7486
func testPublicTypeUsedInPublicFunctionBody() {
7587
XCTAssertRedundantPublicAccessibility((.class, "PublicTypeUsedInPublicFunctionBody"))
7688
}

Tests/Fixtures/FunctionVisitorFixtures/FunctionVisitorFixture.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,16 @@ func functionWithGenericArgument<T: StringProtocol & AnyObject>(_ t: T.Type) whe
2121

2222
@available(macOS 10.15.0, *)
2323
func functionWithSomeReturnType() -> some StringProtocol { "" }
24+
25+
class FixtureClass1 {
26+
init(a: String, b: Int) {}
27+
}
28+
29+
class FixtureClass2 {
30+
init<T: StringProtocol & AnyObject>(_ t: T.Type) where T: RawRepresentable {}
31+
}
32+
33+
class FixtureClass3 {
34+
subscript(a: Int, b: String) -> Int { 0 }
35+
subscript<T: StringProtocol & AnyObject>(_ t: T.Type) -> Int where T: RawRepresentable { 0 }
36+
}

0 commit comments

Comments
 (0)