diff --git a/CHANGELOG.md b/CHANGELOG.md index 4272ff2bb..be025da94 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ - Assign-only properties on structs with synthesized initializers are now detected. - Added the `--retain-codable-properties` option to retain all properties on Codable types. - Results for redundant protocol conformances will now list the inherited protocols that should replace the redundant conformance. +- Added the `--retain-files` option to retain all declarations within the given files. ##### Bug Fixes diff --git a/README.md b/README.md index e4286381e..e85542c17 100644 --- a/README.md +++ b/README.md @@ -405,6 +405,10 @@ To exclude the results from certain files, pass the `--report-exclude ` o To exclude files from being indexed, pass the `--index-exclude ` option to the `scan` command. Excluding files from the index phase means that any declarations and references contained within the files will not be seen by Periphery. Periphery will be behave as if the files do not exist. For example, this option can be used to exclude generated code that holds references to non-generated code, or exclude all `.xib` and `.storyboard` files that hold references to code. +### Retaining File Declarations + +To retain all declarations in files, pass the `--retain-files ` option to the `scan` command. This option is equivalent to adding a `// periphery:ignore:all` comment command at the top of each file. + ## Continuous Integration When integrating Periphery into a CI pipeline, you can potentially skip the build phase if your pipeline has already done so, e.g to run tests. This can be achieved using the `--skip-build` option. However, you also need to tell Periphery the location of the index store using `--index-store-path`. This location is dependent on your project type. diff --git a/Sources/Frontend/Commands/ScanCommand.swift b/Sources/Frontend/Commands/ScanCommand.swift index 8f4e386b2..7c94cffd7 100644 --- a/Sources/Frontend/Commands/ScanCommand.swift +++ b/Sources/Frontend/Commands/ScanCommand.swift @@ -45,6 +45,9 @@ struct ScanCommand: FrontendCommand { @Option(help: "Path glob of source files to include in the results. This option supersedes '--report-exclude'. Note that this option is purely cosmetic, these files will still be indexed. Multiple globs may be delimited by a pipe", transform: split(by: "|")) var reportInclude: [String] = defaultConfiguration.$reportInclude.defaultValue + @Option(parsing: .upToNextOption, help: "Retain all declarations within the given source files. Multiple paths may be specified") + var retainFiles: [String] = defaultConfiguration.$retainFiles.defaultValue + @Option(parsing: .upToNextOption, help: "Path to the index store. Multiple paths may be specified. Implies '--skip-build'") var indexStorePath: [FilePath] = defaultConfiguration.$indexStorePath.defaultValue @@ -132,6 +135,7 @@ struct ScanCommand: FrontendCommand { configuration.apply(\.$reportExclude, reportExclude) configuration.apply(\.$reportInclude, reportInclude) configuration.apply(\.$outputFormat, format) + configuration.apply(\.$retainFiles, retainFiles) configuration.apply(\.$retainPublic, retainPublic) configuration.apply(\.$retainAssignOnlyProperties, retainAssignOnlyProperties) configuration.apply(\.$retainAssignOnlyPropertyTypes, retainAssignOnlyPropertyTypes) diff --git a/Sources/PeripheryKit/Indexer/SwiftIndexer.swift b/Sources/PeripheryKit/Indexer/SwiftIndexer.swift index fe8c9ac83..29b450d66 100644 --- a/Sources/PeripheryKit/Indexer/SwiftIndexer.swift +++ b/Sources/PeripheryKit/Indexer/SwiftIndexer.swift @@ -64,10 +64,17 @@ public final class SwiftIndexer: Indexer { throw PeripheryError.unindexedTargetsError(targets: targets, indexStorePaths: indexStorePaths) } + var retainedFiles: Set = [] + + if !configuration.retainFilesMatchers.isEmpty { + retainedFiles = allSourceFiles.filter { configuration.retainFilesMatchers.anyMatch(filename: $0.string) } + } + let jobs = unitsByFile.map { (file, units) -> Job in return Job( file: file, units: units, + retainAllDeclarations: retainedFiles.contains(file), graph: graph, logger: logger, configuration: configuration @@ -111,10 +118,12 @@ public final class SwiftIndexer: Indexer { private let logger: ContextualLogger private let configuration: Configuration private var sourceFile: SourceFile? + private var retainAllDeclarations: Bool required init( file: FilePath, units: [(IndexStore, IndexStoreUnit)], + retainAllDeclarations: Bool, graph: SourceGraph, logger: ContextualLogger, configuration: Configuration @@ -122,6 +131,7 @@ public final class SwiftIndexer: Indexer { ) { self.file = file self.units = units + self.retainAllDeclarations = retainAllDeclarations self.graph = graph self.logger = logger self.configuration = configuration @@ -225,6 +235,10 @@ public final class SwiftIndexer: Indexer { graph.withLock { graph.addUnsafe(references) graph.addUnsafe(newDeclarations) + + if retainAllDeclarations { + graph.markRetainedUnsafe(newDeclarations) + } } establishDeclarationHierarchy() @@ -490,6 +504,10 @@ public final class SwiftIndexer: Indexer { functionDecl.unusedParameters.insert(paramDecl) graph.addUnsafe(paramDecl) + if retainAllDeclarations { + graph.markRetainedUnsafe(paramDecl) + } + if (functionDecl.isObjcAccessible && configuration.retainObjcAccessible) || ignoredParamNames.contains(param.name) { graph.markRetainedUnsafe(paramDecl) } diff --git a/Sources/PeripheryKit/SourceGraph/SourceGraph.swift b/Sources/PeripheryKit/SourceGraph/SourceGraph.swift index 8b5006fe2..7916a77fe 100644 --- a/Sources/PeripheryKit/SourceGraph/SourceGraph.swift +++ b/Sources/PeripheryKit/SourceGraph/SourceGraph.swift @@ -99,6 +99,10 @@ public final class SourceGraph { _ = retainedDeclarations.insert(declaration) } + func markRetainedUnsafe(_ declarations: Set) { + retainedDeclarations.formUnion(declarations) + } + func markAssignOnlyProperty(_ declaration: Declaration) { withLock { _ = assignOnlyProperties.insert(declaration) @@ -352,3 +356,4 @@ public final class SourceGraph { } } } + diff --git a/Sources/Shared/Configuration.swift b/Sources/Shared/Configuration.swift index b1115192a..6ebde096e 100644 --- a/Sources/Shared/Configuration.swift +++ b/Sources/Shared/Configuration.swift @@ -59,6 +59,9 @@ public final class Configuration { @Setting(key: "retain_objc_annotated", defaultValue: false) public var retainObjcAnnotated: Bool + @Setting(key: "retain_files", defaultValue: []) + public var retainFiles: [String] + @Setting(key: "retain_public", defaultValue: false) public var retainPublic: Bool @@ -161,6 +164,10 @@ public final class Configuration { config[$retainPublic.key] = retainPublic } + if $retainFiles.hasNonDefaultValue { + config[$retainFiles.key] = retainFiles + } + if $retainAssignOnlyProperties.hasNonDefaultValue { config[$retainAssignOnlyProperties.key] = retainAssignOnlyProperties } @@ -273,6 +280,8 @@ public final class Configuration { $outputFormat.assign(value) case $retainPublic.key: $retainPublic.assign(value) + case $retainFiles.key: + $retainFiles.assign(value) case $retainAssignOnlyProperties.key: $retainAssignOnlyProperties.assign(value) case $retainAssignOnlyPropertyTypes.key: @@ -335,6 +344,7 @@ public final class Configuration { $reportInclude.reset() $outputFormat.reset() $retainPublic.reset() + $retainFiles.reset() $retainAssignOnlyProperties.reset() $retainAssignOnlyPropertyTypes.reset() $retainObjcAccessible.reset() @@ -379,8 +389,20 @@ public final class Configuration { return matchers } - public func resetIndexExcludeMatchers() { + private var _retainFilesMatchers: [FilenameMatcher]? + public var retainFilesMatchers: [FilenameMatcher] { + if let _retainFilesMatchers { + return _retainFilesMatchers + } + + let matchers = buildFilenameMatchers(with: retainFiles) + _retainFilesMatchers = matchers + return matchers + } + + public func resetMatchers() { _indexExcludeMatchers = nil + _retainFilesMatchers = nil } public lazy var reportExcludeMatchers: [FilenameMatcher] = { diff --git a/Tests/Fixtures/RetentionFixtures/testRetainsFilesOption.swift b/Tests/Fixtures/RetentionFixtures/testRetainsFilesOption.swift new file mode 100644 index 000000000..fe4f27072 --- /dev/null +++ b/Tests/Fixtures/RetentionFixtures/testRetainsFilesOption.swift @@ -0,0 +1 @@ +class FixtureClass100 {} diff --git a/Tests/PeripheryTests/RetentionTest.swift b/Tests/PeripheryTests/RetentionTest.swift index 4ccfd4d15..aefb13bc3 100644 --- a/Tests/PeripheryTests/RetentionTest.swift +++ b/Tests/PeripheryTests/RetentionTest.swift @@ -1034,6 +1034,20 @@ final class RetentionTest: FixtureSourceGraphTestCase { } } + func testRetainsFilesOption() { + configuration.retainFiles = [testFixturePath.string] + + analyze { + assertReferenced(.class("FixtureClass100")) + } + + configuration.retainFiles = [] + + analyze { + assertNotReferenced(.class("FixtureClass100")) + } + } + // MARK: - Assign-only properties func testStructImplicitInitializer() { diff --git a/Tests/Shared/FixtureSourceGraphTestCase.swift b/Tests/Shared/FixtureSourceGraphTestCase.swift index 1b05e0172..54585394c 100644 --- a/Tests/Shared/FixtureSourceGraphTestCase.swift +++ b/Tests/Shared/FixtureSourceGraphTestCase.swift @@ -17,7 +17,7 @@ class FixtureSourceGraphTestCase: SourceGraphTestCase { configuration.retainObjcAccessible = retainObjcAccessible configuration.retainObjcAnnotated = retainObjcAnnotated configuration.indexExclude = Self.sourceFiles.subtracting([testFixturePath]).map { $0.string } - configuration.resetIndexExcludeMatchers() + configuration.resetMatchers() if !testFixturePath.exists { fatalError("\(testFixturePath.string) does not exist")