Skip to content
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

Automatic code removal, closes #666 #702

Merged
merged 1 commit into from
Jan 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Package.resolved
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/tuist/xcodeproj",
"state" : {
"revision" : "447c159b0c5fb047a024fd8d942d4a76cf47dde0",
"version" : "8.16.0"
"revision" : "a3e5d54f8c8a2964ee54870fda33b28651416581",
"version" : "8.17.0"
}
},
{
Expand Down
4 changes: 4 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@ var targets: [PackageDescription.Target] = [
],
path: "Tests/Fixtures/RetentionFixtures"
),
.target(
name: "RemovalFixtures",
path: "Tests/Fixtures/RemovalFixtures"
),
.target(
name: "UnusedParameterFixtures",
path: "Tests/Fixtures/UnusedParameterFixtures",
Expand Down
5 changes: 5 additions & 0 deletions Sources/Frontend/Commands/ScanBehavior.swift
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,11 @@ final class ScanBehavior {
results = try block(project)
let interval = logger.beginInterval("result:output")
let filteredResults = OutputDeclarationFilter().filter(results)

if configuration.autoRemove {
try ScanResultRemover().remove(results: filteredResults)
}

let output = try configuration.outputFormat.formatter.init(configuration: configuration).format(filteredResults)

if configuration.outputFormat.supportsAuxiliaryOutput {
Expand Down
4 changes: 4 additions & 0 deletions Sources/Frontend/Commands/ScanCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,9 @@ struct ScanCommand: FrontendCommand {
@Flag(help: "Retain properties on Codable types")
var retainCodableProperties: Bool = defaultConfiguration.$retainCodableProperties.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

Expand Down Expand Up @@ -148,6 +151,7 @@ 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)
Expand Down
4 changes: 2 additions & 2 deletions Sources/Frontend/Scan.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,9 @@ final class Scan {
logger.endInterval(analyzeInterval)

let resultInterval = logger.beginInterval("result:build")
let result = ScanResultBuilder.build(for: graph)
let results = ScanResultBuilder.build(for: graph)
logger.endInterval(resultInterval)

return result
return results
}
}
29 changes: 29 additions & 0 deletions Sources/PeripheryKit/CodeRemoval/EmptyExtensionSyntaxRemover.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
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)
}
}
30 changes: 30 additions & 0 deletions Sources/PeripheryKit/CodeRemoval/EmptyFileVisitor.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
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
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
import Foundation
import SwiftParser
import SwiftSyntax
import SystemPackage

final class PublicAccessibilitySyntaxRemover: SyntaxRewriter, SyntaxRemover {
private let resultLocation: SourceLocation
private let locationBuilder: SourceLocationBuilder

init(resultLocation: SourceLocation, 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<T: PublicModifiedDecl, Output: SyntaxProtocol>(
from node: T,
at position: AbsolutePosition,
triviaRecipient: WritableKeyPath<T, Output>
) -> 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 {}
Loading
Loading