diff --git a/Package.swift b/Package.swift index 31a2ac5e0..01d60bea6 100644 --- a/Package.swift +++ b/Package.swift @@ -94,10 +94,6 @@ var targets: [PackageDescription.Target] = [ ], path: "Tests/Fixtures/RetentionFixtures" ), - .target( - name: "RemovalFixtures", - path: "Tests/Fixtures/RemovalFixtures" - ), .target( name: "UnusedParameterFixtures", path: "Tests/Fixtures/UnusedParameterFixtures", diff --git a/Sources/Frontend/Commands/ScanBehavior.swift b/Sources/Frontend/Commands/ScanBehavior.swift index d1c0b2524..cefb2dfc8 100644 --- a/Sources/Frontend/Commands/ScanBehavior.swift +++ b/Sources/Frontend/Commands/ScanBehavior.swift @@ -72,10 +72,6 @@ final class ScanBehavior { let filteredResults = try OutputDeclarationFilter().filter(results, with: baseline) - if configuration.autoRemove { - try ScanResultRemover().remove(results: filteredResults) - } - if let baselinePath = configuration.writeBaseline { let usrs = filteredResults .flatMapSet { $0.usrs } diff --git a/Sources/Frontend/Commands/ScanCommand.swift b/Sources/Frontend/Commands/ScanCommand.swift index b009df607..64d20590c 100644 --- a/Sources/Frontend/Commands/ScanCommand.swift +++ b/Sources/Frontend/Commands/ScanCommand.swift @@ -93,9 +93,6 @@ struct ScanCommand: FrontendCommand { @Flag(help: "Retain properties on Encodable types only") var retainEncodableProperties: Bool = defaultConfiguration.$retainEncodableProperties.defaultValue - @Flag(help: "Automatically remove code that can be done so safely without introducing build errors (experimental)") - var autoRemove: Bool = defaultConfiguration.$autoRemove.defaultValue - @Flag(help: "Clean existing build artifacts before building") var cleanBuild: Bool = defaultConfiguration.$cleanBuild.defaultValue @@ -166,7 +163,6 @@ struct ScanCommand: FrontendCommand { configuration.apply(\.$externalEncodableProtocols, externalEncodableProtocols) configuration.apply(\.$externalCodableProtocols, externalCodableProtocols) configuration.apply(\.$externalTestCaseClasses, externalTestCaseClasses) - configuration.apply(\.$autoRemove, autoRemove) configuration.apply(\.$verbose, verbose) configuration.apply(\.$quiet, quiet) configuration.apply(\.$disableUpdateCheck, disableUpdateCheck) diff --git a/Sources/PeripheryKit/CodeRemoval/EmptyExtensionSyntaxRemover.swift b/Sources/PeripheryKit/CodeRemoval/EmptyExtensionSyntaxRemover.swift deleted file mode 100644 index fee32b5d8..000000000 --- a/Sources/PeripheryKit/CodeRemoval/EmptyExtensionSyntaxRemover.swift +++ /dev/null @@ -1,29 +0,0 @@ -import Foundation -import Foundation -import SwiftParser -import SwiftSyntax -import SystemPackage - -final class EmptyExtensionSyntaxRemover: SyntaxRewriter, TriviaSplitting { - func perform(syntax: SourceFileSyntax) -> SourceFileSyntax { - visit(syntax) - } - - override func visit(_ node: CodeBlockItemListSyntax) -> CodeBlockItemListSyntax { - let newChildren = node.compactMap { child -> CodeBlockItemSyntax? in - guard let extDecl = child.item.as(ExtensionDeclSyntax.self) else { return child } - - let members = extDecl.memberBlock.members - let hasMembers = !(members.count == 0 || (members.count == 1 && members.first?.decl.is(MissingDeclSyntax.self) ?? false)) - let hasInheritance = extDecl.inheritanceClause != nil - - if !hasMembers, !hasInheritance { - return remainingTriviaDecl(from: child.item.leadingTrivia) - } - - return child - } - - return CodeBlockItemListSyntax(newChildren) - } -} diff --git a/Sources/PeripheryKit/CodeRemoval/EmptyFileVisitor.swift b/Sources/PeripheryKit/CodeRemoval/EmptyFileVisitor.swift deleted file mode 100644 index 1e423be8c..000000000 --- a/Sources/PeripheryKit/CodeRemoval/EmptyFileVisitor.swift +++ /dev/null @@ -1,30 +0,0 @@ -import Foundation -import Foundation -import SwiftParser -import SwiftSyntax -import SystemPackage - -final class EmptyFileVisitor: SyntaxVisitor, TriviaSplitting { - private var isEmpty = false - - init() { - super.init(viewMode: .sourceAccurate) - } - - func perform(syntax: SourceFileSyntax) -> Bool { - walk(syntax) - return isEmpty - } - - override func visit(_ node: SourceFileSyntax) -> SyntaxVisitorContinueKind { - if node.statements.count == 0 { - isEmpty = true - } else { - isEmpty = node.statements.allSatisfy { - $0.item.is(ImportDeclSyntax.self) || $0.item.is(MissingDeclSyntax.self) - } - } - - return .skipChildren - } -} diff --git a/Sources/PeripheryKit/CodeRemoval/PublicAccessibilitySyntaxRemover.swift b/Sources/PeripheryKit/CodeRemoval/PublicAccessibilitySyntaxRemover.swift deleted file mode 100644 index 6f246a099..000000000 --- a/Sources/PeripheryKit/CodeRemoval/PublicAccessibilitySyntaxRemover.swift +++ /dev/null @@ -1,195 +0,0 @@ -import Foundation -import SourceGraph -import SwiftParser -import SwiftSyntax -import SystemPackage - -final class PublicAccessibilitySyntaxRemover: SyntaxRewriter, SyntaxRemover { - private let resultLocation: Location - private let locationBuilder: SourceLocationBuilder - - init(resultLocation: Location, replacements: [String], locationBuilder: SourceLocationBuilder) { - self.resultLocation = resultLocation - self.locationBuilder = locationBuilder - } - - func perform(syntax: SourceFileSyntax) -> SourceFileSyntax { - visit(syntax) - } - - override func visit(_ node: ClassDeclSyntax) -> DeclSyntax { - let newNode = removePublicModifier( - from: node, - at: node.name.positionAfterSkippingLeadingTrivia, - triviaRecipient: \.classKeyword - ) - return super.visit(newNode) - } - - override func visit(_ node: StructDeclSyntax) -> DeclSyntax { - let newNode = removePublicModifier( - from: node, - at: node.name.positionAfterSkippingLeadingTrivia, - triviaRecipient: \.structKeyword - ) - return super.visit(newNode) - } - - override func visit(_ node: EnumDeclSyntax) -> DeclSyntax { - let newNode = removePublicModifier( - from: node, - at: node.name.positionAfterSkippingLeadingTrivia, - triviaRecipient: \.enumKeyword - ) - return super.visit(newNode) - } - - override func visit(_ node: ExtensionDeclSyntax) -> DeclSyntax { - let newNode = removePublicModifier( - from: node, - at: node.extendedType.positionAfterSkippingLeadingTrivia, - triviaRecipient: \.extensionKeyword - ) - return super.visit(newNode) - } - - override func visit(_ node: ProtocolDeclSyntax) -> DeclSyntax { - let newNode = removePublicModifier( - from: node, - at: node.name.positionAfterSkippingLeadingTrivia, - triviaRecipient: \.protocolKeyword - ) - return super.visit(newNode) - } - - override func visit(_ node: InitializerDeclSyntax) -> DeclSyntax { - let newNode = removePublicModifier( - from: node, - at: node.initKeyword.positionAfterSkippingLeadingTrivia, - triviaRecipient: \.initKeyword - ) - return super.visit(newNode) - } - - override func visit(_ node: FunctionDeclSyntax) -> DeclSyntax { - let newNode = removePublicModifier( - from: node, - at: node.name.positionAfterSkippingLeadingTrivia, - triviaRecipient: \.funcKeyword - ) - return super.visit(newNode) - } - - override func visit(_ node: SubscriptDeclSyntax) -> DeclSyntax { - let newNode = removePublicModifier( - from: node, - at: node.subscriptKeyword.positionAfterSkippingLeadingTrivia, - triviaRecipient: \.subscriptKeyword - ) - return super.visit(newNode) - } - - override func visit(_ node: TypeAliasDeclSyntax) -> DeclSyntax { - let newNode = removePublicModifier( - from: node, - at: node.name.positionAfterSkippingLeadingTrivia, - triviaRecipient: \.typealiasKeyword - ) - return super.visit(newNode) - } - - override func visit(_ node: VariableDeclSyntax) -> DeclSyntax { - let newNode = removePublicModifier( - from: node, - at: node.bindings.positionAfterSkippingLeadingTrivia, - triviaRecipient: \.bindingSpecifier - ) - return super.visit(newNode) - } - - override func visit(_ node: ActorDeclSyntax) -> DeclSyntax { - let newNode = removePublicModifier( - from: node, - at: node.name.positionAfterSkippingLeadingTrivia, - triviaRecipient: \.actorKeyword - ) - return super.visit(newNode) - } - - override func visit(_ node: AssociatedTypeDeclSyntax) -> DeclSyntax { - let newNode = removePublicModifier( - from: node, - at: node.name.positionAfterSkippingLeadingTrivia, - triviaRecipient: \.associatedtypeKeyword - ) - return super.visit(newNode) - } - - override func visit(_ node: PrecedenceGroupDeclSyntax) -> DeclSyntax { - let newNode = removePublicModifier( - from: node, - at: node.name.positionAfterSkippingLeadingTrivia, - triviaRecipient: \.precedencegroupKeyword - ) - return super.visit(newNode) - } - - // MARK: - Private - - private func removePublicModifier( - from node: T, - at position: AbsolutePosition, - triviaRecipient: WritableKeyPath - ) -> T { - var removedLeadingTrivia = Trivia(pieces: []) - var didRemove = false - - var newModifiers = node.modifiers.filter { modifier in - if locationBuilder.location(at: position) == resultLocation, - modifier.name.text == "public" { - didRemove = true - removedLeadingTrivia = modifier.leadingTrivia - return false - } - - return true - } - - var newNode = node - - if didRemove { - if newModifiers.count == 0 { - let triviaRecipientNode = node[keyPath: triviaRecipient] - let newTriviaRecipientNode = triviaRecipientNode - .with(\.leadingTrivia, removedLeadingTrivia + newModifiers.leadingTrivia) - newNode = newNode.with(triviaRecipient, newTriviaRecipientNode) - } else { - newModifiers = newModifiers - .with(\.leadingTrivia, removedLeadingTrivia + newModifiers.leadingTrivia) - } - - return newNode.with(\.modifiers, newModifiers) - } else { - return node - } - } -} - -protocol PublicModifiedDecl: SyntaxProtocol { - var modifiers: DeclModifierListSyntax { get set } -} - -extension ClassDeclSyntax: PublicModifiedDecl {} -extension StructDeclSyntax: PublicModifiedDecl {} -extension EnumDeclSyntax: PublicModifiedDecl {} -extension EnumCaseDeclSyntax: PublicModifiedDecl {} -extension ExtensionDeclSyntax: PublicModifiedDecl {} -extension ProtocolDeclSyntax: PublicModifiedDecl {} -extension InitializerDeclSyntax: PublicModifiedDecl {} -extension FunctionDeclSyntax: PublicModifiedDecl {} -extension SubscriptDeclSyntax: PublicModifiedDecl {} -extension TypeAliasDeclSyntax: PublicModifiedDecl {} -extension VariableDeclSyntax: PublicModifiedDecl {} -extension ActorDeclSyntax: PublicModifiedDecl {} -extension AssociatedTypeDeclSyntax: PublicModifiedDecl {} -extension PrecedenceGroupDeclSyntax: PublicModifiedDecl {} diff --git a/Sources/PeripheryKit/CodeRemoval/RedundantProtocolSyntaxRemover.swift b/Sources/PeripheryKit/CodeRemoval/RedundantProtocolSyntaxRemover.swift deleted file mode 100644 index c21a257ce..000000000 --- a/Sources/PeripheryKit/CodeRemoval/RedundantProtocolSyntaxRemover.swift +++ /dev/null @@ -1,219 +0,0 @@ -import Foundation -import SwiftParser -import SwiftSyntax -import SystemPackage -import SourceGraph - -final class RedundantProtocolSyntaxRemover: SyntaxRewriter, SyntaxRemover, TriviaSplitting { - private let resultLocation: Location - private let replacements: [String] - private let locationBuilder: SourceLocationBuilder - - init(resultLocation: Location, replacements: [String], locationBuilder: SourceLocationBuilder) { - self.resultLocation = resultLocation - self.replacements = replacements - self.locationBuilder = locationBuilder - } - - func perform(syntax: SourceFileSyntax) -> SourceFileSyntax { - visit(syntax) - } - - override func visit(_ node: CodeBlockItemListSyntax) -> CodeBlockItemListSyntax { - let node = super.visit(node) - var didRemoveDeclaration = false - - let newChildren = node.compactMap { child -> CodeBlockItemSyntax? in - guard let name = child.item.as(ProtocolDeclSyntax.self)?.name else { return child } - - if resultLocation == locationBuilder.location(at: name.positionAfterSkippingLeadingTrivia) { - didRemoveDeclaration = true - return remainingTriviaDecl(from: child.item.leadingTrivia) - } - - return child - } - - if didRemoveDeclaration { - return CodeBlockItemListSyntax(newChildren) - } - - return node - } - - override func visit(_ node: ClassDeclSyntax) -> DeclSyntax { - guard let node = super.visit(node).as(ClassDeclSyntax.self) else { return DeclSyntax(node) } - guard let inheritanceClause = node.inheritanceClause else { return DeclSyntax(node) } - - var didRemoveDeclaration = false - - let newInheritedTypes = inheritanceClause.inheritedTypes.filter { type in - let typeLocation = locationBuilder.location(at: type.type.positionAfterSkippingLeadingTrivia) - - if resultLocation == typeLocation { - didRemoveDeclaration = true - return false - } - - return true - } - - if didRemoveDeclaration { - return replacingInheritedTypes( - node: node, - inheritanceClause: inheritanceClause, - newInheritedTypes: newInheritedTypes, - triviaRecipient: \.name - ) - } - - return DeclSyntax(node) - } - - override func visit(_ node: StructDeclSyntax) -> DeclSyntax { - guard let node = super.visit(node).as(StructDeclSyntax.self) else { return DeclSyntax(node) } - guard let inheritanceClause = node.inheritanceClause else { return DeclSyntax(node) } - - var didRemoveDeclaration = false - - let newInheritedTypes = inheritanceClause.inheritedTypes.filter { type in - let typeLocation = locationBuilder.location(at: type.type.positionAfterSkippingLeadingTrivia) - - if resultLocation == typeLocation { - didRemoveDeclaration = true - return false - } - - return true - } - - if didRemoveDeclaration { - return replacingInheritedTypes( - node: node, - inheritanceClause: inheritanceClause, - newInheritedTypes: newInheritedTypes, - triviaRecipient: \.name - ) - } - - return DeclSyntax(node) - } - - override func visit(_ node: EnumDeclSyntax) -> DeclSyntax { - guard let node = super.visit(node).as(EnumDeclSyntax.self) else { return DeclSyntax(node) } - guard let inheritanceClause = node.inheritanceClause else { return DeclSyntax(node) } - - var didRemoveDeclaration = false - - let newInheritedTypes = inheritanceClause.inheritedTypes.filter { type in - let typeLocation = locationBuilder.location(at: type.type.positionAfterSkippingLeadingTrivia) - - if resultLocation == typeLocation { - didRemoveDeclaration = true - return false - } - - return true - } - - if didRemoveDeclaration { - return replacingInheritedTypes( - node: node, - inheritanceClause: inheritanceClause, - newInheritedTypes: newInheritedTypes, - triviaRecipient: \.name - ) - } - - return DeclSyntax(node) - } - - override func visit(_ node: ExtensionDeclSyntax) -> DeclSyntax { - guard let node = super.visit(node).as(ExtensionDeclSyntax.self) else { return DeclSyntax(node) } - guard let inheritanceClause = node.inheritanceClause else { return DeclSyntax(node) } - - var didRemoveDeclaration = false - - let newInheritedTypes = inheritanceClause.inheritedTypes.filter { type in - let typeLocation = locationBuilder.location(at: type.type.positionAfterSkippingLeadingTrivia) - - if resultLocation == typeLocation { - didRemoveDeclaration = true - return false - } - - return true - } - - if didRemoveDeclaration { - return replacingInheritedTypes( - node: node, - inheritanceClause: inheritanceClause, - newInheritedTypes: newInheritedTypes, - triviaRecipient: \.extendedType - ) - } - - return DeclSyntax(node) - } - - // MARK: - Private - - private func replacingInheritedTypes( - node: T, - inheritanceClause: InheritanceClauseSyntax, - newInheritedTypes: InheritedTypeListSyntax, - triviaRecipient: WritableKeyPath - ) -> DeclSyntax { - var newInheritedTypes = newInheritedTypes - - if !replacements.isEmpty, let last = newInheritedTypes.last { - // Before appending more types we need to add a comma to the current final item. - let newLast = last - .with(\.trailingTrivia, []) - .with(\.trailingComma, .commaToken(trailingTrivia: .space)) - let endIndex = newInheritedTypes.index(newInheritedTypes.startIndex, offsetBy: newInheritedTypes.count - 1) - newInheritedTypes[endIndex] = newLast - } - - for replacement in replacements { - let inheritedType = InheritedTypeSyntax( - type: IdentifierTypeSyntax(name: .identifier(replacement)), - trailingComma: .commaToken(), - trailingTrivia: .space - ) - newInheritedTypes.append(inheritedType) - } - - let newNode: T - - if newInheritedTypes.count > 0 { - // Remove the trailing coma from the final type - let endIndex = newInheritedTypes.index(newInheritedTypes.startIndex, offsetBy: newInheritedTypes.count - 1) - var newType = newInheritedTypes[endIndex] - let preservedTrivia = newType.trailingTrivia - newType = newType.with(\.trailingComma, nil) - newType = newType.with(\.trailingTrivia, preservedTrivia) - newInheritedTypes[endIndex] = newType - - let newInheritanceClause = inheritanceClause.with(\.inheritedTypes, newInheritedTypes) - newNode = node.with(\.inheritanceClause, newInheritanceClause) - } else { - let triviaRecipientNode = node[keyPath: triviaRecipient] - let newExtendedType = triviaRecipientNode.with(\.trailingTrivia, triviaRecipientNode.trailingTrivia + inheritanceClause.trailingTrivia) - newNode = node - .with(\.inheritanceClause, nil) - .with(triviaRecipient, newExtendedType) - } - - return DeclSyntax(newNode) - } -} - -protocol TypeDeclWithInheritanceClause: DeclSyntaxProtocol { - var inheritanceClause: InheritanceClauseSyntax? { get set } -} -extension ExtensionDeclSyntax: TypeDeclWithInheritanceClause {} -extension ClassDeclSyntax: TypeDeclWithInheritanceClause {} -extension StructDeclSyntax: TypeDeclWithInheritanceClause {} -extension EnumDeclSyntax: TypeDeclWithInheritanceClause {} diff --git a/Sources/PeripheryKit/CodeRemoval/ScanResultRemover.swift b/Sources/PeripheryKit/CodeRemoval/ScanResultRemover.swift deleted file mode 100644 index 55578a4bf..000000000 --- a/Sources/PeripheryKit/CodeRemoval/ScanResultRemover.swift +++ /dev/null @@ -1,77 +0,0 @@ -import Foundation -import SwiftParser -import SwiftSyntax -import Shared -import SystemPackage -import SourceGraph - -public final class ScanResultRemover { - private let configuration: Configuration - - public init(configuration: Configuration = .shared) { - self.configuration = configuration - } - - public func remove(results: [ScanResult]) throws { - let locationsByFile: [SourceFile: [(Location, [String], SyntaxRemover.Type)]] = results.reduce(into: .init()) { dict, result in - let location = result.declaration.location - let file = result.declaration.location.file - - switch result.annotation { - case .unused, .assignOnlyProperty: - dict[file, default: []].append((location, [], UnusedDeclarationSyntaxRemover.self)) - case .redundantPublicAccessibility: - dict[file, default: []].append((location, [], PublicAccessibilitySyntaxRemover.self)) - case let .redundantProtocol(references, inherited): - dict[file, default: []].append((location, [], RedundantProtocolSyntaxRemover.self)) - let replacements = inherited.sorted() - - for reference in references { - let location = reference.location - dict[location.file, default: []].append((location, replacements, RedundantProtocolSyntaxRemover.self)) - } - } - } - - for (file, locations) in locationsByFile { - let source = try String(contentsOf: file.path.url) - var syntax = Parser.parse(source: source) - let locationConverter = SourceLocationConverter(fileName: file.path.string, tree: syntax) - let locationBuilder = SourceLocationBuilder(file: file, locationConverter: locationConverter) - let sortedLocations = locations.sorted { $0.0 > $1.0 } - - for (location, replacements, removerType) in sortedLocations { - let remover = removerType.init( - resultLocation: location, - replacements: replacements, - locationBuilder: locationBuilder) - syntax = remover.perform(syntax: syntax) - } - - syntax = EmptyExtensionSyntaxRemover().perform(syntax: syntax) - - let isFileEmpty = EmptyFileVisitor().perform(syntax: syntax) - - var outputPath = file.path - - if let outputBasePath = configuration.removalOutputBasePath, - let fileName = file.path.lastComponent { - outputPath = outputBasePath.appending(fileName) - } - - if isFileEmpty { - let fileManager = FileManager.default - if fileManager.fileExists(atPath: outputPath.string) { - try fileManager.removeItem(at: outputPath.url) - } - } else { - var output = "" - syntax.write(to: &output) - if output != source { - let outputData = output.data(using: .utf8)! - try outputData.write(to: outputPath.url, options: .atomic) - } - } - } - } -} diff --git a/Sources/PeripheryKit/CodeRemoval/SyntaxRemover.swift b/Sources/PeripheryKit/CodeRemoval/SyntaxRemover.swift deleted file mode 100644 index 69ff2c64b..000000000 --- a/Sources/PeripheryKit/CodeRemoval/SyntaxRemover.swift +++ /dev/null @@ -1,7 +0,0 @@ -import SwiftSyntax -import SourceGraph - -protocol SyntaxRemover { - init(resultLocation: Location, replacements: [String], locationBuilder: SourceLocationBuilder) - func perform(syntax: SourceFileSyntax) -> SourceFileSyntax -} diff --git a/Sources/PeripheryKit/CodeRemoval/TriviaSplitting.swift b/Sources/PeripheryKit/CodeRemoval/TriviaSplitting.swift deleted file mode 100644 index 99d2a217c..000000000 --- a/Sources/PeripheryKit/CodeRemoval/TriviaSplitting.swift +++ /dev/null @@ -1,43 +0,0 @@ -import Foundation -import SwiftParser -import SwiftSyntax - -protocol TriviaSplitting { - func remainingTriviaDecl(from trivia: Trivia) -> T? -} - -protocol TriviaInitializedItem { - init(triviaDecl: DeclSyntax) -} - -extension TriviaSplitting { - func remainingTriviaDecl(from trivia: Trivia) -> T? { - let lines = trivia.description.split(separator: "\n", omittingEmptySubsequences: false) - .reversed() - .dropFirst() // Drop the newline that all trivia ends with - - let blankLineIdx = lines.firstIndex { line in - line.trimmingCharacters(in: .whitespaces).isEmpty - } - - guard let blankLineIdx else { return nil } - - let remainingLines = lines[blankLineIdx.. SourceFileSyntax { - visit(syntax) - } - - override func visit(_ node: CodeBlockItemListSyntax) -> CodeBlockItemListSyntax { - let node = super.visit(node) - var didRemoveDeclaration = false - - let newChildren = node.compactMap { child -> CodeBlockItemSyntax? in - if hasRemovableChild(from: child.item) { - didRemoveDeclaration = true - return remainingTriviaDecl(from: child.item.leadingTrivia) - } - - return child - } - - if didRemoveDeclaration { - return CodeBlockItemListSyntax(newChildren) - } - - return node - } - - override func visit(_ node: MemberBlockItemListSyntax) -> MemberBlockItemListSyntax { - let node = super.visit(node) - var didRemoveDeclaration = false - - let newMembers = node.compactMap { member -> MemberBlockItemSyntax? in - if let varDecl = member.decl.as(VariableDeclSyntax.self) { - guard varDecl.bindings.count == 1, // TODO: Handle multiple bindings, - let binding = varDecl.bindings.first - else { return member } - - let patternLocation = locationBuilder.location(at: binding.pattern.positionAfterSkippingLeadingTrivia) - if resultLocation == patternLocation { - didRemoveDeclaration = true - return remainingTriviaDecl(from: varDecl.leadingTrivia) - } - - return member - } else if let subscriptDecl = member.decl.as(SubscriptDeclSyntax.self) { - let patternLocation = locationBuilder.location(at: subscriptDecl.subscriptKeyword.positionAfterSkippingLeadingTrivia) - if resultLocation == patternLocation { - didRemoveDeclaration = true - return remainingTriviaDecl(from: subscriptDecl.leadingTrivia) - } - - return member - } else if let enumDecl = member.decl.as(EnumCaseDeclSyntax.self) { - let indexToRemove = enumDecl.elements.firstIndex { element in - locationBuilder.location(at: element.positionAfterSkippingLeadingTrivia) == resultLocation - } - - guard let indexToRemove else { return member } - - didRemoveDeclaration = true - - var newElements = enumDecl.elements - - newElements.remove(at: indexToRemove) - - if newElements.count == 0 { - return remainingTriviaDecl(from: enumDecl.leadingTrivia) - } - - // Remove the trailing coma from the final element - let endIndex = newElements.index(newElements.startIndex, offsetBy: newElements.count - 1) - newElements[endIndex] = newElements[endIndex].with(\.trailingComma, nil) - - let newEnumDecl = enumDecl.with(\.elements, newElements) - return member.with(\.decl, DeclSyntax(newEnumDecl)) - } else if hasRemovableChild(from: member.decl) { - didRemoveDeclaration = true - return remainingTriviaDecl(from: member.decl.leadingTrivia) - } - - return member - } - - if didRemoveDeclaration { - return MemberBlockItemListSyntax(newMembers) - } - - return node - } - - // MARK: - Private - - private func hasRemovableChild(from node: SyntaxProtocol) -> Bool { - return node.children(viewMode: .sourceAccurate).contains { child in - var position: AbsolutePosition? - - if let initDecl = child.as(InitializerDeclSyntax.self) { - position = initDecl.initKeyword.positionAfterSkippingLeadingTrivia - } else if let identifier = child.as(IdentifierTypeSyntax.self) { - position = identifier.name.positionAfterSkippingLeadingTrivia - } else if let member = child.as(MemberTypeSyntax.self) { - return hasRemovableChild(from: member) - } else if let token = child.as(TokenSyntax.self) { - if token.tokenKind.isRemovableKind { - position = token.positionAfterSkippingLeadingTrivia - } - } - - guard let position else { return false } - - if resultLocation == locationBuilder.location(at: position) { - return true - } - - return false - } - } -} - -extension TokenKind { - var isRemovableKind: Bool { - switch self { - case .identifier, .binaryOperator, .prefixOperator, .postfixOperator: - return true - case .keyword(let keyword) where keyword == .`init`: - return true - default: - return false - } - } -} diff --git a/Sources/Shared/Configuration.swift b/Sources/Shared/Configuration.swift index 09744a1f4..a9fd0757c 100644 --- a/Sources/Shared/Configuration.swift +++ b/Sources/Shared/Configuration.swift @@ -89,9 +89,6 @@ public final class Configuration { @Setting(key: "retain_encodable_properties", defaultValue: false) public var retainEncodableProperties: Bool - @Setting(key: "auto_remove", defaultValue: false) - public var autoRemove: Bool - @Setting(key: "verbose", defaultValue: false) public var verbose: Bool @@ -226,10 +223,6 @@ public final class Configuration { config[$disableUnusedImportAnalysis.key] = disableUnusedImportAnalysis } - if $autoRemove.hasNonDefaultValue { - config[$autoRemove.key] = autoRemove - } - if $verbose.hasNonDefaultValue { config[$verbose.key] = verbose } @@ -357,8 +350,6 @@ public final class Configuration { $disableRedundantPublicAnalysis.assign(value) case $disableUnusedImportAnalysis.key: $disableUnusedImportAnalysis.assign(value) - case $autoRemove.key: - $autoRemove.assign(value) case $verbose.key: $verbose.assign(value) case $quiet.key: @@ -417,7 +408,6 @@ public final class Configuration { $retainSwiftUIPreviews.reset() $disableRedundantPublicAnalysis.reset() $disableUnusedImportAnalysis.reset() - $autoRemove.reset() $externalEncodableProtocols.reset() $externalCodableProtocols.reset() $externalTestCaseClasses.reset() diff --git a/Tests/Fixtures/RemovalFixtures/testClassRedundantPublicAccessibility.swift b/Tests/Fixtures/RemovalFixtures/testClassRedundantPublicAccessibility.swift deleted file mode 100644 index b6af5d0a3..000000000 --- a/Tests/Fixtures/RemovalFixtures/testClassRedundantPublicAccessibility.swift +++ /dev/null @@ -1,11 +0,0 @@ -// periphery:ignore -final class ClassRedundantPublicAccessibilityRetainer { - // periphery:ignore - func retain() { - ClassRedundantPublicAccessibility().someFunc() - } -} - -public final class ClassRedundantPublicAccessibility { - public func someFunc() {} -} diff --git a/Tests/Fixtures/RemovalFixtures/testEmptyExtension.swift b/Tests/Fixtures/RemovalFixtures/testEmptyExtension.swift deleted file mode 100644 index 84ea8eef3..000000000 --- a/Tests/Fixtures/RemovalFixtures/testEmptyExtension.swift +++ /dev/null @@ -1,9 +0,0 @@ -public class EmptyExtension {} -extension EmptyExtension { - func unused() {} -} -extension EmptyExtension { - // Only comments. -} -public protocol EmptyExtensionProtocol {} -extension EmptyExtension: EmptyExtensionProtocol {} diff --git a/Tests/Fixtures/RemovalFixtures/testEmptyFile.swift b/Tests/Fixtures/RemovalFixtures/testEmptyFile.swift deleted file mode 100644 index 627847166..000000000 --- a/Tests/Fixtures/RemovalFixtures/testEmptyFile.swift +++ /dev/null @@ -1,5 +0,0 @@ -import Foundation - -// comment - -class EmptyFile {} diff --git a/Tests/Fixtures/RemovalFixtures/testFunction.swift b/Tests/Fixtures/RemovalFixtures/testFunction.swift deleted file mode 100644 index 367d7ea0a..000000000 --- a/Tests/Fixtures/RemovalFixtures/testFunction.swift +++ /dev/null @@ -1,9 +0,0 @@ -public class FunctionRemoval { - public func used1() {} - func unused1() {} -} - -extension FunctionRemoval { - public func used2() {} - func unused2() {} -} diff --git a/Tests/Fixtures/RemovalFixtures/testFunctionRedundantPublicAccessibility.swift b/Tests/Fixtures/RemovalFixtures/testFunctionRedundantPublicAccessibility.swift deleted file mode 100644 index 11a972f5d..000000000 --- a/Tests/Fixtures/RemovalFixtures/testFunctionRedundantPublicAccessibility.swift +++ /dev/null @@ -1,9 +0,0 @@ -// periphery:ignore -final class FunctionRedundantPublicAccessibilityRetainer { - // periphery:ignore - func retain() { - somePublicFunc() - } -} - -public func somePublicFunc() {} diff --git a/Tests/Fixtures/RemovalFixtures/testInitializerRedundantPublicAccessibility.swift b/Tests/Fixtures/RemovalFixtures/testInitializerRedundantPublicAccessibility.swift deleted file mode 100644 index d3afad730..000000000 --- a/Tests/Fixtures/RemovalFixtures/testInitializerRedundantPublicAccessibility.swift +++ /dev/null @@ -1,11 +0,0 @@ -// periphery:ignore -final class InitializerRedundantPublicAccessibilityRetainer { - // periphery:ignore - func retain() { - _ = InitializerRedundantPublicAccessibility() - } -} - -public class InitializerRedundantPublicAccessibility { - public init() {} -} diff --git a/Tests/Fixtures/RemovalFixtures/testLeadingTriviaSplitting.swift b/Tests/Fixtures/RemovalFixtures/testLeadingTriviaSplitting.swift deleted file mode 100644 index c70ad15b6..000000000 --- a/Tests/Fixtures/RemovalFixtures/testLeadingTriviaSplitting.swift +++ /dev/null @@ -1,9 +0,0 @@ -// Trivia to remain, 1 - -// Trivia to remain, 2 - -// Trivia to remove, 1 -// Trivia to remove, 2 -class LeadingTriviaSplittingUnused {} - -public class LeadingTriviaSplittingUsed {} diff --git a/Tests/Fixtures/RemovalFixtures/testPropertyRedundantPublicAccessibility.swift b/Tests/Fixtures/RemovalFixtures/testPropertyRedundantPublicAccessibility.swift deleted file mode 100644 index f3737416f..000000000 --- a/Tests/Fixtures/RemovalFixtures/testPropertyRedundantPublicAccessibility.swift +++ /dev/null @@ -1,9 +0,0 @@ -// periphery:ignore -final class PropertyRedundantPublicAccessibilityRetainer { - // periphery:ignore - func retain() { - _ = somePublicProperty - } -} - -public let somePublicProperty: Int = 1 diff --git a/Tests/Fixtures/RemovalFixtures/testRedundantProtocol.swift b/Tests/Fixtures/RemovalFixtures/testRedundantProtocol.swift deleted file mode 100644 index b5b48e560..000000000 --- a/Tests/Fixtures/RemovalFixtures/testRedundantProtocol.swift +++ /dev/null @@ -1,22 +0,0 @@ -protocol RedundantProtocol3_Existential1 {} -protocol RedundantProtocol3_Existential2 {} -protocol RedundantProtocol2: RedundantProtocol3_Existential1, RedundantProtocol3_Existential2 {} -protocol RedundantProtocol1 {} -class RedundantProtocolClass1: RedundantProtocol1, RedundantProtocol2, CustomStringConvertible { - var description: String = "" -} -class RedundantProtocolClass2 {} -extension RedundantProtocolClass2: RedundantProtocol1 {} -class RedundantProtocolClass3 { - class RedundantProtocolClass4: CustomStringConvertible, RedundantProtocol1 { - var description: String = "" - } -} - -public class RedundantProtocolRetainer { - public func retain() { - _ = RedundantProtocolClass1() - _ = RedundantProtocolClass2.self - _ = RedundantProtocolClass3.RedundantProtocolClass4.self - } -} diff --git a/Tests/Fixtures/RemovalFixtures/testRedundantPublicAccessibilityWithAttributes.swift b/Tests/Fixtures/RemovalFixtures/testRedundantPublicAccessibilityWithAttributes.swift deleted file mode 100644 index 7a93363e5..000000000 --- a/Tests/Fixtures/RemovalFixtures/testRedundantPublicAccessibilityWithAttributes.swift +++ /dev/null @@ -1,9 +0,0 @@ -// periphery:ignore -private final class Retainer { - func retain() { - redundantPublicAccessibilityWithAttributes() - } -} - -@available(*, message: "hi mum") -public func redundantPublicAccessibilityWithAttributes() {} diff --git a/Tests/Fixtures/RemovalFixtures/testRootDeclaration.swift b/Tests/Fixtures/RemovalFixtures/testRootDeclaration.swift deleted file mode 100644 index 3400856cc..000000000 --- a/Tests/Fixtures/RemovalFixtures/testRootDeclaration.swift +++ /dev/null @@ -1,3 +0,0 @@ -public class UsedRootDeclaration1 {} -class UnusedRootDeclaration {} -public class UsedRootDeclaration2 {} diff --git a/Tests/Fixtures/RemovalFixtures/testSimpleProperty.swift b/Tests/Fixtures/RemovalFixtures/testSimpleProperty.swift deleted file mode 100644 index ceb52b558..000000000 --- a/Tests/Fixtures/RemovalFixtures/testSimpleProperty.swift +++ /dev/null @@ -1,4 +0,0 @@ -public class SimplePropertyRemoval { - public var used = 1 - var unused = 1 -} diff --git a/Tests/Fixtures/RemovalFixtures/testSubscriptRedundantPublicAccessibility.swift b/Tests/Fixtures/RemovalFixtures/testSubscriptRedundantPublicAccessibility.swift deleted file mode 100644 index 9354a1c89..000000000 --- a/Tests/Fixtures/RemovalFixtures/testSubscriptRedundantPublicAccessibility.swift +++ /dev/null @@ -1,14 +0,0 @@ -// periphery:ignore -final class SubscriptRedundantPublicAccessibilityRetainer { - // periphery:ignore - func retain() { - _ = SubscriptRedundantPublicAccessibility()[1] - } -} - -public final class SubscriptRedundantPublicAccessibility { - public subscript(param: Int) -> Int { - return 0 - } -} - diff --git a/Tests/Fixtures/RemovalFixtures/testUnusedCodableProperty.swift b/Tests/Fixtures/RemovalFixtures/testUnusedCodableProperty.swift deleted file mode 100644 index 7952faa03..000000000 --- a/Tests/Fixtures/RemovalFixtures/testUnusedCodableProperty.swift +++ /dev/null @@ -1,9 +0,0 @@ -public class UnusedCodableProperty: Codable { - public var used: String? - var unused: String? - - enum CodingKeys: CodingKey { - case used - case unused - } -} diff --git a/Tests/Fixtures/RemovalFixtures/testUnusedEnumCase.swift b/Tests/Fixtures/RemovalFixtures/testUnusedEnumCase.swift deleted file mode 100644 index c32a265ff..000000000 --- a/Tests/Fixtures/RemovalFixtures/testUnusedEnumCase.swift +++ /dev/null @@ -1,10 +0,0 @@ -enum EnumCaseRemoval { - case used - case unused -} - -public class EnumCaseRemovalRetainer { - public func retain() { - _ = EnumCaseRemoval.used - } -} diff --git a/Tests/Fixtures/RemovalFixtures/testUnusedEnumInListCase.swift b/Tests/Fixtures/RemovalFixtures/testUnusedEnumInListCase.swift deleted file mode 100644 index 87c5584af..000000000 --- a/Tests/Fixtures/RemovalFixtures/testUnusedEnumInListCase.swift +++ /dev/null @@ -1,10 +0,0 @@ -enum EnumInListCaseRemoval { - case used1, unused1, used2, unused2 -} - -public class EnumInListCaseRemovalRetainer { - public func retain() { - _ = EnumInListCaseRemoval.used1 - _ = EnumInListCaseRemoval.used2 - } -} diff --git a/Tests/Fixtures/RemovalFixtures/testUnusedExtension.swift b/Tests/Fixtures/RemovalFixtures/testUnusedExtension.swift deleted file mode 100644 index 93900e707..000000000 --- a/Tests/Fixtures/RemovalFixtures/testUnusedExtension.swift +++ /dev/null @@ -1,9 +0,0 @@ -class UnusedExtension { - class Inner {} -} -extension UnusedExtension { - func someFunc() {} -} -extension UnusedExtension.Inner { - func someFunc() {} -} diff --git a/Tests/Fixtures/RemovalFixtures/testUnusedInitializer.swift b/Tests/Fixtures/RemovalFixtures/testUnusedInitializer.swift deleted file mode 100644 index 06953e91a..000000000 --- a/Tests/Fixtures/RemovalFixtures/testUnusedInitializer.swift +++ /dev/null @@ -1,10 +0,0 @@ -public class UnusedInitializer { - public init(used: Int) {} - init(unused1: Int) {} -} - -extension UnusedInitializer { - convenience init(unused2: Int) { - self.init(unused1: unused2) - } -} diff --git a/Tests/Fixtures/RemovalFixtures/testUnusedNestedDeclaration.swift b/Tests/Fixtures/RemovalFixtures/testUnusedNestedDeclaration.swift deleted file mode 100644 index 37e0ef8ca..000000000 --- a/Tests/Fixtures/RemovalFixtures/testUnusedNestedDeclaration.swift +++ /dev/null @@ -1,5 +0,0 @@ -public class NestedDeclaration { - public class NestedDeclarationInner { - func unused() {} - } -} diff --git a/Tests/Fixtures/RemovalFixtures/testUnusedNestedTypeWithExtension.swift b/Tests/Fixtures/RemovalFixtures/testUnusedNestedTypeWithExtension.swift deleted file mode 100644 index b2a9c768f..000000000 --- a/Tests/Fixtures/RemovalFixtures/testUnusedNestedTypeWithExtension.swift +++ /dev/null @@ -1,7 +0,0 @@ -class UnusedNestedTypeWithExtension { - class Inner {} -} - -extension UnusedNestedTypeWithExtension.Inner { - func unused() {} -} diff --git a/Tests/Fixtures/RemovalFixtures/testUnusedSubscript.swift b/Tests/Fixtures/RemovalFixtures/testUnusedSubscript.swift deleted file mode 100644 index 4e3e83304..000000000 --- a/Tests/Fixtures/RemovalFixtures/testUnusedSubscript.swift +++ /dev/null @@ -1,3 +0,0 @@ -public class UnusedSubscript { - subscript(key: String) -> Bool { true } -} diff --git a/Tests/Fixtures/RemovalFixtures/testUnusedTypealias.swift b/Tests/Fixtures/RemovalFixtures/testUnusedTypealias.swift deleted file mode 100644 index bc32e990e..000000000 --- a/Tests/Fixtures/RemovalFixtures/testUnusedTypealias.swift +++ /dev/null @@ -1,3 +0,0 @@ -public enum CustomTypes { - typealias UnusedTypealias = Int -} diff --git a/Tests/PeripheryTests/RemovalTest.swift b/Tests/PeripheryTests/RemovalTest.swift deleted file mode 100644 index 008be4750..000000000 --- a/Tests/PeripheryTests/RemovalTest.swift +++ /dev/null @@ -1,358 +0,0 @@ -import XCTest -import Shared -import SystemPackage -@testable import TestShared -@testable import PeripheryKit - -final class RemovalTest: FixtureSourceGraphTestCase { - private static let outputBasePath = FilePath("/tmp/periphery/RemovalTest") - - static override func setUp() { - super.setUp() - - configuration.targets = ["RemovalFixtures"] - - _ = try? Shell.shared.exec(["rm", "-rf", outputBasePath.string]) - _ = try? Shell.shared.exec(["mkdir", "-p", outputBasePath.string]) - configuration.removalOutputBasePath = outputBasePath - - build(driver: SPMProjectDriver.self) - } - - func testRootDeclaration() throws { - try assertOutput( - """ - public class UsedRootDeclaration1 {} - public class UsedRootDeclaration2 {} - """ - ) - } - - func testSimpleProperty() throws { - try assertOutput( - """ - public class SimplePropertyRemoval { - public var used = 1 - } - """ - ) - } - - func testMultipleBindingProperty() throws { - // TOOD - } - - func testFunction() throws { - try assertOutput( - """ - public class FunctionRemoval { - public func used1() {} - } - - extension FunctionRemoval { - public func used2() {} - } - """ - ) - } - - func testUnusedTypealias() throws { - try assertOutput( - """ - public enum CustomTypes { - } - """ - ) - } - - func testUnusedEnumCase() throws { - try assertOutput( - """ - enum EnumCaseRemoval { - case used - } - - public class EnumCaseRemovalRetainer { - public func retain() { - _ = EnumCaseRemoval.used - } - } - """ - ) - } - - func testUnusedEnumInListCase() throws { - try assertOutput( - """ - enum EnumInListCaseRemoval { - case used1, used2 - } - - public class EnumInListCaseRemovalRetainer { - public func retain() { - _ = EnumInListCaseRemoval.used1 - _ = EnumInListCaseRemoval.used2 - } - } - """ - ) - } - - func testUnusedExtension() throws { - try assertNoFile() - } - - func testUnusedInitializer() throws { - try assertOutput( - """ - public class UnusedInitializer { - public init(used: Int) {} - } - """ - ) - } - - func testUnusedNestedDeclaration() throws { - try assertOutput( - """ - public class NestedDeclaration { - public class NestedDeclarationInner { - } - } - """ - ) - } - - func testUnusedSubscript() throws { - try assertOutput( - """ - public class UnusedSubscript { - } - """ - ) - } - - func testRedundantProtocol() throws { - try assertOutput( - """ - protocol RedundantProtocol3_Existential1 {} - protocol RedundantProtocol3_Existential2 {} - class RedundantProtocolClass1: CustomStringConvertible, RedundantProtocol3_Existential1, RedundantProtocol3_Existential2 { - var description: String = "" - } - class RedundantProtocolClass2 {} - class RedundantProtocolClass3 { - class RedundantProtocolClass4: CustomStringConvertible { - var description: String = "" - } - } - - public class RedundantProtocolRetainer { - public func retain() { - _ = RedundantProtocolClass1() - _ = RedundantProtocolClass2.self - _ = RedundantProtocolClass3.RedundantProtocolClass4.self - } - } - """ - ) - } - - func testUnusedNestedTypeWithExtension() throws { - try assertNoFile() - } - - func testUnusedCodableProperty() throws { - try assertOutput( - """ - public class UnusedCodableProperty: Codable { - public var used: String? - - enum CodingKeys: CodingKey { - case used - case unused - } - } - """ - ) - } - - // MARK: - Misc. - - func testLeadingTriviaSplitting() throws { - try assertOutput( - """ - // Trivia to remain, 1 - - // Trivia to remain, 2 - - - public class LeadingTriviaSplittingUsed {} - """ - ) - } - - func testEmptyExtension() throws { - try assertOutput( - """ - public class EmptyExtension {} - public protocol EmptyExtensionProtocol {} - extension EmptyExtension: EmptyExtensionProtocol {} - """ - ) - } - - func testEmptyFile() throws { - try assertNoFile() - } - - // MARK: - Redundant Public Accessibility - - func testClassRedundantPublicAccessibility() throws { - try assertOutput( - retainPublic: false, - disableRedundantPublicAnalysis: false, - """ - // periphery:ignore - final class ClassRedundantPublicAccessibilityRetainer { - // periphery:ignore - func retain() { - ClassRedundantPublicAccessibility().someFunc() - } - } - - final class ClassRedundantPublicAccessibility { - func someFunc() {} - } - """ - ) - } - - func testFunctionRedundantPublicAccessibility() throws { - try assertOutput( - retainPublic: false, - disableRedundantPublicAnalysis: false, - """ - // periphery:ignore - final class FunctionRedundantPublicAccessibilityRetainer { - // periphery:ignore - func retain() { - somePublicFunc() - } - } - - func somePublicFunc() {} - """ - ) - } - - func testSubscriptRedundantPublicAccessibility() throws { - try assertOutput( - retainPublic: false, - disableRedundantPublicAnalysis: false, - """ - // periphery:ignore - final class SubscriptRedundantPublicAccessibilityRetainer { - // periphery:ignore - func retain() { - _ = SubscriptRedundantPublicAccessibility()[1] - } - } - - final class SubscriptRedundantPublicAccessibility { - subscript(param: Int) -> Int { - return 0 - } - } - """ - ) - } - - func testPropertyRedundantPublicAccessibility() throws { - try assertOutput( - retainPublic: false, - disableRedundantPublicAnalysis: false, - """ - // periphery:ignore - final class PropertyRedundantPublicAccessibilityRetainer { - // periphery:ignore - func retain() { - _ = somePublicProperty - } - } - - let somePublicProperty: Int = 1 - """ - ) - } - - func testInitializerRedundantPublicAccessibility() throws { - try assertOutput( - retainPublic: false, - disableRedundantPublicAnalysis: false, - """ - // periphery:ignore - final class InitializerRedundantPublicAccessibilityRetainer { - // periphery:ignore - func retain() { - _ = InitializerRedundantPublicAccessibility() - } - } - - class InitializerRedundantPublicAccessibility { - init() {} - } - """ - ) - } - - func testRedundantPublicAccessibilityWithAttributes() throws { - try assertOutput( - retainPublic: false, - disableRedundantPublicAnalysis: false, - """ - // periphery:ignore - private final class Retainer { - func retain() { - redundantPublicAccessibilityWithAttributes() - } - } - - @available(*, message: "hi mum") - func redundantPublicAccessibilityWithAttributes() {} - """ - ) - } - - // MARK: - Private - - private func assertOutput( - retainPublic: Bool = true, - disableRedundantPublicAnalysis: Bool = true, - _ expectedOutput: String, - file: StaticString = #file, - line: UInt = #line - ) throws { - let results = analyze( - retainPublic: retainPublic, - disableRedundantPublicAnalysis: disableRedundantPublicAnalysis - ) {} - try ScanResultRemover().remove(results: results) - - let outputPath = Self.outputBasePath.appending(testFixturePath.lastComponent!) - let output = try String(contentsOf: outputPath.url) - - XCTAssertEqual(output.trimmed, expectedOutput, file: file, line: line) - } - - private func assertNoFile() throws { - let results = analyze( - retainPublic: true, - disableRedundantPublicAnalysis: false - ) {} - try ScanResultRemover().remove(results: results) - - let outputPath = Self.outputBasePath.appending(testFixturePath.lastComponent!) - XCTAssertFalse(FileManager.default.fileExists(atPath: outputPath.string)) - } -}