├── .github └── workflows │ ├── ci.yml │ └── documentation.yml ├── .gitignore ├── Changelog.md ├── LICENSE.md ├── Package.swift ├── Package@swift-5.2.swift ├── Package@swift-5.4.swift ├── Package@swift-5.5.swift ├── README.md ├── Sources └── SwiftSemantics │ ├── Declaration.swift │ ├── DeclarationCollector.swift │ ├── Declarations │ ├── AssociatedType.swift │ ├── Class.swift │ ├── ConditionalCompilationBlock.swift │ ├── Deinitializer.swift │ ├── Enumeration.swift │ ├── Extension.swift │ ├── Function.swift │ ├── Import.swift │ ├── Initializer.swift │ ├── Operator.swift │ ├── PrecedenceGroup.swift │ ├── Protocol.swift │ ├── Structure.swift │ ├── Subscript.swift │ ├── Typealias.swift │ └── Variable.swift │ ├── ExpressibleBySyntax.swift │ ├── Extensions │ ├── StringProtocol+Extensions.swift │ └── SwiftSyntax+Extensions.swift │ └── Supporting Types │ ├── Attribute.swift │ ├── GenericParameter.swift │ ├── GenericRequirement.swift │ └── Modifier.swift └── Tests ├── LinuxMain.swift └── SwiftSemanticsTests ├── AssociatedTypeTests.swift ├── AttributeTests.swift ├── ConditionalCompilationBlockTests.swift ├── DeclarationCollectorTests.swift ├── ExtensionTests.swift ├── Extensions └── SyntaxParser+Declarations.swift ├── FunctionTests.swift ├── GenericRequirementTests.swift ├── ImportTests.swift ├── InitializerTests.swift ├── ModifierTests.swift ├── OperatorTests.swift ├── ProtocolTests.swift ├── StructureTests.swift ├── SubscriptTests.swift ├── TypealiasTests.swift ├── VariableTests.swift └── XCTestManifests.swift /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | 9 | jobs: 10 | macos_big_sur: 11 | runs-on: macos-11.0 12 | 13 | strategy: 14 | matrix: 15 | xcode: 16 | - "11.7" # Swift 5.2 17 | - "12.2" # Swift 5.3 18 | 19 | name: "macOS Big Sur (Xcode ${{ matrix.xcode }})" 20 | 21 | steps: 22 | - name: Checkout 23 | uses: actions/checkout@v1 24 | - uses: actions/cache@v2 25 | with: 26 | path: .build 27 | key: ${{ runner.os }}-spm-xcode-${{ matrix.xcode }}-${{ hashFiles('**/Package.resolved') }} 28 | restore-keys: | 29 | ${{ runner.os }}-spm-xcode-${{ matrix.xcode }}- 30 | - name: Build and Test 31 | run: | 32 | swift test 33 | env: 34 | DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer 35 | 36 | macos_catalina: 37 | runs-on: macos-10.15 38 | 39 | strategy: 40 | matrix: 41 | xcode: 42 | - "11.7" # Swift 5.2 43 | - "12" # Swift 5.3 44 | 45 | name: "macOS Catalina (Xcode ${{ matrix.xcode }})" 46 | 47 | steps: 48 | - name: Checkout 49 | uses: actions/checkout@v1 50 | - uses: actions/cache@v2 51 | with: 52 | path: .build 53 | key: ${{ runner.os }}-spm-xcode-${{ matrix.xcode }}-${{ hashFiles('**/Package.resolved') }} 54 | restore-keys: | 55 | ${{ runner.os }}-spm-xcode-${{ matrix.xcode }}- 56 | - name: Build and Test 57 | run: | 58 | swift test 59 | env: 60 | DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer 61 | 62 | linux: 63 | runs-on: ubuntu-latest 64 | 65 | strategy: 66 | matrix: 67 | swift: ["5.3", "5.2"] 68 | 69 | name: "Linux (Swift ${{ matrix.swift }})" 70 | 71 | container: 72 | image: swift:${{ matrix.swift }} 73 | 74 | steps: 75 | - name: Checkout 76 | uses: actions/checkout@v1 77 | - uses: actions/cache@v2 78 | with: 79 | path: .build 80 | key: ${{ runner.os }}-spm-swift-${{ matrix.swift }}-${{ hashFiles('**/Package.resolved') }} 81 | restore-keys: | 82 | ${{ runner.os }}-spm-swift-${{ matrix.swift }}- 83 | - name: Build and Test 84 | run: | 85 | swift test --enable-test-discovery 86 | 87 | windows: 88 | runs-on: windows-latest 89 | 90 | steps: 91 | - uses: actions/checkout@v2 92 | - uses: seanmiddleditch/gha-setup-vsdevenv@master 93 | 94 | - name: Install swift-5.5 (2021-05-02 SNAPSHOT) 95 | run: | 96 | Install-Binary -Url "https://swift.org/builds/swift-5.5-branch/windows10/swift-5.5-DEVELOPMENT-SNAPSHOT-2021-05-02-a/swift-5.5-DEVELOPMENT-SNAPSHOT-2021-05-02-a-windows10.exe" -Name "installer.exe" -ArgumentList ("-q") 97 | - name: Set Environment Variables 98 | run: | 99 | echo "SDKROOT=C:\Library\Developer\Platforms\Windows.platform\Developer\SDKs\Windows.sdk" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append 100 | echo "DEVELOPER_DIR=C:\Library\Developer" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append 101 | - name: Adjust Paths 102 | run: | 103 | echo "C:\Library\Swift-development\bin;C:\Library\icu-67\usr\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append 104 | echo "C:\Library\Developer\Toolchains\unknown-Asserts-development.xctoolchain\usr\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append 105 | - name: Install Supporting Files 106 | run: | 107 | Copy-Item "$env:SDKROOT\usr\share\ucrt.modulemap" -destination "$env:UniversalCRTSdkDir\Include\$env:UCRTVersion\ucrt\module.modulemap" 108 | Copy-Item "$env:SDKROOT\usr\share\visualc.modulemap" -destination "$env:VCToolsInstallDir\include\module.modulemap" 109 | Copy-Item "$env:SDKROOT\usr\share\visualc.apinotes" -destination "$env:VCToolsInstallDir\include\visualc.apinotes" 110 | Copy-Item "$env:SDKROOT\usr\share\winsdk.modulemap" -destination "$env:UniversalCRTSdkDir\Include\$env:UCRTVersion\um\module.modulemap" 111 | 112 | - name: Build and Test 113 | run: swift test --enable-test-discovery -Xcc -IC:\Library\Developer\Toolchains\unknown-Asserts-development.xctoolchain\usr\include -Xlinker -LC:\Library\Developer\Toolchains\unknown-Asserts-development.xctoolchain\usr\lib 114 | -------------------------------------------------------------------------------- /.github/workflows/documentation.yml: -------------------------------------------------------------------------------- 1 | name: Documentation 2 | 3 | on: 4 | workflow_dispatch: {} 5 | push: 6 | branches: 7 | - main 8 | paths: 9 | - .github/workflows/documentation.yml 10 | - Sources/**.swift 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v1 19 | - name: Generate Documentation 20 | uses: SwiftDocOrg/swift-doc@master 21 | with: 22 | inputs: "Sources" 23 | output: "Documentation" 24 | - name: Upload Documentation to Wiki 25 | uses: SwiftDocOrg/github-wiki-publish-action@master 26 | with: 27 | path: "Documentation" 28 | env: 29 | GITHUB_PERSONAL_ACCESS_TOKEN: ${{ secrets.GITHUB_PERSONAL_ACCESS_TOKEN }} 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | .swiftpm/ 6 | xcuserdata/ 7 | Package.resolved 8 | -------------------------------------------------------------------------------- /Changelog.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [Unreleased] 9 | 10 | ## [0.3.2] - 2021-05-13 11 | 12 | ### Changed 13 | 14 | - Changed swift-syntax dependency to target `release/5.5` branch 15 | to resolve an issue on Windows. 16 | #17 by @compnerd. 17 | 18 | ## [0.3.1] - 2021-05-04 19 | 20 | ### Changed 21 | 22 | - Changed swift-syntax dependency to target `release/5.4` branch 23 | instead of `0.50400.0` tag to resolve an issue on Windows. 24 | #16 by @compnerd. 25 | 26 | ## [0.3.0] - 2021-04-23 27 | 28 | ### Added 29 | 30 | - Added support for Swift 5.4 and 5.5. 31 | #12 by @compnerd and @mattt. 32 | 33 | ## [0.2.0] - 2020-11-11 34 | 35 | ### Added 36 | 37 | - Added support for Swift 5.3. 38 | #10 by @mattt. 39 | 40 | ## [0.1.0] - 2020-03-28 41 | 42 | ### Changed 43 | 44 | - Changed swift-syntax dependency to support Swift 5.2. 45 | 4fdc48b by @mattt. 46 | 47 | ## [0.0.2] - 2020-02-14 48 | 49 | ### Added 50 | 51 | - Added documentation for public APIs. 52 | 1165c7a by @mattt. 53 | 54 | ### Changed 55 | 56 | - Changed `ExpressibleBySyntax` requirement to be optional initializer. 57 | f0f84ab by @mattt. 58 | - Changed `AssociatedType` to conform to `CustomStringConvertible`. 59 | 0495879 by @mattt. 60 | - Changed `Typealias` to conform to `CustomStringConvertible`. 61 | ba52df3 by @mattt. 62 | 63 | ## [0.0.1] - 2020-01-21 64 | 65 | Initial release. 66 | 67 | [unreleased]: https://github.com/SwiftDocOrg/SwiftSemantics/compare/0.3.2...main 68 | [0.3.2]: https://github.com/SwiftDocOrg/SwiftSemantics/releases/tag/0.3.2 69 | [0.3.1]: https://github.com/SwiftDocOrg/SwiftSemantics/releases/tag/0.3.1 70 | [0.3.0]: https://github.com/SwiftDocOrg/SwiftSemantics/releases/tag/0.3.0 71 | [0.2.0]: https://github.com/SwiftDocOrg/SwiftSemantics/releases/tag/0.2.0 72 | [0.1.0]: https://github.com/SwiftDocOrg/SwiftSemantics/releases/tag/0.1.0 73 | [0.0.2]: https://github.com/SwiftDocOrg/SwiftSemantics/releases/tag/0.0.2 74 | [0.0.1]: https://github.com/SwiftDocOrg/SwiftSemantics/releases/tag/0.0.1 75 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright 2019 Read Evaluate Press, LLC 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a 4 | copy of this software and associated documentation files (the "Software"), 5 | to deal in the Software without restriction, including without limitation 6 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 7 | and/or sell copies of the Software, and to permit persons to whom the 8 | Software is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 14 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 19 | DEALINGS IN THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.3 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "SwiftSemantics", 8 | products: [ 9 | // Products define the executables and libraries produced by a package, and make them visible to other packages. 10 | .library( 11 | name: "SwiftSemantics", 12 | targets: ["SwiftSemantics"] 13 | ), 14 | ], 15 | dependencies: [ 16 | // Dependencies declare other packages that this package depends on. 17 | .package(name: "SwiftSyntax", 18 | url: "https://github.com/apple/swift-syntax.git", 19 | .exact("0.50300.0")), 20 | ], 21 | targets: [ 22 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 23 | // Targets can depend on other targets in this package, and on products in packages which this package depends on. 24 | .target( 25 | name: "SwiftSemantics", 26 | dependencies: ["SwiftSyntax"] 27 | ), 28 | .testTarget( 29 | name: "SwiftSemanticsTests", 30 | dependencies: ["SwiftSemantics"] 31 | ), 32 | ] 33 | ) 34 | -------------------------------------------------------------------------------- /Package@swift-5.2.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.1 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "SwiftSemantics", 8 | products: [ 9 | // Products define the executables and libraries produced by a package, and make them visible to other packages. 10 | .library( 11 | name: "SwiftSemantics", 12 | targets: ["SwiftSemantics"] 13 | ), 14 | ], 15 | dependencies: [ 16 | // Dependencies declare other packages that this package depends on. 17 | .package(url: "https://github.com/apple/swift-syntax.git", 18 | .exact("0.50200.0")), 19 | ], 20 | targets: [ 21 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 22 | // Targets can depend on other targets in this package, and on products in packages which this package depends on. 23 | .target( 24 | name: "SwiftSemantics", 25 | dependencies: ["SwiftSyntax"] 26 | ), 27 | .testTarget( 28 | name: "SwiftSemanticsTests", 29 | dependencies: ["SwiftSemantics"] 30 | ), 31 | ] 32 | ) 33 | -------------------------------------------------------------------------------- /Package@swift-5.4.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.3 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "SwiftSemantics", 8 | products: [ 9 | // Products define the executables and libraries produced by a package, and make them visible to other packages. 10 | .library( 11 | name: "SwiftSemantics", 12 | targets: ["SwiftSemantics"] 13 | ), 14 | ], 15 | dependencies: [ 16 | // Dependencies declare other packages that this package depends on. 17 | .package(name: "SwiftSyntax", 18 | url: "https://github.com/apple/swift-syntax.git", 19 | .revision("release/5.4")), 20 | ], 21 | targets: [ 22 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 23 | // Targets can depend on other targets in this package, and on products in packages which this package depends on. 24 | .target( 25 | name: "SwiftSemantics", 26 | dependencies: ["SwiftSyntax"] 27 | ), 28 | .testTarget( 29 | name: "SwiftSemanticsTests", 30 | dependencies: ["SwiftSemantics"] 31 | ), 32 | ] 33 | ) 34 | -------------------------------------------------------------------------------- /Package@swift-5.5.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.3 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "SwiftSemantics", 8 | products: [ 9 | // Products define the executables and libraries produced by a package, and make them visible to other packages. 10 | .library( 11 | name: "SwiftSemantics", 12 | targets: ["SwiftSemantics"] 13 | ), 14 | ], 15 | dependencies: [ 16 | // Dependencies declare other packages that this package depends on. 17 | .package(name: "SwiftSyntax", 18 | url: "https://github.com/apple/swift-syntax.git", 19 | .revision("release/5.5")), 20 | ], 21 | targets: [ 22 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 23 | // Targets can depend on other targets in this package, and on products in packages which this package depends on. 24 | .target( 25 | name: "SwiftSemantics", 26 | dependencies: ["SwiftSyntax"] 27 | ), 28 | .testTarget( 29 | name: "SwiftSemanticsTests", 30 | dependencies: ["SwiftSemantics"] 31 | ), 32 | ] 33 | ) 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SwiftSemantics 2 | 3 | ![CI][ci badge] 4 | [![Documentation][documentation badge]][documentation] 5 | 6 | SwiftSemantics is a package that lets you 7 | parse Swift code into its constituent declarations. 8 | 9 | Use [SwiftSyntax][swiftsyntax] to construct 10 | an abstract syntax tree from Swift source code, 11 | then walk the AST with the provided `DeclarationCollector` 12 | (or with your own `SyntaxVisitor`-conforming type) 13 | and construct a `Declaration` value for each visited `DeclSyntax` node: 14 | 15 | ```swift 16 | import SwiftSyntax 17 | import SwiftSemantics 18 | 19 | let source = #""" 20 | import UIKit 21 | 22 | class ViewController: UIViewController, UITableViewDelegate { 23 | enum Section: Int { 24 | case summary, people, places 25 | } 26 | 27 | var people: [People], places: [Place] 28 | 29 | @IBOutlet private(set) var tableView: UITableView! 30 | } 31 | """# 32 | 33 | var collector = DeclarationCollector() 34 | let tree = try SyntaxParser.parse(source: source) 35 | collector.walk(tree) 36 | 37 | // Import declarations 38 | collector.imports.first?.pathComponents // ["UIKit"] 39 | 40 | // Class declarations 41 | collector.classes.first?.name // "ViewController" 42 | collector.classes.first?.inheritance // ["UIViewController", "UITableViewDelegate"] 43 | 44 | // Enumeration declarations 45 | collector.enumerations.first?.name // "Section" 46 | 47 | // Enumeration case declarations 48 | collector.enumerationCases.count // 3 49 | collector.enumerationCases.map { $0.name } // ["summary", "people", "places"]) 50 | 51 | // Variable (property) declarations 52 | collector.variables.count // 3 53 | collector.variables[0].name // "people" 54 | collector.variables[1].typeAnnotation // "[Place]" 55 | collector.variables[2].name // "tableView" 56 | collector.variables[2].typeAnnotation // "UITableView!" 57 | collector.variables[2].attributes.first?.name // "IBOutlet" 58 | collector.variables[2].modifiers.first?.name // "private" 59 | collector.variables[2].modifiers.first?.detail // "set" 60 | ``` 61 | 62 | > **Note**: 63 | > For more information about SwiftSyntax, 64 | > see [this article from NSHipster][nshipster swiftsyntax]. 65 | 66 | This package is used by [swift-doc][swift-doc] 67 | in coordination with [SwiftMarkup][swiftmarkup] 68 | to generate documentation for Swift projects 69 | _([including this one][swiftsemantics documentation])_. 70 | 71 | ## Requirements 72 | 73 | - Swift 5.2, 5.3, 5.4, or 5.5 74 | 75 | ## Installation 76 | 77 | ### Swift Package Manager 78 | 79 | Add the SwiftSemantics package to your target dependencies in `Package.swift`: 80 | 81 | ```swift 82 | // swift-tools-version:5.3 83 | 84 | import PackageDescription 85 | 86 | let package = Package( 87 | name: "YourProject", 88 | dependencies: [ 89 | .package( 90 | name: "SwiftSemantics", 91 | url: "https://github.com/SwiftDocOrg/SwiftSemantics", 92 | .exact("0.3.2") 93 | ) 94 | ] 95 | ) 96 | ``` 97 | 98 | If your project has a direct dependency `SwiftSyntax`, 99 | use the declaration below that corresponds to your Swift language version: 100 | 101 | ```swift 102 | // Swift 5.2 103 | .package(url: "https://github.com/apple/swift-syntax.git", 104 | .exact("0.50200.0")), 105 | 106 | // Swift 5.3 107 | .package(name: "SwiftSyntax", 108 | url: "https://github.com/apple/swift-syntax.git", 109 | .exact("0.50300.0")), 110 | 111 | // Swift 5.4 112 | .package(name: "SwiftSyntax", 113 | url: "https://github.com/apple/swift-syntax.git", 114 | .revision("release/5.4")), 115 | 116 | // Swift 5.5 117 | .package(name: "SwiftSyntax", 118 | url: "https://github.com/apple/swift-syntax.git", 119 | .revision("release/5.5")), 120 | ``` 121 | 122 | ## Detailed Design 123 | 124 | Swift defines 17 different kinds of declarations, 125 | each of which is represented by a corresponding type in SwiftSemantics 126 | that conforms to the 127 | [`Declaration` protocol](https://github.com/SwiftDocOrg/SwiftSemantics/wiki/Declaration): 128 | 129 | - [`AssociatedType`](https://github.com/SwiftDocOrg/SwiftSemantics/wiki/AssociatedType) 130 | - [`Class`](https://github.com/SwiftDocOrg/SwiftSemantics/wiki/Class) 131 | - [`ConditionalCompilationBlock`](https://github.com/SwiftDocOrg/SwiftSemantics/wiki/ConditionalCompilationBlock) 132 | - [`Deinitializer`](https://github.com/SwiftDocOrg/SwiftSemantics/wiki/Deinitializer) 133 | - [`Enumeration`](https://github.com/SwiftDocOrg/SwiftSemantics/wiki/Enumeration) 134 | - [`Enumeration.Case`](https://github.com/SwiftDocOrg/SwiftSemantics/wiki/Enumeration_Case) 135 | - [`Extension`](https://github.com/SwiftDocOrg/SwiftSemantics/wiki/Extension) 136 | - [`Function`](https://github.com/SwiftDocOrg/SwiftSemantics/wiki/Function) 137 | - [`Import`](https://github.com/SwiftDocOrg/SwiftSemantics/wiki/Import) 138 | - [`Initializer`](https://github.com/SwiftDocOrg/SwiftSemantics/wiki/Initializer) 139 | - [`Operator`](https://github.com/SwiftDocOrg/SwiftSemantics/wiki/Operator) 140 | - [`PrecedenceGroup`](https://github.com/SwiftDocOrg/SwiftSemantics/wiki/PrecedenceGroup) 141 | - [`Protocol`](https://github.com/SwiftDocOrg/SwiftSemantics/wiki/Protocol) 142 | - [`Structure`](https://github.com/SwiftDocOrg/SwiftSemantics/wiki/Structure) 143 | - [`Subscript`](https://github.com/SwiftDocOrg/SwiftSemantics/wiki/Subscript) 144 | - [`Typealias`](https://github.com/SwiftDocOrg/SwiftSemantics/wiki/Typealias) 145 | - [`Variable`](https://github.com/SwiftDocOrg/SwiftSemantics/wiki/Variable) 146 | 147 | > **Note**: 148 | > Examples of each declaration are provided in the documentation 149 | > as well as [unit tests](https://github.com/SwiftDocOrg/SwiftSemantics/tree/master/Tests/SwiftSemanticsTests). 150 | 151 | The `Declaration` protocol itself has no requirements. 152 | However, 153 | adopting types share many of the same properties, 154 | such as 155 | [`attributes`](https://github.com/SwiftDocOrg/SwiftSemantics/wiki/Class#attributes), 156 | [`modifiers`](https://github.com/SwiftDocOrg/SwiftSemantics/wiki/Class#modifiers), 157 | and 158 | [`keyword`](https://github.com/SwiftDocOrg/SwiftSemantics/wiki/Class#keyword). 159 | 160 | SwiftSemantics declaration types are designed to 161 | maximize the information provided by SwiftSyntax, 162 | closely following the structure and naming conventions of syntax nodes. 163 | In some cases, 164 | the library takes additional measures to refine results 165 | into more conventional interfaces. 166 | For example, 167 | the `PrecedenceGroup` type defines nested 168 | [`Associativity`](https://github.com/SwiftDocOrg/SwiftSemantics/wiki/PrecedenceGroup_Associativity) 169 | and 170 | [`Relation`](https://github.com/SwiftDocOrg/SwiftSemantics/wiki/PrecedenceGroup_Relation) 171 | enumerations for greater convenience and type safety. 172 | However, in other cases, 173 | results may be provided in their original, raw `String` values; 174 | this decision is typically motivated either by 175 | concern for possible future changes to the language 176 | or simply out of practicality. 177 | 178 | For the most part, 179 | these design decisions allow developers with even a basic understanding of Swift 180 | to work productively with parsed declarations. 181 | There are, however, some details that warrant further discussion: 182 | 183 | ### Type Members Aren't Provided as Properties 184 | 185 | In Swift, 186 | a class, enumeration, or structure may contain 187 | one or more initializers, properties, subscripts, and methods, 188 | known as _members_. 189 | A type can itself be a member of another type, 190 | such as with `CodingKeys` enumerations nested within `Codable`-conforming types. 191 | Likewise, a type may also have one or more associated type or type alias members. 192 | 193 | SwiftSemantics doesn't provide built-in support for 194 | accessing type members directly from declaration values. 195 | This is probably the most surprising 196 | (and perhaps contentious) 197 | design decision made in the library so far, 198 | but we believe it to be the most reasonable option available. 199 | 200 | One motivation comes down to delegation of responsibility: 201 | `DeclarationCollector` and other types conforming to `SyntaxVisitor` 202 | walk the abstract syntax tree, 203 | respond to nodes as they're visited, 204 | and decide whether to visit or skip a node's children. 205 | If a `Declaration` were to initialize its own members, 206 | it would have the effect of overriding 207 | the tree walker's decision to visit or skip any children. 208 | We believe that an approach involving direct member initialization is inflexible 209 | and more likely to produce unexpected results. 210 | For instance, 211 | if you wanted to walk the AST to collect only Swift class declarations, 212 | there wouldn't be a clear way to avoid needlessly initializing 213 | the members of each top-level class 214 | without potentially missing class declarations nested in other types. 215 | 216 | But really, 217 | the controlling motivation has to do with extensions --- 218 | especially when used across multiple files in a module. 219 | Consider the following two Swift files in the same module: 220 | 221 | ```swift 222 | // First.swift 223 | enum A { enum B { } } 224 | 225 | // Second.swift 226 | extension A.B { static func f(){} } 227 | ``` 228 | 229 | The first file declares two enumerations: 230 | `A` and `B`, which is nested in `A`. 231 | The second file declares an extension on the type `A.B` 232 | that provides a static function `f()`. 233 | Depending on the order in which these files are processed, 234 | the extension on `A.B` may precede any knowledge of `A` or `B`. 235 | The capacity to reconcile these declarations exceeds 236 | that of any individual declaration (or even a syntax walker), 237 | and any intermediate results would necessarily be incomplete 238 | and therefore misleading. 239 | 240 |
241 | And if that weren't enough to dissuade you... 242 | 243 | Consider what happens when we throw generically-constrained extensions 244 | and conditional compilation into the mix... 245 | 246 | ```swift 247 | // Third.swift 248 | #if platform(linux) 249 | enum C {} 250 | #else 251 | protocol P {} 252 | extension A.B where T: P { static func g(){} } 253 | #end 254 | ``` 255 | 256 |
257 | 258 | Instead, 259 | our approach delegates the responsibility for 260 | reconciling declaration contexts to API consumers. 261 | 262 | This is the approach we settled on for [swift-doc][swift-doc], 263 | and it's worked reasonably well so far. 264 | That said, 265 | we're certainly open to hearing any alternative approaches 266 | and invite you to share any feedback about project architecture 267 | by [opening a new Issue](https://github.com/SwiftDocOrg/SwiftSemantics/issues/new). 268 | 269 | ### Not All Language Features Are Encoded 270 | 271 | Swift is a complex language with many different rules and concepts, 272 | and not all of them are represented directly in SwiftSemantics. 273 | 274 | Declaration membership, 275 | discussed in the previous section, 276 | is one such example. 277 | Another is how 278 | declaration access modifiers like `public` and `private(set)` 279 | aren't given any special treatment; 280 | they're [`Modifier`](https://github.com/SwiftDocOrg/SwiftSemantics/wiki/Modifier) values 281 | like any other. 282 | 283 | This design strategy keeps the library narrowly focused 284 | and more adaptable to language evolution over time. 285 | 286 | You can extend SwiftSemantics in your own code 287 | to encode any missing language concepts that are relevant to your problem. 288 | For example, 289 | SwiftSemantics doesn't encode the concept of 290 | [property wrappers](https://nshipster.com/propertywrapper/), 291 | but you could use it as the foundation of your own representation: 292 | 293 | ```swift 294 | protocol PropertyWrapperType { 295 | var attributes: [Attribute] { get } 296 | } 297 | 298 | extension Class: PropertyWrapperType {} 299 | extension Enumeration: PropertyWrapperType {} 300 | extension Structure: PropertyWrapperType {} 301 | 302 | extension PropertyWrapperType { 303 | var isPropertyWrapper: Bool { 304 | return attributes.contains { $0.name == "propertyWrapper" } 305 | } 306 | } 307 | ``` 308 | 309 | ### Declarations Don't Include Header Documentation or Source Location 310 | 311 | Documentation comments, 312 | like regular comments and whitespace, 313 | are deemed by SwiftSyntax to be "trivia" for syntax nodes. 314 | To keep this library narrowly focused, 315 | we don't provide a built-in functionality for symbol documentation 316 | (source location is omitted from declarations for similar reasons). 317 | 318 | If you wanted to do this yourself, 319 | you could subclass `DeclarationCollector` 320 | and override the `visit` delegate methods 321 | to retrieve, parse, and associate documentation comments 322 | with their corresponding declaration. 323 | Alternatively, 324 | you can use [SwiftDoc][swift-doc], 325 | which — in conjunction with [SwiftMarkup][swiftmarkup] — 326 | _does_ offer this functionality. 327 | 328 | ## Known Issues 329 | 330 | - Xcode cannot run unit tests (U) 331 | when opening the SwiftSemantics package directly, 332 | as opposed first to generating an Xcode project file with 333 | `swift package generate-xcodeproj`. 334 | (The reported error is: 335 | `Library not loaded: @rpath/lib_InternalSwiftSyntaxParser.dylib`). 336 | As a workaround, 337 | you can [install the latest toolchain](https://swift.org/download/) 338 | and enable it in "Xcode > Preferences > Components > Toolchains". 339 | Alternatively, 340 | you can run unit tests from the command line 341 | with `swift test`. 342 | 343 | ## License 344 | 345 | MIT 346 | 347 | ## Contact 348 | 349 | Mattt ([@mattt](https://twitter.com/mattt)) 350 | 351 | [swiftsyntax]: https://github.com/apple/swift-syntax 352 | [nshipster swiftsyntax]: https://nshipster.com/swiftsyntax/ 353 | [swift-doc]: https://github.com/SwiftDocOrg/swift-doc 354 | [swiftmarkup]: https://github.com/SwiftDocOrg/SwiftMarkup 355 | [swiftsemantics documentation]: https://github.com/SwiftDocOrg/SwiftSemantics/wiki 356 | 357 | [ci badge]: https://github.com/SwiftDocOrg/SwiftSemantics/workflows/CI/badge.svg 358 | [documentation badge]: https://github.com/SwiftDocOrg/SwiftSemantics/workflows/Documentation/badge.svg 359 | [documentation]: https://github.com/SwiftDocOrg/SwiftSemantics/wiki 360 | -------------------------------------------------------------------------------- /Sources/SwiftSemantics/Declaration.swift: -------------------------------------------------------------------------------- 1 | import SwiftSyntax 2 | 3 | /// A Swift declaration. 4 | public protocol Declaration {} 5 | -------------------------------------------------------------------------------- /Sources/SwiftSemantics/DeclarationCollector.swift: -------------------------------------------------------------------------------- 1 | import SwiftSyntax 2 | 3 | /** 4 | A Swift syntax visitor that collects declarations. 5 | 6 | Create an instance of `DeclarationCollector` 7 | and pass it by reference when walking an AST created by `SyntaxParser` 8 | to collect any visited declarations: 9 | 10 | ```swift 11 | import SwiftSyntax 12 | import SwiftSemantics 13 | 14 | let source = #"enum E {}"# 15 | 16 | var collector = DeclarationCollector() 17 | let tree = try SyntaxParser.parse(source: source) 18 | tree.walk(&collector) 19 | 20 | collector.enumerations.first?.name // "E" 21 | ``` 22 | */ 23 | open class DeclarationCollector: SyntaxVisitor { 24 | /// The collected associated type declarations. 25 | public private(set) var associatedTypes: [AssociatedType] = [] 26 | 27 | /// The collected class declarations. 28 | public private(set) var classes: [Class] = [] 29 | 30 | /// The collected conditional compilation block declarations. 31 | public private(set) var conditionalCompilationBlocks: [ConditionalCompilationBlock] = [] 32 | 33 | /// The collected deinitializer declarations. 34 | public private(set) var deinitializers: [Deinitializer] = [] 35 | 36 | /// The collected enumeration declarations. 37 | public private(set) var enumerations: [Enumeration] = [] 38 | 39 | /// The collected enumeration case declarations. 40 | public private(set) var enumerationCases: [Enumeration.Case] = [] 41 | 42 | /// The collected extension declarations. 43 | public private(set) var extensions: [Extension] = [] 44 | 45 | /// The collected function declarations. 46 | public private(set) var functions: [Function] = [] 47 | 48 | /// The collected import declarations. 49 | public private(set) var imports: [Import] = [] 50 | 51 | /// The collected initializer declarations. 52 | public private(set) var initializers: [Initializer] = [] 53 | 54 | /// The collected operator declarations. 55 | public private(set) var operators: [Operator] = [] 56 | 57 | /// The collected precedence group declarations. 58 | public private(set) var precedenceGroups: [PrecedenceGroup] = [] 59 | 60 | /// The collected protocol declarations. 61 | public private(set) var protocols: [Protocol] = [] 62 | 63 | /// The collected structure declarations. 64 | public private(set) var structures: [Structure] = [] 65 | 66 | /// The collected subscript declarations. 67 | public private(set) var subscripts: [Subscript] = [] 68 | 69 | /// The collected type alias declarations. 70 | public private(set) var typealiases: [Typealias] = [] 71 | 72 | /// The collected variable declarations. 73 | public private(set) var variables: [Variable] = [] 74 | 75 | /// Creates a new declaration collector. 76 | public override init() {} 77 | 78 | // MARK: - SyntaxVisitor 79 | 80 | /// Called when visiting an `AssociatedtypeDeclSyntax` node 81 | public override func visit(_ node: AssociatedtypeDeclSyntax) -> SyntaxVisitorContinueKind { 82 | associatedTypes.append(AssociatedType(node)) 83 | return .skipChildren 84 | } 85 | 86 | /// Called when visiting a `ClassDeclSyntax` node 87 | public override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind { 88 | classes.append(Class(node)) 89 | return .visitChildren 90 | } 91 | 92 | /// Called when visiting a `DeinitializerDeclSyntax` node 93 | public override func visit(_ node: DeinitializerDeclSyntax) -> SyntaxVisitorContinueKind { 94 | deinitializers.append(Deinitializer(node)) 95 | return .skipChildren 96 | } 97 | 98 | /// Called when visiting an `EnumDeclSyntax` node 99 | public override func visit(_ node: EnumDeclSyntax) -> SyntaxVisitorContinueKind { 100 | enumerations.append(Enumeration(node)) 101 | return .visitChildren 102 | } 103 | 104 | /// Called when visiting an `EnumCaseDeclSyntax` node 105 | public override func visit(_ node: EnumCaseDeclSyntax) -> SyntaxVisitorContinueKind { 106 | enumerationCases.append(contentsOf: Enumeration.Case.cases(from: node)) 107 | return .skipChildren 108 | } 109 | 110 | /// Called when visiting an `ExtensionDeclSyntax` node 111 | public override func visit(_ node: ExtensionDeclSyntax) -> SyntaxVisitorContinueKind { 112 | extensions.append(Extension(node)) 113 | return .visitChildren 114 | } 115 | 116 | /// Called when visiting a `FunctionDeclSyntax` node 117 | public override func visit(_ node: FunctionDeclSyntax) -> SyntaxVisitorContinueKind { 118 | functions.append(Function(node)) 119 | return .skipChildren 120 | } 121 | 122 | /// Called when visiting an `IfConfigDeclSyntax` node 123 | public override func visit(_ node: IfConfigDeclSyntax) -> SyntaxVisitorContinueKind { 124 | conditionalCompilationBlocks.append(ConditionalCompilationBlock(node)) 125 | return .visitChildren 126 | } 127 | 128 | /// Called when visiting an `ImportDeclSyntax` node 129 | public override func visit(_ node: ImportDeclSyntax) -> SyntaxVisitorContinueKind { 130 | imports.append(Import(node)) 131 | return .skipChildren 132 | } 133 | 134 | /// Called when visiting an `InitializerDeclSyntax` node 135 | public override func visit(_ node: InitializerDeclSyntax) -> SyntaxVisitorContinueKind { 136 | initializers.append(Initializer(node)) 137 | return .skipChildren 138 | } 139 | 140 | /// Called when visiting an `OperatorDeclSyntax` node 141 | public override func visit(_ node: OperatorDeclSyntax) -> SyntaxVisitorContinueKind { 142 | operators.append(Operator(node)) 143 | return .skipChildren 144 | } 145 | 146 | /// Called when visiting a `PrecedenceGroupDeclSyntax` node 147 | public override func visit(_ node: PrecedenceGroupDeclSyntax) -> SyntaxVisitorContinueKind { 148 | precedenceGroups.append(PrecedenceGroup(node)) 149 | return .skipChildren 150 | } 151 | 152 | /// Called when visiting a `ProtocolDeclSyntax` node 153 | public override func visit(_ node: ProtocolDeclSyntax) -> SyntaxVisitorContinueKind { 154 | protocols.append(Protocol(node)) 155 | return .visitChildren 156 | } 157 | 158 | /// Called when visiting a `SubscriptDeclSyntax` node 159 | public override func visit(_ node: SubscriptDeclSyntax) -> SyntaxVisitorContinueKind { 160 | subscripts.append(Subscript(node)) 161 | return .skipChildren 162 | } 163 | 164 | /// Called when visiting a `StructDeclSyntax` node 165 | public override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind { 166 | structures.append(Structure(node)) 167 | return .visitChildren 168 | } 169 | 170 | /// Called when visiting a `TypealiasDeclSyntax` node 171 | public override func visit(_ node: TypealiasDeclSyntax) -> SyntaxVisitorContinueKind { 172 | typealiases.append(Typealias(node)) 173 | return .skipChildren 174 | } 175 | 176 | /// Called when visiting a `VariableDeclSyntax` node 177 | public override func visit(_ node: VariableDeclSyntax) -> SyntaxVisitorContinueKind { 178 | variables.append(contentsOf: Variable.variables(from: node)) 179 | return .skipChildren 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /Sources/SwiftSemantics/Declarations/AssociatedType.swift: -------------------------------------------------------------------------------- 1 | import SwiftSyntax 2 | 3 | /// An associated type declaration. 4 | public struct AssociatedType: Declaration, Hashable, Codable { 5 | /// The declaration attributes. 6 | public let attributes: [Attribute] 7 | 8 | /// The declaration modifiers. 9 | public let modifiers: [Modifier] 10 | 11 | /// The declaration keyword (`"associatedtype"`). 12 | public let keyword: String 13 | 14 | /// The associated type name. 15 | public let name: String 16 | } 17 | 18 | // MARK: - ExpressibleBySyntax 19 | 20 | extension AssociatedType: ExpressibleBySyntax { 21 | /// Creates an instance initialized with the given syntax node. 22 | public init(_ node: AssociatedtypeDeclSyntax) { 23 | attributes = node.attributes?.compactMap{ $0.as(AttributeSyntax.self) }.map { Attribute($0) } ?? [] 24 | modifiers = node.modifiers?.map { Modifier($0) } ?? [] 25 | keyword = node.associatedtypeKeyword.text.trimmed 26 | name = node.identifier.text.trimmed 27 | } 28 | } 29 | 30 | // MARK: - CustomStringConvertible 31 | 32 | extension AssociatedType: CustomStringConvertible { 33 | public var description: String { 34 | return ( 35 | attributes.map { $0.description } + 36 | modifiers.map { $0.description } + 37 | [keyword, name] 38 | ).joined(separator: " ") 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Sources/SwiftSemantics/Declarations/Class.swift: -------------------------------------------------------------------------------- 1 | import SwiftSyntax 2 | 3 | /// A class declaration. 4 | public struct Class: Declaration, Hashable, Codable { 5 | /// The declaration attributes. 6 | public let attributes: [Attribute] 7 | 8 | /// The declaration modifiers. 9 | public let modifiers: [Modifier] 10 | 11 | /// The declaration keyword (`"class"`). 12 | public let keyword: String 13 | 14 | /// The class name. 15 | public let name: String 16 | 17 | /** 18 | A list of inherited type names. 19 | 20 | If the class is a subclass, 21 | the first element is the superclass. 22 | Any other elements are names of protocols. 23 | 24 | For example, 25 | given the following declarations, 26 | the `inheritance` of class `C` is `["B", "P"]`: 27 | 28 | ```swift 29 | protocol P {} 30 | class A {} 31 | class B: A {} 32 | class C: B, P {} 33 | ``` 34 | */ 35 | public let inheritance: [String] 36 | 37 | /** 38 | The generic parameters for the declaration. 39 | 40 | For example, 41 | the following declaration of class `C` 42 | has a single generic parameter 43 | whose `name` is `"T"` and `type` is `"Equatable"`: 44 | 45 | ```swift 46 | class C {} 47 | ``` 48 | */ 49 | public let genericParameters: [GenericParameter] 50 | 51 | /** 52 | The generic parameter requirements for the declaration. 53 | 54 | For example, 55 | the following declaration of class `C` 56 | has a single requirement 57 | that its generic parameter identified as `"T"` 58 | conforms to the type identified as `"Hashable"`: 59 | 60 | ```swift 61 | class C where T: Hashable {} 62 | ``` 63 | */ 64 | public let genericRequirements: [GenericRequirement] 65 | } 66 | 67 | // MARK: - ExpressibleBySyntax 68 | 69 | extension Class: ExpressibleBySyntax { 70 | /// Creates an instance initialized with the given syntax node. 71 | public init(_ node: ClassDeclSyntax) { 72 | attributes = node.attributes?.compactMap{ $0.as(AttributeSyntax.self) }.map { Attribute($0) } ?? [] 73 | modifiers = node.modifiers?.map { Modifier($0) } ?? [] 74 | 75 | #if swift(>=5.5) 76 | keyword = node.classOrActorKeyword.text.trimmed 77 | #else 78 | keyword = node.classKeyword.text.trimmed 79 | #endif 80 | 81 | name = node.identifier.text.trimmed 82 | inheritance = node.inheritanceClause?.inheritedTypeCollection.map { $0.typeName.description.trimmed } ?? [] 83 | genericParameters = node.genericParameterClause?.genericParameterList.map { GenericParameter($0) } ?? [] 84 | genericRequirements = GenericRequirement.genericRequirements(from: node.genericWhereClause?.requirementList) 85 | } 86 | } 87 | 88 | // MARK: - CustomStringConvertible 89 | 90 | extension Class: CustomStringConvertible { 91 | public var description: String { 92 | var description = ( 93 | attributes.map { $0.description } + 94 | modifiers.map { $0.description } + 95 | [keyword, name] 96 | ).joined(separator: " ") 97 | 98 | if !genericParameters.isEmpty { 99 | description += "<\(genericParameters.map { $0.description }.joined(separator: ", "))>" 100 | } 101 | 102 | if !inheritance.isEmpty { 103 | description += ": \(inheritance.joined(separator: ", "))" 104 | } 105 | 106 | if !genericRequirements.isEmpty { 107 | description += " where \(genericRequirements.map { $0.description }.joined(separator: ", "))" 108 | } 109 | 110 | return description 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /Sources/SwiftSemantics/Declarations/ConditionalCompilationBlock.swift: -------------------------------------------------------------------------------- 1 | import SwiftSyntax 2 | 3 | /// A conditional compilation block declaration. 4 | public struct ConditionalCompilationBlock: Declaration, Hashable, Codable { 5 | /** 6 | The conditional compilation block branches. 7 | 8 | For example, 9 | the following compilation block declaration has two branches: 10 | 11 | ```swift 12 | #if true 13 | enum A {} 14 | #else 15 | enum B {} 16 | #endif 17 | ``` 18 | 19 | The first branch has the keyword `#if` and condition `"true"`. 20 | The second branch has the keyword `#else` and no condition. 21 | */ 22 | public let branches: [Branch] 23 | 24 | /// A conditional compilation block branch. 25 | public enum Branch: Hashable { 26 | /// An `#if` branch. 27 | case `if`(String) 28 | 29 | /// An `#elseif` branch. 30 | case `elseif`(String) 31 | 32 | /// An `#else` branch. 33 | case `else` 34 | 35 | init?(keyword: String, condition: String?) { 36 | switch (keyword, condition) { 37 | case let ("#if", condition?): 38 | self = .if(condition) 39 | case let ("#elseif", condition?): 40 | self = .elseif(condition) 41 | case ("#else", nil): 42 | self = .else 43 | default: 44 | return nil 45 | } 46 | } 47 | 48 | /// The branch keyword, either `"#if"`, `"#elseif"`, or `"#else"`. 49 | public var keyword: String { 50 | switch self { 51 | case .if: 52 | return "#if" 53 | case .elseif: 54 | return "#elseif" 55 | case .else: 56 | return "#else" 57 | } 58 | } 59 | 60 | /** 61 | The branch condition, if any. 62 | 63 | This value is present when `keyword` is equal to `"#if"` or `#elseif` 64 | and `nil` when `keyword` is equal to `"#else"`. 65 | */ 66 | public var condition: String? { 67 | switch self { 68 | case let .if(condition), 69 | let .elseif(condition): 70 | return condition 71 | case .else: 72 | return nil 73 | } 74 | } 75 | } 76 | } 77 | 78 | // MARK: - ExpressibleBySyntax 79 | 80 | extension ConditionalCompilationBlock: ExpressibleBySyntax { 81 | /// Creates an instance initialized with the given syntax node. 82 | public init(_ node: IfConfigDeclSyntax) { 83 | branches = node.clauses.map { Branch($0) } 84 | } 85 | } 86 | 87 | extension ConditionalCompilationBlock.Branch: ExpressibleBySyntax { 88 | /// Creates an instance initialized with the given syntax node. 89 | public init(_ node: IfConfigClauseSyntax) { 90 | let keyword = node.poundKeyword.text.trimmed 91 | let condition = node.condition?.description.trimmed 92 | self.init(keyword: keyword, condition: condition)! 93 | } 94 | } 95 | 96 | // MARK: - Codable 97 | 98 | extension ConditionalCompilationBlock.Branch: Codable { 99 | public init(from decoder: Decoder) throws { 100 | var container = try decoder.unkeyedContainer() 101 | let keyword = try container.decode(String.self) 102 | let condition = try container.decodeIfPresent(String.self) 103 | self.init(keyword: keyword, condition: condition)! 104 | } 105 | 106 | public func encode(to encoder: Encoder) throws { 107 | var container = encoder.unkeyedContainer() 108 | try container.encode(keyword) 109 | try container.encode(condition) 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /Sources/SwiftSemantics/Declarations/Deinitializer.swift: -------------------------------------------------------------------------------- 1 | import SwiftSyntax 2 | 3 | /// A class deinitializer declaration. 4 | public struct Deinitializer: Declaration, Hashable, Codable { 5 | /// The declaration attributes. 6 | public let attributes: [Attribute] 7 | 8 | /// The declaration modifiers. 9 | public let modifiers: [Modifier] 10 | 11 | /// The declaration keyword (`"deinit"`). 12 | public let keyword: String 13 | } 14 | 15 | // MARK: - ExpressibleBySyntax 16 | 17 | extension Deinitializer: ExpressibleBySyntax { 18 | /// Creates an instance initialized with the given syntax node. 19 | public init(_ node: DeinitializerDeclSyntax) { 20 | attributes = node.attributes?.compactMap{ $0.as(AttributeSyntax.self) }.map { Attribute($0) } ?? [] 21 | modifiers = node.modifiers?.map { Modifier($0) } ?? [] 22 | keyword = node.deinitKeyword.text.trimmed 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Sources/SwiftSemantics/Declarations/Enumeration.swift: -------------------------------------------------------------------------------- 1 | import SwiftSyntax 2 | 3 | /// An enumeration declaration. 4 | public struct Enumeration: Declaration, Hashable, Codable { 5 | /// The enumeration declaration attributes. 6 | public let attributes: [Attribute] 7 | 8 | /// The enumeration declaration modifiers. 9 | public let modifiers: [Modifier] 10 | 11 | /// The enumeration declaration keyword (`enum`). 12 | public let keyword: String 13 | 14 | /// The name of the enumeration. 15 | public let name: String 16 | 17 | /** 18 | A list of inherited type names. 19 | 20 | If the enumeration is raw representable, 21 | the first element is the raw value type. 22 | Any other elements are names of protocols. 23 | 24 | For example, 25 | given the following declarations, 26 | the `inheritance` of enumeration `E` is `["Int", "P"]`: 27 | 28 | ```swift 29 | protocol P {} 30 | enum E: Int, P {} 31 | ``` 32 | */ 33 | public let inheritance: [String] 34 | 35 | /** 36 | The generic parameters for the declaration. 37 | 38 | For example, 39 | the following declaration of enumeration `E` 40 | has a single generic parameter 41 | whose `name` is `"T"` and `type` is `"Equatable"`: 42 | 43 | ```swift 44 | enum E {} 45 | ``` 46 | */ 47 | public let genericParameters: [GenericParameter] 48 | 49 | /** 50 | The generic parameter requirements for the declaration. 51 | 52 | For example, 53 | the following declaration of enumeration `E` 54 | has a single requirement 55 | that its generic parameter identified as `"T"` 56 | conforms to the type identified as `"Hashable"`: 57 | 58 | ```swift 59 | enum E where T: Hashable {} 60 | ``` 61 | */ 62 | public let genericRequirements: [GenericRequirement] 63 | 64 | /// An enumeration case. 65 | public struct Case: Declaration, Hashable, Codable { 66 | /// The declaration attributes. 67 | public let attributes: [Attribute] 68 | 69 | /// The declaration modifiers. 70 | public let modifiers: [Modifier] 71 | 72 | /// The declaration keyword (`"case"`). 73 | public let keyword: String 74 | 75 | /// The enumeration case name. 76 | public let name: String 77 | 78 | /// The associated values of the enumeration case, if any. 79 | public let associatedValue: [Function.Parameter]? 80 | 81 | /// The raw value of the enumeration case, if any. 82 | public let rawValue: String? 83 | } 84 | } 85 | 86 | // MARK: - CustomStringConvertible 87 | 88 | extension Enumeration: CustomStringConvertible { 89 | public var description: String { 90 | var description = ( 91 | attributes.map { $0.description } + 92 | modifiers.map { $0.description } + 93 | [keyword, name] 94 | ).joined(separator: " ") 95 | 96 | if !genericParameters.isEmpty { 97 | description += "<\(genericParameters.map { $0.description }.joined(separator: ", "))>" 98 | } 99 | 100 | if !genericRequirements.isEmpty { 101 | description += " where \(genericRequirements.map { $0.description }.joined(separator: ", "))" 102 | } 103 | 104 | return description 105 | } 106 | } 107 | 108 | extension Enumeration.Case: CustomStringConvertible { 109 | public var description: String { 110 | if let associatedValue = associatedValue { 111 | return "\(keyword) \(name)(\(associatedValue.map{"\($0)"}.joined(separator: ", ")))" 112 | } else { 113 | return "\(keyword) \(name)" 114 | } 115 | } 116 | } 117 | 118 | // MARK: - ExpressibleBySyntax 119 | 120 | extension Enumeration: ExpressibleBySyntax { 121 | /// Creates an instance initialized with the given syntax node. 122 | public init(_ node: EnumDeclSyntax) { 123 | attributes = node.attributes?.compactMap{ $0.as(AttributeSyntax.self) }.map { Attribute($0) } ?? [] 124 | modifiers = node.modifiers?.map { Modifier($0) } ?? [] 125 | keyword = node.enumKeyword.text.trimmed 126 | name = node.identifier.text.trimmed 127 | inheritance = node.inheritanceClause?.inheritedTypeCollection.map { $0.typeName.description.trimmed } ?? [] 128 | genericParameters = node.genericParameters?.genericParameterList.map { GenericParameter($0) } ?? [] 129 | genericRequirements = GenericRequirement.genericRequirements(from: node.genericWhereClause?.requirementList) 130 | } 131 | } 132 | 133 | extension Enumeration.Case { 134 | /// Creates and returns enumeration cases from an enumeration case declaration. 135 | public static func cases(from node: EnumCaseDeclSyntax) -> [Enumeration.Case] { 136 | return node.elements.compactMap { Enumeration.Case($0) } 137 | } 138 | 139 | /// Creates an instance initialized with the given syntax node. 140 | public init?(_ node: EnumCaseElementSyntax) { 141 | guard let parent = node.context as? EnumCaseDeclSyntax else { 142 | assertionFailure("EnumCaseElement should be contained within EnumCaseDecl") 143 | return nil 144 | } 145 | 146 | attributes = parent.attributes?.compactMap{ $0.as(AttributeSyntax.self) }.map { Attribute($0) } ?? [] 147 | modifiers = parent.modifiers?.map { Modifier($0) } ?? [] 148 | keyword = parent.caseKeyword.text.trimmed 149 | 150 | name = node.identifier.text.trimmed 151 | associatedValue = node.associatedValue?.parameterList.map { Function.Parameter($0) } 152 | rawValue = node.rawValue?.value.description.trimmed 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /Sources/SwiftSemantics/Declarations/Extension.swift: -------------------------------------------------------------------------------- 1 | import SwiftSyntax 2 | 3 | /// An extension declaration. 4 | public struct Extension: Declaration, Hashable, Codable { 5 | /// The declaration attributes. 6 | public let attributes: [Attribute] 7 | 8 | /// The declaration modifiers. 9 | public let modifiers: [Modifier] 10 | 11 | /// The declaration keyword (`"extension"`). 12 | public let keyword: String 13 | 14 | /// The name of the type extended by the extension. 15 | public let extendedType: String 16 | 17 | /** 18 | A list of protocol names inherited by the extended type. 19 | 20 | For example, 21 | the following extension on structure `S` 22 | has an `inheritance` of `["P", "Q"]`: 23 | 24 | ```swift 25 | struct S {} 26 | protocol P {} 27 | protocol Q {} 28 | extension S: P, Q {} 29 | ``` 30 | */ 31 | public let inheritance: [String] 32 | 33 | /** 34 | The generic parameter requirements for the declaration. 35 | 36 | For example, 37 | the following conditional extension on structure S 38 | has a single requirement 39 | that its generic parameter identified as `"T"` 40 | conforms to the type identified as `"Hashable"`: 41 | 42 | ```swift 43 | struct S {} 44 | extension S where T: Hashable {} 45 | ``` 46 | */ 47 | public let genericRequirements: [GenericRequirement] 48 | } 49 | 50 | // MARK: - ExpressibleBySyntax 51 | 52 | extension Extension: ExpressibleBySyntax { 53 | /// Creates an instance initialized with the given syntax node. 54 | public init(_ node: ExtensionDeclSyntax) { 55 | attributes = node.attributes?.compactMap{ $0.as(AttributeSyntax.self) }.map { Attribute($0) } ?? [] 56 | modifiers = node.modifiers?.map { Modifier($0) } ?? [] 57 | keyword = node.extensionKeyword.text.trimmed 58 | extendedType = node.extendedType.description.trimmed 59 | inheritance = node.inheritanceClause?.inheritedTypeCollection.map { $0.typeName.description.trimmed } ?? [] 60 | genericRequirements = GenericRequirement.genericRequirements(from: node.genericWhereClause?.requirementList) 61 | } 62 | } 63 | 64 | // MARK: - CustomStringConvertible 65 | 66 | extension Extension: CustomStringConvertible { 67 | public var description: String { 68 | var description = ( 69 | attributes.map { $0.description } + 70 | modifiers.map { $0.description } + 71 | [keyword] 72 | ).joined(separator: " ") 73 | 74 | if !genericRequirements.isEmpty { 75 | description += " where \(genericRequirements.map { $0.description }.joined(separator: ", "))" 76 | } 77 | 78 | return description 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /Sources/SwiftSemantics/Declarations/Function.swift: -------------------------------------------------------------------------------- 1 | import SwiftSyntax 2 | 3 | /// A function declaration. 4 | public struct Function: Declaration, Hashable, Codable { 5 | /// The declaration attributes. 6 | public let attributes: [Attribute] 7 | 8 | /// The declaration modifiers. 9 | public let modifiers: [Modifier] 10 | 11 | /// The declaration keyword (`"func"`). 12 | public let keyword: String 13 | 14 | /// The function identifier. 15 | public let identifier: String 16 | 17 | /// The function signature. 18 | public let signature: Signature 19 | 20 | /** 21 | The generic parameters for the declaration. 22 | 23 | For example, 24 | the following declaration of function `f` 25 | has a single generic parameter 26 | whose `identifier` is `"T"` and `type` is `"Equatable"`: 27 | 28 | ```swift 29 | func f(value: T) {} 30 | ``` 31 | */ 32 | public let genericParameters: [GenericParameter] 33 | 34 | /** 35 | The generic parameter requirements for the declaration. 36 | 37 | For example, 38 | the following declaration of function `f` 39 | has a single requirement 40 | that its generic parameter identified as `"T"` 41 | conforms to the type identified as `"Hashable"`: 42 | 43 | ```swift 44 | func f(value: T) where T: Hashable {} 45 | ``` 46 | */ 47 | public let genericRequirements: [GenericRequirement] 48 | 49 | /// Whether the function is an operator. 50 | public var isOperator: Bool { 51 | return Operator.Kind(modifiers) != nil || Operator.isValidIdentifier(identifier) 52 | } 53 | 54 | /// A function signature. 55 | public struct Signature: Hashable, Codable { 56 | /// The function inputs. 57 | public let input: [Parameter] 58 | 59 | /// The function output, if any. 60 | public let output: String? 61 | 62 | /// The `throws` or `rethrows` keyword, if any. 63 | public let throwsOrRethrowsKeyword: String? 64 | } 65 | 66 | /** 67 | A function parameter. 68 | 69 | This type can also be used to represent 70 | initializer parameters and associated values for enumeration cases. 71 | */ 72 | public struct Parameter: Hashable, Codable { 73 | /// The declaration attributes. 74 | public let attributes: [Attribute] 75 | 76 | /** 77 | The first, external name of the parameter. 78 | 79 | For example, 80 | given the following function declaration, 81 | the first parameter has a `firstName` equal to `nil`, 82 | and the second parameter has a `firstName` equal to `"by"`: 83 | 84 | ```swift 85 | func increment(_ number: Int, by amount: Int = 1) 86 | ``` 87 | */ 88 | public let firstName: String? 89 | 90 | /** 91 | The second, internal name of the parameter. 92 | 93 | For example, 94 | given the following function declaration, 95 | the first parameter has a `secondName` equal to `"number"`, 96 | and the second parameter has a `secondName` equal to `"amount"`: 97 | 98 | ```swift 99 | func increment(_ number: Int, by amount: Int = 1) 100 | ``` 101 | */ 102 | public let secondName: String? 103 | 104 | /** 105 | The type identified by the parameter. 106 | 107 | For example, 108 | given the following function declaration, 109 | the first parameter has a `type` equal to `"Person"`, 110 | and the second parameter has a `type` equal to `"String"`: 111 | 112 | ```swift 113 | func greet(_ person: Person, with phrases: String...) 114 | ``` 115 | */ 116 | public let type: String? 117 | 118 | /** 119 | Whether the parameter accepts a variadic argument. 120 | 121 | For example, 122 | given the following function declaration, 123 | the second parameter is variadic: 124 | 125 | ```swift 126 | func greet(_ person: Person, with phrases: String...) 127 | ``` 128 | */ 129 | public let variadic: Bool 130 | 131 | /** 132 | The default argument of the parameter. 133 | 134 | For example, 135 | given the following function declaration, 136 | the second parameter has a default argument equal to `"1"`. 137 | 138 | ```swift 139 | func increment(_ number: Int, by amount: Int = 1) 140 | ``` 141 | */ 142 | public let defaultArgument: String? 143 | } 144 | } 145 | 146 | // MARK: - ExpressibleBySyntax 147 | 148 | extension Function: ExpressibleBySyntax { 149 | /// Creates an instance initialized with the given syntax node. 150 | public init(_ node: FunctionDeclSyntax) { 151 | attributes = node.attributes?.compactMap{ $0.as(AttributeSyntax.self) }.map { Attribute($0) } ?? [] 152 | modifiers = node.modifiers?.map { Modifier($0) } ?? [] 153 | keyword = node.funcKeyword.text.trimmed 154 | identifier = node.identifier.text.trimmed 155 | signature = Signature(node.signature) 156 | genericParameters = node.genericParameterClause?.genericParameterList.map { GenericParameter($0) } ?? [] 157 | genericRequirements = GenericRequirement.genericRequirements(from: node.genericWhereClause?.requirementList) 158 | } 159 | } 160 | 161 | extension Function.Parameter: ExpressibleBySyntax { 162 | /// Creates an instance initialized with the given syntax node. 163 | public init(_ node: FunctionParameterSyntax) { 164 | attributes = node.attributes?.compactMap{ $0.as(AttributeSyntax.self) }.map { Attribute($0) } ?? [] 165 | firstName = node.firstName?.text.trimmed 166 | secondName = node.secondName?.text.trimmed 167 | type = node.type?.description.trimmed 168 | variadic = node.ellipsis != nil 169 | defaultArgument = node.defaultArgument?.value.description.trimmed 170 | } 171 | } 172 | 173 | extension Function.Signature: ExpressibleBySyntax { 174 | /// Creates an instance initialized with the given syntax node. 175 | public init(_ node: FunctionSignatureSyntax) { 176 | input = node.input.parameterList.map { Function.Parameter($0) } 177 | output = node.output?.returnType.description.trimmed 178 | throwsOrRethrowsKeyword = node.throwsOrRethrowsKeyword?.description.trimmed 179 | } 180 | } 181 | 182 | // MARK: - CustomStringConvertible 183 | 184 | extension Function: CustomStringConvertible { 185 | public var description: String { 186 | var description = ( 187 | attributes.map { $0.description } + 188 | modifiers.map { $0.description } + 189 | [keyword, identifier] 190 | ).joined(separator: " ") 191 | 192 | if !genericParameters.isEmpty { 193 | description += "<\(genericParameters.map { $0.description }.joined(separator: ", "))>" 194 | } 195 | 196 | description += signature.description 197 | 198 | if !genericRequirements.isEmpty { 199 | description += " where \(genericRequirements.map { $0.description }.joined(separator: ", "))" 200 | } 201 | 202 | return description 203 | } 204 | } 205 | 206 | extension Function.Signature: CustomStringConvertible { 207 | public var description: String { 208 | var description = "(\(input.map { $0.description }.joined(separator: ", ")))" 209 | if let throwsOrRethrowsKeyword = throwsOrRethrowsKeyword { 210 | description += " \(throwsOrRethrowsKeyword)" 211 | } 212 | 213 | if let output = output { 214 | description += " -> \(output)" 215 | } 216 | 217 | return description 218 | } 219 | } 220 | 221 | extension Function.Parameter: CustomStringConvertible { 222 | public var description: String { 223 | var description: String = (attributes.map { $0.description } + [firstName, secondName].compactMap { $0?.description }).joined(separator: " ") 224 | if let type = type { 225 | description += ": \(type)" 226 | } 227 | 228 | if let defaultArgument = defaultArgument { 229 | description += " = \(defaultArgument)" 230 | } 231 | return description 232 | } 233 | } 234 | -------------------------------------------------------------------------------- /Sources/SwiftSemantics/Declarations/Import.swift: -------------------------------------------------------------------------------- 1 | import SwiftSyntax 2 | 3 | /// An import declaration. 4 | public struct Import: Declaration, Hashable, Codable { 5 | /// The declaration attributes. 6 | public let attributes: [Attribute] 7 | 8 | /// The declaration modifiers. 9 | public let modifiers: [Modifier] 10 | 11 | /// The import keyword (`"import"`). 12 | public let keyword: String 13 | 14 | public let kind: String? 15 | public let pathComponents: [String] 16 | } 17 | 18 | // MARK: - ExpressibleBySyntax 19 | 20 | extension Import: ExpressibleBySyntax { 21 | /// Creates an instance initialized with the given syntax node. 22 | public init(_ node: ImportDeclSyntax) { 23 | attributes = node.attributes?.compactMap{ $0.as(AttributeSyntax.self) }.map { Attribute($0) } ?? [] 24 | modifiers = node.modifiers?.map { Modifier($0) } ?? [] 25 | keyword = node.importTok.text.trimmed 26 | kind = node.importKind?.text.trimmed 27 | pathComponents = node.path.tokens.filter { $0.tokenKind != .period }.map { $0.text.trimmed } 28 | } 29 | } 30 | 31 | // MARK: - CustomStringConvertible 32 | 33 | extension Import: CustomStringConvertible { 34 | public var description: String { 35 | return ( 36 | attributes.map { $0.description } + 37 | modifiers.map { $0.description } + 38 | [keyword, kind] + 39 | [pathComponents.joined(separator: ".")] 40 | ).compactMap { $0 }.joined(separator: " ") 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Sources/SwiftSemantics/Declarations/Initializer.swift: -------------------------------------------------------------------------------- 1 | import SwiftSyntax 2 | 3 | /// An initializer declaration. 4 | public struct Initializer: Declaration, Hashable, Codable { 5 | /// The declaration attributes. 6 | public let attributes: [Attribute] 7 | 8 | /// The declaration modifiers. 9 | public let modifiers: [Modifier] 10 | 11 | /// The declaration keyword (`"associatedtype"`). 12 | public let keyword: String 13 | 14 | /// Whether the initializer is optional. 15 | public let optional: Bool 16 | 17 | /** 18 | The generic parameters for the declaration. 19 | 20 | For example, 21 | the following initializer declaration 22 | has a single generic parameter 23 | whose `name` is `"T"` and `type` is `"Equatable"`: 24 | 25 | ```swift 26 | init(value: T) {} 27 | ``` 28 | */ 29 | public let genericParameters: [GenericParameter] 30 | 31 | /// The initializer inputs. 32 | public let parameters: [Function.Parameter] 33 | 34 | /// The `throws` or `rethrows` keyword, if any. 35 | public let throwsOrRethrowsKeyword: String? 36 | 37 | /** 38 | The generic parameter requirements for the declaration. 39 | 40 | For example, 41 | the following initializer declaration 42 | has a single requirement 43 | that its generic parameter identified as `"T"` 44 | conforms to the type identified as `"Hashable"`: 45 | 46 | ```swift 47 | init(value: T) where T: Hashable {} 48 | ``` 49 | */ 50 | public let genericRequirements: [GenericRequirement] 51 | } 52 | 53 | // MARK: - ExpressibleBySyntax 54 | 55 | extension Initializer: ExpressibleBySyntax { 56 | /// Creates an instance initialized with the given syntax node. 57 | public init(_ node: InitializerDeclSyntax) { 58 | attributes = node.attributes?.compactMap{ $0.as(AttributeSyntax.self) }.map { Attribute($0) } ?? [] 59 | modifiers = node.modifiers?.map { Modifier($0) } ?? [] 60 | keyword = node.initKeyword.text.trimmed 61 | optional = node.optionalMark != nil 62 | genericParameters = node.genericParameterClause?.genericParameterList.map { GenericParameter($0) } ?? [] 63 | parameters = node.parameters.parameterList.map { Function.Parameter($0) } 64 | throwsOrRethrowsKeyword = node.throwsOrRethrowsKeyword?.description.trimmed 65 | genericRequirements = GenericRequirement.genericRequirements(from: node.genericWhereClause?.requirementList) 66 | } 67 | } 68 | 69 | // MARK: - CustomStringConvertible 70 | 71 | extension Initializer: CustomStringConvertible { 72 | public var description: String { 73 | var description = ( 74 | attributes.map { $0.description } + 75 | modifiers.map { $0.description } + 76 | [keyword] 77 | ).joined(separator: " ") 78 | 79 | if optional { 80 | description += "?" 81 | } 82 | 83 | if !genericParameters.isEmpty { 84 | description += "<\(genericParameters.map { $0.description }.joined(separator: ", "))>" 85 | } 86 | 87 | description += "(\(parameters.map { $0.description }.joined(separator: ", ")))" 88 | if let throwsOrRethrowsKeyword = throwsOrRethrowsKeyword { 89 | description += " \(throwsOrRethrowsKeyword)" 90 | } 91 | 92 | if !genericRequirements.isEmpty { 93 | description += " where \(genericRequirements.map { $0.description }.joined(separator: ", "))" 94 | } 95 | 96 | return description 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Sources/SwiftSemantics/Declarations/Operator.swift: -------------------------------------------------------------------------------- 1 | import SwiftSyntax 2 | 3 | /// An operator declaration. 4 | public struct Operator: Declaration, Hashable, Codable { 5 | /// The kind of operator (prefix, infix, or postfix). 6 | public enum Kind: String, Hashable, Codable { 7 | /// A unary operator that comes before its operand. 8 | case prefix 9 | 10 | /// An binary operator that comes between its operands. 11 | case infix 12 | 13 | /// A unary operator that comes after its operand. 14 | case postfix 15 | 16 | init?(_ modifiers: [Modifier]) { 17 | let kinds = modifiers.compactMap { Kind(rawValue: $0.name) } 18 | assert(kinds.count <= 1) 19 | guard let kind = kinds.first else { return nil } 20 | self = kind 21 | } 22 | } 23 | 24 | /// The declaration attributes. 25 | public let attributes: [Attribute] 26 | 27 | /// The declaration modifiers. 28 | public let modifiers: [Modifier] 29 | 30 | /// The declaration keyword (`"operator"`). 31 | public let keyword: String 32 | 33 | /// The operator name. 34 | public let name: String 35 | 36 | /// The kind of operator (prefix, infix, or postfix). 37 | public var kind: Kind { 38 | return Kind(modifiers) ?? .infix 39 | } 40 | 41 | static func isValidIdentifier(_ string: String) -> Bool { 42 | func isValidHeadCharacter(_ character: Character) -> Bool { 43 | switch character { 44 | case // Basic Latin 45 | "/", "=", "-", "+", "!", "*", "%", 46 | "<", ">", "&", "|", "^", "?", "~", 47 | 48 | // Latin-1 Supplement 49 | "\u{00A1}", 50 | "\u{00A2}", 51 | "\u{00A3}", 52 | "\u{00A4}", 53 | "\u{00A5}", 54 | "\u{00A6}", 55 | "\u{00A7}", 56 | "\u{00A9}", 57 | "\u{00AB}", 58 | "\u{00AC}", 59 | "\u{00AE}", 60 | "\u{00B0}", 61 | "\u{00B1}", 62 | "\u{00B6}", 63 | "\u{00BB}", 64 | "\u{00BF}", 65 | "\u{00D7}", 66 | "\u{00F7}", 67 | 68 | // General Punctuation 69 | "\u{2016}"..."\u{2017}", 70 | "\u{2020}"..."\u{2027}", 71 | "\u{2030}"..."\u{203E}", 72 | "\u{2041}"..."\u{2053}", 73 | "\u{2055}"..."\u{205E}", 74 | "\u{2190}"..."\u{23FF}", 75 | 76 | // Box Drawing 77 | "\u{2500}"..."\u{257F}", 78 | 79 | // Block Elements 80 | "\u{2580}"..."\u{259F}", 81 | 82 | // Miscellaneous Symbols 83 | "\u{2600}"..."\u{26FF}", 84 | 85 | // Dingbats 86 | "\u{2700}"..."\u{2775}", 87 | "\u{2794}"..."\u{2BFF}", 88 | 89 | // Supplemental Punctuation 90 | "\u{2E00}"..."\u{2E7F}", 91 | 92 | // CJK Symbols and Punctuation 93 | "\u{3001}"..."\u{3003}", 94 | "\u{3008}"..."\u{3020}", 95 | "\u{3030}": 96 | return true 97 | default: 98 | return false 99 | } 100 | } 101 | 102 | func isValidCharacter(_ character: Character) -> Bool { 103 | switch character { 104 | case "\u{0300}"..."\u{036F}", 105 | "\u{1DC0}"..."\u{1DFF}", 106 | "\u{20D0}"..."\u{20FF}", 107 | "\u{FE00}"..."\u{FE0F}", 108 | "\u{FE20}"..."\u{FE2F}", 109 | "\u{E0100}"..."\u{E01EF}": 110 | return true 111 | default: 112 | return isValidHeadCharacter(character) 113 | } 114 | } 115 | 116 | guard let first = string.first, 117 | isValidHeadCharacter(first) 118 | else { 119 | return false 120 | } 121 | 122 | for character in string.suffix(from: string.startIndex) { 123 | guard isValidCharacter(character) else { return false } 124 | } 125 | 126 | return true 127 | } 128 | } 129 | 130 | // MARK: - ExpressibleBySyntax 131 | 132 | extension Operator: ExpressibleBySyntax { 133 | /// Creates an instance initialized with the given syntax node. 134 | public init(_ node: OperatorDeclSyntax) { 135 | attributes = node.attributes?.compactMap{ $0.as(AttributeSyntax.self) }.map { Attribute($0) } ?? [] 136 | modifiers = node.modifiers?.map { Modifier($0) } ?? [] 137 | keyword = node.operatorKeyword.text.trimmed 138 | name = node.identifier.text.trimmed 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /Sources/SwiftSemantics/Declarations/PrecedenceGroup.swift: -------------------------------------------------------------------------------- 1 | import SwiftSyntax 2 | 3 | /// An operator precedence group declaration. 4 | public struct PrecedenceGroup: Declaration, Hashable, Codable { 5 | /** 6 | The associativity of an operator, 7 | which determines how operators of the same precedence 8 | are grouped in the absence of parentheses. 9 | 10 | Consider the expression `a ~ b ~ c`: 11 | If the `~` operator is *left-associative*, 12 | then the expression is interpreted as `(a ~ b) ~ c`. 13 | If the `~` operator is *right-associative*, 14 | then the expression is interpreted as `a ~ (b ~ c)`. 15 | 16 | For example, 17 | the Swift subtraction operator (`-`) is *left-associative*, 18 | such that `5 - 7 - 2` evaluates to `-4` (`(5 - 7) - 2`) 19 | rather than `0` (`5 - (7 - 2)`). 20 | */ 21 | public enum Associativity: String, Hashable, Codable { 22 | /// Left-associative (operations are grouped from the left). 23 | case left 24 | 25 | /// Right-associative (operations are grouped from the right). 26 | case right 27 | } 28 | 29 | /** 30 | The relation of operators to operators in other precedence groups, 31 | which determines the order in which 32 | operators of different precedence groups are evaluated 33 | in absence of parentheses. 34 | 35 | Consider the expression `a ⧓ b ⧗ c`. 36 | If the `⧓` operator has a *higher* precedence than `⧗`, 37 | then the expression is interpreted as `(a ⧓ b) ⧗ c`. 38 | If the `⧓` operator has a *lower* precedence than `⧗`, 39 | then the expression is interpreted as `a ⧓ (b ⧗ c)`. 40 | 41 | For example, 42 | Swift mathematical operators have the same inherent precedence 43 | as their corresponding arithmetic operations, 44 | such that `1 + 2 * 3` evaluates to `7` (`1 + (2 * 3)`) 45 | rather than `9` (`(1 + 2) * 3`). 46 | */ 47 | public enum Relation: Hashable { 48 | /** 49 | The precedence group has *higher* precedence than 50 | the associated group names. 51 | */ 52 | case higherThan([String]) 53 | 54 | /** 55 | The precedence group has *lower* precedence than 56 | the associated group names. 57 | */ 58 | case lowerThan([String]) 59 | } 60 | 61 | /// The declaration attributes. 62 | public let attributes: [Attribute] 63 | 64 | /// The declaration modifiers. 65 | public let modifiers: [Modifier] 66 | 67 | /// The declaration keyword (`"precedencegroup"`) 68 | public let keyword: String 69 | 70 | /// The precedence group name. 71 | public let name: String 72 | 73 | /** 74 | Whether operators in the precedence group are folded into optional chains. 75 | 76 | For example, 77 | if `assignment` is `true`, 78 | the expression `entry?.count += 1` 79 | has the effect of `entry?(.count += 1)`; 80 | otherwise, the same expression is interpreted as `(entry?.count) += 1` 81 | and fails to type-check. 82 | */ 83 | public let assignment: Bool? 84 | 85 | /// The associativity of operators in the precedence group. 86 | public let associativity: Associativity? 87 | 88 | /// The relation of operators to operators in other precedence groups. 89 | public let relations: [Relation] 90 | } 91 | 92 | // MARK: - 93 | 94 | extension PrecedenceGroup.Relation: Comparable { 95 | public static func < (lhs: PrecedenceGroup.Relation, rhs: PrecedenceGroup.Relation) -> Bool { 96 | switch (lhs, rhs) { 97 | case (.lowerThan, .higherThan): 98 | return true 99 | case (.higherThan, .lowerThan): 100 | return false 101 | case let (.lowerThan(lpg), .lowerThan(rpg)), 102 | let (.higherThan(lpg), .higherThan(rpg)): 103 | return lpg.count < rpg.count || (lpg.count == rpg.count && lpg.sorted().joined(separator: ",") < rpg.sorted().joined(separator: ",")) 104 | } 105 | } 106 | } 107 | 108 | extension PrecedenceGroup.Relation: Codable { 109 | private enum CodingKeys: String, CodingKey { 110 | case higherThan 111 | case lowerThan 112 | } 113 | 114 | public init(from decoder: Decoder) throws { 115 | let container = try decoder.container(keyedBy: CodingKeys.self) 116 | if let others = try? container.decode([String].self, forKey: .higherThan) { 117 | self = .higherThan(others) 118 | } else if let others = try? container.decode([String].self, forKey: .lowerThan) { 119 | self = .lowerThan(others) 120 | } else { 121 | let context = DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Missing key 'higherThan' or 'lowerThan'") 122 | throw DecodingError.dataCorrupted(context) 123 | } 124 | } 125 | 126 | public func encode(to encoder: Encoder) throws { 127 | var container = encoder.container(keyedBy: CodingKeys.self) 128 | switch self { 129 | case .higherThan(let others): 130 | try container.encode(others, forKey: .higherThan) 131 | case .lowerThan(let others): 132 | try container.encode(others, forKey: .lowerThan) 133 | } 134 | } 135 | } 136 | 137 | // MARK: - ExpressibleBySyntax 138 | 139 | extension PrecedenceGroup: ExpressibleBySyntax { 140 | /// Creates an instance initialized with the given syntax node. 141 | public init(_ node: PrecedenceGroupDeclSyntax) { 142 | attributes = node.attributes?.compactMap{ $0.as(AttributeSyntax.self) }.map { Attribute($0) } ?? [] 143 | modifiers = node.modifiers?.map { Modifier($0) } ?? [] 144 | keyword = node.precedencegroupKeyword.text.trimmed 145 | name = node.identifier.text.trimmed 146 | 147 | var assignment: Bool? 148 | var associativity: Associativity? 149 | var relations: [Relation] = [] 150 | 151 | for attribute in node.groupAttributes { 152 | if let attribute = PrecedenceGroupAssignmentSyntax(attribute) { 153 | assignment = Bool(attribute) 154 | } else if let attribute = PrecedenceGroupAssociativitySyntax(attribute) { 155 | associativity = Associativity(attribute) 156 | } else if let attribute = PrecedenceGroupRelationSyntax(attribute) { 157 | if let relation = Relation(attribute) { 158 | relations.append(relation) 159 | } 160 | } 161 | } 162 | 163 | self.assignment = assignment 164 | self.associativity = associativity 165 | self.relations = relations 166 | } 167 | } 168 | 169 | private extension Bool { 170 | /// Creates an instance initialized with the given syntax node. 171 | init?(_ node: PrecedenceGroupAssignmentSyntax) { 172 | self.init(node.flag.text) 173 | } 174 | } 175 | 176 | extension PrecedenceGroup.Associativity { 177 | /// Creates an instance initialized with the given syntax node. 178 | public init?(_ node: PrecedenceGroupAssociativitySyntax) { 179 | self.init(rawValue: node.value.description) 180 | } 181 | } 182 | 183 | extension PrecedenceGroup.Relation { 184 | /// Creates an instance initialized with the given syntax node. 185 | public init?(_ node: PrecedenceGroupRelationSyntax) { 186 | let otherNames = node.otherNames.map { $0.name.description } 187 | 188 | switch node.higherThanOrLowerThan.text { 189 | case "higherThan": 190 | self = .higherThan(otherNames) 191 | case "lowerThan": 192 | self = .lowerThan(otherNames) 193 | default: 194 | return nil 195 | } 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /Sources/SwiftSemantics/Declarations/Protocol.swift: -------------------------------------------------------------------------------- 1 | import SwiftSyntax 2 | 3 | /// A protocol declaration. 4 | public struct Protocol: Declaration, Hashable, Codable { 5 | /// The declaration attributes. 6 | public let attributes: [Attribute] 7 | 8 | /// The declaration modifiers. 9 | public let modifiers: [Modifier] 10 | 11 | /// The declaration keyword (`"protocol"`). 12 | public let keyword: String 13 | 14 | /// The protocol name. 15 | public let name: String 16 | 17 | /** 18 | A list of adopted protocols. 19 | 20 | For example, 21 | given the following declarations, 22 | the `inheritance` of protocol `P` is `["Q"]`: 23 | 24 | ```swift 25 | protocol Q {} 26 | protocol P: Q {} 27 | ``` 28 | */ 29 | public let inheritance: [String] 30 | } 31 | 32 | // MARK: - ExpressibleBySyntax 33 | 34 | extension Protocol: ExpressibleBySyntax { 35 | /// Creates an instance initialized with the given syntax node. 36 | public init(_ node: ProtocolDeclSyntax) { 37 | attributes = node.attributes?.compactMap{ $0.as(AttributeSyntax.self) }.map { Attribute($0) } ?? [] 38 | modifiers = node.modifiers?.map { Modifier($0) } ?? [] 39 | keyword = node.protocolKeyword.text.trimmed 40 | name = node.identifier.text.trimmed 41 | inheritance = node.inheritanceClause?.inheritedTypeCollection.map { $0.typeName.description.trimmed } ?? [] 42 | } 43 | } 44 | 45 | // MARK: - CustomStringConvertible 46 | 47 | extension Protocol: CustomStringConvertible { 48 | public var description: String { 49 | var description = ( 50 | attributes.map { $0.description } + 51 | modifiers.map { $0.description } + 52 | [keyword, name] 53 | ).joined(separator: " ") 54 | 55 | if !inheritance.isEmpty { 56 | description += ": \(inheritance.joined(separator: ", "))" 57 | } 58 | 59 | return description 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Sources/SwiftSemantics/Declarations/Structure.swift: -------------------------------------------------------------------------------- 1 | import SwiftSyntax 2 | 3 | /// A structure declaration. 4 | public struct Structure: Declaration, Hashable, Codable { 5 | /// The declaration attributes. 6 | public let attributes: [Attribute] 7 | 8 | /// The declaration modifiers. 9 | public let modifiers: [Modifier] 10 | 11 | /// The declaration keyword (`"struct"`). 12 | public let keyword: String 13 | 14 | /// The structure name. 15 | public let name: String 16 | 17 | /** 18 | A list of adopted protocols. 19 | 20 | For example, 21 | given the following declarations, 22 | the `inheritance` of structure `S` is `["P", "Q"]`: 23 | 24 | ```swift 25 | protocol P {} 26 | protocol Q {} 27 | struct S {} 28 | ``` 29 | */ 30 | public let inheritance: [String] 31 | 32 | /** 33 | The generic parameters for the declaration. 34 | 35 | For example, 36 | the following declaration of structure `S` 37 | has a single generic parameter 38 | whose `name` is `"T"` and `type` is `"Equatable"`: 39 | 40 | ```swift 41 | struct S {} 42 | ``` 43 | */ 44 | public let genericParameters: [GenericParameter] 45 | 46 | /** 47 | The generic parameter requirements for the declaration. 48 | 49 | For example, 50 | the following declaration of structure `S` 51 | has a single requirement 52 | that its generic parameter identified as `"T"` 53 | conforms to the type identified as `"Hashable"`: 54 | 55 | ```swift 56 | struct S where T: Hashable {} 57 | ``` 58 | */ 59 | public let genericRequirements: [GenericRequirement] 60 | } 61 | 62 | // MARK: - ExpressibleBySyntax 63 | 64 | extension Structure: ExpressibleBySyntax { 65 | /// Creates an instance initialized with the given syntax node. 66 | public init(_ node: StructDeclSyntax) { 67 | attributes = node.attributes?.compactMap{ $0.as(AttributeSyntax.self) }.map { Attribute($0) } ?? [] 68 | modifiers = node.modifiers?.map { Modifier($0) } ?? [] 69 | keyword = node.structKeyword.text.trimmed 70 | name = node.identifier.text.trimmed 71 | inheritance = node.inheritanceClause?.inheritedTypeCollection.map { $0.typeName.description.trimmed } ?? [] 72 | genericParameters = node.genericParameterClause?.genericParameterList.map { GenericParameter($0) } ?? [] 73 | genericRequirements = GenericRequirement.genericRequirements(from: node.genericWhereClause?.requirementList) 74 | } 75 | } 76 | 77 | // MARK: - CustomStringConvertible 78 | 79 | extension Structure: CustomStringConvertible { 80 | public var description: String { 81 | var description = ( 82 | attributes.map { $0.description } + 83 | modifiers.map { $0.description } + 84 | [keyword, name] 85 | ).joined(separator: " ") 86 | 87 | if !genericParameters.isEmpty { 88 | description += "<\(genericParameters.map { $0.description }.joined(separator: ", "))>" 89 | } 90 | 91 | if !inheritance.isEmpty { 92 | description += ": \(inheritance.joined(separator: ", "))" 93 | } 94 | 95 | if !genericRequirements.isEmpty { 96 | description += " where \(genericRequirements.map { $0.description }.joined(separator: ", "))" 97 | } 98 | 99 | return description 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /Sources/SwiftSemantics/Declarations/Subscript.swift: -------------------------------------------------------------------------------- 1 | import SwiftSyntax 2 | 3 | /// A subscript declaration. 4 | public struct Subscript: Declaration, Hashable, Codable { 5 | /// The declaration attributes. 6 | public let attributes: [Attribute] 7 | 8 | /// The declaration modifiers. 9 | public let modifiers: [Modifier] 10 | 11 | /// The declaration keyword (`"subscript"`). 12 | public let keyword: String 13 | 14 | /// The subscript indices. 15 | public let indices: [Function.Parameter] 16 | 17 | /** 18 | The generic parameters for the declaration. 19 | 20 | For example, 21 | the following subscript declaration 22 | has a single generic parameter 23 | whose `name` is `"T"` and `type` is `"Equatable"`: 24 | 25 | ```swift 26 | subscript(value: T) {} 27 | ``` 28 | */ 29 | public let genericParameters: [GenericParameter] 30 | 31 | /** 32 | The generic parameter requirements for the declaration. 33 | 34 | For example, 35 | the following subscript declaration 36 | has a single requirement 37 | that its generic parameter identified as `"T"` 38 | conforms to the type identified as `"Hashable"`: 39 | 40 | ```swift 41 | subscript(value: T) where T: Hashable {} 42 | ``` 43 | */ 44 | public let genericRequirements: [GenericRequirement] 45 | 46 | /// The return type of the subscript. 47 | public let returnType: String 48 | 49 | /// The subscript getter and/or setter. 50 | public let accessors: [Variable.Accessor] 51 | } 52 | 53 | // MARK: - ExpressibleBySyntax 54 | 55 | extension Subscript: ExpressibleBySyntax { 56 | /// Creates an instance initialized with the given syntax node. 57 | public init(_ node: SubscriptDeclSyntax) { 58 | attributes = node.attributes?.compactMap{ $0.as(AttributeSyntax.self) }.map { Attribute($0) } ?? [] 59 | modifiers = node.modifiers?.map { Modifier($0) } ?? [] 60 | keyword = node.subscriptKeyword.text.trimmed 61 | indices = node.indices.parameterList.map { Function.Parameter($0) } 62 | genericParameters = node.genericParameterClause?.genericParameterList.map { GenericParameter($0) } ?? [] 63 | returnType = node.result.returnType.description.trimmed 64 | genericRequirements = GenericRequirement.genericRequirements(from: node.genericWhereClause?.requirementList) 65 | accessors = Variable.Accessor.accessors(from: node.accessor?.as(AccessorBlockSyntax.self)) 66 | } 67 | } 68 | 69 | // MARK: - CustomStringConvertible 70 | 71 | extension Subscript: CustomStringConvertible { 72 | public var description: String { 73 | var description = ( 74 | attributes.map { $0.description } + 75 | modifiers.map { $0.description } + 76 | [keyword] 77 | ).joined(separator: " ") 78 | 79 | if !genericParameters.isEmpty { 80 | description += "<\(genericParameters.map { $0.description }.joined(separator: ", "))>" 81 | } 82 | 83 | description += "(\(indices.map { $0.description }.joined(separator: ", ")))" 84 | 85 | if !genericRequirements.isEmpty { 86 | description += " where \(genericRequirements.map { $0.description }.joined(separator: ", "))" 87 | } 88 | 89 | description += " -> \(returnType)" 90 | 91 | return description 92 | } 93 | 94 | } 95 | 96 | -------------------------------------------------------------------------------- /Sources/SwiftSemantics/Declarations/Typealias.swift: -------------------------------------------------------------------------------- 1 | import SwiftSyntax 2 | 3 | /// A type alias declaration. 4 | public struct Typealias: Declaration, Hashable, Codable { 5 | /// The declaration attributes. 6 | public let attributes: [Attribute] 7 | 8 | /// The declaration modifiers. 9 | public let modifiers: [Modifier] 10 | 11 | /// The declaration keyword (`"typealias"`). 12 | public let keyword: String 13 | 14 | /// The type alias name. 15 | public let name: String 16 | 17 | /// The initialized type, if any. 18 | public let initializedType: String? 19 | 20 | /** 21 | The generic parameters for the declaration. 22 | 23 | For example, 24 | the following typealias declaration 25 | has a single generic parameter 26 | whose `name` is `"T"` and `type` is `"Comparable"`: 27 | 28 | ```swift 29 | typealias SortableArray = Array 30 | ``` 31 | */ 32 | public let genericParameters: [GenericParameter] 33 | 34 | /** 35 | The generic parameter requirements for the declaration. 36 | 37 | For example, 38 | the following typealias declaration 39 | has a single requirement 40 | that its generic parameter identified as `"T"` 41 | conforms to the type identified as `"Numeric"`: 42 | 43 | ```swift 44 | typealias ArrayOfNumbers = Array where T: Numeric 45 | ``` 46 | */ 47 | public let genericRequirements: [GenericRequirement] 48 | } 49 | 50 | // MARK: - ExpressibleBySyntax 51 | 52 | extension Typealias: ExpressibleBySyntax { 53 | /// Creates an instance initialized with the given syntax node. 54 | public init(_ node: TypealiasDeclSyntax) { 55 | attributes = node.attributes?.compactMap{ $0.as(AttributeSyntax.self) }.map { Attribute($0) } ?? [] 56 | modifiers = node.modifiers?.map { Modifier($0) } ?? [] 57 | keyword = node.typealiasKeyword.text.trimmed 58 | name = node.identifier.text.trimmed 59 | initializedType = node.initializer?.value.description.trimmed 60 | genericParameters = node.genericParameterClause?.genericParameterList.map { GenericParameter($0) } ?? [] 61 | genericRequirements = GenericRequirement.genericRequirements(from: node.genericWhereClause?.requirementList) 62 | } 63 | } 64 | 65 | // MARK: - CustomStringConvertible 66 | 67 | extension Typealias: CustomStringConvertible { 68 | public var description: String { 69 | var description = ( 70 | attributes.map { $0.description } + 71 | modifiers.map { $0.description } + 72 | [keyword, name] 73 | ).joined(separator: " ") 74 | 75 | if !genericParameters.isEmpty { 76 | description += "<\(genericParameters.map { $0.description }.joined(separator: ", "))>" 77 | } 78 | 79 | if let initializedType = initializedType { 80 | description += " = \(initializedType)" 81 | } 82 | 83 | if !genericRequirements.isEmpty { 84 | description += " where \(genericRequirements.map { $0.description }.joined(separator: ", "))" 85 | } 86 | 87 | return description 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /Sources/SwiftSemantics/Declarations/Variable.swift: -------------------------------------------------------------------------------- 1 | import SwiftSyntax 2 | 3 | /// A declaration for a property or a top-level variable or constant. 4 | public struct Variable: Declaration, Hashable, Codable { 5 | /// The declaration attributes. 6 | public let attributes: [Attribute] 7 | 8 | /// The declaration modifiers. 9 | public let modifiers: [Modifier] 10 | 11 | /// The declaration keyword (`"let"` or `"var"`). 12 | public let keyword: String 13 | 14 | /// The name of the property or top-level variable or constant. 15 | public let name: String 16 | 17 | /// The type annotation for the declaration, if any. 18 | public let typeAnnotation: String? 19 | 20 | /// The initialized value for the declaration, if any. 21 | public let initializedValue: String? 22 | 23 | /// The variable or property accessors. 24 | public let accessors: [Accessor] 25 | 26 | /// A computed variable or computed property accessor. 27 | public struct Accessor: Hashable, Codable { 28 | /// The kind of accessor (`get` or `set`). 29 | public enum Kind: String, Hashable, Codable { 30 | /// A getter that returns a value. 31 | case get 32 | 33 | /// A setter that sets a value. 34 | case set 35 | } 36 | 37 | /// The accessor attributes. 38 | public let attributes: [Attribute] 39 | 40 | /// The accessor modifiers. 41 | public let modifier: Modifier? 42 | 43 | /// The kind of accessor. 44 | public let kind: Kind? 45 | } 46 | } 47 | 48 | // MARK: - ExpressibleBySyntax 49 | 50 | extension Variable: ExpressibleBySyntax { 51 | /** 52 | Creates and returns variables from a variable declaration, 53 | which may contain one or more pattern bindings, 54 | such as `let x: Int = 1, y: Int = 2`. 55 | */ 56 | public static func variables(from node: VariableDeclSyntax) -> [Variable] { 57 | return node.bindings.compactMap { Variable($0) } 58 | } 59 | 60 | /// Creates an instance initialized with the given syntax node. 61 | public init?(_ node: PatternBindingSyntax) { 62 | guard let parent = node.context as? VariableDeclSyntax else { 63 | preconditionFailure("PatternBindingSyntax should be contained within VariableDeclSyntax") 64 | return nil 65 | } 66 | 67 | attributes = parent.attributes?.compactMap{ $0.as(AttributeSyntax.self) }.map { Attribute($0) } ?? [] 68 | modifiers = parent.modifiers?.map { Modifier($0) } ?? [] 69 | keyword = parent.letOrVarKeyword.text.trimmed 70 | name = node.pattern.description.trimmed 71 | typeAnnotation = node.typeAnnotation?.type.description.trimmed 72 | initializedValue = node.initializer?.value.description.trimmed 73 | accessors = Accessor.accessors(from: node.accessor?.as(AccessorBlockSyntax.self)) 74 | } 75 | } 76 | 77 | extension Variable.Accessor: ExpressibleBySyntax { 78 | public static func accessors(from node: AccessorBlockSyntax?) -> [Variable.Accessor] { 79 | guard let node = node else { return [] } 80 | return node.accessors.compactMap { Variable.Accessor($0) } 81 | } 82 | 83 | public init?(_ node: AccessorDeclSyntax) { 84 | let rawValue = node.accessorKind.text.trimmed 85 | if rawValue.isEmpty { 86 | self.kind = nil 87 | } else if let kind = Kind(rawValue: rawValue) { 88 | self.kind = kind 89 | } else { 90 | return nil 91 | } 92 | 93 | attributes = node.attributes?.compactMap{ $0.as(AttributeSyntax.self) }.map { Attribute($0) } ?? [] 94 | modifier = node.modifier.map { Modifier($0) } 95 | } 96 | } 97 | 98 | // MARK: - CustomStringConvertible 99 | 100 | extension Variable: CustomStringConvertible { 101 | public var description: String { 102 | switch (self.typeAnnotation, self.initializedValue) { 103 | case let (typeAnnotation?, initializedValue?): 104 | return "\(keyword) \(name): \(typeAnnotation) = \(initializedValue)" 105 | case let (typeAnnotation?, _): 106 | return "\(keyword) \(name): \(typeAnnotation)" 107 | case let (_, initializedValue?): 108 | return "\(keyword) \(name) = \(initializedValue)" 109 | default: 110 | return "\(keyword) \(name)" 111 | } 112 | } 113 | } 114 | 115 | -------------------------------------------------------------------------------- /Sources/SwiftSemantics/ExpressibleBySyntax.swift: -------------------------------------------------------------------------------- 1 | import SwiftSyntax 2 | 3 | /// A type that can be initialized with a Swift syntax node. 4 | public protocol ExpressibleBySyntax { 5 | associatedtype Syntax: SyntaxProtocol 6 | 7 | /// Creates an instance initialized with the given syntax node. 8 | init?(_ node: Syntax) 9 | } 10 | -------------------------------------------------------------------------------- /Sources/SwiftSemantics/Extensions/StringProtocol+Extensions.swift: -------------------------------------------------------------------------------- 1 | extension StringProtocol { 2 | var nonEmpty: String? { 3 | return isEmpty ? nil : String(self) 4 | } 5 | 6 | var trimmed: String { 7 | let startIndex = firstIndex(where: { !$0.isWhitespace }) ?? self.startIndex 8 | let endIndex = lastIndex(where: { !$0.isWhitespace }) ?? self.endIndex 9 | return String(self[startIndex...endIndex]) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Sources/SwiftSemantics/Extensions/SwiftSyntax+Extensions.swift: -------------------------------------------------------------------------------- 1 | import SwiftSyntax 2 | 3 | extension SyntaxProtocol { 4 | var context: DeclSyntaxProtocol? { 5 | for case let node? in sequence(first: parent, next: { $0?.parent }) { 6 | guard let declaration = node.asProtocol(DeclSyntaxProtocol.self) else { continue } 7 | return declaration 8 | } 9 | 10 | return nil 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Sources/SwiftSemantics/Supporting Types/Attribute.swift: -------------------------------------------------------------------------------- 1 | import SwiftSyntax 2 | 3 | /** 4 | A declaration attribute. 5 | 6 | Attributes provide additional information about a declaration. 7 | For example, 8 | the `@discardableResult` attribute indicates that 9 | a function may be called without using the result. 10 | */ 11 | public struct Attribute: Hashable, Codable { 12 | /** 13 | The attribute name. 14 | 15 | An attribute's name is everything after the at-sign (`@`) 16 | and before the argument clause. 17 | For example, 18 | the name of the attribute in the following declaration is `"available"`: 19 | 20 | ```swift 21 | @available(macOS 10.15, iOS 13, *) 22 | ``` 23 | */ 24 | public let name: String 25 | 26 | /// The attribute's arguments, if any. 27 | public let arguments: [Argument] 28 | 29 | /** 30 | An attribute argument. 31 | 32 | Certain attributes take one or more arguments, 33 | each of which have a value and optional name. 34 | For example, 35 | the following attribute declaration has three arguments: 36 | 37 | ```swift 38 | @available(*, unavailable, message: "🚫") 39 | ``` 40 | 41 | - The first argument is unnamed and has the value `"*"` 42 | - The second argument is unnamed and has the value `"unavailable"` 43 | - The third argument has the name "`renamed`" and the value `"🚫"` 44 | */ 45 | public struct Argument: Hashable, Codable { 46 | /// The argument name, if any. 47 | public let name: String? 48 | 49 | /// The argument value. 50 | public let value: String 51 | 52 | static func arguments(from node: SwiftSyntax.Syntax?) -> [Argument] { 53 | guard let node = node else { return [] } 54 | return node.description.split(separator: ",").compactMap { token in 55 | let components = token.split(separator: ":", maxSplits: 1) 56 | if components.count == 2, 57 | let name = components.first, 58 | let value = components.last 59 | { 60 | return Argument(name: name.trimmed, value: value.trimmed) 61 | } else if components.count == 1, 62 | let value = components.last 63 | { 64 | return Argument(name: nil, value: value.trimmed) 65 | } else { 66 | assertionFailure("invalid argument token: \(token)") 67 | return nil 68 | } 69 | } 70 | } 71 | } 72 | } 73 | 74 | // MARK: - ExpressibleBySyntax 75 | 76 | extension Attribute: ExpressibleBySyntax { 77 | /// Creates an instance initialized with the given syntax node. 78 | public init(_ node: AttributeSyntax) { 79 | name = node.attributeName.text.trimmed 80 | arguments = Argument.arguments(from: node.argument) 81 | } 82 | } 83 | 84 | // MARK: - CustomStringConvertible 85 | 86 | extension Attribute: CustomStringConvertible { 87 | public var description: String { 88 | if arguments.isEmpty { 89 | return "@\(name)" 90 | } else { 91 | return "@\(name)(\(arguments.map { $0.description }.joined(separator: ", ")))" 92 | } 93 | } 94 | } 95 | 96 | extension Attribute.Argument: CustomStringConvertible { 97 | public var description: String { 98 | if let name = name { 99 | return "\(name): \(value)" 100 | } else { 101 | return "\(value)" 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /Sources/SwiftSemantics/Supporting Types/GenericParameter.swift: -------------------------------------------------------------------------------- 1 | import SwiftSyntax 2 | 3 | /** 4 | A generic parameter. 5 | 6 | A generic type or function declaration includes a generic parameter clause, 7 | consisting of one or more generic parameters enclosed by angle brackets (`<>`). 8 | Each generic parameter has a name, 9 | and may also specify a type constraint. 10 | For example, 11 | the following structure declaration has two generic parameters: 12 | 13 | ```swift 14 | struct S 15 | ``` 16 | 17 | - The first generic parameter is named `"T"` 18 | and has no type constraint. 19 | - The second generic parameter is named `"U"` 20 | and a type constraint on `"Equatable"`. 21 | */ 22 | public struct GenericParameter: Hashable, Codable { 23 | /// The generic parameter attributes. 24 | public let attributes: [Attribute] 25 | 26 | /// The generic parameter name. 27 | public let name: String 28 | 29 | /// The generic parameter type, if any. 30 | public let type: String? 31 | } 32 | 33 | // MARK: - ExpressibleBySyntax 34 | 35 | extension GenericParameter: ExpressibleBySyntax { 36 | /// Creates an instance initialized with the given syntax node. 37 | public init(_ node: GenericParameterSyntax) { 38 | attributes = node.attributes?.compactMap{ $0.as(AttributeSyntax.self) }.map { Attribute($0) } ?? [] 39 | name = node.name.text.trimmed 40 | type = node.inheritedType?.description 41 | } 42 | } 43 | 44 | // MARK: - CustomStringConvertible 45 | 46 | extension GenericParameter: CustomStringConvertible { 47 | public var description: String { 48 | var description: String = (attributes.map { $0.description } + [name]).joined(separator: " ") 49 | if let type = type { 50 | description += ": \(type)" 51 | } 52 | 53 | return description 54 | } 55 | } 56 | 57 | -------------------------------------------------------------------------------- /Sources/SwiftSemantics/Supporting Types/GenericRequirement.swift: -------------------------------------------------------------------------------- 1 | import SwiftSyntax 2 | 3 | /** 4 | A generic requirement. 5 | 6 | A generic type or function declaration may specifying one or more requirements 7 | in a generic where clause before the opening curly brace (`{`) its body. 8 | Each generic requirement establishes a relation between two type identifiers. 9 | 10 | For example, 11 | the following declaration specifies two generic requirements: 12 | 13 | ```swift 14 | func difference(between lhs: C1, and rhs: C2) -> [C1.Element] 15 | where C1.Element: Equatable, C1.Element == C2.Element 16 | ``` 17 | 18 | - The first generic requirement establishes a `conformance` relation 19 | between the generic types identified by `"C1.Element"` and `"Equatable"` 20 | - The second generic requirement establsihes a `sameType` relation 21 | between the generic types identified by `"C1.Element"` and `"C2.Element"` 22 | */ 23 | public struct GenericRequirement: Hashable, Codable { 24 | /** 25 | A relation between the two types identified 26 | in the generic requirement. 27 | 28 | For example, 29 | the declaration `struct S` 30 | has a single generic requirement 31 | that the type identified by `"T"` 32 | conforms to the type identified by `"Equatable"`. 33 | */ 34 | public enum Relation: String, Hashable, Codable { 35 | /** 36 | The type identified on the left-hand side is equivalent to 37 | the type identified on the right-hand side of the generic requirement. 38 | */ 39 | case sameType 40 | 41 | /** 42 | The type identified on the left-hand side conforms to 43 | the type identified on the right-hand side of the generic requirement. 44 | */ 45 | case conformance 46 | } 47 | 48 | /// The relation between the two identified types. 49 | public let relation: Relation 50 | 51 | /// The identifier for the left-hand side type. 52 | public let leftTypeIdentifier: String 53 | 54 | /// The identifier for the right-hand side type. 55 | public let rightTypeIdentifier: String 56 | 57 | /** 58 | Creates and returns generic requirements initialized from a 59 | generic requirement list syntax node. 60 | 61 | - Parameter from: The generic requirement list syntax node, or `nil`. 62 | - Returns: An array of generic requirements, or `nil` if the node is `nil`. 63 | */ 64 | public static func genericRequirements(from node: GenericRequirementListSyntax?) -> [GenericRequirement] { 65 | guard let node = node else { return [] } 66 | return node.compactMap { GenericRequirement($0) } 67 | } 68 | 69 | private init?(_ node: GenericRequirementSyntax) { 70 | if let node = SameTypeRequirementSyntax(node.body) { 71 | self.relation = .sameType 72 | self.leftTypeIdentifier = node.leftTypeIdentifier.description.trimmed 73 | self.rightTypeIdentifier = node.rightTypeIdentifier.description.trimmed 74 | } else if let node = ConformanceRequirementSyntax(node.body) { 75 | self.relation = .conformance 76 | self.leftTypeIdentifier = node.leftTypeIdentifier.description.trimmed 77 | self.rightTypeIdentifier = node.rightTypeIdentifier.description.trimmed 78 | } else { 79 | return nil 80 | } 81 | } 82 | } 83 | 84 | // MARK: - CustomStringConvertible 85 | 86 | extension GenericRequirement: CustomStringConvertible { 87 | public var description: String { 88 | switch relation { 89 | case .sameType: 90 | return "\(leftTypeIdentifier) == \(rightTypeIdentifier)" 91 | case .conformance: 92 | return "\(leftTypeIdentifier): \(rightTypeIdentifier)" 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /Sources/SwiftSemantics/Supporting Types/Modifier.swift: -------------------------------------------------------------------------------- 1 | import SwiftSyntax 2 | 3 | /** 4 | A declaration modifier. 5 | 6 | A declaration may have one or more modifiers to 7 | specify access control (`private` / `public` / etc.), 8 | declare a type member (`class` / `static`), 9 | or designate its mutability (`nonmutating`). 10 | A declaration modifier may specify an additional detail 11 | within enclosing parentheses (`()`) 12 | following its name. 13 | 14 | For example, 15 | the following property declaration has two modifiers: 16 | 17 | ```swift 18 | public private(set) var title: String 19 | ``` 20 | 21 | - The first modifier has a `name` equal to `"public"`, 22 | and a nil `detail` 23 | - The second modifier has a `name` equal to `"private"` 24 | and a `detail` equal to `"set"` 25 | */ 26 | public struct Modifier: Hashable, Codable { 27 | /// The declaration modifier name. 28 | public let name: String 29 | 30 | /// The modifier detail, if any. 31 | public let detail: String? 32 | } 33 | 34 | // MARK: - ExpressibleBySyntax 35 | 36 | extension Modifier: ExpressibleBySyntax { 37 | /// Creates an instance initialized with the given syntax node. 38 | public init(_ node: DeclModifierSyntax) { 39 | name = node.name.text.trimmed 40 | detail = node.detail?.description 41 | } 42 | } 43 | 44 | // MARK: - CustomStringConvertible 45 | 46 | extension Modifier: CustomStringConvertible { 47 | public var description: String { 48 | if let detail = detail { 49 | return "\(name)(\(detail))" 50 | } else { 51 | return name 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | import SwiftSemanticsTests 4 | 5 | var tests = [XCTestCaseEntry]() 6 | tests += SwiftSemanticsTests.allTests() 7 | XCTMain(tests) 8 | -------------------------------------------------------------------------------- /Tests/SwiftSemanticsTests/AssociatedTypeTests.swift: -------------------------------------------------------------------------------- 1 | @testable import SwiftSemantics 2 | import SwiftSyntax 3 | import XCTest 4 | 5 | final class AssociatedTypeTests: XCTestCase { 6 | func testAssociatedTypeDeclaration() throws { 7 | let source = #""" 8 | associatedtype T 9 | """# 10 | 11 | let declarations = try SyntaxParser.declarations(of: AssociatedType.self, source: source) 12 | XCTAssertEqual(declarations.count, 1) 13 | let associatedType = declarations.first! 14 | 15 | XCTAssertEqual(associatedType.attributes.count, 0) 16 | XCTAssertEqual(associatedType.name, "T") 17 | XCTAssertEqual(associatedType.description, "associatedtype T") 18 | } 19 | 20 | static var allTests = [ 21 | ("testAssociatedTypeDeclaration", testAssociatedTypeDeclaration), 22 | ] 23 | } 24 | 25 | -------------------------------------------------------------------------------- /Tests/SwiftSemanticsTests/AttributeTests.swift: -------------------------------------------------------------------------------- 1 | @testable import SwiftSemantics 2 | import SwiftSyntax 3 | import XCTest 4 | 5 | final class AttributeTests: XCTestCase { 6 | func testPropertyWrapperAttribute() throws { 7 | let source = #""" 8 | @propertyWrapper 9 | struct Atomic {} 10 | """# 11 | 12 | let declarations = try SyntaxParser.declarations(of: Structure.self, source: source) 13 | XCTAssertEqual(declarations.count, 1) 14 | let structure = declarations.first! 15 | XCTAssertEqual(structure.attributes.count, 1) 16 | let attribute = structure.attributes.first! 17 | 18 | XCTAssertEqual(attribute.name, "propertyWrapper") 19 | XCTAssert(attribute.arguments.isEmpty) 20 | XCTAssertEqual(attribute.description, "@propertyWrapper") 21 | } 22 | 23 | func testAvailableAttribute() throws { 24 | let source = #""" 25 | @available(*, unavailable, renamed: "New") 26 | class Old {} 27 | """# 28 | 29 | let declarations = try SyntaxParser.declarations(of: Class.self, source: source) 30 | XCTAssertEqual(declarations.count, 1) 31 | let `class` = declarations.first! 32 | XCTAssertEqual(`class`.attributes.count, 1) 33 | let attribute = `class`.attributes.first! 34 | 35 | XCTAssertEqual(attribute.name, "available") 36 | XCTAssertEqual(attribute.arguments.count, 3) 37 | 38 | XCTAssertNil(attribute.arguments[0].name) 39 | XCTAssertEqual(attribute.arguments[0].value, "*") 40 | 41 | XCTAssertNil(attribute.arguments[1].name) 42 | XCTAssertEqual(attribute.arguments[1].value, "unavailable") 43 | 44 | XCTAssertEqual(attribute.arguments[2].name, "renamed") 45 | XCTAssertEqual(attribute.arguments[2].value, #""New""#) 46 | 47 | XCTAssertEqual(attribute.description, #"@available(*, unavailable, renamed: "New")"#) 48 | } 49 | 50 | static var allTests = [ 51 | ("testPropertyWrapperAttribute", testPropertyWrapperAttribute), 52 | ("testAvailableAttribute", testAvailableAttribute), 53 | ] 54 | } 55 | 56 | -------------------------------------------------------------------------------- /Tests/SwiftSemanticsTests/ConditionalCompilationBlockTests.swift: -------------------------------------------------------------------------------- 1 | @testable import SwiftSemantics 2 | import SwiftSyntax 3 | import XCTest 4 | 5 | final class ConditionalCompilationBlockTests: XCTestCase { 6 | func testConditionalCompilationBlock() throws { 7 | let source = #""" 8 | #if compiler(>=5) && os(Linux) 9 | enum A {} 10 | #elseif swift(>=4.2) 11 | enum B {} 12 | #else 13 | enum C {} 14 | #endif 15 | """# 16 | 17 | let declarations = try SyntaxParser.declarations(of: ConditionalCompilationBlock.self, source: source) 18 | XCTAssertEqual(declarations.count, 1) 19 | let conditionalCompilationBlock = declarations.first! 20 | 21 | XCTAssertEqual(conditionalCompilationBlock.branches.count, 3) 22 | 23 | XCTAssertEqual(conditionalCompilationBlock.branches[0].keyword, "#if") 24 | XCTAssertEqual(conditionalCompilationBlock.branches[0].condition, "compiler(>=5) && os(Linux)") 25 | 26 | XCTAssertEqual(conditionalCompilationBlock.branches[1].keyword, "#elseif") 27 | XCTAssertEqual(conditionalCompilationBlock.branches[1].condition, "swift(>=4.2)") 28 | 29 | XCTAssertEqual(conditionalCompilationBlock.branches[2].keyword, "#else") 30 | XCTAssertNil(conditionalCompilationBlock.branches[2].condition) 31 | } 32 | 33 | static var allTests = [ 34 | ("testConditionalCompilationBlock", testConditionalCompilationBlock), 35 | ] 36 | } 37 | 38 | -------------------------------------------------------------------------------- /Tests/SwiftSemanticsTests/DeclarationCollectorTests.swift: -------------------------------------------------------------------------------- 1 | @testable import SwiftSemantics 2 | import SwiftSyntax 3 | import XCTest 4 | 5 | final class DeclarationCollectorTests: XCTestCase { 6 | func testDeclarationCollector() throws { 7 | let source = #""" 8 | import UIKit 9 | 10 | class ViewController: UIViewController, UITableViewDelegate { 11 | enum Section: Int { 12 | case summary, people, places 13 | } 14 | 15 | var people: [People], places: [Place] 16 | 17 | @IBOutlet private(set) var tableView: UITableView! 18 | } 19 | 20 | """# 21 | 22 | let collector = DeclarationCollector() 23 | let tree = try SyntaxParser.parse(source: source) 24 | collector.walk(tree) 25 | 26 | XCTAssertEqual(collector.imports.count, 1) 27 | XCTAssertEqual(collector.imports.first?.pathComponents, ["UIKit"]) 28 | 29 | XCTAssertEqual(collector.classes.count, 1) 30 | XCTAssertEqual(collector.classes.first?.name, "ViewController") 31 | XCTAssertEqual(collector.classes.first?.inheritance, ["UIViewController", "UITableViewDelegate"]) 32 | 33 | XCTAssertEqual(collector.enumerations.count, 1) 34 | XCTAssertEqual(collector.enumerations.first?.name, "Section") 35 | XCTAssertEqual(collector.enumerations.first?.inheritance, ["Int"]) 36 | 37 | XCTAssertEqual(collector.enumerationCases.count, 3) 38 | XCTAssertEqual(collector.enumerationCases.map { $0.name }, ["summary", "people", "places"]) 39 | 40 | XCTAssertEqual(collector.variables.count, 3) 41 | XCTAssertEqual(collector.variables[0].name, "people") 42 | XCTAssertEqual(collector.variables[0].typeAnnotation, "[People]") 43 | XCTAssertEqual(collector.variables[1].name, "places") 44 | XCTAssertEqual(collector.variables[1].typeAnnotation, "[Place]") 45 | XCTAssertEqual(collector.variables[2].name, "tableView") 46 | XCTAssertEqual(collector.variables[2].typeAnnotation, "UITableView!") 47 | XCTAssertEqual(collector.variables[2].attributes.first?.name, "IBOutlet") 48 | XCTAssertEqual(collector.variables[2].modifiers.first?.name, "private") 49 | XCTAssertEqual(collector.variables[2].modifiers.first?.detail, "set") 50 | } 51 | 52 | static var allTests = [ 53 | ("testDeclarationCollector", testDeclarationCollector), 54 | ] 55 | } 56 | 57 | -------------------------------------------------------------------------------- /Tests/SwiftSemanticsTests/ExtensionTests.swift: -------------------------------------------------------------------------------- 1 | @testable import SwiftSemantics 2 | import SwiftSyntax 3 | import XCTest 4 | 5 | final class ExtensionTests: XCTestCase { 6 | func testExtensionDeclarationWithGenericRequirements() throws { 7 | let source = #""" 8 | extension Array where Element == String, Element: StringProtocol {} 9 | """# 10 | 11 | let declarations = try SyntaxParser.declarations(of: Extension.self, source: source) 12 | XCTAssertEqual(declarations.count, 1) 13 | let declaration = declarations.first! 14 | 15 | XCTAssert(declaration.attributes.isEmpty) 16 | XCTAssertEqual(declaration.extendedType, "Array") 17 | XCTAssertEqual(declaration.genericRequirements.map { $0.description }, ["Element == String", "Element: StringProtocol"]) 18 | } 19 | 20 | func testFunctionDeclarationWithinExtension() throws { 21 | let source = #""" 22 | extension Collection { 23 | var hasAny: Bool { !isEmpty } 24 | } 25 | """# 26 | let extensions = try SyntaxParser.declarations(of: Extension.self, source: source) 27 | XCTAssertEqual(extensions.count, 1) 28 | 29 | let properties = try SyntaxParser.declarations(of: Variable.self, source: source) 30 | XCTAssertEqual(properties.count, 1) 31 | } 32 | 33 | func testFunctionDeclarationWithinConstrainedExtension() throws { 34 | let source = #""" 35 | extension Collection where Element: Comparable { 36 | func hasAny(lessThan element: Element) -> Bool { 37 | guard !isEmpty else { return false } 38 | return sorted().first < element 39 | } 40 | } 41 | """# 42 | 43 | let extensions = try SyntaxParser.declarations(of: Extension.self, source: source) 44 | XCTAssertEqual(extensions.count, 1) 45 | XCTAssertEqual(extensions[0].genericRequirements.count, 1) 46 | XCTAssertEqual(extensions[0].genericRequirements[0].leftTypeIdentifier, "Element") 47 | XCTAssertEqual(extensions[0].genericRequirements[0].rightTypeIdentifier, "Comparable") 48 | 49 | let functions = try SyntaxParser.declarations(of: Function.self, source: source) 50 | XCTAssertEqual(functions.count, 1) 51 | } 52 | 53 | func testinheritanceInConstrainedExtension() throws { 54 | let source = #""" 55 | extension Collection: Hashable where Element: Hashable {} 56 | """# 57 | 58 | let extensions = try SyntaxParser.declarations(of: Extension.self, source: source) 59 | XCTAssertEqual(extensions.count, 1) 60 | 61 | XCTAssertEqual(extensions[0].genericRequirements.count, 1) 62 | XCTAssertEqual(extensions[0].genericRequirements[0].leftTypeIdentifier, "Element") 63 | XCTAssertEqual(extensions[0].genericRequirements[0].rightTypeIdentifier, "Hashable") 64 | 65 | XCTAssertEqual(extensions[0].inheritance.count, 1) 66 | XCTAssertEqual(extensions[0].inheritance[0], "Hashable") 67 | } 68 | 69 | static var allTests = [ 70 | ("testExtensionDeclarationWithGenericRequirements", testExtensionDeclarationWithGenericRequirements), 71 | ("testFunctionDeclarationWithinExtension", testFunctionDeclarationWithinExtension), 72 | ("testFunctionDeclarationWithinConstrainedExtension", testFunctionDeclarationWithinConstrainedExtension), 73 | ("testinheritanceInConstrainedExtension", testinheritanceInConstrainedExtension), 74 | ] 75 | } 76 | 77 | -------------------------------------------------------------------------------- /Tests/SwiftSemanticsTests/Extensions/SyntaxParser+Declarations.swift: -------------------------------------------------------------------------------- 1 | import SwiftSyntax 2 | import SwiftSemantics 3 | import struct SwiftSemantics.Protocol 4 | 5 | extension SyntaxParser { 6 | static func declarations(of type: T.Type, source: String) throws -> [T] { 7 | let collector = DeclarationCollector() 8 | let tree = try parse(source: source) 9 | collector.walk(tree) 10 | 11 | switch type { 12 | case is AssociatedType.Type: 13 | return collector.associatedTypes as! [T] 14 | case is Class.Type: 15 | return collector.classes as! [T] 16 | case is ConditionalCompilationBlock.Type: 17 | return collector.conditionalCompilationBlocks as! [T] 18 | case is Deinitializer.Type: 19 | return collector.deinitializers as! [T] 20 | case is Enumeration.Type: 21 | return collector.enumerations as! [T] 22 | case is Enumeration.Case.Type: 23 | return collector.enumerationCases as! [T] 24 | case is Extension.Type: 25 | return collector.extensions as! [T] 26 | case is Function.Type: 27 | return collector.functions as! [T] 28 | case is Import.Type: 29 | return collector.imports as! [T] 30 | case is Initializer.Type: 31 | return collector.initializers as! [T] 32 | case is Operator.Type: 33 | return collector.operators as! [T] 34 | case is PrecedenceGroup.Type: 35 | return collector.precedenceGroups as! [T] 36 | case is Protocol.Type: 37 | return collector.protocols as! [T] 38 | case is Structure.Type: 39 | return collector.structures as! [T] 40 | case is Subscript.Type: 41 | return collector.subscripts as! [T] 42 | case is Typealias.Type: 43 | return collector.typealiases as! [T] 44 | case is Variable.Type: 45 | return collector.variables as! [T] 46 | default: 47 | fatalError("Unimplemented for type \(T.self)") 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Tests/SwiftSemanticsTests/FunctionTests.swift: -------------------------------------------------------------------------------- 1 | @testable import SwiftSemantics 2 | import SwiftSyntax 3 | import XCTest 4 | 5 | final class FunctionTests: XCTestCase { 6 | func testComplexFunctionDeclaration() throws { 7 | let source = #""" 8 | public func dump(_ value: T, to target: inout TargetStream, name: String? = nil, indent: Int = 0, maxDepth: Int = .max, maxItems: Int = .max) -> T where TargetStream: TextOutputStream 9 | """# 10 | 11 | let declarations = try SyntaxParser.declarations(of: Function.self, source: source) 12 | XCTAssertEqual(declarations.count, 1) 13 | let declaration = declarations.first! 14 | 15 | XCTAssert(declaration.attributes.isEmpty) 16 | XCTAssertEqual(declaration.identifier, "dump") 17 | XCTAssertEqual(declaration.description, source) 18 | } 19 | 20 | func testOperatorFunctionDeclarations() throws { 21 | let source = #""" 22 | prefix func ¬(value: Bool) -> Bool { !value } 23 | func ±(lhs: Int, rhs: Int) -> (Int, Int) { (lhs + rhs, lhs - rhs) } 24 | postfix func °(value: Double) -> String { "\(value)°)" } 25 | extension Int { 26 | static func ∓(lhs: Int, rhs: Int) -> (Int, Int) { (lhs - rhs, lhs + rhs) } 27 | } 28 | func sayHello() { print("Hello") } 29 | """# 30 | 31 | let declarations = try SyntaxParser.declarations(of: Function.self, source: source) 32 | 33 | XCTAssertEqual(declarations.count, 5) 34 | 35 | let prefix = declarations[0] 36 | XCTAssertEqual(prefix.modifiers.map { $0.description}, ["prefix"]) 37 | XCTAssertEqual(prefix.identifier, "¬") 38 | XCTAssertTrue(prefix.isOperator) 39 | 40 | let infix = declarations[1] 41 | XCTAssert(infix.modifiers.isEmpty) 42 | XCTAssertEqual(infix.identifier, "±") 43 | XCTAssertTrue(infix.isOperator) 44 | 45 | let postfix = declarations[2] 46 | XCTAssertEqual(postfix.modifiers.map { $0.description}, ["postfix"]) 47 | XCTAssertEqual(postfix.identifier, "°") 48 | XCTAssertTrue(postfix.isOperator) 49 | 50 | let staticInfix = declarations[3] 51 | XCTAssertEqual(staticInfix.modifiers.map { $0.description}, ["static"]) 52 | XCTAssertEqual(staticInfix.identifier, "∓") 53 | XCTAssertTrue(staticInfix.isOperator) 54 | 55 | let nonoperator = declarations[4] 56 | XCTAssert(nonoperator.modifiers.isEmpty) 57 | XCTAssertEqual(nonoperator.identifier, "sayHello") 58 | XCTAssertFalse(nonoperator.isOperator) 59 | } 60 | 61 | static var allTests = [ 62 | ("testComplexFunctionDeclaration", testComplexFunctionDeclaration), 63 | ("testOperatorFunctionDeclarations", testOperatorFunctionDeclarations), 64 | ] 65 | } 66 | 67 | -------------------------------------------------------------------------------- /Tests/SwiftSemanticsTests/GenericRequirementTests.swift: -------------------------------------------------------------------------------- 1 | @testable import SwiftSemantics 2 | import SwiftSyntax 3 | import XCTest 4 | 5 | final class GenericRequirementTests: XCTestCase { 6 | func testGenericRequirementsInFunction() throws { 7 | let source = #""" 8 | func difference(between lhs: C1, and rhs: C2) -> [C1.Element] 9 | where C1.Element: Equatable, C1.Element == C2.Element 10 | """# 11 | 12 | let declarations = try SyntaxParser.declarations(of: Function.self, source: source) 13 | XCTAssertEqual(declarations.count, 1) 14 | let function = declarations.first! 15 | XCTAssertEqual(function.genericRequirements.count, 2) 16 | 17 | XCTAssertEqual(function.genericRequirements[0].leftTypeIdentifier, "C1.Element") 18 | XCTAssertEqual(function.genericRequirements[0].relation, .conformance) 19 | XCTAssertEqual(function.genericRequirements[0].rightTypeIdentifier, "Equatable") 20 | XCTAssertEqual(function.genericRequirements[0].description, "C1.Element: Equatable") 21 | 22 | XCTAssertEqual(function.genericRequirements[1].leftTypeIdentifier, "C1.Element") 23 | XCTAssertEqual(function.genericRequirements[1].relation, .sameType) 24 | XCTAssertEqual(function.genericRequirements[1].rightTypeIdentifier, "C2.Element") 25 | XCTAssertEqual(function.genericRequirements[1].description, "C1.Element == C2.Element") 26 | } 27 | 28 | static var allTests = [ 29 | ("testGenericRequirementsInFunction", testGenericRequirementsInFunction), 30 | ] 31 | } 32 | 33 | -------------------------------------------------------------------------------- /Tests/SwiftSemanticsTests/ImportTests.swift: -------------------------------------------------------------------------------- 1 | @testable import SwiftSemantics 2 | import SwiftSyntax 3 | import XCTest 4 | 5 | final class ImportTests: XCTestCase { 6 | func testSimpleImportDeclaration() throws { 7 | let source = #""" 8 | import Foundation 9 | """# 10 | 11 | let declarations = try SyntaxParser.declarations(of: Import.self, source: source) 12 | XCTAssertEqual(declarations.count, 1) 13 | let declaration = declarations.first! 14 | 15 | XCTAssert(declaration.attributes.isEmpty) 16 | XCTAssertNil(declaration.kind) 17 | XCTAssertEqual(declaration.pathComponents, ["Foundation"]) 18 | XCTAssertEqual(declaration.description, source) 19 | } 20 | 21 | func testComplexImportDeclaration() throws { 22 | let source = #""" 23 | import struct SwiftSemantics.Import 24 | """# 25 | 26 | let declarations = try SyntaxParser.declarations(of: Import.self, source: source) 27 | XCTAssertEqual(declarations.count, 1) 28 | let declaration = declarations.first! 29 | 30 | XCTAssert(declaration.attributes.isEmpty) 31 | XCTAssertEqual(declaration.kind, "struct") 32 | XCTAssertEqual(declaration.pathComponents, ["SwiftSemantics", "Import"]) 33 | XCTAssertEqual(declaration.description, source) 34 | } 35 | 36 | static var allTests = [ 37 | ("testSimpleImportDeclaration", testSimpleImportDeclaration), 38 | ("testComplexImportDeclaration", testComplexImportDeclaration), 39 | ] 40 | } 41 | 42 | -------------------------------------------------------------------------------- /Tests/SwiftSemanticsTests/InitializerTests.swift: -------------------------------------------------------------------------------- 1 | @testable import SwiftSemantics 2 | import SwiftSyntax 3 | import XCTest 4 | 5 | final class InitializerTests: XCTestCase { 6 | func testInitializerDeclaration() throws { 7 | let source = #""" 8 | public class Person { 9 | public init?(names: String...) throws 10 | } 11 | """# 12 | 13 | let declarations = try SyntaxParser.declarations(of: Initializer.self, source: source) 14 | XCTAssertEqual(declarations.count, 1) 15 | let initializer = declarations.first! 16 | 17 | XCTAssert(initializer.attributes.isEmpty) 18 | XCTAssertEqual(initializer.keyword, "init") 19 | XCTAssertEqual(initializer.parameters.count, 1) 20 | XCTAssertEqual(initializer.parameters[0].firstName, "names") 21 | XCTAssertNil(initializer.parameters[0].secondName) 22 | XCTAssertEqual(initializer.parameters[0].type, "String") 23 | XCTAssertTrue(initializer.parameters[0].variadic) 24 | XCTAssertEqual(initializer.throwsOrRethrowsKeyword, "throws") 25 | } 26 | 27 | static var allTests = [ 28 | ("testInitializerDeclaration", testInitializerDeclaration), 29 | ] 30 | } 31 | 32 | -------------------------------------------------------------------------------- /Tests/SwiftSemanticsTests/ModifierTests.swift: -------------------------------------------------------------------------------- 1 | @testable import SwiftSemantics 2 | import SwiftSyntax 3 | import XCTest 4 | 5 | final class ModifierTests: XCTestCase { 6 | func testModifiersForPropertyDeclaration() throws { 7 | let source = #""" 8 | public private(set) var title: String 9 | """# 10 | 11 | let declarations = try SyntaxParser.declarations(of: Variable.self, source: source) 12 | XCTAssertEqual(declarations.count, 1) 13 | let property = declarations.first! 14 | XCTAssertEqual(property.modifiers.count, 2) 15 | 16 | XCTAssertEqual(property.modifiers[0].name, "public") 17 | XCTAssertNil(property.modifiers[0].detail) 18 | 19 | XCTAssertEqual(property.modifiers[1].name, "private") 20 | XCTAssertEqual(property.modifiers[1].detail, "set") 21 | } 22 | 23 | static var allTests = [ 24 | ("testModifiersForPropertyDeclaration", testModifiersForPropertyDeclaration), 25 | ] 26 | } 27 | 28 | -------------------------------------------------------------------------------- /Tests/SwiftSemanticsTests/OperatorTests.swift: -------------------------------------------------------------------------------- 1 | @testable import SwiftSemantics 2 | import SwiftSyntax 3 | import XCTest 4 | 5 | final class OperatorTests: XCTestCase { 6 | func testSimpleOperatorDeclaration() throws { 7 | let source = #""" 8 | prefix operator +++ 9 | """# 10 | 11 | let declarations = try SyntaxParser.declarations(of: Operator.self, source: source) 12 | XCTAssertEqual(declarations.count, 1) 13 | let declaration = declarations.first! 14 | 15 | XCTAssert(declaration.attributes.isEmpty) 16 | XCTAssertEqual(declaration.modifiers.count, 1) 17 | XCTAssertEqual(declaration.modifiers.first?.name, "prefix") 18 | XCTAssertEqual(declaration.kind, .prefix) 19 | XCTAssertEqual(declaration.name, "+++") 20 | // XCTAssertEqual(declaration.description, source) 21 | } 22 | 23 | static var allTests = [ 24 | ("testSimpleOperatorDeclaration", testSimpleOperatorDeclaration), 25 | ] 26 | } 27 | 28 | -------------------------------------------------------------------------------- /Tests/SwiftSemanticsTests/ProtocolTests.swift: -------------------------------------------------------------------------------- 1 | @testable import SwiftSemantics 2 | import SwiftSyntax 3 | import XCTest 4 | 5 | final class ProtocolTests: XCTestCase { 6 | func testProtocolDeclaration() throws { 7 | let source = #""" 8 | public protocol P {} 9 | """# 10 | 11 | let declarations = try SyntaxParser.declarations(of: Protocol.self, source: source) 12 | XCTAssertEqual(declarations.count, 1) 13 | let declaration = declarations.first! 14 | 15 | XCTAssert(declaration.attributes.isEmpty) 16 | XCTAssertEqual(declaration.name, "P") 17 | } 18 | 19 | static var allTests = [ 20 | ("testProtocolDeclaration", testProtocolDeclaration), 21 | ] 22 | } 23 | 24 | -------------------------------------------------------------------------------- /Tests/SwiftSemanticsTests/StructureTests.swift: -------------------------------------------------------------------------------- 1 | @testable import SwiftSemantics 2 | import SwiftSyntax 3 | import XCTest 4 | 5 | final class StructureTests: XCTestCase { 6 | func testNestedStructureDeclarations() throws { 7 | let source = #""" 8 | struct A { struct B { struct C {} } } 9 | """# 10 | 11 | let declarations = try SyntaxParser.declarations(of: Structure.self, source: source) 12 | XCTAssertEqual(declarations.count, 3) 13 | 14 | XCTAssertEqual(declarations[0].name, "A") 15 | XCTAssertEqual(declarations[1].name, "B") 16 | XCTAssertEqual(declarations[2].name, "C") 17 | } 18 | 19 | static var allTests = [ 20 | ("testNestedStructureDeclarations", testNestedStructureDeclarations), 21 | ] 22 | } 23 | 24 | -------------------------------------------------------------------------------- /Tests/SwiftSemanticsTests/SubscriptTests.swift: -------------------------------------------------------------------------------- 1 | @testable import SwiftSemantics 2 | import SwiftSyntax 3 | import XCTest 4 | 5 | final class SubscriptTests: XCTestCase { 6 | func testSubscriptDeclaration() throws { 7 | let source = #""" 8 | subscript(index: Int) -> Int? 9 | """# 10 | 11 | let declarations = try SyntaxParser.declarations(of: Subscript.self, source: source) 12 | XCTAssertEqual(declarations.count, 1) 13 | let declaration = declarations.first! 14 | 15 | XCTAssert(declaration.attributes.isEmpty) 16 | XCTAssertEqual(declaration.indices.count, 1) 17 | XCTAssertEqual(declaration.indices[0].firstName, "index") 18 | XCTAssertEqual(declaration.indices[0].type, "Int") 19 | XCTAssertEqual(declaration.returnType, "Int?") 20 | XCTAssertEqual(declaration.description, source) 21 | } 22 | 23 | static var allTests = [ 24 | ("testSubscriptDeclaration", testSubscriptDeclaration), 25 | ] 26 | } 27 | 28 | -------------------------------------------------------------------------------- /Tests/SwiftSemanticsTests/TypealiasTests.swift: -------------------------------------------------------------------------------- 1 | @testable import SwiftSemantics 2 | import SwiftSyntax 3 | import XCTest 4 | 5 | final class TypealiasTests: XCTestCase { 6 | func testTypealiasDeclarationsWithGenericParameter() throws { 7 | let source = #""" 8 | typealias SortableArray = Array 9 | """# 10 | 11 | let declarations = try SyntaxParser.declarations(of: Typealias.self, source: source) 12 | XCTAssertEqual(declarations.count, 1) 13 | let `typealias` = declarations.first! 14 | 15 | XCTAssertEqual(`typealias`.attributes.count, 0) 16 | XCTAssertEqual(`typealias`.name, "SortableArray") 17 | XCTAssertEqual(`typealias`.genericParameters.count, 1) 18 | XCTAssertEqual(`typealias`.genericParameters[0].name, "T") 19 | XCTAssertEqual(`typealias`.genericParameters[0].type, "Comparable") 20 | XCTAssertEqual(`typealias`.initializedType, "Array") 21 | } 22 | 23 | func testTypealiasDeclarationsWithGenericRequirement() throws { 24 | let source = #""" 25 | typealias ArrayOfNumbers = Array where T: Numeric 26 | """# 27 | 28 | let declarations = try SyntaxParser.declarations(of: Typealias.self, source: source) 29 | XCTAssertEqual(declarations.count, 1) 30 | let `typealias` = declarations.first! 31 | 32 | XCTAssertEqual(`typealias`.attributes.count, 0) 33 | XCTAssertEqual(`typealias`.name, "ArrayOfNumbers") 34 | XCTAssertEqual(`typealias`.genericParameters.count, 1) 35 | XCTAssertEqual(`typealias`.genericParameters[0].name, "T") 36 | XCTAssertNil(`typealias`.genericParameters[0].type) 37 | XCTAssertEqual(`typealias`.initializedType, "Array") 38 | XCTAssertEqual(`typealias`.genericRequirements.count, 1) 39 | XCTAssertEqual(`typealias`.genericRequirements[0].leftTypeIdentifier, "T") 40 | XCTAssertEqual(`typealias`.genericRequirements[0].relation, .conformance) 41 | XCTAssertEqual(`typealias`.genericRequirements[0].rightTypeIdentifier, "Numeric") 42 | } 43 | 44 | static var allTests = [ 45 | ("testTypealiasDeclarationsWithGenericParameter", testTypealiasDeclarationsWithGenericParameter), 46 | ("testTypealiasDeclarationsWithGenericRequirement", testTypealiasDeclarationsWithGenericRequirement), 47 | ] 48 | } 49 | 50 | -------------------------------------------------------------------------------- /Tests/SwiftSemanticsTests/VariableTests.swift: -------------------------------------------------------------------------------- 1 | @testable import SwiftSemantics 2 | import SwiftSyntax 3 | import XCTest 4 | 5 | final class VariableTests: XCTestCase { 6 | func testVariableDeclarationWithTypeAnnotation() throws { 7 | let source = #""" 8 | let greeting: String = "Hello" 9 | """# 10 | 11 | let declarations = try SyntaxParser.declarations(of: Variable.self, source: source) 12 | XCTAssertEqual(declarations.count, 1) 13 | let declaration = declarations.first! 14 | 15 | XCTAssert(declaration.attributes.isEmpty) 16 | XCTAssertEqual(declaration.typeAnnotation, "String") 17 | XCTAssertEqual(declaration.initializedValue, "\"Hello\"") 18 | XCTAssertEqual(declaration.description, source) 19 | } 20 | 21 | func testVariableDeclarationWithoutTypeAnnotation() throws { 22 | let source = #""" 23 | let greeting = "Hello" 24 | """# 25 | 26 | let declarations = try SyntaxParser.declarations(of: Variable.self, source: source) 27 | XCTAssertEqual(declarations.count, 1) 28 | let declaration = declarations.first! 29 | 30 | XCTAssert(declaration.attributes.isEmpty) 31 | XCTAssertNil(declaration.typeAnnotation) 32 | XCTAssertEqual(declaration.initializedValue, "\"Hello\"") 33 | XCTAssertEqual(declaration.description, source) 34 | } 35 | 36 | func testTupleVariableDeclaration() throws { 37 | let source = #""" 38 | let (greeting, addressee): (String, Thing) = ("Hello", .world) 39 | """# 40 | 41 | let declarations = try SyntaxParser.declarations(of: Variable.self, source: source) 42 | XCTAssertEqual(declarations.count, 1) 43 | let declaration = declarations.first! 44 | 45 | XCTAssert(declaration.attributes.isEmpty) 46 | XCTAssertEqual(declaration.typeAnnotation, "(String, Thing)") 47 | XCTAssertEqual(declaration.initializedValue, "(\"Hello\", .world)") 48 | XCTAssertEqual(declaration.description, source) 49 | } 50 | 51 | func testMultipleVariableDeclaration() throws { 52 | let source = #""" 53 | let greeting: String = "Hello", addressee: Thing = .world 54 | """# 55 | 56 | let declarations = try SyntaxParser.declarations(of: Variable.self, source: source) 57 | 58 | XCTAssertEqual(declarations.count, 2) 59 | 60 | let first = declarations.first! 61 | XCTAssert(first.attributes.isEmpty) 62 | XCTAssertEqual(first.typeAnnotation, "String") 63 | XCTAssertEqual(first.initializedValue, "\"Hello\"") 64 | 65 | let last = declarations.last! 66 | XCTAssert(last.attributes.isEmpty) 67 | XCTAssertEqual(last.typeAnnotation, "Thing") 68 | XCTAssertEqual(last.initializedValue, ".world") 69 | } 70 | 71 | static var allTests = [ 72 | ("testVariableDeclarationWithTypeAnnotation", testVariableDeclarationWithTypeAnnotation), 73 | ("testVariableDeclarationWithoutTypeAnnotation", testVariableDeclarationWithoutTypeAnnotation), 74 | ("testTupleVariableDeclaration", testTupleVariableDeclaration), 75 | ("testMultipleVariableDeclaration", testMultipleVariableDeclaration), 76 | ] 77 | } 78 | 79 | -------------------------------------------------------------------------------- /Tests/SwiftSemanticsTests/XCTestManifests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | #if !canImport(ObjectiveC) 4 | public func allTests() -> [XCTestCaseEntry] { 5 | return [ 6 | testCase(AttributeTests.allTests), 7 | testCase(ConditionalCompilationBlockTests.allTests), 8 | testCase(DeclarationCollectorTests.allTests), 9 | testCase(ExtensionTests.allTests), 10 | testCase(FunctionTests.allTests), 11 | testCase(GenericRequirementTests.allTests), 12 | testCase(InitializerTests.allTests), 13 | testCase(ImportTests.allTests), 14 | testCase(OperatorTests.allTests), 15 | testCase(ProtocolTests.allTests), 16 | testCase(StructureTests.allTests), 17 | testCase(SubscriptTests.allTests), 18 | testCase(VariableTests.allTests), 19 | ] 20 | } 21 | #endif 22 | --------------------------------------------------------------------------------