Repository: uber/SwiftCodeSan
Branch: master
Commit: b586387590bd
Files: 40
Total size: 185.8 KB
Directory structure:
gitextract__6ffq4gh/
├── .github/
│ └── workflows/
│ └── ci.yml
├── .gitignore
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE.txt
├── NOTICE.txt
├── Package.swift
├── README.md
├── Sources/
│ ├── SwiftCodeSan/
│ │ ├── Executor.swift
│ │ └── main.swift
│ └── SwiftCodeSanKit/
│ ├── Core/
│ │ ├── AccessLevelRewriter.swift
│ │ ├── DeclMetaTypes.swift
│ │ ├── DeclRemover.swift
│ │ ├── DeclVisitor.swift
│ │ ├── ImportRewriter.swift
│ │ └── RefChecker.swift
│ ├── FileParsers/
│ │ └── DeclParser.swift
│ ├── FileUpdaters/
│ │ └── DeclUpdater.swift
│ ├── Operations/
│ │ ├── RemoveDeadDecls.swift
│ │ ├── RemoveUnusedImports.swift
│ │ └── UpdateAccessLevels.swift
│ └── Utils/
│ ├── Extensions/
│ │ ├── FileManagerExtensions.swift
│ │ ├── SequenceExtensions.swift
│ │ ├── StringExtensions.swift
│ │ ├── SyntaxExtensions.swift
│ │ └── SyntaxParserExtensions.swift
│ ├── Logger.swift
│ └── Scanner.swift
├── Tests/
│ ├── SwiftCodeSanTestCase.swift
│ └── TestClasses/
│ ├── Fixtures/
│ │ ├── test0.swift
│ │ ├── test1.swift
│ │ ├── test2.swift
│ │ ├── test3.swift
│ │ ├── test4.swift
│ │ ├── test5.swift
│ │ ├── test6.swift
│ │ ├── test7.swift
│ │ └── test8.swift
│ └── SwiftCodeSanTests.swift
└── install-script.sh
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/workflows/ci.yml
================================================
name: CI
on:
push:
branches: [master]
pull_request:
branches: [master]
jobs:
macos:
runs-on: macOS-latest
steps:
- name: Checkout
uses: actions/checkout@v1
- name: Build
run: swift build -v
- name: Test
run: swift test -v -c release
================================================
FILE: .gitignore
================================================
## Xcode projects
*.xcodeproj
*.xcworkspace
## Build generated
build/
DerivedData/
## Various settings
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata/
## Other
.DS_Store
*.moved-aside
*.xccheckout
*.xcscmblueprint
## Obj-C/Swift specific
*.hmap
*.ipa
*.dSYM.zip
*.dSYM
# Swift Package Manager
#
# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
Packages/
Package.pins
Package.resolved
.build/
================================================
FILE: CODE_OF_CONDUCT.md
================================================
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at mobile-open-source@uber.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/
================================================
FILE: CONTRIBUTING.md
================================================
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at mobile-open-source@uber.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/
================================================
FILE: LICENSE.txt
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
================================================
FILE: NOTICE.txt
================================================
SwiftMockGen depends on the following libraries:
Swift Package Manager (https://github.com/apple/swift-package-manager)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
================================================
FILE: Package.swift
================================================
// swift-tools-version:5.2
import PackageDescription
let package = Package(
name: "SwiftCodeSan",
platforms: [
.macOS(.v10_15),
],
products: [
.executable(name: "SwiftCodeSan", targets: ["SwiftCodeSan"]),
.library(name: "SwiftCodeSanKit", targets: ["SwiftCodeSanKit"]),
],
dependencies: [
.package(url: "https://github.com/apple/swift-argument-parser", .upToNextMinor(from: "1.0.2")),
.package(name: "SwiftSyntax", url: "https://github.com/apple/swift-syntax.git", .branch("swift-5.6-RELEASE"))
],
targets: [
.target(
name: "SwiftCodeSan",
dependencies: [
"SwiftCodeSanKit",
.product(name: "ArgumentParser", package: "swift-argument-parser"),
]),
.target(
name: "SwiftCodeSanKit",
dependencies: [
.product(name: "SwiftSyntax", package: "SwiftSyntax"),
.product(name: "SwiftSyntaxParser", package: "SwiftSyntax"),
]
),
.testTarget(
name: "SwiftCodeSanTests",
dependencies: [
"SwiftCodeSanKit",
],
path: "Tests"
)
]
)
================================================
FILE: README.md
================================================
# 
[](https://bestpractices.coreinfrastructure.org/projects/2964)
[](https://github.com/uber/SwiftCodeSan/actions)
[](https://opensource.org/licenses/Apache-2.0)
[](https://app.fossa.com/projects/git%2Bgithub.com%2Fuber%2FSwiftCodeSan?ref=badge_shield)
# Welcome to SwiftCodeSan
**SwiftCodeSan** is a tool that "sanitizes" code written in Swift. It has support for removing dead code (unreferenced decls) and unused imports, and narrowing access levels (public to internal), which will not only help clean up the codebase but also reduce the build time and the binary size.
It uses `SwiftSyntax` for parsing and uses concurrency for faster performance. Unlike other tools, `SwiftCodeSan` does not involve compiling; it handles reference checks directly. This eliminates the need to compile the entire project before running an analysis, which can take a long time for codebases like Uber's (~3M LoC).
Main objectives of `SwiftCodeSan` are accuracy, performance, and ease of use. It's a lightweight commandline tool, which uses the `SwiftCodeSanKit` framework underneath. It can be used as a standalone tool or integrated into other tools such as a linter. Try `SwiftCodeSan` and clean up your codebase, and see an improvement in the code quality and the build time.
## Motivation
Main objectives of `SwiftCodeSan` are accuracy, performance, flexibility, and ease of use. There aren't many 3rd party tools that perform fast on a large codebase containing, for example, over 3M LoC. They require building the entire projects on Xcode (for index stores), and take several hours to run analyses. The results contain false postives and negatives. They don't provide support to modify files directly with the results, and lack features such as finding unused imports or redundant access levels.
`SwiftCodeSan` was built for scalability and performance so running analyses takes a few minutes instead of hours. Since it does not require compiling the codebase, it can also run on code being developed with any IDEs (not just Xcode). It's a lightweight commandline tool, and uses a minimal set of frameworks necessary (see the Used Libraries below) to keep the code lean and efficient. It provides an input option to directly modify files with results, and features other than removing dead code, such as updating access levels and removing unused import statments.
## Disclaimer
This project may contain unstable APIs which may not be ready for general use. Support and/or new releases may be limited.
## System Requirements
* Swift 5.3 or later
* Xcode 12.0 or later
* MacOS 10.15.4 or later
* Support is included for the Swift Package Manager
## Build / Install
Option 1: Clone and build
```
$ git clone https://github.com/uber/SwiftCodeSan.git
$ cd SwiftCodeSan
$ swift build -c release
$ .build/release/SwiftCodeSan -h // see commandline input options below
```
Instead of calling the binary `SwiftCodeSan` built in `.build/release`, you can copy the executable into a directory that is part of your `PATH` environment variable and call `SwiftCodeSan`.
Or use Xcode, via following.
```
$ swift package generate-xcodeproj
```
## Run
`SwiftCodeSan` is a commandline executable. To run it, pass in a list of the source file directories or file paths of a build target, and the destination filepath for the mock output. To see other arguments to the commandline, run `SwiftCodeSan --help`.
```
./SwiftCodeSan --files-to-modules [file_to_module_list] --remove-deadcode --in-place
```
The `file_to_module_list` contains a map of source file paths to corresponding module names. Other input options are `--remove-unused-imports` and `--update-access-levels`. If `--in-place` is set, files will be modified directly.
Use --help to see the complete list of argument options.
## Add SwiftCodeSanKit to your project
Option 1: SPM
```swift
dependencies: [
.package(url: "https://github.com/uber/SwiftCodeSan.git", from: "0.0.1"),
],
targets: [
.target(name: "MyTarget", dependencies: ["SwiftCodeSanKit"]),
]
```
## Distribution
The `install-script.sh` will build and package up the `SwiftCodeSan` binary and other necessary resources in the same bundle.
```
$ ./install-script.sh -h // see input options
$ ./install-script.sh -s [source dir] -t SwiftCodeSan -d [destination dir] -o [output filename]
```
This will create a tarball for distribution, which contains the `SwiftCodeSan` executable along with a necessary SwiftSyntax parser dylib (lib_InternalSwiftSyntaxParser.dylib). This allows running `SwiftCodeSan` without depending on where the dylib lives.
## Used libraries
[SwiftSyntax](https://github.com/apple/swift-syntax) |
[SPM](https://github.com/swift-package-manager)
## How to contribute to SwiftCodeSan
See [CONTRIBUTING](CONTRIBUTING.md) for more info.
## Report any issues
If you run into any problems, please file a git issue. Please include:
* The OS version (e.g. macOS 10.15.6)
* The Swift version installed on your machine (from `swift --version`)
* The Xcode version
* The specific release version of this source code (you can use `git tag` to get a list of all the release versions or `git log` to get a specific commit sha)
* Any local changes on your machine
## License
SwiftCodeSan is licensed under Apache License 2.0. See [LICENSE](LICENSE.txt) for more information.
Copyright (C) 2017 Uber Technologies
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
================================================
FILE: Sources/SwiftCodeSan/Executor.swift
================================================
//
// Copyright (c) 2018. Uber Technologies
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
import ArgumentParser
import SwiftCodeSanKit
struct Executor: ParsableCommand {
static var configuration = CommandConfiguration(commandName: "SwiftCodeSan", abstract: "SwiftCodeSan: Code Sanitizer for Swift.")
private enum Operation: EnumerableFlag {
case removeDeadcode
case removeUnusedImports
case updateAccessLevels
static func help(for value: Executor.Operation) -> ArgumentHelp? {
switch value {
case .removeDeadcode:
return "If set, it will remove dead code and generate a report in the logfile. If an --in-place option is set, files will be modified directly."
case .removeUnusedImports:
return "If set, it will remove unused import statements and generate a report in the logfile. If an --in-place option is set, files will be modified directly."
case .updateAccessLevels:
return "If set, it will remove unnecessary public or open access levels from decls and generate a report in the logfile. If an --in-place option is set, files will be modified directly."
}
}
}
// MARK: - Private
@Option(name: [.long, .customShort("v")],
help: "The logging level to use. Default is set to 0 (info only). Set 1 for verbose, 2 for warning, and 3 for error.")
private var loggingLevel: Int = 0
@Option(name: .customLong("logfile"),
help: "Log file path containing the analysis results. If no value is given, it will be saved to a tmp file.",
completion: .file())
private var logFilePath: String?
@Option(name: [.customLong("files-to-modules"), .short],
parsing: .upToNextOption,
help: "File paths each containing a map of source files and corresponding module names.",
completion: .file())
private var fileLists: [String] = []
@Option(name: .customLong("syslib-list"),
parsing: .upToNextOption,
help: "File paths each containing a list of (weak) system frameworks.",
completion: .file())
private var syslibLists: [String] = []
@Option(name: .customLong("test-list"),
parsing: .upToNextOption,
help: "File paths each containing a list of test files.",
completion: .file())
private var testFileLists: [String] = []
@Option(name: [.long, .short],
help: "The root path. If given, it will be prepended to the source file paths.",
completion: .file())
private var root: String?
@Option(name: [.long, .customShort("j")],
help: "Maximum number of threads to execute concurrently (default = number of cores on the running machine)")
private var concurrencyLimit: Int?
@Option(name: [.long, .short],
parsing: .upToNextOption,
help: "List of declarations to whitelist (separated by a comma or a space)",
completion: .file())
private var whitelistDecls: [String] = []
@Option(name: .long,
parsing: .upToNextOption,
help: "List of declarations with given prefixes to whitelist (separated by a comma or a space)",
completion: .file())
private var whitelistDeclsPrefix: [String] = []
@Option(name: .long,
parsing: .upToNextOption,
help: "List of declarations with given suffixes to whitelist (separated by a comma or a space).",
completion: .file())
private var whitelistDeclsSuffix: [String] = []
@Option(name: .long,
parsing: .upToNextOption,
help: "List of declarations with given parent types to whitelist (separated by a comma or a space).",
completion: .file())
private var whitelistParents: [String] = []
@Option(name: .long,
parsing: .upToNextOption,
help: "List of declarations in the given modules to whitelist (separated by a comma or a space).",
completion: .file())
private var whitelistModules: [String] = []
@Option(name: .long,
parsing: .upToNextOption,
help: "List of declarations in the modules with given prefixes to whitelist (separated by a comma or a space).",
completion: .file())
private var whitelistModulesPrefix: [String] = []
@Option(name: .long,
parsing: .upToNextOption,
help: "List of declarations in the modules with given suffixes to whitelist (separated by a comma or a space).",
completion: .file())
private var whitelistModulesSuffix: [String] = []
@Option(name: .long,
parsing: .upToNextOption,
help: "List of member declarations with given names to whitelist (separated by a comma or a space).",
completion: .file())
private var whitelistMembers: [String] = []
@Option(name: [.long, .short],
help: "If set, files modified within the set number of days (leading up to today) will be whitelisted, i.e. all declarations in such files will be whitelisted.")
private var thresholdDays: Int?
@Flag private var operation: Operation
@Option(name: .customLong("remove-annotation"),
help: "If set, it will remove the annotation passed in from decls and generate a report in the logfile. If an --in-place option is set, files will be modified directly. ")
private var deleteAnnotation: String?
@Flag(name: [.customLong("in-place"), .short],
help: "If set, given source files will be modified with results.")
private var inplace: Bool = false
@Flag(name: .customLong("in-place-tests"),
help: "If set, given test files will be modified with results.")
private var inplaceTests: Bool = false
@Flag(name: .long,
help: "If set, only top level decls will be parsed/used for analysis.")
private var topDeclsOnly: Bool = false
private func fullPath(_ path: String) -> String {
if path.hasPrefix("/") {
return path
}
if path.hasPrefix("~") {
let home = FileManager.default.homeDirectoryForCurrentUser.path
return path.replacingOccurrences(of: "~", with: home, range: path.range(of: "~"))
}
return FileManager.default.currentDirectoryPath + "/" + path
}
mutating func run() throws {
minLogLevel = loggingLevel
var filesToModules = [String: String]()
fileLists.forEach { arg in
let line = arg.components(separatedBy: ":")
if let key = line.first, let val = line.last {
filesToModules[key] = val
}
}
let whitelist = Whitelist(thresholdDays: thresholdDays,
decls: whitelistDecls,
declsPrefix: whitelistDeclsPrefix,
declsSuffix: whitelistDeclsSuffix,
modules: [whitelistModules, syslibLists].compactMap{$0}.flatMap{$0},
modulesPrefix: whitelistModulesPrefix,
modulesSuffix: whitelistModulesSuffix,
inheritedTypes: whitelistParents,
members: whitelistMembers)
execute(with: filesToModules,
nil,
root,
logFilePath,
inplace,
inplaceTests,
topDeclsOnly,
concurrencyLimit,
whitelist,
operation,
deleteAnnotation)
}
private func execute(with filesToModules: [String: String],
_ testfiles: [String]?,
_ root: String?,
_ logfile: String?,
_ inplace: Bool,
_ inplaceTests: Bool,
_ topDeclsOnly: Bool,
_ jobs: Int?,
_ whitelist: Whitelist?,
_ operation: Operation,
_ deleteAnnotation: String?) {
switch operation {
case .removeUnusedImports:
removeUnusedImports(fileToModuleMap: filesToModules,
whitelist: whitelist,
topDeclsOnly: topDeclsOnly,
inplace: inplace,
logFilePath: logfile,
concurrencyLimit: jobs)
case .removeDeadcode:
removeDeadDecls(filesToModules: filesToModules,
whitelist: whitelist,
topDeclsOnly: topDeclsOnly,
inplace: inplace,
testFiles: testfiles,
inplaceTests: inplaceTests,
logFilePath: logfile,
concurrencyLimit: jobs,
onCompletion: {})
case .updateAccessLevels:
updateAccessLevels(filesToModules: filesToModules,
whitelist: whitelist,
inplace: inplace,
concurrencyLimit: jobs,
onCompletion: {})
}
}
}
================================================
FILE: Sources/SwiftCodeSan/main.swift
================================================
//
// Copyright (c) 2018. Uber Technologies
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import ArgumentParser
import Foundation
func main() {
let inputs = Array(CommandLine.arguments.dropFirst())
print("Start...")
Executor.main(inputs)
print("Done.")
}
main()
================================================
FILE: Sources/SwiftCodeSanKit/Core/AccessLevelRewriter.swift
================================================
//
// Copyright (c) 2018. Uber Technologies
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
import SwiftSyntax
/**
Updates access levels in the source code
*/
public final class AccessLevelRewriter: SyntaxRewriter {
var decls: [DeclMetadata]
let path: String
let module: String
public init(_ path: String, module: String?, decls: [DeclMetadata]) {
self.path = path
self.module = module ?? ""
self.decls = decls
}
private func updateModifiers(_ name: String, fullName: String, description: String, declType: DeclType, modifiers: ModifierListSyntax?) -> (ModifierListSyntax, Bool)? {
let contains = decls.contains(where: { (d: DeclMetadata) -> Bool in
return d.name == name && d.fullName == fullName && d.declDescription == description && d.declType == declType
})
if contains {
var isModified = false
var list = [DeclModifierSyntax]()
if let modifiers = modifiers {
for modifier in modifiers {
if modifier.name.text == String.public || modifier.name.text == String.open {
let updatedAcl = modifier.name.withKind(.stringLiteral("")).withoutTrailingTrivia()
let updatedModifier = SyntaxFactory.makeDeclModifier(name: updatedAcl, detailLeftParen: modifier.detailLeftParen, detail: modifier.detail, detailRightParen: modifier.detailRightParen)
isModified = true
list.append(updatedModifier)
} else {
if isModified, modifier.name.text == String.internal, modifier.detail?.text == "set" {
let updatedAcl = modifier.name.withKind(.stringLiteral("")).withoutTrailingTrivia()
let updatedModifier = SyntaxFactory.makeDeclModifier(name: updatedAcl, detailLeftParen: nil, detail: nil, detailRightParen: nil)
list.append(updatedModifier)
} else {
list.append(modifier)
}
}
}
}
return (SyntaxFactory.makeModifierList(list), isModified)
}
return nil
}
override public func visit(_ node: ExtensionDeclSyntax) -> DeclSyntax {
var mutableNode = node
if let (updatedModifier, isModified) = updateModifiers(node.name, fullName: node.fullName, description: node.description, declType: node.declType, modifiers: node.modifiers) {
if isModified {
mutableNode.modifiers = updatedModifier
}
return DeclSyntax(mutableNode)
}
return super.visit(node)
}
override public func visit(_ node: EnumDeclSyntax) -> DeclSyntax {
var mutableNode = node
if let (updatedModifier, isModified) = updateModifiers(node.name, fullName: node.fullName, description: node.description, declType: node.declType, modifiers: node.modifiers) {
if isModified {
mutableNode.modifiers = updatedModifier
}
return DeclSyntax(mutableNode)
}
return super.visit(node)
}
override public func visit(_ node: StructDeclSyntax) -> DeclSyntax {
var mutableNode = node
if let (updatedModifier, isModified) = updateModifiers(node.name, fullName: node.fullName, description: node.description, declType: node.declType, modifiers: node.modifiers) {
if isModified {
mutableNode.modifiers = updatedModifier
}
return DeclSyntax(mutableNode)
}
return super.visit(node)
}
override public func visit(_ node: ProtocolDeclSyntax) -> DeclSyntax {
var mutableNode = node
if let (updatedModifier, isModified) = updateModifiers(node.name, fullName: node.fullName, description: node.description, declType: node.declType, modifiers: node.modifiers) {
if isModified {
mutableNode.modifiers = updatedModifier
}
return DeclSyntax(mutableNode)
}
return super.visit(node)
}
override public func visit(_ node: ClassDeclSyntax) -> DeclSyntax {
var mutableNode = node
if let (updatedModifier, isModified) = updateModifiers(node.name, fullName: node.fullName, description: node.description, declType: node.declType, modifiers: node.modifiers) {
if isModified {
mutableNode.modifiers = updatedModifier
}
return DeclSyntax(mutableNode)
}
return super.visit(node)
}
override public func visit(_ node: FunctionDeclSyntax) -> DeclSyntax {
var mutableNode = node
if let (updatedModifier, isModified) = updateModifiers(node.name, fullName: node.fullName, description: node.description, declType: node.declType, modifiers: node.modifiers) {
if isModified {
mutableNode.modifiers = updatedModifier
}
return DeclSyntax(mutableNode)
}
return super.visit(node)
}
override public func visit(_ node: SubscriptDeclSyntax) -> DeclSyntax {
var mutableNode = node
if let (updatedModifier, isModified) = updateModifiers(node.name, fullName: node.fullName, description: node.description, declType: node.declType, modifiers: node.modifiers) {
if isModified {
mutableNode.modifiers = updatedModifier
}
return DeclSyntax(mutableNode)
}
return super.visit(node)
}
override public func visit(_ node: InitializerDeclSyntax) -> DeclSyntax {
var mutableNode = node
if let (updatedModifier, isModified) = updateModifiers(node.name, fullName: node.fullName, description: node.description, declType: node.declType, modifiers: node.modifiers) {
if isModified {
mutableNode.modifiers = updatedModifier
}
return DeclSyntax(mutableNode)
}
return super.visit(node)
}
override public func visit(_ node: VariableDeclSyntax) -> DeclSyntax {
var mutableNode = node
if let (updatedModifier, isModified) = updateModifiers(node.name, fullName: node.fullName, description: node.description, declType: node.declType, modifiers: node.modifiers) {
if isModified {
mutableNode.modifiers = updatedModifier
}
return DeclSyntax(mutableNode)
}
return super.visit(node)
}
override public func visit(_ node: TypealiasDeclSyntax) -> DeclSyntax {
var mutableNode = node
if let (updatedModifier, isModified) = updateModifiers(node.name, fullName: node.fullName, description: node.description, declType: node.declType, modifiers: node.modifiers) {
if isModified {
mutableNode.modifiers = updatedModifier
}
return DeclSyntax(mutableNode)
}
return super.visit(node)
}
}
================================================
FILE: Sources/SwiftCodeSanKit/Core/DeclMetaTypes.swift
================================================
//
// Copyright (c) 2018. Uber Technologies
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
import SwiftSyntax
/**
Decl metadata needed for decls in source code being parsed
*/
public typealias DeclMap = [String: [DeclMetadata]]
public enum DeclType {
case protocolType, classType, extensionType, structType, enumType
case typealiasType, patType
case varType, subscriptType, funcType, initType, operatorType, enumCaseType
case other
}
extension DeclType {
var isEncloserType: Bool {
if self == .protocolType ||
self == .classType ||
self == .extensionType ||
self == .structType ||
self == .enumType {
return true
}
return false
}
}
public final class DeclMetadata: Hashable {
let name: String
var type: String
let fullName: String
let declType: DeclType
var inheritedTypes: [String]
let boundTypes: [String]
let boundTypesAL: [String]
var members: [DeclMetadata] = []
let path: String
let module: String
let imports: [String]
var encloser: String
var declDescription: String
var annotated: Bool = false
var isOverride: Bool
var isExtensionMember: Bool = false
var isPublicOrOpen: Bool
var shouldExpose: Bool = false
var visited: Bool = false
var used: Bool = false
public func hash(into hasher: inout Hasher) {
hasher.combine(fullName)
hasher.combine(declType)
hasher.combine(encloser)
hasher.combine(path)
hasher.combine(module)
}
public static func == (lhs: DeclMetadata, rhs: DeclMetadata) -> Bool {
if lhs.name == rhs.name,
lhs.type == rhs.type,
lhs.fullName == rhs.fullName,
lhs.declType == rhs.declType,
lhs.encloser == rhs.encloser,
lhs.path == rhs.path,
lhs.module == rhs.module {
return true
}
return false
}
public init(path: String,
module: String,
imports: [String],
encloser: String,
name: String,
type: String,
fullName: String,
description: String,
declType: DeclType,
inheritedTypes: [String],
boundTypes: [String],
boundTypesAL: [String],
isPublicOrOpen: Bool,
isOverride: Bool,
annotated: Bool = false,
used: Bool) {
self.path = path
self.module = module
self.imports = imports
self.encloser = encloser
self.name = name
self.type = type
self.fullName = fullName
self.declDescription = description
self.declType = declType
self.inheritedTypes = inheritedTypes
self.boundTypes = boundTypes
self.boundTypesAL = boundTypesAL
self.annotated = annotated
self.isPublicOrOpen = isPublicOrOpen
self.isOverride = isOverride
}
}
struct AnnotationMetadata {
var module: String?
var typeAliases: [String: String]?
var varTypes: [String: String]?
}
public struct Whitelist {
public let thresholdDays: Int?
public let decls: [String]?
public let declsPrefix: [String]?
public let declsSuffix: [String]?
public let modules: [String]?
public let modulesPrefix: [String]?
public let modulesSuffix: [String]?
public let inheritedTypes: [String]?
public let members: [String]?
public init(thresholdDays: Int?,
decls: [String]?,
declsPrefix: [String]?,
declsSuffix: [String]?,
modules: [String]?,
modulesPrefix: [String]?,
modulesSuffix: [String]?,
inheritedTypes: [String]?,
members: [String]?) {
self.thresholdDays = thresholdDays
self.decls = decls
self.declsPrefix = declsPrefix
self.declsSuffix = declsSuffix
self.modules = modules
self.modulesPrefix = modulesPrefix
self.modulesSuffix = modulesSuffix
self.inheritedTypes = inheritedTypes
self.members = members
}
func declWhitelisted(name: String, isMember: Bool, module: String?, parents: [String]?, path: String?) -> Bool {
if let module = module {
if let list = modules, list.contains(module) {
return true
}
if let list = modulesPrefix {
let moduleHasPrefix = !list.filter{module.hasPrefix($0)}.isEmpty
if moduleHasPrefix { return true }
}
if let list = modulesSuffix {
let moduleHasSuffix = !list.filter{module.hasSuffix($0)}.isEmpty
if moduleHasSuffix { return true }
}
}
if let parents = parents, let list = inheritedTypes {
let inParentsList = !list.filter{ parents.contains($0) }.isEmpty
if inParentsList { return true }
}
if isMember {
if let list = members, list.contains(name) { return true }
} else {
if let list = decls, list.contains(name) { return true }
if let list = declsPrefix {
let declHasPrefix = !list.filter { name.hasPrefix($0) }.isEmpty
if declHasPrefix { return true }
}
if let list = declsSuffix {
let declHasSuffix = !list.filter { name.hasSuffix($0) }.isEmpty
if declHasSuffix { return true }
}
}
return false
}
}
public func flatten(declMap: DeclMap) -> DeclMap {
var flatDeclMap = DeclMap()
for (k, vals) in declMap {
for v in vals {
if flatDeclMap[k] == nil {
flatDeclMap[k] = []
}
if flatDeclMap[k]?.contains(v) ?? false {
} else {
flatDeclMap[k]?.append(v)
}
for m in v.members {
if flatDeclMap[m.name] == nil {
flatDeclMap[m.name] = []
}
flatDeclMap[m.name]?.append(m)
}
}
}
return flatDeclMap
}
================================================
FILE: Sources/SwiftCodeSanKit/Core/DeclRemover.swift
================================================
//
// Copyright (c) 2018. Uber Technologies
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
import SwiftSyntax
/**
Removes unused decls
*/
public final class DeclRemover: SyntaxRewriter {
let path: String
let decls: [DeclMetadata]
public init(_ path: String, decls: [DeclMetadata]) {
self.path = path
self.decls = decls
}
override public func visit(_ node: ExtensionDeclSyntax) -> DeclSyntax {
if shouldRemove(node.name, fullName: node.fullName, description: node.description, declType: node.declType) {
return DeclSyntax(SyntaxFactory.makeBlankExtensionDecl())
}
return super.visit(node)
}
override public func visit(_ node: EnumDeclSyntax) -> DeclSyntax {
if shouldRemove(node.name, fullName: node.fullName, description: node.description, declType: node.declType) {
return DeclSyntax(SyntaxFactory.makeBlankEnumDecl())
}
return super.visit(node)
}
override public func visit(_ node: StructDeclSyntax) -> DeclSyntax {
if shouldRemove(node.name, fullName: node.fullName, description: node.description, declType: node.declType) {
return DeclSyntax(SyntaxFactory.makeBlankStructDecl())
}
return super.visit(node)
}
override public func visit(_ node: ProtocolDeclSyntax) -> DeclSyntax {
if shouldRemove(node.name, fullName: node.fullName, description: node.description, declType: node.declType) {
return DeclSyntax(SyntaxFactory.makeBlankProtocolDecl())
}
return super.visit(node)
}
override public func visit(_ node: ClassDeclSyntax) -> DeclSyntax {
if shouldRemove(node.name, fullName: node.fullName, description: node.description, declType: node.declType) {
return DeclSyntax(SyntaxFactory.makeBlankClassDecl())
}
return super.visit(node)
}
override public func visit(_ node: FunctionDeclSyntax) -> DeclSyntax {
if shouldRemove(node.name, fullName: node.fullName, description: node.description, declType: node.declType) {
return DeclSyntax(SyntaxFactory.makeBlankFunctionDecl())
}
return super.visit(node)
}
override public func visit(_ node: SubscriptDeclSyntax) -> DeclSyntax {
if shouldRemove(node.name, fullName: node.fullName, description: node.description, declType: node.declType) {
return DeclSyntax(SyntaxFactory.makeBlankSubscriptDecl())
}
return super.visit(node)
}
override public func visit(_ node: InitializerDeclSyntax) -> DeclSyntax {
if shouldRemove(node.name, fullName: node.fullName, description: node.description, declType: node.declType) {
return DeclSyntax(SyntaxFactory.makeBlankInitializerDecl())
}
return super.visit(node)
}
override public func visit(_ node: VariableDeclSyntax) -> DeclSyntax {
if shouldRemove(node.name, fullName: node.fullName, description: node.description, declType: node.declType) {
return DeclSyntax(SyntaxFactory.makeBlankVariableDecl())
}
return super.visit(node)
}
override public func visit(_ node: TypealiasDeclSyntax) -> DeclSyntax {
if shouldRemove(node.name, fullName: node.fullName, description: node.description, declType: node.declType) {
return DeclSyntax(SyntaxFactory.makeBlankTypealiasDecl())
}
return super.visit(node)
}
override public func visit(_ node: AssociatedtypeDeclSyntax) -> DeclSyntax {
if shouldRemove(node.name, fullName: node.fullName, description: node.description, declType: node.declType) {
return DeclSyntax(SyntaxFactory.makeBlankAssociatedtypeDecl())
}
return super.visit(node)
}
override public func visit(_ node: EnumCaseDeclSyntax) -> DeclSyntax {
if shouldRemove(node.name, fullName: node.fullName, description: node.description, declType: node.declType) {
return DeclSyntax(SyntaxFactory.makeBlankEnumCaseDecl())
}
return super.visit(node)
}
private func shouldRemove(_ name: String, fullName: String, description: String, declType: DeclType) -> Bool {
let inList = decls.contains(where: { (d: DeclMetadata) -> Bool in
return d.name == name && d.fullName == fullName && d.declDescription == description && d.declType == declType
})
return inList
}
}
================================================
FILE: Sources/SwiftCodeSanKit/Core/DeclVisitor.swift
================================================
//
// Copyright (c) 2018. Uber Technologies
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
import SwiftSyntax
/**
Visit decls in source code being parsed
*/
final class DeclVisitor: SyntaxVisitor {
var declMap = DeclMap()
let path: String
let module: String
let topDeclsOnly: Bool
let whitelistPath: Bool
let whitelist: Whitelist?
var importedModules = [String]()
init(_ path: String,
module: String?,
topDeclsOnly: Bool,
whitelistPath: Bool,
whitelist: Whitelist?) {
self.whitelist = whitelist
self.whitelistPath = whitelistPath
self.path = path
self.module = module ?? ""
self.topDeclsOnly = topDeclsOnly
}
override func visit(_ node: ProtocolDeclSyntax) -> SyntaxVisitorContinueKind {
updateDecl(node, description: node.description, members: topDeclsOnly ? nil : node.members.members)
return .skipChildren
}
override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind {
if node.attributesDescription.contains(String.propertyWrapper) {
return .skipChildren
}
updateDecl(node, description: node.description, members: topDeclsOnly ? nil : node.members.members)
return .visitChildren
}
override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind {
if node.attributesDescription.contains(String.propertyWrapper) {
return .skipChildren
}
updateDecl(node, description: node.description, members: topDeclsOnly ? nil : node.members.members)
return .skipChildren
}
override func visit(_ node: EnumDeclSyntax) -> SyntaxVisitorContinueKind {
updateDecl(node, description: node.description, members: topDeclsOnly ? nil : node.members.members)
return .skipChildren
}
override func visit(_ node: ExtensionDeclSyntax) -> SyntaxVisitorContinueKind {
updateDecl(node, description: node.description, members: topDeclsOnly ? nil : node.members.members)
return .skipChildren
}
override func visit(_ node: ImportDeclSyntax) -> SyntaxVisitorContinueKind {
importedModules.append(node.path.description.trimmed)
return .visitChildren
}
override func visit(_ node: CodeBlockItemSyntax) -> SyntaxVisitorContinueKind {
if let item = node.item.as(FunctionDeclSyntax.self) {
updateDecl(item, description: item.description, members: nil)
return .skipChildren
} else if let _ = node.item.as(OperatorDeclSyntax.self) {
return .skipChildren
} else if let item = node.item.as(VariableDeclSyntax.self) {
updateDecl(item, description: item.description, members: nil)
return .skipChildren
} else if let item = node.item.as(TypealiasDeclSyntax.self) {
updateDecl(item, description: item.description, members: nil)
return .skipChildren
}
return .visitChildren
}
private func memberDecls(_ decl: DeclSyntax, encloser: String, encloserDeclType: DeclType, encloserWhitelisted: Bool) -> [DeclMetadata] {
let mdecls = decl.declMetadatas(path: path, module: module, encloser: encloser, description: decl.description, imports: importedModules)
for mdecl in mdecls {
if encloserDeclType == .extensionType {
mdecl.isExtensionMember = true
}
let memberWhitelisted = whitelist?.declWhitelisted(name: mdecl.name, isMember: true, module: nil, parents: nil, path: mdecl.path) ?? false
if encloserWhitelisted ||
memberWhitelisted ||
mdecl.declType == .initType ||
mdecl.declType == .subscriptType ||
mdecl.declType == .operatorType {
if mdecl.isPublicOrOpen {
mdecl.shouldExpose = true
}
mdecl.used = true
}
}
return mdecls
}
private func updateDecl(_ item: DeclProtocol, description: String, members: MemberDeclListSyntax?) {
let decls = item.declMetadatas(path: path, module: module, encloser: "", description: description, imports: importedModules)
for decl in decls {
var shouldWhitelist = (decl.declType == .operatorType)
if !shouldWhitelist, !decl.name.isEmpty {
if let whitelist = whitelist, whitelist.declWhitelisted(name: decl.name, isMember: false, module: module, parents: decl.inheritedTypes, path: decl.path) {
// whitelisted so don't add to declMap
shouldWhitelist = true
}
}
if shouldWhitelist {
if decl.isPublicOrOpen {
decl.shouldExpose = true
}
decl.used = true
}
if let members = members {
var list = [DeclMetadata]()
for m in members {
if let ifconfig = m.decl.as(IfConfigDeclSyntax.self) {
for clause in ifconfig.clauses {
if let clauseMembers = clause.elements.as(MemberDeclListSyntax.self) {
for el in clauseMembers {
let mdecls = memberDecls(el.decl, encloser: decl.name, encloserDeclType: decl.declType, encloserWhitelisted: shouldWhitelist)
list.append(contentsOf: mdecls)
}
}
}
} else {
let mdecls = memberDecls(m.decl, encloser: decl.name, encloserDeclType: decl.declType, encloserWhitelisted: shouldWhitelist)
list.append(contentsOf: mdecls)
}
}
decl.members = list
}
if !decl.name.isEmpty, declMap[decl.name] == nil {
declMap[decl.name] = []
}
declMap[decl.name]?.append(decl)
}
}
}
================================================
FILE: Sources/SwiftCodeSanKit/Core/ImportRewriter.swift
================================================
//
// Copyright (c) 2018. Uber Technologies
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
import SwiftSyntax
/**
Updates import statements in source code
*/
public final class ImportRewriter: SyntaxRewriter {
let unused: [String]
public init(_ path: String, unusedModules: [String]?) {
self.unused = unusedModules ?? []
}
override public func visit(_ node: ImportDeclSyntax) -> DeclSyntax {
var remove = false
let str = node.path.description.trimmed
if unused.contains(str) {
remove = true
} else {
for t in node.path.tokens {
if unused.contains(t.text) {
remove = true
}
}
}
if remove {
if let trivia = node.importTok.leadingTrivia {
let t = SyntaxFactory.makeUnknown("", leadingTrivia: trivia, trailingTrivia: Trivia(pieces: []))
let ret = SyntaxFactory.makeImportDecl(attributes: nil, modifiers: nil, importTok: t, importKind: nil, path: SyntaxFactory.makeAccessPath([]))
return DeclSyntax(ret)
} else {
let ret = SyntaxFactory.makeBlankImportDecl()
return DeclSyntax(ret)
}
}
return super.visit(node)
}
}
================================================
FILE: Sources/SwiftCodeSanKit/Core/RefChecker.swift
================================================
//
// Copyright (c) 2018. Uber Technologies
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
import SwiftSyntax
/**
Checks for referenced decls
*/
final class RefChecker: SyntaxVisitor {
var imports = [String]()
private var declMap = DeclMap()
private var path: String
private var module: String
private var reflist = [String]()
var refs: Set<String> {
return Set(reflist)
}
init(_ path: String, module: String, declMap: DeclMap) {
self.path = path
self.module = module
self.declMap = declMap
}
override func visit(_ node: CodeBlockItemSyntax) -> SyntaxVisitorContinueKind {
if node.item.is(ExprSyntax.self) || node.item.is(StmtSyntax.self) {
reflist.append(contentsOf: node.item.refTypes(with: declMap))
return .skipChildren
}
return .visitChildren
}
override func visit(_ node: VariableDeclSyntax) -> SyntaxVisitorContinueKind {
reflist.append(contentsOf: node.refTypes(with: declMap))
if node.isOverride {
reflist.append(node.name)
}
return .visitChildren
}
override func visit(_ node: FunctionDeclSyntax) -> SyntaxVisitorContinueKind {
reflist.append(contentsOf: node.refTypes(with: declMap))
if node.isOverride {
reflist.append(node.name)
}
return .visitChildren
}
override func visit(_ node: SubscriptDeclSyntax) -> SyntaxVisitorContinueKind {
reflist.append(contentsOf: node.refTypes(with: declMap))
return .visitChildren
}
override func visit(_ node: InitializerDeclSyntax) -> SyntaxVisitorContinueKind {
reflist.append(contentsOf: node.refTypes(with: declMap))
if node.isOverride {
reflist.append(node.name)
}
return .visitChildren
}
override func visit(_ node: EnumCaseDeclSyntax) -> SyntaxVisitorContinueKind {
reflist.append(contentsOf: node.refTypes(with: declMap))
return .visitChildren
}
override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind {
reflist.append(contentsOf: node.refTypes(with: declMap))
return .visitChildren
}
override func visit(_ node: ProtocolDeclSyntax) -> SyntaxVisitorContinueKind {
reflist.append(contentsOf: node.refTypes(with: declMap))
return .visitChildren
}
override func visit(_ node: ExtensionDeclSyntax) -> SyntaxVisitorContinueKind {
reflist.append(contentsOf: node.refTypes(with: declMap))
reflist.append(node.name)
return .visitChildren
}
override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind {
reflist.append(contentsOf: node.refTypes(with: declMap))
return .visitChildren
}
override func visit(_ node: EnumDeclSyntax) -> SyntaxVisitorContinueKind {
reflist.append(contentsOf: node.refTypes(with: declMap))
return .visitChildren
}
override func visit(_ node: TypealiasDeclSyntax) -> SyntaxVisitorContinueKind {
reflist.append(contentsOf: node.refTypes(with: declMap))
return .visitChildren
}
override func visit(_ node: AssociatedtypeDeclSyntax) -> SyntaxVisitorContinueKind {
reflist.append(contentsOf: node.refTypes(with: declMap))
return .visitChildren
}
override func visit(_ node: ImportDeclSyntax) -> SyntaxVisitorContinueKind {
if node.attributes == nil, node.importKind == nil {
let str = node.path.description.trimmed
imports.append(str)
}
return .skipChildren
}
}
================================================
FILE: Sources/SwiftCodeSanKit/FileParsers/DeclParser.swift
================================================
//
// Copyright (c) 2018. Uber Technologies
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
import SwiftSyntax
import SwiftSyntaxParser
public class DeclParser {
public init() {}
func scanAndMapDecls(fileToModuleMap: [String: String],
topDeclsOnly: Bool,
whitelist: Whitelist?) -> DeclMap {
var allDeclMap = DeclMap()
scanDecls(fileToModuleMap: fileToModuleMap, topDeclsOnly: topDeclsOnly, whitelist: whitelist) { (filepath, subResults) in
for (k, decls) in subResults {
if allDeclMap[k] == nil {
allDeclMap[k] = []
}
for decl in decls {
if allDeclMap[k]?.contains(decl) ?? false {
// Already added, so do nothing
} else {
allDeclMap[k]?.append(decl)
}
}
}
}
return allDeclMap
}
func scanAndMapDecls(fileToModuleMap: [String: String],
topDeclsOnly: Bool) -> DeclMap {
return scanAndMapDecls(fileToModuleMap: fileToModuleMap,
topDeclsOnly: topDeclsOnly,
whitelist: nil)
}
func scanDecls(fileToModuleMap: [String: String],
topDeclsOnly: Bool,
completion: @escaping (String, DeclMap) -> ()) {
scan(fileToModuleMap) { (path: String, module: String, lock: NSLock?) in
self.visitSrc(path: path,
module: module,
topDeclsOnly: topDeclsOnly,
whitelist: nil,
lock: lock,
completion: completion)
}
}
func scanDecls(fileToModuleMap: [String: String],
topDeclsOnly: Bool,
whitelist: Whitelist?,
completion: @escaping (String, DeclMap) -> ()) {
scan(fileToModuleMap) { (path: String, module: String, lock: NSLock?) in
self.visitSrc(path: path,
module: module,
topDeclsOnly: topDeclsOnly,
whitelist: whitelist,
lock: lock,
completion: completion)
}
}
var wpaths = 0
var npaths = 0
private func visitSrc(path: String,
module: String?,
topDeclsOnly: Bool,
whitelist: Whitelist?,
lock: NSLock?,
completion: @escaping (String, DeclMap) -> ()) {
do {
let node = try SyntaxParser.parse(path)
let whitelistPath = FileManager.modifiedWithin(whitelist?.thresholdDays, at: path)
if whitelistPath {
wpaths += 1
}
npaths += 1
let visitor = DeclVisitor(path,
module: module,
topDeclsOnly: topDeclsOnly,
whitelistPath: whitelistPath,
whitelist: whitelist)
visitor.walk(node)
lock?.lock()
defer {lock?.unlock()}
completion(path, visitor.declMap)
} catch {
fatalError(error.localizedDescription)
}
}
func checkRefs(fileToModuleMap: [String: String],
declMap: DeclMap,
completion: @escaping (String, Set<String>, [String]) -> ()) {
scan(fileToModuleMap) { (path: String, module: String, lock: NSLock?) in
self.referenceSrc(path: path, module: module, declMap: declMap, lock: lock, completion: completion)
}
}
private func referenceSrc(path: String,
module: String,
declMap: DeclMap,
lock: NSLock?,
completion: @escaping (String, Set<String>, [String]) -> ()) {
do {
let node = try SyntaxParser.parse(path)
let visitor = RefChecker(path, module: module, declMap: declMap)
visitor.walk(node)
lock?.lock()
completion(path, visitor.refs, visitor.imports)
lock?.unlock()
} catch {
fatalError(error.localizedDescription)
}
}
// MARK - input is dirs or filepaths
func scanDecls(paths: [String],
isDirs: Bool,
topDeclsOnly: Bool,
pathToModules: [String: String],
whitelist: Whitelist?,
completion: @escaping (String, DeclMap) -> ()) {
if isDirs {
scan(dirs: paths) { (path: String, lock: NSLock?) in
self.visitSrc(path: path,
module: pathToModules[path],
topDeclsOnly: topDeclsOnly,
whitelist: whitelist,
lock: lock,
completion: completion)
}
} else {
scan(paths) { (path: String, lock: NSLock?) in
self.visitSrc(path: path,
module: pathToModules[path],
topDeclsOnly: topDeclsOnly,
whitelist: whitelist,
lock: lock,
completion: completion)
}
}
}
func checkRefs(paths: [String],
isDirs: Bool,
pathToModules: [String: String],
declMap: DeclMap,
completion: @escaping (String, Set<String>, [String]) -> ()) {
if isDirs {
scan(dirs: paths) { (path: String, lock: NSLock?) in
self.referenceSrc(path: path, module: pathToModules[path] ?? "", declMap: declMap, lock: lock, completion: completion)
}
} else {
scan(paths) { (path: String, lock: NSLock?) in
self.referenceSrc(path: path, module: pathToModules[path] ?? "", declMap: declMap, lock: lock, completion: completion)
}
}
}
}
================================================
FILE: Sources/SwiftCodeSanKit/FileUpdaters/DeclUpdater.swift
================================================
//
// Copyright (c) 2018. Uber Technologies
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
import SwiftSyntax
import SwiftSyntaxParser
final class DeclUpdater {
func updateAccessLevels(filesToDecls: [String: [DeclMetadata]],
filesToModules: [String: String],
completion: @escaping (String, String) -> ()) {
scan(filesToDecls) { (path, decls, lock) in
do {
let node = try SyntaxParser.parse(path)
let rewriter = AccessLevelRewriter(path, module: filesToModules[path], decls: decls)
let ret = rewriter.visit(node)
lock?.lock()
completion(path, ret.description)
lock?.unlock()
} catch {
fatalError(error.localizedDescription)
}
}
}
func removeDeadDecls(filesToDecls: [String: [DeclMetadata]],
completion: @escaping (String, String) -> ()) {
scan(filesToDecls) { (path, decls, lock) in
do {
let node = try SyntaxParser.parse(path)
let remover = DeclRemover(path, decls: decls)
let ret = remover.visit(node)
lock?.lock()
completion(path, ret.description)
lock?.unlock()
} catch {
fatalError(error.localizedDescription)
}
}
}
func removeUnusedImports(paths: [String],
isDirs: Bool,
unusedImports: [String: [String]],
completion: @escaping (String, String) -> ()) {
if isDirs {
scan(dirs: paths) { (path, lock) in
self.updateSrcs(path: path, module: "", lock: lock, unusedImports: unusedImports, completion: completion)
}
} else {
scan(paths) { (path, lock) in
self.updateSrcs(path: path, module: "", lock: lock, unusedImports: unusedImports, completion: completion)
}
}
}
func removeUnusedImports(fileToModuleMap: [String: String],
unusedImports: [String: [String]],
completion: @escaping (String, String) -> ()) {
scan(fileToModuleMap) { (path, module, lock) in
self.updateSrcs(path: path, module: module, lock: lock, unusedImports: unusedImports, completion: completion)
}
}
private func updateSrcs(path: String,
module: String,
lock: NSLock?,
unusedImports: [String: [String]],
completion: @escaping (String, String) -> ()) {
do {
let node = try SyntaxParser.parse(path)
let remover = ImportRewriter(path, unusedModules: unusedImports[path])
let ret = remover.visit(node)
lock?.lock()
completion(path, ret.description)
lock?.unlock()
} catch {
fatalError(error.localizedDescription)
}
}
}
================================================
FILE: Sources/SwiftCodeSanKit/Operations/RemoveDeadDecls.swift
================================================
//
// Copyright (c) 2018. Uber Technologies
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
public func removeDeadDecls(filesToModules: [String: String],
whitelist: Whitelist?,
topDeclsOnly: Bool,
inplace: Bool,
testFiles: [String]?,
inplaceTests: Bool,
logFilePath: String? = nil,
concurrencyLimit: Int? = nil,
onCompletion: @escaping () -> ()) {
log("Start of removing dead code: topDeclsOnly", topDeclsOnly)
scanConcurrencyLimit = concurrencyLimit
let p = DeclParser()
var pathToDeclsUpdate = [String: [DeclMetadata]]()
log("Scan and map top-level decls...")
logTime()
let declMap = p.scanAndMapDecls(fileToModuleMap: filesToModules,
topDeclsOnly: false,
whitelist: whitelist)
logTime()
print("WWW: ", p.npaths, p.wpaths)
log("Check references, look up their source modules, and mark used...")
let flatDeclMap = flatten(declMap: declMap)
var nref = 0
p.checkRefs(fileToModuleMap: filesToModules, declMap: flatDeclMap) { (path, refs, imports) in
if let refModule = filesToModules[path] {
markUsed(refs, in: refModule, imports: imports, with: flatDeclMap, updateMembers: true)
}
log("#Checked refs", counter: &nref, interval: 1000, timed: true)
}
logTime()
repeat {
log("Look up interface members and mark used if any...")
shouldRetry = false
markInterfaceMembersUsed(declMap: declMap)
logTime()
} while shouldRetry
var i = flatDeclMap.values.flatMap{$0}.filter{$0.used}.count
log("Mark bound types used...")
markBoundTypesUsed(declMap: flatDeclMap)
resetVisited(declMap: declMap)
var j = flatDeclMap.values.flatMap{$0}.filter{$0.used}.count
logTime()
while i != j {
log("#Remaining decls to mark used", j-i, j, i)
log("Repeat: Look up interface members and mark used if any...")
markInterfaceMembersUsed(declMap: declMap)
i = flatDeclMap.values.flatMap{$0}.filter{$0.used}.count
logTime()
log("Repeat: Mark bound types used...")
markBoundTypesUsed(declMap: flatDeclMap)
resetVisited(declMap: flatDeclMap)
j = flatDeclMap.values.flatMap{$0}.filter{$0.used}.count
logTime()
}
log("Filter out used decls...")
for (_, decls) in flatDeclMap {
for decl in decls {
if !decl.used {
if pathToDeclsUpdate[decl.path] == nil {
pathToDeclsUpdate[decl.path] = []
}
pathToDeclsUpdate[decl.path]?.append(decl)
}
}
}
let totalUnused = pathToDeclsUpdate.values.flatMap{$0}.count
let totalUsed = flatDeclMap.values.flatMap{$0}.filter{$0.used}.count
logTime()
log("#Total top-level decls: ", declMap.values.flatMap{$0}.count,
"#Total decls", flatDeclMap.values.flatMap{$0}.count,
"#Decls Unused", totalUnused,
"#Decls Used", totalUsed,
"#Files to update", pathToDeclsUpdate.count)
if let logfile = logFilePath {
log("Save results to", logfile)
let ret = pathToDeclsUpdate.map { arg in
let vals = arg.value.map{ ObjectIdentifier($0).debugDescription + ", " + $0.fullName + ", " + $0.encloser }
let valStr = vals.joined(separator: "\n")
return "\(arg.key)\n--- \(valStr)\n"
}.joined(separator: "\n")
try? ret.write(toFile: logfile, atomically: true, encoding: .utf8)
logTime()
}
if inplace {
log("Remove unused decls from files...", pathToDeclsUpdate.count)
let updater = DeclUpdater()
updater.removeDeadDecls(filesToDecls: pathToDeclsUpdate) { (path, content) in
try? content.write(toFile: path, atomically: true, encoding: .utf8)
}
logTime()
}
logTotalElapsed("Done")
onCompletion()
}
// MARK - private functions
private func markBoundTypesUsed(declMap: DeclMap) {
for (k, decls) in declMap {
if !k.isEmpty { // Empty means expr or stmt
for decl in decls {
if decl.used {
decl.visited = true
markBoundTypesUsed(decl, level: 0, declMap: declMap)
}
}
}
}
}
private func markBoundTypesUsed(_ decl: DeclMetadata, level: Int, declMap: DeclMap) {
for boundType in decl.boundTypes {
if !boundType.isEmpty {
var bases: [String]?
var leaf: String?
if boundType.contains(".") {
bases = boundType.components(separatedBy: ".")
leaf = bases?.removeLast()
}
let key = leaf ?? boundType
if let boundDecls = declMap[key] {
for boundDecl in boundDecls {
if boundDecl.visited, boundDecl.used {
continue
}
boundDecl.visited = true
if decl.module == boundDecl.module || decl.imports.contains(boundDecl.module) {
boundDecl.used = true
markBoundTypesUsed(boundDecl, level: level + 1, declMap: declMap)
}
}
}
}
}
}
var shouldRetry = false
private func markInterfaceMembersUsed(declMap: DeclMap) {
var ndecls = 0
scan(declMap) { (key, vals, lock) in
for cur in vals {
var members = [DeclMetadata]()
var interfaceMembers = [DeclMetadata]()
var userDefinedTypes = [String]()
var stdlibTypes = [String]()
let level = 0
markBoundMembersUsed(key: cur, declMap: declMap, level: level, members: &members, interfaceMembers: &interfaceMembers, userDefinedTypes: &userDefinedTypes, stdlibTypes: &stdlibTypes)
log("#Marked used members", counter: &ndecls, interval: 10000, timed: true)
}
}
}
private func markBoundMembersUsed(key cur: DeclMetadata,
declMap: DeclMap,
level: Int,
members: inout [DeclMetadata],
interfaceMembers: inout [DeclMetadata],
userDefinedTypes: inout [String],
stdlibTypes: inout [String]) {
// First resolve inheritance (loop up protocol conformance, subclassing, and update member ALs)
var parents = cur.inheritedTypes
let curIsExtension = cur.declType == .extensionType
if curIsExtension {
parents.append(cur.name)
}
resolveInheritance(key: cur, inheritedTypes: parents, declMap: declMap, level: level, members: &members, interfaceMembers: &interfaceMembers, userDefinedTypes: &userDefinedTypes, stdlibTypes: &stdlibTypes)
let stdTypes = stdlibTypes.filter{!userDefinedTypes.contains($0)}
for member in members {
let matchingMembers = interfaceMembers.filter {$0.name == member.name}
for matched in matchingMembers {
if matched.used {
member.used = true
} else if member.used || member.isOverride {
if !matched.used {
matched.used = true
shouldRetry = true
}
} else if !stdTypes.isEmpty {
if !matched.used {
matched.used = true
shouldRetry = true
}
member.used = true
}
}
if matchingMembers.isEmpty, member.isOverride {
// This might be a member overriding stdlib api
member.used = true
}
}
// For the following decl types, check bound types and update member ALs.
if cur.declType == .extensionType || cur.declType == .enumType {
if !cur.used {
for m in cur.members {
if m.used {
cur.used = true
break
}
}
}
if !cur.used {
let boundTypes = cur.boundTypes.filter{!cur.inheritedTypes.contains($0)}
for boundType in boundTypes {
if boundType.isEmpty {
continue
}
if cur.name != boundType, let _ = declMap[boundType] {
// even if boundtype is used, cur might not be used
} else if cur.inheritedTypes.contains(boundType) {
// If parent is not in declMap, assume it's in stdlib.
for member in cur.members {
member.used = true
cur.used = true
}
if cur.used {
break
}
}
}
}
}
}
private func resolveInheritance(key cur: DeclMetadata,
inheritedTypes: [String]?,
declMap: DeclMap,
level: Int,
members: inout [DeclMetadata],
interfaceMembers: inout [DeclMetadata],
userDefinedTypes: inout [String],
stdlibTypes: inout [String]) {
let parents = inheritedTypes ?? cur.inheritedTypes
for parent in parents {
if parent.isEmpty {
continue
}
if let parentDecls = declMap[parent] {
for parentDecl in parentDecls {
if parentDecl.name.isEmpty {
continue
}
if parentDecl.declType == .protocolType || parentDecl.declType == .classType || parentDecl.declType == .typealiasType {
if parentDecl.declType == .protocolType {
interfaceMembers.append(contentsOf: parentDecl.members)
} else if parentDecl.declType == .classType, cur.declType == .classType {
interfaceMembers.append(contentsOf: parentDecl.members)
}
userDefinedTypes.append(parentDecl.name)
members.append(contentsOf: cur.members)
let optionalInitialTypes = parentDecl.declType == .typealiasType ? parentDecl.boundTypes : nil
resolveInheritance(key: parentDecl, inheritedTypes: optionalInitialTypes, declMap: declMap, level: level+1, members: &members, interfaceMembers: &interfaceMembers, userDefinedTypes: &userDefinedTypes, stdlibTypes: &stdlibTypes)
} else if parentDecl.declType == .extensionType {
// Parent could be a user defined type or a stdlib type. Add to a list for now and filter out below.
stdlibTypes.append(parentDecl.name)
members.append(contentsOf: cur.members)
}
}
} else {
// If parent is not in declMap, assume it's in stdlib.
stdlibTypes.append(parent)
}
}
for stdlibType in stdlibTypes {
if userDefinedTypes.contains(stdlibType) {
continue
}
interfaceMembers.append(contentsOf: cur.members)
members.append(contentsOf: cur.members)
break
}
}
private func accessMembers(_ bases: [String], _ i: Int, _ refModule: String, _ imports: [String], declMap: DeclMap) -> Bool {
let j = i + 1
if j < bases.count {
let cur = bases[i]
let next = bases[j]
if let prefixDecls = declMap[cur] {
for prefixDecl in prefixDecls {
var list: [DeclMetadata]?
if prefixDecl.declType == .funcType ||
prefixDecl.declType == .operatorType || // This is handled here but shouldn't be member-accessed
prefixDecl.declType == .varType {
if let typeDecls = declMap[prefixDecl.type] {
for t in typeDecls {
list = t.members.filter{$0.name == next}
}
}
} else {
list = prefixDecl.members.filter{$0.name == next}
}
if let list = list, !list.isEmpty {
let accessed = accessMembers(bases, i + 1, refModule, imports, declMap: declMap)
if accessed, (refModule == prefixDecl.module || imports.contains(prefixDecl.module)) {
for member in list {
member.used = true
}
}
} else {
return false
}
}
}
}
return true
}
private func markUsed(_ refs: Set<String>, in refModule: String, imports: [String], with declMap: DeclMap, updateMembers: Bool) {
// Leaf level node checks
for r in refs {
var bases: [String]?
var leaf: String?
if r.contains(".") {
bases = r.components(separatedBy: ".")
}
// First, traverse member access, and update visibility along the way
var accessedMembers = false
if let bases = bases {
accessedMembers = accessMembers(bases, 0, refModule, imports, declMap: declMap)
}
if accessedMembers {
continue
}
leaf = bases?.removeLast()
let refKey = leaf ?? r
// If above fails (e.g. encloser type is not found), or non-member access, try following
if let refDecls = declMap[refKey] {
for refDecl in refDecls {
if refModule == refDecl.module || imports.contains(refDecl.module) || refDecl.isOverride {
refDecl.used = true
}
}
}
}
}
private func resetVisited(declMap: DeclMap) {
for (_, decls) in declMap {
for decl in decls {
decl.visited = false
}
}
}
================================================
FILE: Sources/SwiftCodeSanKit/Operations/RemoveUnusedImports.swift
================================================
//
// Copyright (c) 2018. Uber Technologies
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
public func removeUnusedImports(fileToModuleMap: [String: String],
whitelist: Whitelist?,
topDeclsOnly: Bool,
inplace: Bool,
logFilePath: String? = nil,
concurrencyLimit: Int? = nil) {
scanConcurrencyLimit = concurrencyLimit
let p = DeclParser()
log("Scan all decls and generate a decl map...")
logTime()
let allDeclMap = p.scanAndMapDecls(fileToModuleMap: fileToModuleMap,
topDeclsOnly: topDeclsOnly)
logTime()
log("#Decls", allDeclMap.keys.count)
var unusedImports = [String: [String]]()
let whitelistModulesBlock = { (module: String) -> Bool in
let moduleComps = module.components(separatedBy: ".").filter {!$0.isEmpty}
for comp in moduleComps {
if let list = whitelist?.modules, list.contains(comp) {
return true
}
if let list = whitelist?.modulesSuffix {
for suffix in list {
if comp.hasSuffix(suffix) {
return true
}
}
}
if let list = whitelist?.modulesPrefix {
for prefix in list {
if comp.hasPrefix(prefix) {
return true
}
}
}
}
return false
}
log("Check referenced decls and compare their source modules against imported modules to filter out unused imports...")
var total = 0
p.checkRefs(fileToModuleMap: fileToModuleMap, declMap: allDeclMap) { (filepath, refs, imports) in
var usedImportsInFile = [String: Bool]()
for i in imports {
usedImportsInFile[i] = whitelistModulesBlock(i)
}
for r in refs {
if let refDecls = allDeclMap[r] {
for refDecl in refDecls {
let m = refDecl.module
if imports.contains(m) {
usedImportsInFile[m] = true
} else {
let refinedImports = imports.filter {$0.contains(".")}
for item in refinedImports {
let comps = item.components(separatedBy: ".")
if comps.contains(m) {
usedImportsInFile[item] = true
}
}
}
}
} else if imports.contains(r) {
// Sometimes a module name can be used in code, e.g. CoreFoundation.Foo
usedImportsInFile[r] = true
}
}
var unusedListInFile = [String]()
for (module, used) in usedImportsInFile {
if !used {
total += 1
unusedListInFile.append(module)
}
}
if !unusedListInFile.isEmpty {
unusedImports[filepath] = Set(unusedListInFile).compactMap{$0}
}
// log(total, interval: 200)
}
logTime()
log("#Unused imports", total)
if let op = logFilePath {
log("Save results...")
var totalUnused = 0
var ret = unusedImports.map { (path, unusedlist) -> String in
totalUnused += unusedlist.count
return path + "\n" + String(unusedlist.count) + "\n" + unusedlist.joined(separator: ", ")
}
assert(total == totalUnused)
ret.append("Total unused: \(totalUnused)")
let retStr = ret.joined(separator: "\n\n")
let declstr = allDeclMap.map{ (k, v) -> String in
let t = """
\(k): \(v.map { $0.path }.joined(separator: ", "))
"""
return t
}.joined(separator: "\n")
try? retStr.write(toFile: op, atomically: true, encoding: .utf8)
try? declstr.write(toFile: op+"-decls", atomically: true, encoding: .utf8)
}
if inplace {
log("Remove unused imports from files...", unusedImports.keys.count)
let updater = DeclUpdater()
updater.removeUnusedImports(fileToModuleMap: fileToModuleMap,
unusedImports: unusedImports) { (path, result) in
try? result.write(toFile: path, atomically: true, encoding: .utf8)
}
}
logTime()
logTotalElapsed("Done")
}
================================================
FILE: Sources/SwiftCodeSanKit/Operations/UpdateAccessLevels.swift
================================================
//
// Copyright (c) 2018. Uber Technologies
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
public func updateAccessLevels(filesToModules: [String: String],
whitelist: Whitelist?,
inplace: Bool,
logFilePath: String? = nil,
concurrencyLimit: Int? = nil,
onCompletion: @escaping () -> ()) {
scanConcurrencyLimit = concurrencyLimit
let p = DeclParser()
var pathToDeclsUpdate = [String: [DeclMetadata]]()
log("Scan and map top-level decls...")
logTime()
let declMap = p.scanAndMapDecls(fileToModuleMap: filesToModules,
topDeclsOnly: false,
whitelist: whitelist)
logTime()
log("Check references, look up their source modules, and mark visibility...")
p.checkRefs(fileToModuleMap: filesToModules, declMap: declMap) { (path, refs, imports) in
if let refModule = filesToModules[path] {
markVisiblity(refs, in: refModule, imports: imports, with: declMap, updateMembers: true)
}
}
logTime()
log("Update ALs (access levels) of top-level decls and their bound types...")
updateBoundTypeALs(declMap: declMap)
resetVisited(declMap: declMap)
log("Update ALs of member decls of interfaces (protocol / base class)...")
updateMemberALs(declMap: declMap)
logTime()
log("Flatten decls, and check references again, for member decls...")
var nref = 0
let flatDeclMap = flatten(declMap: declMap)
p.checkRefs(fileToModuleMap: filesToModules, declMap: flatDeclMap) { (path, refs, imports) in
if let refModule = filesToModules[path] {
markVisiblity(refs, in: refModule, imports: imports, with: flatDeclMap, updateMembers: false)
}
log(counter: &nref, interval: 1000)
}
log(nref)
logTime()
log("Update ALs of all decls and their bound types...")
updateBoundTypeALs(declMap: flatDeclMap)
resetVisited(declMap: flatDeclMap)
var i = -1
var j = 0
while i != j {
log("If bound types are modified, update their member ALs as well...")
updateMemberALs(declMap: declMap)
i = flatDeclMap.values.flatMap{$0}.filter{$0.shouldExpose}.count
log("Again, update ALs of of all decls and their bound types...")
updateBoundTypeALs(declMap: flatDeclMap)
resetVisited(declMap: flatDeclMap)
j = flatDeclMap.values.flatMap{$0}.filter{$0.shouldExpose}.count
log("#Remaining decls to update", i-j, i, j)
}
log("Save decls to update per files...")
for (_, decls) in flatDeclMap {
for decl in decls {
if decl.isPublicOrOpen, !decl.shouldExpose {
if pathToDeclsUpdate[decl.path] == nil {
pathToDeclsUpdate[decl.path] = []
}
pathToDeclsUpdate[decl.path]?.append(decl)
}
}
}
if let logfile = logFilePath {
log("Save results to", logfile)
let ret = pathToDeclsUpdate.map {"\($0.key): \($0.value.map{$0.name + ", " + $0.encloser}.joined(separator: "\n"))"}.joined(separator: "\n")
try? ret.write(toFile: logfile, atomically: true, encoding: .utf8)
}
if inplace {
log("Update decl ALs in files...")
let updater = DeclUpdater()
updater.updateAccessLevels(filesToDecls: pathToDeclsUpdate, filesToModules: filesToModules) { (path, content) in
try? content.write(toFile: path, atomically: true, encoding: .utf8)
}
}
logTime()
let total = pathToDeclsUpdate.values.flatMap{$0}.count
log("#Total top-level decls: ", declMap.count, "#Total decls", flatDeclMap.count, "#Decls updated", total, "#Files updated", pathToDeclsUpdate.count)
logTotalElapsed("Done")
onCompletion()
}
// MARK - private functions
private func updateBoundTypeALs(declMap: DeclMap) {
for (k, decls) in declMap {
if !k.isEmpty { // Empty means expr or stmt
for decl in decls {
if (decl.isPublicOrOpen && decl.shouldExpose) ||
decl.declType == .extensionType ||
decl.isExtensionMember {
decl.visited = true
updateBoundTypeALs(decl, level: 0, declMap: declMap)
}
}
}
}
}
private func updateBoundTypeALs(_ decl: DeclMetadata, level: Int, declMap: DeclMap) {
for boundType in decl.boundTypesAL {
if !boundType.isEmpty {
var bases: [String]?
var leaf: String?
if boundType.contains(".") {
bases = boundType.components(separatedBy: ".")
leaf = bases?.removeLast()
}
let key = leaf ?? boundType
if let boundDecls = declMap[key] {
for boundDecl in boundDecls {
if boundDecl.visited, boundDecl.shouldExpose {
continue
}
boundDecl.visited = true
if decl.module == boundDecl.module || decl.imports.contains(boundDecl.module) {
boundDecl.shouldExpose = true
updateBoundTypeALs(boundDecl, level: level + 1, declMap: declMap)
}
}
}
}
}
}
private func updateMemberALs(declMap: DeclMap) {
for (_, vals) in declMap {
for cur in vals {
var members = [DeclMetadata]()
var interfaceMembers = [DeclMetadata]()
let level = 0
updateBoundMemberALs(key: cur, declMap: declMap, level: level, members: &members, interfaceMembers: &interfaceMembers)
}
}
}
private func updateBoundMemberALs(key cur: DeclMetadata,
declMap: DeclMap,
level: Int,
members: inout [DeclMetadata],
interfaceMembers: inout [DeclMetadata]) {
// First resolve inheritance (loop up protocol conformance, subclassing, and update member ALs)
var parents = cur.inheritedTypes
let curIsExtension = cur.declType == .extensionType
if curIsExtension {
parents.append(cur.name)
}
resolveInheritance(key: cur, inheritedTypes: parents, declMap: declMap, level: level, members: &members, interfaceMembers: &interfaceMembers)
let interfaceMemberNames = interfaceMembers.map{$0.name}
for member in members {
if interfaceMemberNames.contains(member.name) {
if member.isPublicOrOpen || (curIsExtension && cur.isPublicOrOpen) {
member.shouldExpose = true
// If encloser is extension, it should be also exposed since its member is public/exposed
if curIsExtension, !cur.shouldExpose {
cur.shouldExpose = true
}
}
} else if member.isPublicOrOpen, member.isOverride {
// This might be a member overriding stdlib api
member.shouldExpose = true
}
}
// For the following decl types, check bound types and update member ALs.
if cur.declType == .extensionType || cur.declType == .enumType {
var visitedCurrent = false
let boundTypesAL = cur.boundTypesAL.filter{!cur.inheritedTypes.contains($0)}
for boundType in boundTypesAL {
if !boundType.isEmpty, cur.name != boundType, let boundTypeVals = declMap[boundType] {
for boundDecl in boundTypeVals {
if !visitedCurrent,
boundDecl.isPublicOrOpen,
boundDecl.shouldExpose {
for member in cur.members {
if member.isPublicOrOpen {
member.shouldExpose = true
}
}
visitedCurrent = true
}
}
} else if !visitedCurrent, cur.inheritedTypes.contains(boundType) {
// If parent is not in declMap, assume it's in stdlib.
for member in cur.members {
if member.isPublicOrOpen {
member.shouldExpose = true
}
}
visitedCurrent = true
}
}
if visitedCurrent, !cur.shouldExpose {
cur.shouldExpose = true
}
}
}
private func resolveInheritance(key cur: DeclMetadata,
inheritedTypes: [String]?,
declMap: DeclMap,
level: Int,
members: inout [DeclMetadata],
interfaceMembers: inout [DeclMetadata]) {
let parents = inheritedTypes ?? cur.inheritedTypes
var stdlibTypes = [String]()
var userDefinedTypes = [String]()
for parent in parents {
if parent.isEmpty {
continue
}
if let parentDecls = declMap[parent] {
for parentDecl in parentDecls {
if parentDecl.name.isEmpty {
continue
}
if parentDecl.declType == .protocolType || parentDecl.declType == .classType || parentDecl.declType == .typealiasType {
if parentDecl.isPublicOrOpen, parentDecl.shouldExpose {
if parentDecl.declType == .protocolType {
interfaceMembers.append(contentsOf: parentDecl.members)
} else if parentDecl.declType == .classType, cur.declType == .classType {
interfaceMembers.append(contentsOf: parentDecl.members)
}
}
userDefinedTypes.append(parentDecl.name)
members.append(contentsOf: cur.members)
let optionalInitialTypes = parentDecl.declType == .typealiasType ? parentDecl.boundTypesAL : nil
resolveInheritance(key: parentDecl, inheritedTypes: optionalInitialTypes, declMap: declMap, level: level+1, members: &members, interfaceMembers: &interfaceMembers)
} else if parentDecl.declType == .extensionType {
// Parent could be a user defined type or a stdlib type. Add to a list for now and filter out below.
stdlibTypes.append(parentDecl.name)
}
}
} else {
// If parent is not in declMap, assume it's in stdlib.
stdlibTypes.append(parent)
}
}
for stdlibType in stdlibTypes {
if userDefinedTypes.contains(stdlibType) {
continue
}
for member in cur.members {
if member.isPublicOrOpen {
interfaceMembers.append(member)
members.append(member)
}
}
break
}
}
private func traverseMembers(_ bases: [String], _ i: Int, _ refModule: String, _ imports: [String], declMap: DeclMap) -> Bool {
let j = i + 1
if j < bases.count {
let cur = bases[i]
let next = bases[j]
if let prefixDecls = declMap[cur] {
for prefixDecl in prefixDecls {
var list: [DeclMetadata]?
if prefixDecl.declType == .funcType ||
prefixDecl.declType == .operatorType || // This is handled here but shouldn't be member-accessed
prefixDecl.declType == .varType {
if let typeDecls = declMap[prefixDecl.type] {
for t in typeDecls {
list = t.members.filter{$0.name == next}
}
}
} else {
list = prefixDecl.members.filter{$0.name == next}
}
if let list = list, !list.isEmpty {
let checked = traverseMembers(bases, i + 1, refModule, imports, declMap: declMap)
if checked, refModule != prefixDecl.module, imports.contains(prefixDecl.module) {
for member in list {
member.shouldExpose = true
}
}
} else {
return false
}
}
}
}
return true
}
private func markVisiblity(_ refs: Set<String>, in refModule: String, imports: [String], with declMap: DeclMap, updateMembers: Bool) {
// Leaf level node checks
for r in refs {
var bases: [String]?
var leaf: String?
if r.contains(".") {
bases = r.components(separatedBy: ".")
}
// First, traverse member access, and update visibility along the way
var accessedMembers = false
if let bases = bases {
accessedMembers = traverseMembers(bases, 0, refModule, imports, declMap: declMap)
}
if accessedMembers {
continue
}
leaf = bases?.removeLast()
let refKey = leaf ?? r
// If above fails (e.g. encloser type is not found), or non-member access, try following
if let refDecls = declMap[refKey] {
for refDecl in refDecls {
if true ||
refDecl.isPublicOrOpen ||
refDecl.declType == .extensionType ||
refDecl.isExtensionMember {
// multi modules w/ same decls (foo):
// 1. shadowing: if ref'd, it uses a decl in the same module even if the others are imported.
// - if foo from another module should be called, it's required to use qualifier X.foo
// 2. if not decl's in the same module as ref, uses corresponding modules, so need to look up imports
// 3. if foo inits are the same for multi-modules:
// - need qualifier X.foo
if refModule == refDecl.module {
// r is either declared internally
// so r should not be public, so add [decl.path: r] to pathToUpdateDecls
} else {
// look up imports and check decl.module is in the imports, then decl.shouldBePublic = true, so do nothing.
if imports.contains(refDecl.module) {
if !refDecl.encloser.isEmpty {
// If it has an encloser (part of a class, protocol, etc),
// check if the encloser is in ref'd.
// Encloser type might not be listed, leakdetect.inst.accumulatedLeaksStream
refDecl.shouldExpose = true
} else {
// then r in decl.module should remain public
refDecl.shouldExpose = true
}
} else {
// r must be part of stdlib, handled in updateMemberALs above.
}
}
}
}
}
}
}
private func shouldMatchACLForMembers(_ declType: DeclType) -> Bool {
return declType == .protocolType ||
declType == .extensionType ||
declType == .enumType
}
private func resetVisited(declMap: DeclMap) {
for (_, decls) in declMap {
for decl in decls {
decl.visited = false
}
}
}
================================================
FILE: Sources/SwiftCodeSanKit/Utils/Extensions/FileManagerExtensions.swift
================================================
//
// Copyright (c) 2018. Uber Technologies
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
extension FileManager {
static func modifiedWithin(_ delta: Int?, at path: String) -> Bool {
if let fileAttrs = try? FileManager.default.attributesOfItem(atPath: path),
let modifiedDate = fileAttrs[FileAttributeKey.creationDate] as? Date {
let now = Date()
let days = Int(floor(modifiedDate.distance(to: now) / 60 / 60 / 24))
if let delta = delta, days < delta {
return true
}
}
return false
}
}
================================================
FILE: Sources/SwiftCodeSanKit/Utils/Extensions/SequenceExtensions.swift
================================================
//
// Copyright (c) 2018. Uber Technologies
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
public extension Sequence {
func compactMap<T>(path: KeyPath<Element, T?>) -> [T] {
return compactMap { (element) -> T? in
element[keyPath: path]
}
}
func map<T>(path: KeyPath<Element, T>) -> [T] {
return map { (element) -> T in
element[keyPath: path]
}
}
func filter(path: KeyPath<Element, Bool>) -> [Element] {
return filter { (element) -> Bool in
element[keyPath: path]
}
}
func sorted<T>(path: KeyPath<Element, T>) -> [Element] where T: Comparable {
return sorted { (lhs, rhs) -> Bool in
lhs[keyPath: path] < rhs[keyPath: path]
}
}
func sorted<T, U>(path: KeyPath<Element, T>, fallback: KeyPath<Element, U>) -> [Element] where T: Comparable, U: Comparable {
return sorted { (lhs, rhs) -> Bool in
if lhs[keyPath: path] == rhs[keyPath: path] {
return lhs[keyPath: fallback] < rhs[keyPath: fallback]
}
return lhs[keyPath: path] < rhs[keyPath: path]
}
}
}
================================================
FILE: Sources/SwiftCodeSanKit/Utils/Extensions/StringExtensions.swift
================================================
//
// Copyright (c) 2018. Uber Technologies
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
var alphanumericSet = CharacterSet.alphanumerics
extension String {
static public let `final` = "final"
static let override = "override"
static let unknownVal = "Unknown"
static let prefix = "prefix"
static let `public` = "public"
static let `open` = "open"
static let `internal` = "internal"
static let `required` = "required"
static let `convenience` = "convenience"
static let moduleColon = "module:"
static let typealiasColon = "typealias:"
static let rxColon = "rx:"
static let varColon = "var:"
static let annotationArgDelimiter = ";"
static let transparent = "@_transparent"
static let propertyWrapper = "propertyWrapper"
var raw: String {
if hasPrefix("`"), hasSuffix("`") {
var val = dropFirst()
val = val.dropLast()
return String(val)
}
return self
}
func arguments(with delimiter: String) -> [String: String]? {
let argstr = self
let args = argstr.components(separatedBy: delimiter)
var argsMap = [String: String]()
for item in args {
let keyVal = item.components(separatedBy: "=").map{$0.trimmed}
if let k = keyVal.first {
if k.contains(":") {
break
}
if let v = keyVal.last {
argsMap[k] = v
}
}
}
return !argsMap.isEmpty ? argsMap : nil
}
public var trimmed: String {
return self.trimmingCharacters(in: .whitespaces)
}
var isAlphanumeric: Bool {
let ret = self.unicodeScalars.filter {alphanumericSet.contains($0) || $0 == "_"}
return !ret.isEmpty
}
var isPublicOrOpen: Bool {
return self == .public || self == .open
}
}
================================================
FILE: Sources/SwiftCodeSanKit/Utils/Extensions/SyntaxExtensions.swift
================================================
//
// Copyright (c) 2018. Uber Technologies
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
import SwiftSyntax
protocol DeclProtocol {
var type: String { get }
var name: String { get }
var fullName: String { get }
var declType: DeclType { get }
var inheritedTypes: [String] { get }
func refTypes(with declMap: DeclMap, filterKey: String?) -> [String]
var refTypes: [String] { get }
var boundTypes: [String] { get }
var boundTypesAL: [String] { get } // Bound types for access levels
var accessLevel: String { get }
var isOverride: Bool { get }
var isExprOrStmt: Bool { get }
func declMetadatas(path: String, module: String, encloser: String, description: String, imports: [String]) -> [DeclMetadata]
}
extension DeclProtocol {
func declMetadatas(path: String, module: String, encloser: String, description: String, imports: [String]) -> [DeclMetadata] {
if let declSyntax = self as? DeclSyntax, let varSyntax = declSyntax.as(VariableDeclSyntax.self) {
return varSyntax.declMetadatas(path: path, module: module, encloser: encloser, description: description, imports: imports)
}
let val = DeclMetadata(path: path,
module: module,
imports: imports,
encloser: encloser,
name: name,
type: type,
fullName: fullName,
description: description,
declType: declType,
inheritedTypes: inheritedTypes,
boundTypes: boundTypes,
boundTypesAL: boundTypesAL,
isPublicOrOpen: accessLevel.isPublicOrOpen,
isOverride: isOverride,
used: false)
return [val]
}
func refTypes(with declMap: DeclMap, filterKey: String? = nil) -> [String] {
return refTypes.filter { declMap[$0] != nil || $0.contains(".") || $0.hasSuffix("Strings") || $0.hasSuffix("Images") }
}
}
extension Syntax: DeclProtocol {
var name: String {
return ""
}
var type: String {
return ""
}
var fullName: String {
return name
}
var accessLevel: String {
return ""
}
var isOverride: Bool {
return false
}
var isExprOrStmt: Bool {
return false
}
var declType: DeclType {
return .other
}
var inheritedTypes: [String] {
return []
}
var refTypes: [String] {
return boundTypesAL
}
var boundTypes: [String] {
return tokens.exprTokenList
}
var boundTypesAL: [String] {
return boundTypes
}
}
extension DeclSyntax: DeclProtocol {
var name: String {
if let d = self.as(FunctionDeclSyntax.self) {
return d.name
} else if let d = self.as(VariableDeclSyntax.self) {
return d.name
} else if let d = self.as(InitializerDeclSyntax.self) {
return d.name
} else if let d = self.as(SubscriptDeclSyntax.self) {
return d.name
} else if let d = self.as(ProtocolDeclSyntax.self) {
return d.name
} else if let d = self.as(ClassDeclSyntax.self) {
return d.name
} else if let d = self.as(ExtensionDeclSyntax.self) {
return d.name
} else if let d = self.as(StructDeclSyntax.self) {
return d.name
} else if let d = self.as(EnumDeclSyntax.self) {
return d.name
} else if let d = self.as(EnumCaseDeclSyntax.self) {
return d.name
} else if let d = self.as(TypealiasDeclSyntax.self) {
return d.name
} else if let d = self.as(AssociatedtypeDeclSyntax.self) {
return d.name
} else {
return ""
}
}
var type: String {
if let d = self.as(FunctionDeclSyntax.self) {
return d.type
} else if let d = self.as(SubscriptDeclSyntax.self) {
return d.type
} else if let d = self.as(VariableDeclSyntax.self) {
return d.type
}
return name
}
var fullName: String {
if let d = self.as(FunctionDeclSyntax.self) {
return d.fullName
} else if let d = self.as(VariableDeclSyntax.self) {
return d.fullName
} else if let d = self.as(InitializerDeclSyntax.self) {
return d.fullName
} else if let d = self.as(SubscriptDeclSyntax.self) {
return d.fullName
} else if let d = self.as(ProtocolDeclSyntax.self) {
return d.fullName
} else if let d = self.as(ClassDeclSyntax.self) {
return d.fullName
} else if let d = self.as(StructDeclSyntax.self) {
return d.fullName
} else if let d = self.as(EnumDeclSyntax.self) {
return d.fullName
} else if let d = self.as(ExtensionDeclSyntax.self) {
return d.fullName
} else if let d = self.as(EnumCaseDeclSyntax.self) {
return d.fullName
} else if let d = self.as(TypealiasDeclSyntax.self) {
return d.fullName
} else if let d = self.as(AssociatedtypeDeclSyntax.self) {
return d.fullName
}
return name
}
var declType: DeclType {
if let d = self.as(FunctionDeclSyntax.self) {
return d.declType
} else if let d = self.as(VariableDeclSyntax.self) {
return d.declType
} else if let d = self.as(InitializerDeclSyntax.self) {
return d.declType
} else if let d = self.as(SubscriptDeclSyntax.self) {
return d.declType
} else if let d = self.as(ProtocolDeclSyntax.self) {
return d.declType
} else if let d = self.as(ClassDeclSyntax.self) {
return d.declType
} else if let d = self.as(ExtensionDeclSyntax.self) {
return d.declType
} else if let d = self.as(StructDeclSyntax.self) {
return d.declType
} else if let d = self.as(EnumDeclSyntax.self) {
return d.declType
} else if let d = self.as(EnumCaseDeclSyntax.self) {
return d.declType
} else if let d = self.as(TypealiasDeclSyntax.self) {
return d.declType
} else if let d = self.as(AssociatedtypeDeclSyntax.self) {
return d.declType
}
return .other
}
var inheritedTypes: [String] {
if let d = self.as(ProtocolDeclSyntax.self) {
return d.inheritedTypes
} else if let d = self.as(ClassDeclSyntax.self) {
return d.inheritedTypes
} else if let d = self.as(ExtensionDeclSyntax.self) {
return d.inheritedTypes
} else if let d = self.as(StructDeclSyntax.self) {
return d.inheritedTypes
} else if let d = self.as(EnumDeclSyntax.self) {
return d.inheritedTypes
} else if let d = self.as(TypealiasDeclSyntax.self) {
return d.inheritedTypes
} else if let d = self.as(AssociatedtypeDeclSyntax.self) {
return d.inheritedTypes
}
return []
}
func refTypes(with declMap: DeclMap, filterKey: String?) -> [String] {
var list = refTypes
if !declMap.isEmpty {
list = list.filter{declMap[$0] != nil}
}
return list
}
var refTypes: [String] {
if let d = self.as(FunctionDeclSyntax.self) {
return d.refTypes
} else if let d = self.as(VariableDeclSyntax.self) {
return d.refTypes
} else if let d = self.as(InitializerDeclSyntax.self) {
return d.refTypes
} else if let d = self.as(SubscriptDeclSyntax.self) {
return d.refTypes
} else if let d = self.as(ProtocolDeclSyntax.self) {
return d.refTypes
} else if let d = self.as(ClassDeclSyntax.self) {
return d.refTypes
} else if let d = self.as(ExtensionDeclSyntax.self) {
return d.refTypes
} else if let d = self.as(StructDeclSyntax.self) {
return d.refTypes
} else if let d = self.as(EnumDeclSyntax.self) {
return d.refTypes
} else if let d = self.as(EnumCaseDeclSyntax.self) {
return d.refTypes
} else if let d = self.as(TypealiasDeclSyntax.self) {
return d.refTypes
} else if let d = self.as(AssociatedtypeDeclSyntax.self) {
return d.refTypes
}
return []
}
var boundTypes: [String] {
if let d = self.as(FunctionDeclSyntax.self) {
return d.boundTypes
} else if let d = self.as(VariableDeclSyntax.self) {
return d.boundTypes
} else if let d = self.as(InitializerDeclSyntax.self) {
return d.boundTypes
} else if let d = self.as(SubscriptDeclSyntax.self) {
return d.boundTypes
} else if let d = self.as(ProtocolDeclSyntax.self) {
return d.boundTypes
} else if let d = self.as(ClassDeclSyntax.self) {
return d.boundTypes
} else if let d = self.as(ExtensionDeclSyntax.self) {
return d.boundTypes
} else if let d = self.as(StructDeclSyntax.self) {
return d.boundTypes
} else if let d = self.as(EnumDeclSyntax.self) {
return d.boundTypes
} else if let d = self.as(EnumCaseDeclSyntax.self) {
return d.boundTypes
} else if let d = self.as(TypealiasDeclSyntax.self) {
return d.boundTypes
} else if let d = self.as(AssociatedtypeDeclSyntax.self) {
return d.boundTypes
}
return []
}
var boundTypesAL: [String] {
if let d = self.as(FunctionDeclSyntax.self) {
return d.boundTypesAL
} else if let d = self.as(VariableDeclSyntax.self) {
return d.boundTypesAL
} else if let d = self.as(InitializerDeclSyntax.self) {
return d.boundTypesAL
} else if let d = self.as(SubscriptDeclSyntax.self) {
return d.boundTypesAL
} else if let d = self.as(ProtocolDeclSyntax.self) {
return d.boundTypesAL
} else if let d = self.as(ClassDeclSyntax.self) {
return d.boundTypesAL
} else if let d = self.as(ExtensionDeclSyntax.self) {
return d.boundTypesAL
} else if let d = self.as(StructDeclSyntax.self) {
return d.boundTypesAL
} else if let d = self.as(EnumDeclSyntax.self) {
return d.boundTypesAL
} else if let d = self.as(EnumCaseDeclSyntax.self) {
return d.boundTypesAL
} else if let d = self.as(TypealiasDeclSyntax.self) {
return d.boundTypesAL
} else if let d = self.as(AssociatedtypeDeclSyntax.self) {
return d.boundTypesAL
}
return []
}
var accessLevel: String {
if let d = self.as(FunctionDeclSyntax.self) {
return d.accessLevel
} else if let d = self.as(VariableDeclSyntax.self) {
return d.accessLevel
} else if let d = self.as(InitializerDeclSyntax.self) {
return d.accessLevel
} else if let d = self.as(SubscriptDeclSyntax.self) {
return d.accessLevel
} else if let d = self.as(ProtocolDeclSyntax.self) {
return d.accessLevel
} else if let d = self.as(ClassDeclSyntax.self) {
return d.accessLevel
} else if let d = self.as(ExtensionDeclSyntax.self) {
return d.accessLevel
} else if let d = self.as(StructDeclSyntax.self) {
return d.accessLevel
} else if let d = self.as(EnumDeclSyntax.self) {
return d.accessLevel
} else if let d = self.as(EnumCaseDeclSyntax.self) {
return d.accessLevel
} else if let d = self.as(TypealiasDeclSyntax.self) {
return d.accessLevel
} else if let d = self.as(AssociatedtypeDeclSyntax.self) {
return d.accessLevel
}
return ""
}
var isOverride: Bool {
if let d = self.as(FunctionDeclSyntax.self) {
return d.isOverride
} else if let d = self.as(VariableDeclSyntax.self) {
return d.isOverride
} else if let d = self.as(InitializerDeclSyntax.self) {
return d.isOverride
}
return false
}
var isExprOrStmt: Bool {
_syntaxNode.is(StmtSyntax.self) || _syntaxNode.is(ExprSyntax.self)
}
}
extension MemberDeclListItemSyntax: DeclProtocol {
var refTypes: [String] {
return decl.refTypes
}
var declType: DeclType {
return decl.declType
}
var inheritedTypes: [String] {
return decl.inheritedTypes
}
var boundTypes: [String] {
return decl.boundTypes
}
var boundTypesAL: [String] {
return decl.boundTypesAL
}
var accessLevel: String {
return decl.accessLevel
}
var isOverride: Bool {
return decl.isOverride
}
var isExprOrStmt: Bool {
return decl.isExprOrStmt
}
var name: String {
return decl.name
}
var type: String {
return decl.type
}
var fullName: String {
return decl.fullName
}
}
extension MemberDeclListSyntax: DeclProtocol {
var name: String {
return ""
}
var type: String {
return name
}
var fullName: String {
return name
}
var declType: DeclType {
return .other
}
var inheritedTypes: [String] {
return []
}
var accessLevel: String {
return ""
}
var isOverride: Bool {
return false
}
var isExprOrStmt: Bool {
return false
}
var boundTypes: [String] {
return self.map { $0.decl.boundTypes }.flatMap{$0}
}
var boundTypesAL: [String] {
return self.map { $0.decl.boundTypesAL }.flatMap{$0}
}
var refTypes: [String] {
return boundTypesAL
}
}
extension ProtocolDeclSyntax: DeclProtocol {
var fullName: String {
return name
}
var type: String {
return name
}
var isExprOrStmt: Bool {
return false
}
var isOverride: Bool {
return false
}
var name: String {
return identifier.text.raw
}
var accessLevel: String {
return self.modifiers?.acl ?? ""
}
var inheritedTypes: [String] {
return [inheritanceClause?.tokens.exprTokenList,
genericWhereClause?.tokens.exprTokenList,
].compactMap{$0}.flatMap{$0}.filter{$0 != name}
}
var boundTypes: [String] {
return inheritedTypes
}
var boundTypesAL: [String] {
return [boundTypes,
members.members.boundTypesAL,
].compactMap{$0}.flatMap{$0}
}
var refTypes: [String] {
return boundTypesAL
}
var declType: DeclType {
return .protocolType
}
var isPrivate: Bool {
return self.modifiers?.isPrivate ?? false
}
var attributesDescription: String {
self.attributes?.trimmedDescription ?? ""
}
var offset: Int64 {
return Int64(self.position.utf8Offset)
}
func annotationMetadata(with annotation: String) -> AnnotationMetadata? {
return leadingTrivia?.annotationMetadata(with: annotation)
}
}
extension ClassDeclSyntax: DeclProtocol {
var fullName: String {
return name
}
var type: String {
return name
}
var isExprOrStmt: Bool {
return false
}
var isOverride: Bool {
return false
}
var name: String {
return identifier.text.raw
}
var accessLevel: String {
return self.modifiers?.acl ?? ""
}
var declType: DeclType {
return .classType
}
var boundTypes: [String] {
return inheritedTypes
}
var boundTypesAL: [String] {
return boundTypes
}
var refTypes: [String] {
return [boundTypesAL,
members.members.boundTypesAL,
].compactMap{$0}.flatMap{$0}
}
var inheritedTypes: [String] {
return [genericParameterClause?.genericParameterList.tokens.exprTokenList,
genericWhereClause?.tokens.exprTokenList,
inheritanceClause?.tokens.exprTokenList
].compactMap{$0}.flatMap{$0}.filter{$0 != name}
}
var attributesDescription: String {
self.attributes?.trimmedDescription ?? ""
}
var offset: Int64 {
return Int64(self.position.utf8Offset)
}
func annotationMetadata(with annotation: String) -> AnnotationMetadata? {
return leadingTrivia?.annotationMetadata(with: annotation)
}
}
extension ExtensionDeclSyntax: DeclProtocol {
var fullName: String {
return extendedType.description.trimmed + "_" + inheritedTypes.joined()
}
var type: String {
return name
}
var isExprOrStmt: Bool {
return false
}
var isOverride: Bool {
return false
}
var name: String {
let str = extendedType.description.trimmed.raw
let comps = str.components(separatedBy: ".")
if let last = comps.last, !last.isEmpty {
return last
}
return str
}
var declType: DeclType {
return .extensionType
}
var accessLevel: String {
return self.modifiers?.acl ?? ""
}
var inheritedTypes: [String] {
return [inheritanceClause?.tokens.exprTokenList,
genericWhereClause?.tokens.exprTokenList,
].compactMap{$0}.flatMap{$0}.filter{$0 != name}
}
var boundTypes: [String] {
var ret = inheritedTypes
ret.append(name)
return ret
}
var boundTypesAL: [String] {
return [boundTypes,
members.members.boundTypesAL,
].compactMap{$0}.flatMap{$0}
}
var refTypes: [String] {
return boundTypesAL
}
func refTypes(with declMap: DeclMap, filterKey: String? = nil) -> [String] {
var list = [extendedType.tokens.exprTokenList,
refTypes
].compactMap{$0}.flatMap{$0}
if !declMap.isEmpty {
list = list.filter{declMap[$0] != nil}
}
return list
}
}
extension EnumCaseDeclSyntax: DeclProtocol {
var inheritedTypes: [String] {
return []
}
var type: String {
return name
}
var isExprOrStmt: Bool {
return false
}
var isOverride: Bool {
return false
}
var name: String {
let ret = elements.map{$0.identifier.text}.first ?? elements.description
return ret.raw
}
var fullName: String {
return self.elements.description
}
var accessLevel: String {
return self.modifiers?.acl ?? ""
}
var declType: DeclType {
return .enumCaseType
}
var boundTypes: [String] {
let list = elements.compactMap{$0.associatedValue?.parameterList.compactMap{$0.type?.tokens.exprTokenList}.flatMap{$0}}.flatMap{$0}
return list
}
var boundTypesAL: [String] {
return boundTypes
}
var refTypes: [String] {
return boundTypesAL
}
}
extension EnumDeclSyntax: DeclProtocol {
var fullName: String {
return name + "_" + inheritedTypes.joined()
}
var type: String {
return name
}
var isExprOrStmt: Bool {
return false
}
var isOverride: Bool {
return false
}
var attributesDescription: String {
self.attributes?.description.trimmed ?? ""
}
var name: String {
return identifier.text.trimmed.raw
}
var accessLevel: String {
return self.modifiers?.acl ?? ""
}
var declType: DeclType {
return .enumType
}
var inheritedTypes: [String] {
return [inheritanceClause?.tokens.exprTokenList,
genericParameters?.tokens.exprTokenList,
genericWhereClause?.tokens.exprTokenList
].compactMap{$0}.flatMap{$0}.filter{ $0 != name }
}
var boundTypes: [String] {
return inheritedTypes
}
var boundTypesAL: [String] {
return [boundTypes,
members.members.boundTypesAL
].compactMap{$0}.flatMap{$0}
}
var refTypes: [String] {
return boundTypesAL
}
}
extension StructDeclSyntax: DeclProtocol {
var fullName: String {
return name + "_" + inheritedTypes.joined()
}
var type: String {
return name
}
var isExprOrStmt: Bool {
return false
}
var isOverride: Bool {
return false
}
var attributesDescription: String {
self.attributes?.description.trimmed ?? ""
}
var name: String {
return identifier.text.trimmed.raw
}
var declType: DeclType {
return .structType
}
var accessLevel: String {
return self.modifiers?.acl ?? ""
}
var inheritedTypes: [String] {
return [inheritanceClause?.tokens.exprTokenList,
genericParameterClause?.tokens.exprTokenList,
genericWhereClause?.tokens.exprTokenList,
].compactMap{$0}.flatMap{$0}.filter{ $0 != name }
}
var boundTypes: [String] {
return inheritedTypes
}
var boundTypesAL: [String] {
return boundTypes
}
var refTypes: [String] {
return [boundTypesAL,
members.members.boundTypesAL,
].compactMap{$0}.flatMap{$0}
}
}
extension AssociatedtypeDeclSyntax: DeclProtocol {
var isExprOrStmt: Bool {
return false
}
var isOverride: Bool {
return false
}
var type: String {
return name
}
var name: String {
return identifier.text.trimmed.raw
}
var fullName: String {
return name + "_" + boundTypes.joined()
}
var declType: DeclType {
return .patType
}
var accessLevel: String {
return self.modifiers?.acl ?? ""
}
var inheritedTypes: [String] {
return [inheritanceClause?.tokens.exprTokenList,
genericWhereClause?.tokens.exprTokenList,
].compactMap{$0}.flatMap{$0}.filter{$0 != name}
}
var boundTypes: [String] {
return [inheritedTypes,
initializer?.value.tokens.exprTokenList.filter{$0 != name}
].compactMap{$0}.flatMap{$0}
}
var boundTypesAL: [String] {
return boundTypes
}
var refTypes: [String] {
return boundTypesAL
}
}
extension TypealiasDeclSyntax: DeclProtocol {
var isExprOrStmt: Bool {
return false
}
var isOverride: Bool {
return false
}
var type: String {
return name
}
var name: String {
return identifier.text.trimmed.raw
}
var fullName: String {
return name + "_" + boundTypes.joined()
}
var declType: DeclType {
return .typealiasType
}
var accessLevel: String {
return self.modifiers?.acl ?? ""
}
var inheritedTypes: [String] {
return [genericParameterClause?.tokens.exprTokenList,
genericWhereClause?.tokens.exprTokenList,
].compactMap{$0}.flatMap{$0}.filter{$0 != name}
}
var boundTypes: [String] {
return [inheritedTypes,
initializer?.value.tokens.exprTokenList.filter{$0 != name}
].compactMap{$0}.flatMap{$0}
}
var boundTypesAL: [String] {
return boundTypes
}
var refTypes: [String] {
return boundTypesAL
}
}
extension PatternBindingSyntax {
var type: String {
if let t = typeAnnotation?.type.description.trimmed {
return t
}
if let val = initializer?.value {
if let expr = val.as(FunctionCallExprSyntax.self) {
return expr.calledExpression.description.trimmed
} else if let expr = val.as(ExprSyntax.self) {
return expr.description.trimmed
}
}
return .unknownVal
}
func boundTypes(isTransparent: Bool) -> [String] {
var list = [String]()
if let bound = typeAnnotation?.type.tokens.exprTokenList {
list.append(contentsOf: bound)
}
if let val = initializer?.value {
if let expr = val.as(FunctionCallExprSyntax.self) {
let exprList = [
expr.calledExpression.tokens.exprTokenList,
expr.argumentList.map{$0.expression.tokens.exprTokenList}.flatMap{$0}
].flatMap{$0}
list.append(contentsOf: exprList)
} else if let expr = val.as(ExprSyntax.self) {
list.append(contentsOf: expr.tokens.exprTokenList)
}
}
if isTransparent {
if let bodyTokens = accessor?.tokens.exprTokenList {
list.append(contentsOf: bodyTokens)
}
}
return list
}
}
extension VariableDeclSyntax: DeclProtocol {
func declMetadatas(path: String, module: String, encloser: String, description: String, imports: [String]) -> [DeclMetadata] {
var list = [DeclMetadata]()
for binding in bindings {
if let idpattern = binding.pattern.as(IdentifierPatternSyntax.self) {
let id = idpattern.identifier.text
if id == "_" { continue }
let ty = binding.type
let full = id + "_" + ty
let bound = binding.boundTypes(isTransparent: isTransparent)
let val = DeclMetadata(path: path,
module: module,
imports: imports,
encloser: encloser,
name: id,
type: ty,
fullName: full,
description: description,
declType: declType,
inheritedTypes: inheritedTypes,
boundTypes: bound,
boundTypesAL: bound,
isPublicOrOpen: accessLevel.isPublicOrOpen,
isOverride: isOverride,
used: false)
list.append(val)
} else if let tuple = binding.pattern.as(TuplePatternSyntax.self) {
for el in tuple.elements {
if let idpattern = el.pattern.as(IdentifierPatternSyntax.self) {
let id = idpattern.identifier.text
if id == "_" { continue }
let ty = binding.type
let full = id + "_" + ty
let bound = binding.boundTypes(isTransparent: isTransparent)
let val = DeclMetadata(path: path,
module: module,
imports: imports,
encloser: encloser,
name: id,
type: ty,
fullName: full,
description: description,
declType: declType,
inheritedTypes: inheritedTypes,
boundTypes: bound,
boundTypesAL: bound,
isPublicOrOpen: accessLevel.isPublicOrOpen,
isOverride: isOverride,
used: false)
list.append(val)
}
}
}
}
return list
}
var isTransparent: Bool {
return attributesDescription.contains(String.transparent)
}
var name: String {
let ret = bindings.compactMap { $0.pattern.description.trimmed }.joined().raw
return ret
}
var inheritedTypes: [String] {
return []
}
var isExprOrStmt: Bool {
return false
}
var fullName: String {
return name + "_" + type
}
var declType: DeclType {
return .varType
}
var accessLevel: String {
return self.modifiers?.acl ?? ""
}
var isOverride: Bool {
return modifiers?.isOverride ?? false
}
var attributesDescription: String {
return attributes?.trimmedDescription ?? ""
}
var type: String {
return bindings.first?.type ?? ""
}
var boundTypes: [String] {
var list = [String]()
for b in bindings {
list.append(contentsOf: b.boundTypes(isTransparent: isTransparent))
}
if let attrs = attributes?.tokens.exprTokenList {
list.append(contentsOf: attrs)
}
return list.filter{$0 != name}
}
var boundTypesAL: [String] {
return boundTypes
}
var refTypes: [String] {
let ret = [boundTypesAL,
bindings.compactMap{$0.accessor?.tokens.exprTokenList}.flatMap{$0}
].compactMap{$0}.flatMap{$0}
return ret
}
}
extension FunctionDeclSyntax: DeclProtocol {
var name: String {
return self.identifier.description.trimmed.raw
}
var type: String {
return signature.output?.returnType.description.trimmed ?? ""
}
var fullName: String {
return name + signature.description.trimmed
}
var declType: DeclType {
if self.identifier.tokenKind == .spacedBinaryOperator(self.identifier.text) {
return .operatorType
}
return .funcType
}
var accessLevel: String {
return self.modifiers?.acl ?? ""
}
var isOverride: Bool {
return modifiers?.isOverride ?? false
}
var attributesDescription: String {
return attributes?.trimmedDescription ?? ""
}
var inheritedTypes: [String] {
return []
}
var isExprOrStmt: Bool {
return false
}
var boundTypes: [String] {
let genericParamTypes = genericParameterClause?.genericParameterList.tokens.exprTokenList
let genericWhereTypes = genericWhereClause?.tokens.exprTokenList
let paramTypes = signature.input.parameterList.compactMap{$0.type?.tokens.exprTokenList}.flatMap{$0}
let paramVals = signature.input.parameterList.compactMap{$0.defaultArgument?.value.tokens.exprTokenList}.flatMap{$0}
let returnTypes = signature.output?.returnType.tokens.exprTokenList
let attrs = attributes?.tokens.exprTokenList // e.g. @FunctionBuilder
var list = [genericParamTypes, genericWhereTypes, paramTypes, paramVals, returnTypes, attrs].compactMap{$0}.flatMap{$0}
if attributesDescription.contains(String.transparent) {
if let bodyTokens = body?.tokens.exprTokenList {
list.append(contentsOf: bodyTokens)
}
}
return list.filter{$0 != name}
}
var boundTypesAL: [String] {
return boundTypes
}
var refTypes: [String] {
return [boundTypesAL,
body?.tokens.exprTokenList
].compactMap{$0}.flatMap{$0}
}
}
extension InitializerDeclSyntax: DeclProtocol {
var name: String {
return "init"
}
var type: String {
return name
}
var fullName: String {
return name + "_" + parameters.description.trimmed
}
var declType: DeclType {
return .initType
}
var isOverride: Bool {
return modifiers?.isOverride ?? false
}
var attributesDescription: String {
return attributes?.trimmedDescription ?? ""
}
var accessLevel: String {
return modifiers?.acl ?? ""
}
var boundTypes: [String] {
let genericParamTypes = genericParameterClause?.genericParameterList.tokens.exprTokenList
let genericWhereTypes = genericWhereClause?.tokens.exprTokenList
var paramList = [String]()
for param in parameters.parameterList {
if let pval = param.defaultArgument?.value {
if let accessed = pval.as(MemberAccessExprSyntax.self), let base = accessed.base {
paramList.append(accessed.description.trimmed)
paramList.append(contentsOf: base.tokens.exprTokenList)
} else {
paramList.append(contentsOf: pval.tokens.exprTokenList)
}
}
if let ptypes = param.type?.tokens.exprTokenList {
paramList.append(contentsOf: ptypes)
}
}
var list = [genericParamTypes, genericWhereTypes, paramList].compactMap{$0}.flatMap{$0}
// @_transparent on public or @usableFromInline functions require all types in sig and body to be public
if attributesDescription.contains(String.transparent) {
if let bodyTokens = body?.tokens.exprTokenList {
list.append(contentsOf: bodyTokens)
}
}
return list.filter{$0 != name}
}
var boundTypesAL: [String] {
return boundTypes
}
var refTypes: [String] {
return [boundTypesAL,
body?.tokens.exprTokenList
].compactMap{$0}.flatMap{$0}
}
var inheritedTypes: [String] {
return []
}
var isExprOrStmt: Bool {
return false
}
func isRequired(with declType: DeclType) -> Bool {
if declType == .protocolType {
return true
} else if declType == .classType {
if let modifiers = self.modifiers {
if modifiers.isConvenience {
return false
}
return modifiers.isRequired
}
}
return false
}
}
extension SubscriptDeclSyntax: DeclProtocol {
var fullName: String {
return name + "_" + result.returnType.description.trimmed
}
var name: String {
return self.subscriptKeyword.text
}
var declType: DeclType {
return .subscriptType
}
var accessLevel: String {
return modifiers?.acl ?? ""
}
var inheritedTypes: [String] {
return []
}
var isExprOrStmt: Bool {
return false
}
var isOverride: Bool {
return false
}
var type: String {
return result.returnType.description.trimmed
}
var boundTypes: [String] {
return [result.returnType.tokens.exprTokenList,
genericParameterClause?.genericParameterList.tokens.exprTokenList,
genericWhereClause?.tokens.exprTokenList,
attributes?.tokens.exprTokenList,
].compactMap{$0}.flatMap{$0}.filter {$0 != name }
}
var boundTypesAL: [String] {
return boundTypes
}
var refTypes: [String] {
return [boundTypesAL,
accessor?.tokens.exprTokenList
].compactMap{$0}.flatMap{$0}
}
}
// MARK -
extension AttributeListSyntax {
var trimmedDescription: String? {
return self.withoutTrivia().description.trimmingCharacters(in: .whitespacesAndNewlines)
}
}
extension ModifierListSyntax {
var acl: String {
for modifier in self {
for token in modifier.tokens {
switch token.tokenKind {
case .publicKeyword, .internalKeyword, .privateKeyword, .fileprivateKeyword:
return token.text
default:
// For some reason openKeyword option is not available in TokenKind so need to address separately
if token.text == String.open {
return token.text
}
}
}
}
return ""
}
var isStatic: Bool {
return self.tokens.filter {$0.tokenKind == .staticKeyword }.count > 0
}
var isRequired: Bool {
return self.tokens.filter {$0.text == String.required }.count > 0
}
var isConvenience: Bool {
return self.tokens.filter {$0.text == String.convenience }.count > 0
}
var isOverride: Bool {
return self.tokens.filter {$0.text == String.override }.count > 0
}
var isFinal: Bool {
return self.tokens.filter {$0.text == String.final }.count > 0
}
var isPrivate: Bool {
return self.tokens.filter {$0.tokenKind == .privateKeyword || $0.tokenKind == .fileprivateKeyword }.count > 0
}
var isPublic: Bool {
return self.tokens.filter {$0.tokenKind == .publicKeyword }.count > 0
}
var isOpen: Bool {
return self.tokens.filter {$0.text == String.open }.count > 0
}
}
extension Trivia {
// This parses arguments in annotation which can be used to override certain types.
//
// E.g. given /// @mockable(typealias: T = Any; U = AnyObject), it returns
// a dictionary: [T: Any, U: AnyObject] which will be used to override inhertied types
// of typealias decls for T and U.
private func metadata(with annotation: String, in val: String) -> AnnotationMetadata? {
if val.contains(annotation) {
let comps = val.components(separatedBy: annotation)
var ret = AnnotationMetadata()
if var argsStr = comps.last, !argsStr.isEmpty {
if argsStr.hasPrefix("(") {
argsStr.removeFirst()
}
if argsStr.hasSuffix(")") {
argsStr.removeLast()
}
if argsStr.contains(String.typealiasColon), let subStr = argsStr.components(separatedBy: String.typealiasColon).last, !subStr.isEmpty {
ret.typeAliases = subStr.arguments(with: .annotationArgDelimiter)
}
if argsStr.contains(String.moduleColon), let subStr = argsStr.components(separatedBy: String.moduleColon).last, !subStr.isEmpty {
let val = subStr.arguments(with: .annotationArgDelimiter)
ret.module = val?[.prefix]
}
if argsStr.contains(String.rxColon), let subStr = argsStr.components(separatedBy: String.rxColon).last, !subStr.isEmpty {
ret.varTypes = subStr.arguments(with: .annotationArgDelimiter)
}
if argsStr.contains(String.varColon), let subStr = argsStr.components(separatedBy: String.varColon).last, !subStr.isEmpty {
if let val = subStr.arguments(with: .annotationArgDelimiter) {
if ret.varTypes == nil {
ret.varTypes = val
} else {
ret.varTypes?.merge(val, uniquingKeysWith: {$1})
}
}
}
}
return ret
}
return nil
}
// Looks up an annotation (e.g. /// @mockable) and its arguments if any.
// See metadata(with:, in:) for more info on the annotation arguments.
func annotationMetadata(with annotation: String) -> AnnotationMetadata? {
guard !annotation.isEmpty else { return nil }
var ret: AnnotationMetadata?
for i in 0..<count {
let trivia = self[i]
switch trivia {
case .docLineComment(let val):
ret = metadata(with: annotation, in: val)
if ret != nil {
return ret
}
case .docBlockComment(let val):
ret = metadata(with: annotation, in: val)
if ret != nil {
return ret
}
default:
continue
}
}
return nil
}
}
extension TokenSyntax {
var stringToken: String? {
if text == "self" || text == "Self" || text == "super" || text == "nil" ||
text == "as" || text == "true" || text == "false" || text == "AnyObject" || text == "Any" {
return nil
}
let startsWithLetter = text.first?.isLetter ?? false
let validFirstChar = text.first == "_" || startsWithLetter
if (validFirstChar && (text.isAlphanumeric || text.contains("_"))) ||
tokenKind == .spacedBinaryOperator(text) {
return text
}
return nil
}
var exprToken: String? {
if tokenKind != .stringQuote,
tokenKind != .stringSegment(text),
tokenKind != .spacedBinaryOperator(text),
tokenKind != .initKeyword {
return stringToken
}
return nil
}
func filteredToken(with suffix: String) -> String? {
if text.hasSuffix(suffix) {
return String(text.dropLast(suffix.count))
}
return nil
}
}
extension TokenSequence {
var tokenList: [String] {
return self.compactMap { $0.stringToken }
}
var exprTokenList: [String] {
return self.compactMap { $0.exprToken }
}
}
================================================
FILE: Sources/SwiftCodeSanKit/Utils/Extensions/SyntaxParserExtensions.swift
================================================
//
// Copyright (c) 2018. Uber Technologies
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
import SwiftSyntax
import SwiftSyntaxParser
extension SyntaxParser {
public static func parse(_ fileData: Data, path: String,
diagnosticHandler: ((Diagnostic) -> Void)? = nil) throws -> SourceFileSyntax {
// Avoid using `String(contentsOf:)` because it creates a wrapped NSString.
let source = fileData.withUnsafeBytes { buf in
return String(decoding: buf.bindMemory(to: UInt8.self), as: UTF8.self)
}
return try parse(source: source, filenameForDiagnostics: path,
diagnosticHandler: diagnosticHandler)
}
public static func parse(_ path: String) throws -> SourceFileSyntax {
guard let fileData = FileManager.default.contents(atPath: path) else {
fatalError("Retrieving contents of \(path) failed")
}
return try parse(fileData, path: path)
}
}
================================================
FILE: Sources/SwiftCodeSanKit/Utils/Logger.swift
================================================
//
// Copyright (c) 2018. Uber Technologies
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
import os.signpost
fileprivate let perfLog = OSLog(subsystem: "SwiftCodeSan", category: "PointsOfInterest")
fileprivate var prevTime: CFAbsoluteTime?
fileprivate var startTime: CFAbsoluteTime?
public var minLogLevel = 0
/// Logs status and other messages depending on the level provided
public enum LogLevel: Int {
case verbose
case info
case warning
case error
}
public func logTotalElapsed(_ arg: Any...) {
let cur = CFAbsoluteTimeGetCurrent()
if let startTime = startTime {
print(arg, cur-startTime)
} else {
print("0.00")
}
}
public func logTime(_ arg: Any...) {
let cur = CFAbsoluteTimeGetCurrent()
if let prevTime = prevTime {
var str = arg
let delta = (cur-prevTime)
str.append("Took \(delta)")
print(str)
}
prevTime = cur
if startTime == nil {
startTime = cur
}
}
public func log(_ arg: Int, level: LogLevel = .info, interval: Int) {
if arg > 0, arg % interval == 0 {
log(arg, level: level)
}
}
public func log(_ arg: Any..., level: LogLevel = .info, counter: inout Int, interval: Int, timed: Bool = false) {
if counter > 0, counter % interval == 0 {
log(arg, counter, level: level)
if timed {
logTime()
}
}
counter += 1
}
public func log(_ arg: Any..., level: LogLevel = .info) {
guard level.rawValue >= minLogLevel else { return }
switch level {
case .info, .verbose:
print(arg)
case .warning:
print("WARNING: \(arg)")
case .error:
print("ERROR: \(arg)")
}
}
public func signpost_begin(name: StaticString) {
if minLogLevel == LogLevel.verbose.rawValue {
os_signpost(.begin, log: perfLog, name: name)
}
}
public func signpost_end(name: StaticString) {
if minLogLevel == LogLevel.verbose.rawValue {
os_signpost(.end, log: perfLog, name: name)
}
}
================================================
FILE: Sources/SwiftCodeSanKit/Utils/Scanner.swift
================================================
//
// Copyright (c) 2018. Uber Technologies
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
public var scanConcurrencyLimit: Int? = nil
func semaphore(_ numThreads: Int?) -> DispatchSemaphore? {
let limit = concurrencyLimit(numThreads)
var sema: DispatchSemaphore?
if limit > 1 {
sema = DispatchSemaphore(value: limit)
}
return sema
}
func queue(_ numThreads: Int?) -> DispatchQueue? {
var q: DispatchQueue?
if concurrencyLimit(numThreads) > 1 {
q = DispatchQueue(label: "SwiftCodeSan-queue", qos: DispatchQoS.userInteractive, attributes: DispatchQueue.Attributes.concurrent)
}
return q
}
func concurrencyLimit(_ numThreads: Int?) -> Int {
var limit = 1
if let n = numThreads {
limit = n
} else if let n = scanConcurrencyLimit {
limit = n
}
return limit
}
public func scan(_ paths: [String],
isDirectory: Bool,
numThreads: Int? = nil,
block: @escaping (_ path: String, _ lock: NSLock?) -> ()) {
if isDirectory {
scan(dirs: paths, block: block)
} else {
scan(paths, block: block)
}
}
public func scan(dirs: [String],
numThreads: Int? = nil,
block: @escaping (_ path: String, _ lock: NSLock?) -> ()) {
if let queue = queue(numThreads) {
let sema = semaphore(numThreads)
let lock = NSLock()
scanDirs(dirs) { filePath in
_ = sema?.wait(timeout: DispatchTime.distantFuture)
queue.async {
block(filePath, lock)
sema?.signal()
}
}
// Wait for queue to drain
queue.sync(flags: .barrier) {}
} else {
scanDirs(dirs) { filePath in
block(filePath, nil)
}
}
}
public func scan<T>(_ elements: [T],
numThreads: Int? = nil,
block: @escaping (T, NSLock?) -> ()) {
if let queue = queue(numThreads) {
let sema = semaphore(numThreads)
let lock = NSLock()
for element in elements {
_ = sema?.wait(timeout: DispatchTime.distantFuture)
queue.async {
block(element, lock)
sema?.signal()
}
}
queue.sync(flags: .barrier) { }
} else {
for element in elements {
block(element, nil)
}
}
}
public func scan<T, U>(_ elements: [T: U],
numThreads: Int? = nil,
block: @escaping (T, U, NSLock?) -> ()) {
if let queue = queue(numThreads) {
let sema = semaphore(numThreads)
let lock = NSLock()
for element in elements {
_ = sema?.wait(timeout: DispatchTime.distantFuture)
queue.async {
block(element.key, element.value, lock)
sema?.signal()
}
}
queue.sync(flags: .barrier) { }
} else {
for element in elements {
block(element.key, element.value, nil)
}
}
}
public func scanDirs(_ paths: [String], with callBack: (String) -> Void) {
for path in paths {
scanDir(path, with: callBack)
}
}
func scanDir(_ path: String, with callBack: (String) -> Void) {
let errorHandler = { (url: URL, error: Error) -> Bool in
fatalError("Failed to traverse \(url) with error \(error).")
}
if let enumerator = FileManager.default.enumerator(at: URL(fileURLWithPath: path, isDirectory: true), includingPropertiesForKeys: nil, options: [.skipsHiddenFiles], errorHandler: errorHandler) {
while let nextObjc = enumerator.nextObject() {
if let fileUrl = nextObjc as? URL {
callBack(fileUrl.path)
}
}
}
}
================================================
FILE: Tests/SwiftCodeSanTestCase.swift
================================================
import XCTest
import SwiftCodeSanKit
class SwiftCodeSanTestCase: XCTestCase {
var srcFilePathsCount = 1
var mockFilePathsCount = 1
let bundle = Bundle(for: SwiftCodeSanTestCase.self)
lazy var dstFilePath: String = {
return bundle.bundlePath + "/Dst.swift"
}()
lazy var srcFilePaths: [String] = {
var idx = 0
var paths = [String]()
let prefix = bundle.bundlePath + "/Src"
let suffix = ".swift"
while idx < srcFilePathsCount {
let path = prefix + "\(idx)" + suffix
paths.append(path)
idx += 1
}
return paths
}()
lazy var mockFilePaths: [String] = {
var idx = 0
var paths = [String]()
let prefix = bundle.bundlePath + "/Mocks"
let suffix = ".swift"
while idx < mockFilePathsCount {
let path = prefix + "\(idx)" + suffix
paths.append(path)
idx += 1
}
return paths
}()
override func setUp() {
// Put setup code here. This method is called before the invocation of each test method in the class.
let created = FileManager.default.createFile(atPath: dstFilePath, contents: nil, attributes: nil)
XCTAssert(created)
}
override func tearDown() {
// Put teardown code here. This method is called after the invocation of each test method in the class.
try? FileManager.default.removeItem(atPath: dstFilePath)
for srcpath in srcFilePaths {
try? FileManager.default.removeItem(atPath: srcpath)
}
}
func verify(srcContent: String, dstContent: String, header: String = "", declType: DeclType = .protocolType, useTemplateFunc: Bool = false, testableImports: [String]? = [], concurrencyLimit: Int? = 1) {
verify(srcContents: [srcContent], dstContent: dstContent, header: header, declType: declType, useTemplateFunc: useTemplateFunc, testableImports: testableImports, concurrencyLimit: concurrencyLimit)
}
func verify(srcContents: [String], dstContent: String, header: String, declType: DeclType, useTemplateFunc: Bool, testableImports: [String]?, concurrencyLimit: Int?) {
var index = 0
srcFilePathsCount = srcContents.count
for src in srcContents {
if index < srcContents.count {
let srcCreated = FileManager.default.createFile(atPath: srcFilePaths[index], contents: src.data(using: .utf8), attributes: nil)
index += 1
XCTAssert(srcCreated)
}
}
// TODO: Pass in srcfile path - module list
removeDeadDecls(filesToModules: ["test1.swift": "test1"],
whitelist: nil,
topDeclsOnly: true,
inplace: true,
testFiles: [],
inplaceTests: false,
logFilePath: nil,
concurrencyLimit: nil,
onCompletion: {
let output = (try? String(contentsOf: URL(fileURLWithPath: self.dstFilePath), encoding: .utf8)) ?? ""
let outputContents = output.components(separatedBy: .whitespacesAndNewlines).filter{!$0.isEmpty}
XCTAssert(outputContents.count > 0)
})
}
}
================================================
FILE: Tests/TestClasses/Fixtures/test0.swift
================================================
#if TESTFILE
extension Integration {
var isUnitTest: Bool {
#if TEST
if case .test = RunType.current {
return !(Handler.isHandlerActive() || runner.sharedInstance.isActive)
}
#else
let x = SomeType().someMethod()
return x
#endif
}
var workersProviderMock: IntegrationProviderMock { shared { IntegrationProviderMock() } }
}
let s = SwipeTransitionController()
#endif
================================================
FILE: Tests/TestClasses/Fixtures/test1.swift
================================================
#if TESTFILE
import Foundation
public protocol FileHandler {
func createFile(atPath: String, contents: Data?, attributes: [FileAttributeKey: Any]?) -> Bool
func moveItem(at url: URL, to: URL) throws
func contentsOfDirectory(at url: URL, includingPropertiesForKeys keys: [URLResourceKey]?, options mask: FileManager.DirectoryEnumerationOptions) throws -> [URL]
func createDirectory(at url: URL, withIntermediateDirectories createIntermediates: Bool, attributes: [FileAttributeKey: Any]?) throws
func fileExists(atPath: String) -> Bool
func createFileHandle(forWritingToURL: URL) throws -> FileHandle
func read(contentsOf url: URL) throws -> String
@discardableResult
func write(dictionary: [String: Any], to url: URL, atomically: Bool) -> Bool
func write(data: Data, to url: URL, options: Data.WritingOptions) throws
func read(dictionaryAt url: URL) -> [String: Any]?
func data(forURL url: URL) -> Data?
func isDeletableFile(atURL url: URL) -> Bool
func removeItem(at URL: URL) throws
func url(for directory: FileManager.SearchPathDirectory, in domain: FileManager.SearchPathDomainMask) throws -> URL
func urls(for directory: FileManager.SearchPathDirectory, in domainMask: FileManager.SearchPathDomainMask) -> [URL]
func copyItem(at srcURL: URL, to dstURL: URL) throws
func setResourceValues(_ values: URLResourceValues, at url: inout URL) throws
}
extension FileManager: FileHandler {
public func write(data: Data, to url: URL, options: Data.WritingOptions) throws {
try data.write(to: url, options: options)
}
public func createFileHandle(forWritingToURL: URL) throws -> FileHandle {
return try FileHandle(forWritingTo: forWritingToURL)
}
public func read(contentsOf url: URL) throws -> String {
return try String(contentsOf: url, encoding: String.Encoding.utf8)
}
public func write(dictionary: [String: Any], to url: URL, atomically: Bool) -> Bool {
return (dictionary as NSDictionary).write(to: url as URL, atomically: atomically)
}
public func read(dictionaryAt url: URL) -> [String: Any]? {
return NSDictionary(contentsOf: url as URL) as? [String: Any]
}
public func data(forURL url: URL) -> Data? {
return try? Data(contentsOf: url)
}
public func isDeletableFile(atURL url: URL) -> Bool {
return isDeletableFile(atPath: url.path)
}
public func url(for directory: FileManager.SearchPathDirectory, in domain: FileManager.SearchPathDomainMask) throws -> URL {
return try url(for: directory, in: domain, appropriateFor: nil, create: true)
}
public func setResourceValues(_ values: URLResourceValues, at url: inout URL) throws {
try url.setResourceValues(values)
}
}
#endif
================================================
FILE: Tests/TestClasses/Fixtures/test2.swift
================================================
//
// Copyright © Uber Technologies, Inc. All rights reserved.
//
#if TESTFILE
import Foundation
@_transparent public func testAssert(_ resumable: Bool) {
let x = AssertStaticString()
print(x)
if testAssertionDisabled {
return
}
reassert(message(), file: unsafeBitCast(assertStaticString, to: StaticString.self), function: function, line: line)
let handler = AssertionHandlers.assertionFailure
print(handler)
}
class AssertionHandlers {
}
public struct AssertStaticString {
public init() {}
public func check() -> Int {
return 5
}
}
public extension String {
func someAssert(_ assertStaticString: AssertStaticString) {
}
}
public typealias AssertionHandler = (String?, data: LogModelMetadata?, line: UInt)
public let testAssertionDisabled: Bool = {
return false
}()
public func reassert(_ message: String, file: StaticString, function: String, line: UInt) {
}
public class SynchronizedFoo {
}
public protocol KeyValueSubscripting {
associatedtype Key
associatedtype Value
subscript(key: Key) -> Value? { get set }
}
public extension SynchronizedFoo: KeyValueSubscripting where T: KeyValueSubscripting {
public subscript(key: T.Key) -> T.Value? {
get {
return read { (collection) -> T.Value? in
return collection[key]
}
}
set {
write { (collection) -> () in
collection[key] = newValue
}
}
}
public func bar() -> String {
return "bar"
}
}
#endif
================================================
FILE: Tests/TestClasses/Fixtures/test3.swift
================================================
#if TESTFILE
import Foundation
public protocol ProtocolY {
func filterBy(uuid: String) -> Int?
}
public protocol ProtocolX: ProtocolY {
}
public final class FooKlass {
public static var sharedInstance = FooKlass()
func bar(arg: String) -> Int {
let (lhs, rhs) = arg
let ret = Double()
ret.listener = lhs
return ret
}
}
final class ListInteractor {
public var x: String = "", y: Int, z: Double
public var (v, w): (String, Int) = ("", 0)
public let
application: UIApplicationProtocol,
cachedExperiments: CachedExperimenting,
deepLinkBuilder: (_ url: URL) -> DetailsDeeplink?,
detailsBuilder: DetailsBuildable,
listStream: Observable<[DetailsListStreaming]>
}
#endif
================================================
FILE: Tests/TestClasses/Fixtures/test4.swift
================================================
#if TESTFILE
import Foundation
public enum BarType {
case current
}
class Klass: P {
private static var unusedP: SomeType = SomeType()
public init() {
}
func isActive(_ arg: AmbProtocol?) -> Bool {
arg?.usedFunc()
return true
}
public override func isApplicable(context: SomeContext, with flag: Flag) -> Bool {
return flag.isActive
}
func unusedFunc() {
print("...")
}
}
public final class OtherKlass {
public static var sharedInstance = OtherKlass()
public var shouldRun: Bool {
let (lhs, mhs, rhs) = asdf()
print(lhs, rhs)
return false
}
}
public protocol Flag {
}
public extension Flag {
var isActive: Bool {
return true
}
var isActiveLong: Bool {
return isEnabled(for: "Exp1") || isEnabled(for: "Exp2")
}
}
public protocol YProtocol {
var unusedY: String { get }
}
public extension YProtocol {
func usedZ() {
}
}
protocol UnusedInternal {
}
extension UnusedInternal {
func unusedX() {
}
}
#endif
================================================
FILE: Tests/TestClasses/Fixtures/test5.swift
================================================
#if TESTFILE
enum State {
case eat(SomeFood)
case sleep(SomeTime)
}
public func toJSON(arg: String) -> JSON {
var dict = [String: JSON]()
switch arg {
case .eat(let value):
print(value)
case .sleep(let value):
dict["state"] = value
}
return .dictionary(dict)
}
public typealias T = (String, EventHandler)
public typealias EventHandler<StreamingResponse> = (Bool, StreamingResponse?, Error?) -> ()
#if SOME_CONDITION
public extension String {
func withAssertion(_ block: (_ str: StaticString) -> ()) {
if let stringData = self.data(using: .utf8, allowLossyConversion: false) {
var x = StaticString()
x._utf8CodeUnitCount = stringData.count
stringData.withUnsafeBytes { rawBufferPointer in
if let rawPtr = rawBufferPointer.baseAddress {
x._startPtrOrData = Int(bitPattern: rawPtr)
}
block(x)
}
}
}
}
#endif
#endif
================================================
FILE: Tests/TestClasses/Fixtures/test6.swift
================================================
#if TESTFILE
import Foundation
import Test1
private let error = NSError(domain: TestDomain, code: InvalidData, userInfo: nil)
extension String {
static func instance(from data: Data) throws -> String {
let result = self.init(data: data, encoding: .utf8)
if let result = result {
return result
} else {
throw error
}
}
}
let SomeSingleton = SomeSingletonObject()
// This is a comment
// doc comment
// asdf
public protocol Bar: P1, P2 {
}
public typealias P1 = Cat.P1
public protocol XAB {
subscript(key: Int) -> String? { get }
}
class KEY {}
extension KEY: XAB {
public subscript(key: Int) -> String? {
return nil
}
func bar() {
}
var debugDescription: String {
return "this is KEY"
}
}
public protocol ObjectReference {
}
public protocol P {
func iteratedObjects(_ object: Any) -> [ObjectReference]
}
public final class SynchronousKeyValueStoreWrapper<Key>: SynchronousKeyValueAssociating where Key: StoredKeying {
var base: KeyValueStore<Key>?
public subscript(key: T.Key) -> T.Value? {
return nil
}
}
#endif
================================================
FILE: Tests/TestClasses/Fixtures/test7.swift
================================================
#if TESTFILE
extension Foo {
#if DEBUG
var baz: Bool {
if case .test = RunType.current {
return !(Handler.isHandlerActive() || runner.sharedInstance.isActive)
}
return false
}
var zmock: ZMock { shared { ZMock() } }
fileprivate var zvar: Z { shared { Z(with: self) } }
#else
fileprivate var cat: Z { shared { Z(with: self) } }
#endif
}
import Test6
let k = Klass()
class Klass: P {
public func iteratedObjects(_ object: Any) -> [Result] {
return []
}
private static var loadedFonts: Synchronized<[String: UIFont]> = Synchronized()
public init() {
testAssert(true)
}
public init(store: KeyValueStore<Key>,
storeKey: Key,
target: Int,
initialHitTargetValue: Bool? = nil) {
self.store = store
self.storeKey = storeKey
self.target = target + 1
self.hitTargetSubject = ReplaySubject<Bool>.create(bufferSize: 1)
queue.async {
let count = store.synchronously(operate: { (container: SynchronousKeyValueStoreContainer<Key>?) -> Int? in
return container?.item(for: storeKey)
})
print(count)
}
}
}
public final class KeyValueStore<Key> where Key: StoredKeying {
public func synchronously<T>(operations: (SynchronousKeyValueStoreContainer<Key>?) -> T) -> T {
let container = SynchronousKeyValueStoreContainer(self)
let result = operations(container)
container.invalidate()
return result
}
}
#endif
================================================
FILE: Tests/TestClasses/Fixtures/test8.swift
================================================
#if TESTFILE
import Test9
import Test10
import Foundation
class Foo: ProtocolX {
func filterBy(uuid: String) -> Int? {
return nil
}
var bar: Bool {
let found = FooKlass.sharedInstance.bar(arg)
let notFound = ListInteractor.application
if found, !notFound {
return true
}
}
var baz: String {
if case let .someCase = BarType.current {
return !(Klass().isActive() ||
Klass().isApplicable() ||
OtherKlass.sharedInstance.shouldRun)
}
return ""
}
}
let foo = Foo()
#endif
================================================
FILE: Tests/TestClasses/SwiftCodeSanTests.swift
================================================
import Foundation
class DCETests: SwiftCodeSanTestCase {
func testExample() {
// TODO: add a test
}
}
================================================
FILE: install-script.sh
================================================
#!/bin/bash
# Causes the shell to exit if any subcommand returns a non-zero status
set -e
showhelp() {
echo "Description: Builds and installs target specified
Usage: -s/--source-dir [source dir], -t/--target [name of target to build/install], -d/--destination-dir [destination dir], -o/--output [output file name in tar.gz]"
exit
}
if [[ $1 == "" ]]
then
showhelp
fi
realpath() {
[[ $1 = /* ]] && echo "$1" || echo "$PWD"
}
while [[ $# -gt 0 ]]
do
key="$1"
case $key in
-s|--source-dir)
SRCDIR=$(realpath "$2")
shift # past argument
shift # past value
;;
-d|--destination-dir)
DESTDIR=$(realpath "$2")
shift # past argument
shift # past value
;;
-t|--target)
TARGET="$2"
shift # past argument
shift # past value
;;
-o|--output)
OUTFILE="$2"
shift # past argument
shift # past value
;;
-h|--help)
showhelp
;;
*)
showhelp
;;
esac
done
CUR=$PWD
echo "** Clean/Build..."
echo "SOURCE DIR = ${SRCDIR}"
echo "TARGET = ${TARGET}"
echo "DESTINATION DIR = ${DESTDIR}"
echo "OUTPUT FILE = ${OUTFILE}"
cd "$SRCDIR"
rm -rf .build
swift build -c release
cd .build/release
echo "** Install..."
cp "$(xcode-select -p)"/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/macosx/lib_InternalSwiftSyntaxParser.dylib .
install_name_tool -change @rpath/lib_InternalSwiftSyntaxParser.dylib @executable_path/lib_InternalSwiftSyntaxParser.dylib "$TARGET"
tar -cvzf "$OUTFILE" "$TARGET" lib_InternalSwiftSyntaxParser.dylib
mv "$OUTFILE" "$DESTDIR"
cd "$CUR"
echo "** Output file is at $DESTDIR/$OUTFILE"
echo "** Done."
gitextract__6ffq4gh/ ├── .github/ │ └── workflows/ │ └── ci.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE.txt ├── NOTICE.txt ├── Package.swift ├── README.md ├── Sources/ │ ├── SwiftCodeSan/ │ │ ├── Executor.swift │ │ └── main.swift │ └── SwiftCodeSanKit/ │ ├── Core/ │ │ ├── AccessLevelRewriter.swift │ │ ├── DeclMetaTypes.swift │ │ ├── DeclRemover.swift │ │ ├── DeclVisitor.swift │ │ ├── ImportRewriter.swift │ │ └── RefChecker.swift │ ├── FileParsers/ │ │ └── DeclParser.swift │ ├── FileUpdaters/ │ │ └── DeclUpdater.swift │ ├── Operations/ │ │ ├── RemoveDeadDecls.swift │ │ ├── RemoveUnusedImports.swift │ │ └── UpdateAccessLevels.swift │ └── Utils/ │ ├── Extensions/ │ │ ├── FileManagerExtensions.swift │ │ ├── SequenceExtensions.swift │ │ ├── StringExtensions.swift │ │ ├── SyntaxExtensions.swift │ │ └── SyntaxParserExtensions.swift │ ├── Logger.swift │ └── Scanner.swift ├── Tests/ │ ├── SwiftCodeSanTestCase.swift │ └── TestClasses/ │ ├── Fixtures/ │ │ ├── test0.swift │ │ ├── test1.swift │ │ ├── test2.swift │ │ ├── test3.swift │ │ ├── test4.swift │ │ ├── test5.swift │ │ ├── test6.swift │ │ ├── test7.swift │ │ └── test8.swift │ └── SwiftCodeSanTests.swift └── install-script.sh
Condensed preview — 40 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (199K chars).
[
{
"path": ".github/workflows/ci.yml",
"chars": 297,
"preview": "name: CI\non:\n push:\n branches: [master]\n pull_request:\n branches: [master]\njobs:\n macos:\n runs-on: macOS-lat"
},
{
"path": ".gitignore",
"chars": 534,
"preview": "## Xcode projects\n*.xcodeproj\n*.xcworkspace\n\n## Build generated\nbuild/\nDerivedData/\n\n## Various settings\n*.pbxuser\n!defa"
},
{
"path": "CODE_OF_CONDUCT.md",
"chars": 3224,
"preview": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, w"
},
{
"path": "CONTRIBUTING.md",
"chars": 3224,
"preview": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, w"
},
{
"path": "LICENSE.txt",
"chars": 11358,
"preview": "\n Apache License\n Version 2.0, January 2004\n "
},
{
"path": "NOTICE.txt",
"chars": 642,
"preview": "SwiftMockGen depends on the following libraries:\n\nSwift Package Manager (https://github.com/apple/swift-package-manager)"
},
{
"path": "Package.swift",
"chars": 1243,
"preview": "// swift-tools-version:5.2\nimport PackageDescription\n\nlet package = Package(\n name: \"SwiftCodeSan\",\n platforms: [\n"
},
{
"path": "README.md",
"chars": 6321,
"preview": "# \n[](https:/"
},
{
"path": "Sources/SwiftCodeSan/Executor.swift",
"chars": 9978,
"preview": "//\n// Copyright (c) 2018. Uber Technologies\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// "
},
{
"path": "Sources/SwiftCodeSan/main.swift",
"chars": 802,
"preview": "//\n// Copyright (c) 2018. Uber Technologies\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// "
},
{
"path": "Sources/SwiftCodeSanKit/Core/AccessLevelRewriter.swift",
"chars": 7660,
"preview": "//\n// Copyright (c) 2018. Uber Technologies\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// "
},
{
"path": "Sources/SwiftCodeSanKit/Core/DeclMetaTypes.swift",
"chars": 6834,
"preview": "//\n// Copyright (c) 2018. Uber Technologies\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// "
},
{
"path": "Sources/SwiftCodeSanKit/Core/DeclRemover.swift",
"chars": 4988,
"preview": "//\n// Copyright (c) 2018. Uber Technologies\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// "
},
{
"path": "Sources/SwiftCodeSanKit/Core/DeclVisitor.swift",
"chars": 6669,
"preview": "//\n// Copyright (c) 2018. Uber Technologies\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// "
},
{
"path": "Sources/SwiftCodeSanKit/Core/ImportRewriter.swift",
"chars": 1852,
"preview": "//\n// Copyright (c) 2018. Uber Technologies\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// "
},
{
"path": "Sources/SwiftCodeSanKit/Core/RefChecker.swift",
"chars": 4183,
"preview": "//\n// Copyright (c) 2018. Uber Technologies\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// "
},
{
"path": "Sources/SwiftCodeSanKit/FileParsers/DeclParser.swift",
"chars": 6939,
"preview": "//\n// Copyright (c) 2018. Uber Technologies\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// "
},
{
"path": "Sources/SwiftCodeSanKit/FileUpdaters/DeclUpdater.swift",
"chars": 3684,
"preview": "//\n// Copyright (c) 2018. Uber Technologies\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// "
},
{
"path": "Sources/SwiftCodeSanKit/Operations/RemoveDeadDecls.swift",
"chars": 15234,
"preview": "//\n// Copyright (c) 2018. Uber Technologies\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// "
},
{
"path": "Sources/SwiftCodeSanKit/Operations/RemoveUnusedImports.swift",
"chars": 5158,
"preview": "//\n// Copyright (c) 2018. Uber Technologies\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// "
},
{
"path": "Sources/SwiftCodeSanKit/Operations/UpdateAccessLevels.swift",
"chars": 16768,
"preview": "//\n// Copyright (c) 2018. Uber Technologies\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// "
},
{
"path": "Sources/SwiftCodeSanKit/Utils/Extensions/FileManagerExtensions.swift",
"chars": 1137,
"preview": "//\n// Copyright (c) 2018. Uber Technologies\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// "
},
{
"path": "Sources/SwiftCodeSanKit/Utils/Extensions/SequenceExtensions.swift",
"chars": 1743,
"preview": "\n//\n// Copyright (c) 2018. Uber Technologies\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// "
},
{
"path": "Sources/SwiftCodeSanKit/Utils/Extensions/StringExtensions.swift",
"chars": 2505,
"preview": "//\n// Copyright (c) 2018. Uber Technologies\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// "
},
{
"path": "Sources/SwiftCodeSanKit/Utils/Extensions/SyntaxExtensions.swift",
"chars": 42524,
"preview": "//\n// Copyright (c) 2018. Uber Technologies\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// "
},
{
"path": "Sources/SwiftCodeSanKit/Utils/Extensions/SyntaxParserExtensions.swift",
"chars": 1525,
"preview": "//\n// Copyright (c) 2018. Uber Technologies\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// "
},
{
"path": "Sources/SwiftCodeSanKit/Utils/Logger.swift",
"chars": 2551,
"preview": "//\n// Copyright (c) 2018. Uber Technologies\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// "
},
{
"path": "Sources/SwiftCodeSanKit/Utils/Scanner.swift",
"chars": 4350,
"preview": "//\n// Copyright (c) 2018. Uber Technologies\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// "
},
{
"path": "Tests/SwiftCodeSanTestCase.swift",
"chars": 3424,
"preview": "import XCTest\nimport SwiftCodeSanKit\n\nclass SwiftCodeSanTestCase: XCTestCase {\n var srcFilePathsCount = 1\n var moc"
},
{
"path": "Tests/TestClasses/Fixtures/test0.swift",
"chars": 450,
"preview": "#if TESTFILE\nextension Integration {\n var isUnitTest: Bool {\n #if TEST\n if case .test = RunType.current"
},
{
"path": "Tests/TestClasses/Fixtures/test1.swift",
"chars": 2813,
"preview": "#if TESTFILE\nimport Foundation\n\npublic protocol FileHandler {\n\n func createFile(atPath: String, contents: Data?, attr"
},
{
"path": "Tests/TestClasses/Fixtures/test2.swift",
"chars": 1598,
"preview": "//\n// Copyright © Uber Technologies, Inc. All rights reserved.\n//\n#if TESTFILE\n\nimport Foundation\n\n@_transparent public"
},
{
"path": "Tests/TestClasses/Fixtures/test3.swift",
"chars": 750,
"preview": "#if TESTFILE\n\n\nimport Foundation\n\npublic protocol ProtocolY {\n func filterBy(uuid: String) -> Int?\n}\npublic protocol "
},
{
"path": "Tests/TestClasses/Fixtures/test4.swift",
"chars": 1082,
"preview": "#if TESTFILE\n\n\nimport Foundation\n\npublic enum BarType {\n case current\n}\n\n\nclass Klass: P {\n private static var unu"
},
{
"path": "Tests/TestClasses/Fixtures/test5.swift",
"chars": 1083,
"preview": "#if TESTFILE\n\nenum State {\n case eat(SomeFood)\n case sleep(SomeTime)\n}\n\npublic func toJSON(arg: String) -> JSON {\n"
},
{
"path": "Tests/TestClasses/Fixtures/test6.swift",
"chars": 1169,
"preview": "#if TESTFILE\n\nimport Foundation\nimport Test1\n\n\nprivate let error = NSError(domain: TestDomain, code: InvalidData, userIn"
},
{
"path": "Tests/TestClasses/Fixtures/test7.swift",
"chars": 1633,
"preview": "#if TESTFILE\n\nextension Foo {\n #if DEBUG\n var baz: Bool {\n if case .test = RunType.current {\n "
},
{
"path": "Tests/TestClasses/Fixtures/test8.swift",
"chars": 617,
"preview": "#if TESTFILE\n\nimport Test9\nimport Test10\nimport Foundation\n\nclass Foo: ProtocolX {\n func filterBy(uuid: String) -> In"
},
{
"path": "Tests/TestClasses/SwiftCodeSanTests.swift",
"chars": 130,
"preview": "import Foundation\n\nclass DCETests: SwiftCodeSanTestCase {\n \n func testExample() {\n // TODO: add a test\n "
},
{
"path": "install-script.sh",
"chars": 1633,
"preview": "#!/bin/bash\n\n# Causes the shell to exit if any subcommand returns a non-zero status\nset -e\n\nshowhelp() {\n echo \"Descr"
}
]
About this extraction
This page contains the full source code of the uber/SwiftCodeSan GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 40 files (185.8 KB), approximately 42.4k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.