├── .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 |
--------------------------------------------------------------------------------