-
-
Notifications
You must be signed in to change notification settings - Fork 215
Add redundant internal and fileprivate analysis to go alongside redundant public analysis. #976
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -231,6 +231,7 @@ public final class Declaration { | |
| public var related: Set<Reference> = [] | ||
| public var isImplicit: Bool = false | ||
| public var isObjcAccessible: Bool = false | ||
| public var referencedFiles: Set<SourceFile> | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What's the purpose of this property? I see that we insert items into it, but it doesn't appear to be used anywhere. |
||
|
|
||
| private let hashValueCache: Int | ||
|
|
||
|
|
@@ -290,6 +291,7 @@ public final class Declaration { | |
| self.kind = kind | ||
| self.usrs = usrs | ||
| self.location = location | ||
| self.referencedFiles = [location.file] | ||
| hashValueCache = usrs.hashValue | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,70 @@ | ||
| import Configuration | ||
| import Shared | ||
|
|
||
| final class RedundantFilePrivateAccessibilityMarker: SourceGraphMutator { | ||
| private let graph: SourceGraph | ||
| private let configuration: Configuration | ||
|
|
||
| required init(graph: SourceGraph, configuration: Configuration, swiftVersion _: SwiftVersion) { | ||
| self.graph = graph | ||
| self.configuration = configuration | ||
| } | ||
|
|
||
| func mutate() throws { | ||
| guard !configuration.disableRedundantFilePrivateAnalysis else { return } | ||
|
|
||
| let nonExtensionKinds = graph.rootDeclarations.filter { !$0.kind.isExtensionKind } | ||
| let extensionKinds = graph.rootDeclarations.filter(\.kind.isExtensionKind) | ||
|
|
||
| for decl in nonExtensionKinds { | ||
| try validate(decl) | ||
| } | ||
|
|
||
| for decl in extensionKinds { | ||
| try validateExtension(decl) | ||
| } | ||
| } | ||
|
|
||
| // MARK: - Private | ||
|
|
||
| private func validate(_ decl: Declaration) throws { | ||
| if decl.accessibility.isExplicitly(.fileprivate) { | ||
| if !graph.isRetained(decl), !isReferencedOutsideFile(decl) { | ||
| mark(decl) | ||
| markExplicitFilePrivateDescendentDeclarations(from: decl) | ||
| } | ||
| } else { | ||
| markExplicitFilePrivateDescendentDeclarations(from: decl) | ||
| } | ||
| } | ||
|
|
||
| private func validateExtension(_ decl: Declaration) throws { | ||
| if decl.accessibility.isExplicitly(.fileprivate) { | ||
| if let extendedDecl = try? graph.extendedDeclaration(forExtension: decl), | ||
| graph.redundantFilePrivateAccessibility.keys.contains(extendedDecl) { | ||
| mark(decl) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| private func mark(_ decl: Declaration) { | ||
| guard !graph.isRetained(decl) else { return } | ||
| graph.markRedundantFilePrivateAccessibility(decl, file: decl.location.file) | ||
| } | ||
|
|
||
| private func markExplicitFilePrivateDescendentDeclarations(from decl: Declaration) { | ||
| for descDecl in descendentFilePrivateDeclarations(from: decl) { | ||
| mark(descDecl) | ||
| } | ||
| } | ||
|
|
||
| private func isReferencedOutsideFile(_ decl: Declaration) -> Bool { | ||
| let referenceFiles = graph.references(to: decl).map(\.location.file) | ||
| return referenceFiles.contains { $0 != decl.location.file } | ||
| } | ||
|
|
||
| private func descendentFilePrivateDeclarations(from decl: Declaration) -> Set<Declaration> { | ||
| let filePrivateDeclarations = decl.declarations.filter { !$0.isImplicit && $0.accessibility.isExplicitly(.fileprivate) } | ||
| return filePrivateDeclarations.flatMapSet { descendentFilePrivateDeclarations(from: $0) }.union(filePrivateDeclarations) | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,82 @@ | ||
| import Configuration | ||
| import Shared | ||
|
|
||
| final class RedundantInternalAccessibilityMarker: SourceGraphMutator { | ||
| private let graph: SourceGraph | ||
| private let configuration: Configuration | ||
|
|
||
| required init(graph: SourceGraph, configuration: Configuration, swiftVersion _: SwiftVersion) { | ||
| self.graph = graph | ||
| self.configuration = configuration | ||
| } | ||
|
|
||
| func mutate() throws { | ||
| guard !configuration.disableRedundantInternalAnalysis else { return } | ||
|
|
||
| let nonExtensionKinds = graph.rootDeclarations.filter { !$0.kind.isExtensionKind } | ||
| let extensionKinds = graph.rootDeclarations.filter(\.kind.isExtensionKind) | ||
|
|
||
| for decl in nonExtensionKinds { | ||
| try validate(decl) | ||
| } | ||
|
|
||
| for decl in extensionKinds { | ||
| try validateExtension(decl) | ||
| } | ||
| } | ||
|
|
||
| // MARK: - Private | ||
|
|
||
| private func validate(_ decl: Declaration) throws { | ||
| if decl.accessibility.isExplicitly(.internal) { | ||
| if !graph.isRetained(decl) { | ||
| let isReferencedOutside = isReferencedOutsideFile(decl) | ||
| if !isReferencedOutside { | ||
| mark(decl) | ||
| markExplicitInternalDescendentDeclarations(from: decl) | ||
| } | ||
| } | ||
| } else { | ||
| markExplicitInternalDescendentDeclarations(from: decl) | ||
| } | ||
| } | ||
|
|
||
| private func validateExtension(_ decl: Declaration) throws { | ||
| if decl.accessibility.isExplicitly(.internal) { | ||
| if let extendedDecl = try? graph.extendedDeclaration(forExtension: decl), | ||
| graph.redundantInternalAccessibility.keys.contains(extendedDecl) { | ||
| mark(decl) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| private func mark(_ decl: Declaration) { | ||
| guard !graph.isRetained(decl) else { return } | ||
| graph.markRedundantInternalAccessibility(decl, file: decl.location.file) | ||
| } | ||
|
|
||
| private func markExplicitInternalDescendentDeclarations(from decl: Declaration) { | ||
| for descDecl in descendentInternalDeclarations(from: decl) { | ||
| if !graph.isRetained(descDecl) { | ||
| let isReferencedOutside = isReferencedOutsideFile(descDecl) | ||
| if !isReferencedOutside { | ||
| mark(descDecl) | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| private func isReferencedOutsideFile(_ decl: Declaration) -> Bool { | ||
| // Use graph.references(to: decl) to get all references to this declaration | ||
| let allReferences = graph.references(to: decl) | ||
| let referenceFiles = allReferences.map(\.location.file) | ||
|
|
||
| let result = referenceFiles.contains { $0 != decl.location.file } | ||
| return result | ||
| } | ||
|
|
||
| private func descendentInternalDeclarations(from decl: Declaration) -> Set<Declaration> { | ||
| let internalDeclarations = decl.declarations.filter { !$0.isImplicit && $0.accessibility.isExplicitly(.internal) } | ||
| return internalDeclarations.flatMapSet { descendentInternalDeclarations(from: $0) }.union(internalDeclarations) | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,6 +9,8 @@ public final class SourceGraph { | |
| public private(set) var redundantProtocols: [Declaration: (references: Set<Reference>, inherited: Set<Reference>)] = [:] | ||
| public private(set) var rootDeclarations: Set<Declaration> = [] | ||
| public private(set) var redundantPublicAccessibility: [Declaration: Set<String>] = [:] | ||
| public private(set) var redundantInternalAccessibility: [Declaration: Set<SourceFile>] = [:] | ||
| public private(set) var redundantFilePrivateAccessibility: [Declaration: Set<SourceFile>] = [:] | ||
| public private(set) var rootReferences: Set<Reference> = [] | ||
| public private(set) var allReferences: Set<Reference> = [] | ||
| public private(set) var retainedDeclarations: Set<Declaration> = [] | ||
|
|
@@ -85,6 +87,22 @@ public final class SourceGraph { | |
| _ = redundantPublicAccessibility.removeValue(forKey: declaration) | ||
| } | ||
|
|
||
| func markRedundantInternalAccessibility(_ declaration: Declaration, file: SourceFile) { | ||
| redundantInternalAccessibility[declaration, default: []].insert(file) | ||
| } | ||
|
|
||
| func unmarkRedundantInternalAccessibility(_ declaration: Declaration) { | ||
| _ = redundantInternalAccessibility.removeValue(forKey: declaration) | ||
| } | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is unused?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess all PRs in this repo should be self-checked by periphery 😁 |
||
|
|
||
| func markRedundantFilePrivateAccessibility(_ declaration: Declaration, file: SourceFile) { | ||
| redundantFilePrivateAccessibility[declaration, default: []].insert(file) | ||
| } | ||
|
|
||
| func unmarkRedundantFilePrivateAccessibility(_ declaration: Declaration) { | ||
| _ = redundantFilePrivateAccessibility.removeValue(forKey: declaration) | ||
| } | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is unused? |
||
|
|
||
| func markIgnored(_ declaration: Declaration) { | ||
| _ = ignoredDeclarations.insert(declaration) | ||
| } | ||
|
|
@@ -143,6 +161,9 @@ public final class SourceGraph { | |
| public func add(_ reference: Reference) { | ||
| _ = allReferences.insert(reference) | ||
| allReferencesByUsr[reference.usr, default: []].insert(reference) | ||
| if let decl = declaration(withUsr: reference.usr) { | ||
| decl.referencedFiles.insert(reference.location.file) | ||
| } | ||
| } | ||
|
|
||
| public func add(_ references: Set<Reference>) { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -79,3 +79,6 @@ inlinableFunction() | |
| // Associated types | ||
| _ = PublicInheritedAssociatedTypeClass().items | ||
| _ = PublicInheritedAssociatedTypeDefaultTypeClass().items | ||
|
|
||
| // Internal accessibility tests | ||
| // _ = InternalPropertyUsedInExtension() // Commented out for now | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When would we need to uncomment this? |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| // InternalPropertyExtension.swift | ||
| // Extension that uses the internal property from InternalPropertyUsedInExtension | ||
| // This should prevent the property from being flagged as redundant | ||
|
|
||
| extension InternalPropertyUsedInExtension { | ||
| func useProperty() { | ||
| print(propertyUsedInExtension) // This reference should prevent propertyUsedInExtension from being flagged as redundant | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| // InternalPropertyOwner.swift | ||
| internal class InternalPropertyOwner { | ||
| internal var value: Int = 42 | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| // InternalPropertyUsedInExtension.swift | ||
| // Tests the case where an internal property is used in an extension in a different file | ||
| // This should NOT be flagged as redundant | ||
|
|
||
| internal class InternalPropertyUsedInExtension { | ||
| internal var propertyUsedInExtension: String = "test" | ||
| internal var propertyOnlyUsedInSameFile: String = "test" // This should be flagged as redundant | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This message isn't quite right, how about: