This package makes your Decodable types resilient to decoding errors and allows you to inspect those errors.
An example app showing how Buck can be used to build a simple iOS app.
A performant cache for logging systems. CacheAdvance persists log events 30x faster than SQLite.
XCTest-watchOS provides an implementation of XCTest that can run on watchOS
Floatation is a lightweight dependency injection framework that makes it easy to surface dependencies to consuming code
De qué hablo cuando hablo de trabajo remoto
I hate public speaking. So why do I keep doing it?
📱 A strongly-typed, caching GraphQL client for iOS, written in Swift
:atom: The hackable text editor
Contains all files needed for my Combine workshop.
issue commentQuick/Nimble
Release Nimble v9 for bumping Swift version requirement (to 5.2) and adjust the deprecation roadmap
Now that Xcode 12 is GM it'd be nice to have a release version here if there's nothing else missing!
comment created time in 5 days
push eventfdiaz/SwiftInspector
commit sha 8669c0d8c930c0e9b58e16fb4974bc27186b0b95
Update to use Xcode 12
push time in 6 days
PR opened fdiaz/SwiftInspector
Closes #45
This PR is easier to review commit-by-commit
pr created time in 6 days
push eventfdiaz/SwiftInspector
commit sha 8aa3e4ccd1caa16bd311ac0fe1cbe4c46a3e1f1f
Update ArgumentParser call sites
push time in 6 days
push eventfdiaz/SwiftInspector
commit sha 3314c50bc9f7d1fa511f6c0f45a7991507c5e37e
Allow Quick to work with generate-xcodeproj See https://github.com/Quick/Quick/issues/707 for more information Fix Quick
commit sha 0f484ae82af7b2863622611d22f3991bf579b325
Disable failing test
commit sha 1b10356d646ea09593242ed1a6ec40a4c606ede6
Update Arguments Parser
commit sha b5e90115a8bb17c30047539d8b6125f7fc328327
Update ArgumentParser call sites
push time in 6 days
push eventfdiaz/SwiftInspector
commit sha aba12919629dc3e38cbe448e390908501a37e282
Allow Quick to work with generate-xcodeproj See https://github.com/Quick/Quick/issues/707 for more information
commit sha 758bcab213bbe2230ee128f1d1751805cb0a9159
Disable failing test
commit sha 53150a265854b3f4531a341120d99fa8f27d6020
Update Arguments Parser
push time in 6 days
push eventfdiaz/SwiftInspector
commit sha 43cd80e87ac94ff2c9c6067ad6e0fd6c6fd561df
Disable failing test
commit sha 495c19fcb5f74bc2ecdd6ab16746fc0aeda9323c
Update Arguments Parser
push time in 6 days
push eventfdiaz/SwiftInspector
commit sha 6592e247b40c5768b0d552da34172ddbb726fc77
Bump Swift version to 5.3.
commit sha a6ebed22f0d9a7eaec76e8ab8286dd93d71e10b6
Update Package to use SwiftSyntax 5.3
commit sha 9b54f2e9d41bf018a5fba35b363d5bf81a5d839f
Update Quick to 3.0
push time in 6 days
issue openedfdiaz/SwiftInspector
Migrate to use Swift 5.3 by default
Now that Xcode 12 is in GM we should update this to work with Swift 5.3.
Tracking this work in https://github.com/fdiaz/SwiftInspector/tree/0.8.0b
created time in 6 days
pull request commentairbnb/swift
Explicitly mention typealiases in the subsection organization rule
@bachand I like that! Maybe as a comment on top of https://github.com/airbnb/swift/blob/master/resources/airbnb.swiftformat for now?
comment created time in 22 days
push eventfdiaz/SwiftInspector
commit sha 99cd9bb4c5fc1fd3dde8b374475772cb9e7be8b9
[iOS] Rename executable from SwiftInspector to swiftinspector (#44) * Rename executable from SwiftInspector to swift-inspector * Remove hyphen
push time in a month
PR merged fdiaz/SwiftInspector
I started this project intending to call the executable SwiftInspector but pretty quickly it became clear that we should use swift-inspector for the binary name.
This PR accomplishes that by no longer renaming the binary after building for release.
pr closed time in a month
push eventfdiaz/SwiftInspector
commit sha 4e3650450139245649edd51cd8d550f6aae8c053
Remove hyphen
push time in a month
Pull request review commentfdiaz/SwiftInspector
[iOS] Rename executable from SwiftInspector to swift-inspector
let package = Package( .macOS(.v10_13) ], products: [- .executable(name: "SwiftInspector", targets: ["SwiftInspector"]),+ .executable(name: "swift-inspector", targets: ["SwiftInspector"]),
I like that, since we're changing the name lets go with the best option 👍
comment created time in a month
push eventfdiaz/SwiftInspector
commit sha 93cff3806c0775a920b2289292571083c162d437
Rename executable from SwiftInspector to swift-inspector
push time in a month
PR opened fdiaz/SwiftInspector
I started this project intending to call the executable SwiftInspector but pretty quickly it became clear that we should use swift-inspector for the binary name.
This PR accomplishes that by no longer renaming the binary after building for release.
pr created time in a month
push eventfdiaz/SwiftInspector
commit sha 7e9eceadf506f093b3593957b7373d5b340f0915
add property analyzer (#43) * add property analyzer * address comments * add new test for nested types * final comments addressed
push time in a month
PR merged fdiaz/SwiftInspector
Changes
This adds a new property analyzer and command. As with the TypeCommand / TypeAnalyzer this has more information in the analyzer than it does in the command.
Here's the command
swift-inspector properties --help
OVERVIEW: Finds property information for the provided type
USAGE: SwiftInspector properties --name <name> --path <path>
OPTIONS:
--name <name> The name of the type to find property information on
This may be a enum, class, struct, or protocol.
--path <path> The absolute path of the file to inspect
-h, --help Show help information.
This command will find property information for a provided type including:
- Type name
- Each property includes:
- Property name
- Access (public, private, internal, fileprivate)
- Scope (instance, scope)
- Comment (Analyzer only)
- Property type (Analyzer only)
Please review
@fdiaz
pr closed time in a month
Pull request review commentfdiaz/SwiftInspector
+// Created by Tyler Hedrick on 8/14/20.+//+// Distributed under the MIT License+//+// Permission is hereby granted, free of charge, to any person obtaining a copy+// of this software and associated documentation files (the "Software"), to deal+// in the Software without restriction, including without limitation the rights+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+// copies of the Software, and to permit persons to whom the Software is+// furnished to do so, subject to the following conditions:+//+// The above copyright notice and this permission notice shall be included in all+// copies or substantial portions of the Software.+//+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+// SOFTWARE.++import Foundation+import SwiftSyntax++// MARK: - PropertyAnalyzer++public final class PropertyAnalyzer: Analyzer {++ /// - Parameter typeName: The name of the type to get property information for+ /// - Parameter cachedSyntaxTree: The cached syntax tree to return the AST tree from+ public init(typeName: String, cachedSyntaxTree: CachedSyntaxTree = .init()) {+ self.typeName = typeName+ self.cachedSyntaxTree = cachedSyntaxTree+ }++ /// Finds property information for the provided type in the Swift file+ /// - Parameter fileURL: The fileURL where the Swift file is located+ public func analyze(fileURL: URL) throws -> TypeProperties? {+ let syntax: SourceFileSyntax = try cachedSyntaxTree.syntaxTree(for: fileURL)+ var result: TypeProperties?+ let visitor = TypeSyntaxVisitor(typeName: typeName) { [unowned self] typeProperties in+ guard self.typeName == typeProperties.name else { return }+ result = try? typeProperties.merge(with: result)+ }+ visitor.walk(syntax)+ return result+ }++ // MARK: Private++ private let cachedSyntaxTree: CachedSyntaxTree+ private let typeName: String+}++// MARK: - TypeSyntaxVisitor++private final class TypeSyntaxVisitor: SyntaxVisitor {++ init(typeName: String, onNodeVisit: @escaping (_ info: TypeProperties) -> Void) {+ self.onNodeVisit = onNodeVisit+ self.typeName = typeName+ }++ override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind {+ if node.identifier.text == typeName {+ processNode(node, withName: node.identifier.text, members: node.members.members)+ }+ return .visitChildren+ }++ override func visit(_ node: EnumDeclSyntax) -> SyntaxVisitorContinueKind {+ if node.identifier.text == typeName {+ processNode(node, withName: node.identifier.text, members: node.members.members)+ }+ return .visitChildren+ }++ override func visit(_ node: ProtocolDeclSyntax) -> SyntaxVisitorContinueKind {+ if node.identifier.text == typeName {+ processNode(node, withName: node.identifier.text, members: node.members.members)+ }+ return .visitChildren+ }++ override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind {+ if node.identifier.text == typeName {+ processNode(node, withName: node.identifier.text, members: node.members.members)+ }+ return .visitChildren+ }++ override func visit(_ node: ExtensionDeclSyntax) -> SyntaxVisitorContinueKind {+ if+ let typeIdentifier = node.extendedType.as(SimpleTypeIdentifierSyntax.self),+ typeIdentifier.name.text == typeName+ {+ processNode(node, withName: typeIdentifier.name.text, members: node.members.members)+ }+ return .visitChildren+ }++ // MARK: Private++ private let onNodeVisit: (_ info: TypeProperties) -> Void+ private let typeName: String++ private func processNode<Node>(_ node: Node, withName name: String, members: MemberDeclListSyntax) where Node: SyntaxProtocol {+ var result = [TypeProperties.PropertyData]()+ let propertyVisitor = PropertySyntaxVisitor(typeName: name) { info in+ result.append(info)+ }+ propertyVisitor.walk(node)+ onNodeVisit(.init(name: name, properties: result))+ }+}++// MARK: - PropertySyntaxVisitor++private final class PropertySyntaxVisitor: SyntaxVisitor {++ init(typeName: String, onNodeVisit: @escaping (_ info: TypeProperties.PropertyData) -> Void) {+ self.typeName = typeName+ self.onNodeVisit = onNodeVisit+ }++ override func visit(_ node: VariableDeclSyntax) -> SyntaxVisitorContinueKind {+ var leadingTrivia: Trivia? = node.leadingTrivia+ let modifier = findModifiers(from: node)++ node.modifiers?.forEach { modifier in+ // leading trivia is on the first modifier, or the node itself if no modifiers are present+ leadingTrivia = leadingTrivia ?? modifier.leadingTrivia+ }++ node.bindings.forEach { binding in+ if let identifier = binding.pattern.as(IdentifierPatternSyntax.self) {+ if+ let typeAnnotation = binding.typeAnnotation,+ let simpleTypeIdentifier = typeAnnotation.type.as(SimpleTypeIdentifierSyntax.self)+ {+ onNodeVisit(.init(+ name: identifier.identifier.text,+ typeAnnotation: simpleTypeIdentifier.name.text,+ comment: comment(from: leadingTrivia),+ modifiers: modifier))+ return+ }+ // no type annotation was found. In this case it's much more complicated to get+ // the type information for a variable.+ // e.g.:+ // public let thing: String = "Hello" has a type annotation, String which makes this easy+ // public let thing = "Hello" does not have a type annotation, and I don't know how to handle this case.+ // TODO: Include logic to get types of any variable declaration+ onNodeVisit(.init(+ name: identifier.identifier.text,+ typeAnnotation: nil,+ comment: comment(from: leadingTrivia),+ modifiers: modifier))+ }+ }++ return .skipChildren+ }++ // MARK: Private++ private let typeName: String+ private let onNodeVisit: (_ info: TypeProperties.PropertyData) -> Void++ private func comment(from trivia: Trivia?) -> String {+ guard let trivia = trivia else { return "" }+ return trivia.compactMap { piece -> String? in+ switch piece {+ case .lineComment(let str): return str+ case .blockComment(let str): return str+ case .docLineComment(let str): return str+ case .docBlockComment(let str): return str+ default: return nil+ }+ }.joined(separator: "\n")+ }++ private func findModifiers(from node: VariableDeclSyntax) -> TypeProperties.Modifier {+ let modifiersString: [String] = node.children+ .compactMap { $0.as(ModifierListSyntax.self) }+ .reduce(into: []) { result, syntax in+ let modifiers: [String] = syntax.children+ .compactMap { $0.as(DeclModifierSyntax.self) }+ .map { modifierSyntax in+ if+ let leftParen = modifierSyntax.detailLeftParen,+ let detail = modifierSyntax.detail,+ let rightParen = modifierSyntax.detailRightParen+ {+ return modifierSyntax.name.text + leftParen.text + detail.text + rightParen.text+ }+ return modifierSyntax.name.text+ }+ result.append(contentsOf: modifiers)+ }++ var modifier = modifiersString.reduce(TypeProperties.Modifier()) { result, stringValue in+ let modifier = TypeProperties.Modifier(stringValue: stringValue)+ return result.union(modifier)+ }++ // If there are no explicit modifiers, this is an internal property+ if !modifier.contains(.public) &&+ !modifier.contains(.fileprivate) &&+ !modifier.contains(.private)+ {+ modifier = modifier.union(.internal)+ }++ // If the variable isn't static, it's an instance variable+ if !modifier.contains(.static) {+ modifier = modifier.union(.instance)+ }++ return modifier+ }+}++// MARK: - TypeProperties++/// Information about a type as well as its associated properties+public struct TypeProperties: Hashable {++ public struct Modifier: Hashable, OptionSet {+ public let rawValue: Int++ public static let `internal` = Modifier(rawValue: 1 << 0)+ public static let `public` = Modifier(rawValue: 1 << 1)+ public static let `private` = Modifier(rawValue: 1 << 2)+ public static let privateSet = Modifier(rawValue: 1 << 3)+ public static let `fileprivate` = Modifier(rawValue: 1 << 4)+ public static let `instance` = Modifier(rawValue: 1 << 5)+ public static let `static` = Modifier(rawValue: 1 << 6)++ public init(rawValue: Int) {+ self.rawValue = rawValue+ }++ public init(stringValue: String) {+ switch stringValue {+ case "public": self = .public+ case "private": self = .private+ case "private(set)": self = .privateSet+ case "fileprivate": self = .fileprivate+ case "internal": self = .internal+ case "static": self = .static+ default: self = []+ }+ }+ }++ public struct PropertyData: Hashable {+ /// The name of the property+ public let name: String+ /// The Type annotation of the property if it's present+ public let typeAnnotation: String?+ /// Any comments associated with the property+ public let comment: String+ /// Modifier set for this type+ public let modifiers: Modifier+ }++ /// The name of the type.+ public let name: String+ /// The properties on this type+ public let properties: [PropertyData]+}++extension TypeProperties {+ struct MergeError: Error, CustomStringConvertible {++ init(errorMessage: String) {+ self.errorMessage = errorMessage+ }++ var description: String { errorMessage }++ private let errorMessage: String+ }+}++extension TypeProperties.MergeError {+ static var invalidNames = TypeProperties.MergeError(+ errorMessage: "Invalid types - the type names must match to be merged.")+}++extension TypeProperties {+ func merge(with other: TypeProperties?) throws -> TypeProperties {+ guard let other = other else {+ return self+ }+ guard name == other.name else {+ throw MergeError.invalidNames+ }+ return TypeProperties(+ name: name,+ properties: Array(Set(properties).union(Set(other.properties))))+ }+}++extension TypeProperties.Modifier: CustomStringConvertible, CustomDebugStringConvertible {
Let's move this extension to the Command since this is related to how we output things to the standard output which the Analyzer shouldn't care about see: https://github.com/fdiaz/SwiftInspector/blob/main/CONTRIBUTING.md#swiftinspectorcommand
comment created time in a month
Pull request review commentfdiaz/SwiftInspector
+// Created by Tyler Hedrick on 8/14/20.+//+// Distributed under the MIT License+//+// Permission is hereby granted, free of charge, to any person obtaining a copy+// of this software and associated documentation files (the "Software"), to deal+// in the Software without restriction, including without limitation the rights+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+// copies of the Software, and to permit persons to whom the Software is+// furnished to do so, subject to the following conditions:+//+// The above copyright notice and this permission notice shall be included in all+// copies or substantial portions of the Software.+//+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+// SOFTWARE.++import Foundation+import SwiftSyntax++// MARK: - PropertyAnalyzer++public final class PropertyAnalyzer: Analyzer {++ /// - Parameter typeName: The name of the type to get property information for+ /// - Parameter cachedSyntaxTree: The cached syntax tree to return the AST tree from+ public init(typeName: String, cachedSyntaxTree: CachedSyntaxTree = .init()) {+ self.typeName = typeName+ self.cachedSyntaxTree = cachedSyntaxTree+ }++ /// Finds property information for the provided type in the Swift file+ /// - Parameter fileURL: The fileURL where the Swift file is located+ public func analyze(fileURL: URL) throws -> TypeProperties? {+ let syntax: SourceFileSyntax = try cachedSyntaxTree.syntaxTree(for: fileURL)+ var result: TypeProperties?+ let visitor = TypeSyntaxVisitor(typeName: typeName) { [unowned self] typeProperties in+ guard self.typeName == typeProperties.name else { return }+ result = try? typeProperties.merge(with: result)+ }+ visitor.walk(syntax)+ return result+ }++ // MARK: Private++ private let cachedSyntaxTree: CachedSyntaxTree+ private let typeName: String+}++// MARK: - TypeSyntaxVisitor++private final class TypeSyntaxVisitor: SyntaxVisitor {++ init(typeName: String, onNodeVisit: @escaping (_ info: TypeProperties) -> Void) {+ self.onNodeVisit = onNodeVisit+ self.typeName = typeName+ }++ override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind {+ if node.identifier.text == typeName {+ processNode(node, withName: node.identifier.text, members: node.members.members)+ }+ return .visitChildren+ }++ override func visit(_ node: EnumDeclSyntax) -> SyntaxVisitorContinueKind {+ if node.identifier.text == typeName {+ processNode(node, withName: node.identifier.text, members: node.members.members)+ }+ return .visitChildren+ }++ override func visit(_ node: ProtocolDeclSyntax) -> SyntaxVisitorContinueKind {+ if node.identifier.text == typeName {+ processNode(node, withName: node.identifier.text, members: node.members.members)+ }+ return .visitChildren+ }++ override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind {+ if node.identifier.text == typeName {+ processNode(node, withName: node.identifier.text, members: node.members.members)+ }+ return .visitChildren+ }++ override func visit(_ node: ExtensionDeclSyntax) -> SyntaxVisitorContinueKind {+ if+ let typeIdentifier = node.extendedType.as(SimpleTypeIdentifierSyntax.self),+ typeIdentifier.name.text == typeName+ {+ processNode(node, withName: typeIdentifier.name.text, members: node.members.members)+ }+ return .visitChildren+ }++ // MARK: Private++ private let onNodeVisit: (_ info: TypeProperties) -> Void+ private let typeName: String++ private func processNode<Node>(_ node: Node, withName name: String, members: MemberDeclListSyntax) where Node: SyntaxProtocol {+ var result = [TypeProperties.PropertyData]()+ let propertyVisitor = PropertySyntaxVisitor(typeName: name) { info in+ result.append(info)+ }+ propertyVisitor.walk(node)+ onNodeVisit(.init(name: name, properties: result))+ }+}++// MARK: - PropertySyntaxVisitor++private final class PropertySyntaxVisitor: SyntaxVisitor {++ init(typeName: String, onNodeVisit: @escaping (_ info: TypeProperties.PropertyData) -> Void) {+ self.typeName = typeName+ self.onNodeVisit = onNodeVisit+ }++ override func visit(_ node: VariableDeclSyntax) -> SyntaxVisitorContinueKind {+ var leadingTrivia: Trivia? = node.leadingTrivia+ let modifier = findModifiers(from: node)++ node.modifiers?.forEach { modifier in+ // leading trivia is on the first modifier, or the node itself if no modifiers are present+ leadingTrivia = leadingTrivia ?? modifier.leadingTrivia+ }++ node.bindings.forEach { binding in+ if let identifier = binding.pattern.as(IdentifierPatternSyntax.self) {+ if+ let typeAnnotation = binding.typeAnnotation,+ let simpleTypeIdentifier = typeAnnotation.type.as(SimpleTypeIdentifierSyntax.self)+ {+ onNodeVisit(.init(+ name: identifier.identifier.text,+ typeAnnotation: simpleTypeIdentifier.name.text,+ comment: comment(from: leadingTrivia),+ modifiers: modifier))+ return+ }+ // no type annotation was found. In this case it's much more complicated to get+ // the type information for a variable.+ // e.g.:+ // public let thing: String = "Hello" has a type annotation, String which makes this easy+ // public let thing = "Hello" does not have a type annotation, and I don't know how to handle this case.+ // TODO: Include logic to get types of any variable declaration+ onNodeVisit(.init(+ name: identifier.identifier.text,+ typeAnnotation: nil,+ comment: comment(from: leadingTrivia),+ modifiers: modifier))+ }+ }++ return .skipChildren+ }++ // MARK: Private++ private let typeName: String+ private let onNodeVisit: (_ info: TypeProperties.PropertyData) -> Void++ private func comment(from trivia: Trivia?) -> String {+ guard let trivia = trivia else { return "" }+ return trivia.compactMap { piece -> String? in+ switch piece {+ case .lineComment(let str): return str+ case .blockComment(let str): return str+ case .docLineComment(let str): return str+ case .docBlockComment(let str): return str+ default: return nil+ }+ }.joined(separator: "\n")+ }++ private func findModifiers(from node: VariableDeclSyntax) -> TypeProperties.Modifier {+ let modifiersString: [String] = node.children+ .compactMap { $0.as(ModifierListSyntax.self) }+ .reduce(into: []) { result, syntax in+ let modifiers: [String] = syntax.children+ .compactMap { $0.as(DeclModifierSyntax.self) }+ .map { modifierSyntax in+ if+ let leftParen = modifierSyntax.detailLeftParen,+ let detail = modifierSyntax.detail,+ let rightParen = modifierSyntax.detailRightParen+ {+ return modifierSyntax.name.text + leftParen.text + detail.text + rightParen.text+ }+ return modifierSyntax.name.text+ }+ result.append(contentsOf: modifiers)+ }++ var modifier = modifiersString.reduce(TypeProperties.Modifier()) { result, stringValue in+ let modifier = TypeProperties.Modifier(stringValue: stringValue)+ return result.union(modifier)+ }++ // If there are no explicit modifiers, this is an internal property+ if !modifier.contains(.public) &&+ !modifier.contains(.fileprivate) &&+ !modifier.contains(.private)+ {+ modifier = modifier.union(.internal)+ }++ // If the variable isn't static, it's an instance variable+ if !modifier.contains(.static) {+ modifier = modifier.union(.instance)+ }++ return modifier+ }+}++// MARK: - TypeProperties++/// Information about a type as well as its associated properties+public struct TypeProperties: Hashable {++ public struct Modifier: Hashable, OptionSet {+ public let rawValue: Int++ public static let `internal` = Modifier(rawValue: 1 << 0)+ public static let `public` = Modifier(rawValue: 1 << 1)+ public static let `private` = Modifier(rawValue: 1 << 2)+ public static let privateSet = Modifier(rawValue: 1 << 3)
Should we include internal(set) and public(set)?
comment created time in a month
Pull request review commentfdiaz/SwiftInspector
+// Created by Tyler Hedrick on 8/18/20.+//+// Copyright (c) 2020 Tyler Hedrick+//+// Distributed under the MIT License+//+// Permission is hereby granted, free of charge, to any person obtaining a copy+// of this software and associated documentation files (the "Software"), to deal+// in the Software without restriction, including without limitation the rights+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+// copies of the Software, and to permit persons to whom the Software is+// furnished to do so, subject to the following conditions:+//+// The above copyright notice and this permission notice shall be included in all+// copies or substantial portions of the Software.+//+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+// SOFTWARE.++import Nimble+import Quick+import Foundation++@testable import SwiftInspectorCore++final class PropertyAnalyzerSpec: QuickSpec {+ override func spec() {+ var fileURL: URL!+ var sut = PropertyAnalyzer(typeName: "FakeType")++ beforeEach {+ sut = PropertyAnalyzer(typeName: "FakeType")+ }++ afterEach {+ guard let fileURL = fileURL else {+ return+ }+ try? Temporary.removeItem(at: fileURL)+ }++ describe("analyze(fileURL:)") {+ context("when there are no properties") {+ beforeEach {+ let content = """+ public final class FakeType {}+ """+ fileURL = try? Temporary.makeFile(content: content)+ }++ it("returns type info with empty property list") {+ let result = try? sut.analyze(fileURL: fileURL)+ expect(result?.properties).to(beEmpty())+ }+ }++ context("when there is a property") {+ beforeEach {+ let content = """+ public final class FakeType {+ public var thing: String = "Hello, World"+ }+ """+ fileURL = try? Temporary.makeFile(content: content)+ }++ it("detects the type name") {+ let result = try? sut.analyze(fileURL: fileURL)+ expect(result?.name) == "FakeType"+ }++ it("returns nil if the type name is not present") {+ let sut = PropertyAnalyzer(typeName: "AnotherType")+ let result = try? sut.analyze(fileURL: fileURL)+ expect(result).to(beNil())+ }++ it("detects the properties") {+ let result = try? sut.analyze(fileURL: fileURL)+ expect(result?.properties) == [+ TypeProperties.PropertyData(+ name: "thing",+ typeAnnotation: "String",+ comment: "",+ modifiers: [.public, .instance])+ ]+ }+ }++ context("when there is a property in a nested type with the same type name") {+ beforeEach {+ let content = """+ public final class FakeType {+ public var thing: String = "Hello, World"++ enum FakeType {+ static let foo: String = "Hola"+ }+ }+ """+ fileURL = try? Temporary.makeFile(content: content)+ }++ it("detects the type name") {+ let result = try? sut.analyze(fileURL: fileURL)+ expect(result?.name) == "FakeType"+ }++ /*+ This is actually not the ideal result of this and is a limitation of the implementation+ Ideally you would have to pass in `FakeType.FakeType` to get this nested type's property+ information. For now we are accepting this limitation and have this test to showcase+ what happens in this scenario.+ */+ it("detects and merges the properties") {+ let result = try? sut.analyze(fileURL: fileURL)+ let propSet = Set(result?.properties ?? [])+ let expectedPropSet: Set<TypeProperties.PropertyData> = [+ .init(+ name: "thing",+ typeAnnotation: "String",+ comment: "",+ modifiers: [.public, .instance]),+ .init(+ name: "foo",+ typeAnnotation: "String",+ comment: "",+ modifiers: [.internal, .static])+ ]+ expect(propSet) == expectedPropSet+ }
Agreed with Michael, it's great to have a test like this and document that it's not ideal but it's what a workaround we're ok with.
comment created time in a month
Pull request review commentfdiaz/SwiftInspector
+// Created by Tyler Hedrick on 8/14/20.+//+// Distributed under the MIT License+//+// Permission is hereby granted, free of charge, to any person obtaining a copy+// of this software and associated documentation files (the "Software"), to deal+// in the Software without restriction, including without limitation the rights+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+// copies of the Software, and to permit persons to whom the Software is+// furnished to do so, subject to the following conditions:+//+// The above copyright notice and this permission notice shall be included in all+// copies or substantial portions of the Software.+//+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+// SOFTWARE.++import Foundation+import SwiftSyntax++// MARK: - PropertyAnalyzer++public final class PropertyAnalyzer: Analyzer {++ /// - Parameter typeName: The name of the type to get property information for+ /// - Parameter cachedSyntaxTree: The cached syntax tree to return the AST tree from+ public init(typeName: String, cachedSyntaxTree: CachedSyntaxTree = .init()) {+ self.typeName = typeName+ self.cachedSyntaxTree = cachedSyntaxTree+ }++ /// Finds property information for the provided type in the Swift file+ /// - Parameter fileURL: The fileURL where the Swift file is located+ public func analyze(fileURL: URL) throws -> TypeProperties? {+ let syntax: SourceFileSyntax = try cachedSyntaxTree.syntaxTree(for: fileURL)+ var result: TypeProperties?+ let visitor = TypeSyntaxVisitor(typeName: typeName) { [unowned self] typeProperties in+ guard self.typeName == typeProperties.name else { return }+ result = try? typeProperties.merge(with: result)+ }+ visitor.walk(syntax)+ return result+ }++ // MARK: Private++ private let cachedSyntaxTree: CachedSyntaxTree+ private let typeName: String+}++// MARK: - TypeSyntaxVisitor++private final class TypeSyntaxVisitor: SyntaxVisitor {++ init(typeName: String, onNodeVisit: @escaping (_ info: TypeProperties) -> Void) {+ self.onNodeVisit = onNodeVisit+ self.typeName = typeName+ }++ override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind {+ if node.identifier.text == typeName {+ processNode(node, withName: node.identifier.text, members: node.members.members)+ }+ return .visitChildren+ }++ override func visit(_ node: EnumDeclSyntax) -> SyntaxVisitorContinueKind {+ if node.identifier.text == typeName {+ processNode(node, withName: node.identifier.text, members: node.members.members)+ }+ return .visitChildren+ }++ override func visit(_ node: ProtocolDeclSyntax) -> SyntaxVisitorContinueKind {+ if node.identifier.text == typeName {+ processNode(node, withName: node.identifier.text, members: node.members.members)+ }+ return .visitChildren+ }++ override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind {+ if node.identifier.text == typeName {+ processNode(node, withName: node.identifier.text, members: node.members.members)+ }+ return .visitChildren+ }++ override func visit(_ node: ExtensionDeclSyntax) -> SyntaxVisitorContinueKind {+ if+ let typeIdentifier = node.extendedType.as(SimpleTypeIdentifierSyntax.self),+ typeIdentifier.name.text == typeName+ {+ processNode(node, withName: typeIdentifier.name.text, members: node.members.members)+ }+ return .visitChildren+ }++ // MARK: Private++ private let onNodeVisit: (_ info: TypeProperties) -> Void+ private let typeName: String++ private func processNode<Node>(_ node: Node, withName name: String, members: MemberDeclListSyntax) where Node: SyntaxProtocol {+ var result = [TypeProperties.PropertyData]()+ let propertyVisitor = PropertySyntaxVisitor(typeName: name) { info in+ result.append(info)+ }+ propertyVisitor.walk(node)+ onNodeVisit(.init(name: name, properties: result))+ }+}++// MARK: - PropertySyntaxVisitor++private final class PropertySyntaxVisitor: SyntaxVisitor {++ init(typeName: String, onNodeVisit: @escaping (_ info: TypeProperties.PropertyData) -> Void) {+ self.typeName = typeName+ self.onNodeVisit = onNodeVisit+ }++ override func visit(_ node: VariableDeclSyntax) -> SyntaxVisitorContinueKind {+ var leadingTrivia: Trivia? = node.leadingTrivia+ let modifier = findModifiers(from: node)++ node.modifiers?.forEach { modifier in+ // leading trivia is on the first modifier, or the node itself if no modifiers are present+ leadingTrivia = leadingTrivia ?? modifier.leadingTrivia+ }++ node.bindings.forEach { binding in+ if let identifier = binding.pattern.as(IdentifierPatternSyntax.self) {+ if+ let typeAnnotation = binding.typeAnnotation,+ let simpleTypeIdentifier = typeAnnotation.type.as(SimpleTypeIdentifierSyntax.self)+ {+ onNodeVisit(.init(+ name: identifier.identifier.text,+ typeAnnotation: simpleTypeIdentifier.name.text,+ comment: comment(from: leadingTrivia),+ modifiers: modifier))+ return+ }+ // no type annotation was found. In this case it's much more complicated to get+ // the type information for a variable.+ // e.g.:+ // public let thing: String = "Hello" has a type annotation, String which makes this easy+ // public let thing = "Hello" does not have a type annotation, and I don't know how to handle this case.+ // TODO: Include logic to get types of any variable declaration+ onNodeVisit(.init(+ name: identifier.identifier.text,+ typeAnnotation: nil,+ comment: comment(from: leadingTrivia),+ modifiers: modifier))+ }+ }++ return .skipChildren+ }++ // MARK: Private++ private let typeName: String+ private let onNodeVisit: (_ info: TypeProperties.PropertyData) -> Void++ private func comment(from trivia: Trivia?) -> String {+ guard let trivia = trivia else { return "" }+ return trivia.compactMap { piece -> String? in+ switch piece {+ case .lineComment(let str): return str+ case .blockComment(let str): return str+ case .docLineComment(let str): return str+ case .docBlockComment(let str): return str+ default: return nil+ }+ }.joined(separator: "\n")+ }++ private func findModifiers(from node: VariableDeclSyntax) -> TypeProperties.Modifier {+ let modifiersString: [String] = node.children+ .compactMap { $0.as(ModifierListSyntax.self) }+ .reduce(into: []) { result, syntax in+ let modifiers: [String] = syntax.children+ .compactMap { $0.as(DeclModifierSyntax.self) }+ .map { modifierSyntax in+ if+ let leftParen = modifierSyntax.detailLeftParen,+ let detail = modifierSyntax.detail,+ let rightParen = modifierSyntax.detailRightParen+ {+ return modifierSyntax.name.text + leftParen.text + detail.text + rightParen.text+ }+ return modifierSyntax.name.text+ }+ result.append(contentsOf: modifiers)+ }++ var modifier = modifiersString.reduce(TypeProperties.Modifier()) { result, stringValue in+ let modifier = TypeProperties.Modifier(stringValue: stringValue)+ return result.union(modifier)+ }++ // If there are no explicit modifiers, this is an internal property+ if !modifier.contains(.public) &&+ !modifier.contains(.fileprivate) &&+ !modifier.contains(.private)+ {+ modifier = modifier.union(.internal)+ }++ // If the variable isn't static, it's an instance variable+ if !modifier.contains(.static) {+ modifier = modifier.union(.instance)+ }++ return modifier+ }+}++// MARK: - TypeProperties++/// Information about a type as well as its associated properties+public struct TypeProperties: Hashable {++ public struct Modifier: Hashable, OptionSet {+ public let rawValue: Int++ public static let `internal` = Modifier(rawValue: 1 << 0)+ public static let `public` = Modifier(rawValue: 1 << 1)+ public static let `private` = Modifier(rawValue: 1 << 2)+ public static let privateSet = Modifier(rawValue: 1 << 3)+ public static let `fileprivate` = Modifier(rawValue: 1 << 4)+ public static let `instance` = Modifier(rawValue: 1 << 5)+ public static let `static` = Modifier(rawValue: 1 << 6)++ public init(rawValue: Int) {+ self.rawValue = rawValue+ }++ public init(stringValue: String) {+ switch stringValue {+ case "public": self = .public+ case "private": self = .private+ case "private(set)": self = .privateSet+ case "fileprivate": self = .fileprivate+ case "internal": self = .internal+ case "static": self = .static+ default: self = []+ }+ }+ }++ public struct PropertyData: Hashable {+ /// The name of the property+ public let name: String+ /// The Type annotation of the property if it's present+ public let typeAnnotation: String?+ /// Any comments associated with the property+ public let comment: String+ /// Modifier set for this type+ public let modifiers: Modifier+ }++ /// The name of the type.+ public let name: String+ /// The properties on this type+ public let properties: [PropertyData]+}++extension TypeProperties {+ struct MergeError: Error, CustomStringConvertible {++ init(errorMessage: String) {+ self.errorMessage = errorMessage+ }++ var description: String { errorMessage }++ private let errorMessage: String+ }+}++extension TypeProperties.MergeError {+ static var invalidNames = TypeProperties.MergeError(+ errorMessage: "Invalid types - the type names must match to be merged.")+}++extension TypeProperties {+ func merge(with other: TypeProperties?) throws -> TypeProperties {+ guard let other = other else {+ return self+ }+ guard name == other.name else {+ throw MergeError.invalidNames+ }+ return TypeProperties(+ name: name,+ properties: Array(Set(properties).union(Set(other.properties))))+ }+}++extension TypeProperties.Modifier: CustomStringConvertible, CustomDebugStringConvertible {+ public var description: String {+ var outputValues: [String] = []+ // Order is important here! Access control modifiers should always go before scope modifiers+ // Access control modifiers+ if contains(.public) { outputValues.append("public") }+ if contains(.private) { outputValues.append("private") }+ if contains(.privateSet) { outputValues.append("private(set)") }
Should setters go after fileprivate and internal?
comment created time in a month
Pull request review commentfdiaz/SwiftInspector
+// Created by Tyler Hedrick on 3/27/20.+//+// Copyright (c) 2020 Tyler Hedrick+//+// Distributed under the MIT License+//+// Permission is hereby granted, free of charge, to any person obtaining a copy+// of this software and associated documentation files (the "Software"), to deal+// in the Software without restriction, including without limitation the rights+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+// copies of the Software, and to permit persons to whom the Software is+// furnished to do so, subject to the following conditions:+//+// The above copyright notice and this permission notice shall be included in all+// copies or substantial portions of the Software.+//+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+// SOFTWARE.++import Nimble+import Quick+import Foundation++@testable import SwiftInspectorCore++final class PropertyAnalyzerSpec: QuickSpec {+ override func spec() {+ var fileURL: URL!+ var sut = PropertyAnalyzer(typeName: "FakeType")++ beforeEach {+ sut = PropertyAnalyzer(typeName: "FakeType")+ }++ afterEach {+ guard let fileURL = fileURL else {+ return+ }+ try? Temporary.removeItem(at: fileURL)+ }++ describe("TypeWithPropInfo.merge(other:)") {+ let type1 = TypeWithPropInfo(+ name: "MyType",+ properties: [+ TypeWithPropInfo.PropInfo(name: "thing", typeAnnotation: "String", comment: "", access: .public, scope: .instance)+ ])+ let type2 = TypeWithPropInfo(+ name: "MyType",+ properties: [+ TypeWithPropInfo.PropInfo(name: "foo", typeAnnotation: "Int", comment: "", access: .public, scope: .instance)+ ])+ let type3 = TypeWithPropInfo(+ name: "AnotherType",+ properties: [+ TypeWithPropInfo.PropInfo(name: "foo", typeAnnotation: "Int", comment: "", access: .public, scope: .instance)+ ])++ context("when both types have the same name") {+ let result = try? type1.merge(with: type2)+ it("succeeds") {+ expect(result?.name) == "MyType"+ }++ it("has merged props") {+ let set = Set(result?.properties ?? [])+ let expectedSet: Set<TypeWithPropInfo.PropInfo> = [+ TypeWithPropInfo.PropInfo(name: "thing", typeAnnotation: "String", comment: "", access: .public, scope: .instance),+ TypeWithPropInfo.PropInfo(name: "foo", typeAnnotation: "Int", comment: "", access: .public, scope: .instance)+ ]+ expect(set) == expectedSet+ }+ }++ context("when the types don't match") {+ it("fails to merge and asserts") {+ expect { try type1.merge(with: type3) }.to(throwError())+ }+ }++ context("when the other type is nil") {+ it("returns the original") {+ expect(try? type1.merge(with: nil)) == type1+ }+ }+ }++ describe("analyze(fileURL:)") {+ context("when there are no properties") {+ beforeEach {+ let content = """+ public final class FakeType {}+ """+ fileURL = try? Temporary.makeFile(content: content)+ }++ it("returns type info with empty property list") {+ let result = try? sut.analyze(fileURL: fileURL)+ expect(result?.properties).to(beEmpty())+ }+ }++ context("when there is a property") {+ beforeEach {+ let content = """+ public final class FakeType {+ public var thing: String = "Hello, World"+ }+ """+ fileURL = try? Temporary.makeFile(content: content)+ }++ it("detects the type name") {+ let result = try? sut.analyze(fileURL: fileURL)+ expect(result?.name) == "FakeType"+ }++ it("returns nil if the type name is not present") {+ let sut = PropertyAnalyzer(typeName: "AnotherType")+ let result = try? sut.analyze(fileURL: fileURL)+ expect(result).to(beNil())+ }++ it("detects the properties") {+ let result = try? sut.analyze(fileURL: fileURL)+ expect(result?.properties) == [+ TypeWithPropInfo.PropInfo(+ name: "thing",+ typeAnnotation: "String",+ comment: "",+ access: .public,+ scope: .instance)+ ]+ }+ }++ context("when there is a property (struct)") {+ beforeEach {+ let content = """+ public struct FakeType {+ public var thing: String = "Hello, World"+ }+ """+ fileURL = try? Temporary.makeFile(content: content)+ }++ it("detects the type name") {+ let result = try? sut.analyze(fileURL: fileURL)+ expect(result?.name) == "FakeType"+ }++ it("detects the properties") {+ let result = try? sut.analyze(fileURL: fileURL)+ expect(result?.properties) == [+ TypeWithPropInfo.PropInfo(+ name: "thing",+ typeAnnotation: "String",+ comment: "",+ access: .public,+ scope: .instance)+ ]+ }+ }++ context("when there is a property (enum)") {+ beforeEach {+ let content = """+ public enum FakeType {+ public var thing: String = "Hello, World"+ }+ """+ fileURL = try? Temporary.makeFile(content: content)+ }++ it("detects the type name") {+ let result = try? sut.analyze(fileURL: fileURL)+ expect(result?.name) == "FakeType"+ }++ it("detects the properties") {+ let result = try? sut.analyze(fileURL: fileURL)+ expect(result?.properties) == [+ TypeWithPropInfo.PropInfo(+ name: "thing",+ typeAnnotation: "String",+ comment: "",+ access: .public,+ scope: .instance)+ ]+ }+ }++ context("when there is a property (protocol)") {+ beforeEach {+ let content = """+ public protocol FakeType {+ var thing: String { get }+ }+ """+ fileURL = try? Temporary.makeFile(content: content)+ }++ it("detects the type name") {+ let result = try? sut.analyze(fileURL: fileURL)+ expect(result?.name) == "FakeType"+ }++ it("detects the properties") {+ let result = try? sut.analyze(fileURL: fileURL)+ expect(result?.properties) == [+ TypeWithPropInfo.PropInfo(+ name: "thing",+ typeAnnotation: "String",+ comment: "",+ access: .internal,+ scope: .instance)+ ]+ }+ }++ context("when there is a property extension") {+ beforeEach {+ let content = """+ public class FakeType { }++ extension FakeType {+ var thing: String = "Hello, World"+ }+ """+ fileURL = try? Temporary.makeFile(content: content)+ }++ it("detects the type name") {+ let result = try? sut.analyze(fileURL: fileURL)+ expect(result?.name) == "FakeType"+ }++ it("detects the properties") {+ let result = try? sut.analyze(fileURL: fileURL)+ expect(result?.properties) == [+ TypeWithPropInfo.PropInfo(+ name: "thing",+ typeAnnotation: "String",+ comment: "",+ access: .internal,+ scope: .instance)+ ]+ }+ }++ context("when there are multiple properites") {+ beforeEach {+ let content = """+ public final class FakeType {+ public var thing: String = "Hello, World"+ var foo: Int = 4+ }+ """+ fileURL = try? Temporary.makeFile(content: content)+ }++ it("detects the properties") {+ let result = try? sut.analyze(fileURL: fileURL)+ let propSet = Set(result?.properties ?? [])+ let expectedPropSet: Set<TypeWithPropInfo.PropInfo> = [+ .init(name: "thing", typeAnnotation: "String", comment: "", access: .public, scope: .instance),+ .init(name: "foo", typeAnnotation: "Int", comment: "", access: .internal, scope: .instance)+ ]+ expect(propSet) == expectedPropSet+ }+ }++ context("when there is a static property") {+ beforeEach {+ let content = """+ public final class FakeType {+ public static var thing: String = "Hello, World"+ }+ """+ fileURL = try? Temporary.makeFile(content: content)+ }++ it("detects the property") {+ let result = try? sut.analyze(fileURL: fileURL)+ expect(result?.properties) == [+ TypeWithPropInfo.PropInfo(+ name: "thing",+ typeAnnotation: "String",+ comment: "",+ access: .public,+ scope: .static)+ ]+ }+ }++ context("when there is a private property") {+ beforeEach {+ let content = """+ public final class FakeType {+ private static var thing: String = "Hello, World"+ }+ """+ fileURL = try? Temporary.makeFile(content: content)+ }++ it("detects the property") {+ let result = try? sut.analyze(fileURL: fileURL)+ expect(result?.properties) == [+ TypeWithPropInfo.PropInfo(+ name: "thing",+ typeAnnotation: "String",+ comment: "",+ access: .private,+ scope: .static)+ ]+ }+ }++ context("when there is a fileprivate property") {+ beforeEach {+ let content = """+ public final class FakeType {+ fileprivate static var thing: String = "Hello, World"+ }+ """+ fileURL = try? Temporary.makeFile(content: content)+ }++ it("detects the property") {+ let result = try? sut.analyze(fileURL: fileURL)+ expect(result?.properties) == [+ TypeWithPropInfo.PropInfo(+ name: "thing",+ typeAnnotation: "String",+ comment: "",+ access: .fileprivate,+ scope: .static)+ ]+ }+ }++ context("when there is no type annotation") {+ beforeEach {+ let content = """+ public final class FakeType {+ private static var thing = "Hello, World"+ }+ """+ fileURL = try? Temporary.makeFile(content: content)+ }++ it("detects the property with no type information") {+ let result = try? sut.analyze(fileURL: fileURL)+ expect(result?.properties) == [+ TypeWithPropInfo.PropInfo(+ name: "thing",+ typeAnnotation: nil,+ comment: "",+ access: .private,+ scope: .static)+ ]+ }+ }++ context("when there is a line comment") {+ beforeEach {+ let content = """+ public final class FakeType {+ // The thing+ public var thing: String = "Hello, World"+ }+ """+ fileURL = try? Temporary.makeFile(content: content)+ }++ it("detects the property with the comment") {+ let result = try? sut.analyze(fileURL: fileURL)+ expect(result?.properties) == [+ TypeWithPropInfo.PropInfo(+ name: "thing",+ typeAnnotation: "String",+ comment: "// The thing",+ access: .public,+ scope: .instance)+ ]+ }+ }++ context("when there is a doc line comment") {+ beforeEach {+ let content = """+ public final class FakeType {+ /// The thing+ public var thing: String = "Hello, World"+ }+ """+ fileURL = try? Temporary.makeFile(content: content)+ }++ it("detects the property with the comment") {+ let result = try? sut.analyze(fileURL: fileURL)+ expect(result?.properties) == [+ TypeWithPropInfo.PropInfo(+ name: "thing",+ typeAnnotation: "String",+ comment: "/// The thing",+ access: .public,+ scope: .instance)+ ]+ }+ }++ context("when there is a block comment") {+ beforeEach {+ let content = """+ public final class FakeType {+ /* The thing */+ public var thing: String = "Hello, World"+ }+ """+ fileURL = try? Temporary.makeFile(content: content)+ }++ it("detects the property with the comment") {+ let result = try? sut.analyze(fileURL: fileURL)+ expect(result?.properties) == [+ TypeWithPropInfo.PropInfo(+ name: "thing",+ typeAnnotation: "String",+ comment: "/* The thing */",+ access: .public,+ scope: .instance)+ ]+ }+ }++ context("when there is a doc block comment") {+ beforeEach {+ let content = """+ public final class FakeType {+ /** The thing */+ public var thing: String = "Hello, World"+ }+ """+ fileURL = try? Temporary.makeFile(content: content)+ }++ it("detects the property with the comment") {+ let result = try? sut.analyze(fileURL: fileURL)+ expect(result?.properties) == [+ TypeWithPropInfo.PropInfo(+ name: "thing",+ typeAnnotation: "String",+ comment: "/** The thing */",+ access: .public,+ scope: .instance)+ ]+ }+ }++ }+ }+}
I agree, let's just add it as a follow-up if needed.
comment created time in a month
Pull request review commentfdiaz/SwiftInspector
+// Created by Tyler Hedrick on 3/27/20.+//+// Copyright (c) 2020 Tyler Hedrick+//+// Distributed under the MIT License+//+// Permission is hereby granted, free of charge, to any person obtaining a copy+// of this software and associated documentation files (the "Software"), to deal+// in the Software without restriction, including without limitation the rights+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+// copies of the Software, and to permit persons to whom the Software is+// furnished to do so, subject to the following conditions:+//+// The above copyright notice and this permission notice shall be included in all+// copies or substantial portions of the Software.+//+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+// SOFTWARE.++import Nimble+import Quick+import Foundation++@testable import SwiftInspectorCore++final class PropertyAnalyzerSpec: QuickSpec {+ override func spec() {+ var fileURL: URL!+ var sut = PropertyAnalyzer(typeName: "FakeType")++ beforeEach {+ sut = PropertyAnalyzer(typeName: "FakeType")+ }++ afterEach {+ guard let fileURL = fileURL else {+ return+ }+ try? Temporary.removeItem(at: fileURL)+ }++ describe("TypeWithPropInfo.merge(other:)") {+ let type1 = TypeWithPropInfo(+ name: "MyType",+ properties: [+ TypeWithPropInfo.PropInfo(name: "thing", typeAnnotation: "String", comment: "", access: .public, scope: .instance)+ ])+ let type2 = TypeWithPropInfo(+ name: "MyType",+ properties: [+ TypeWithPropInfo.PropInfo(name: "foo", typeAnnotation: "Int", comment: "", access: .public, scope: .instance)+ ])+ let type3 = TypeWithPropInfo(+ name: "AnotherType",+ properties: [+ TypeWithPropInfo.PropInfo(name: "foo", typeAnnotation: "Int", comment: "", access: .public, scope: .instance)+ ])++ context("when both types have the same name") {+ let result = try? type1.merge(with: type2)+ it("succeeds") {+ expect(result?.name) == "MyType"+ }++ it("has merged props") {+ let set = Set(result?.properties ?? [])+ let expectedSet: Set<TypeWithPropInfo.PropInfo> = [+ TypeWithPropInfo.PropInfo(name: "thing", typeAnnotation: "String", comment: "", access: .public, scope: .instance),+ TypeWithPropInfo.PropInfo(name: "foo", typeAnnotation: "Int", comment: "", access: .public, scope: .instance)+ ]+ expect(set) == expectedSet+ }+ }++ context("when the types don't match") {+ it("fails to merge and asserts") {+ expect { try type1.merge(with: type3) }.to(throwError())+ }+ }++ context("when the other type is nil") {+ it("returns the original") {+ expect(try? type1.merge(with: nil)) == type1+ }+ }+ }++ describe("analyze(fileURL:)") {+ context("when there are no properties") {+ beforeEach {+ let content = """+ public final class FakeType {}+ """+ fileURL = try? Temporary.makeFile(content: content)+ }++ it("returns type info with empty property list") {+ let result = try? sut.analyze(fileURL: fileURL)+ expect(result?.properties).to(beEmpty())+ }+ }++ context("when there is a property") {+ beforeEach {+ let content = """+ public final class FakeType {
I think this is fine for now. Let's keep it as is and if we see problems in the real world or a use case where we need to handle this we can build on top of this.
comment created time in a month
Pull request review commentfdiaz/SwiftInspector
+// Created by Tyler Hedrick on 8/17/20.+//+// Copyright (c) 2020 Tyler Hedrick+//+// Distributed under the MIT License+//+// Permission is hereby granted, free of charge, to any person obtaining a copy+// of this software and associated documentation files (the "Software"), to deal+// in the Software without restriction, including without limitation the rights+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+// copies of the Software, and to permit persons to whom the Software is+// furnished to do so, subject to the following conditions:+//+// The above copyright notice and this permission notice shall be included in all+// copies or substantial portions of the Software.+//+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+// SOFTWARE.++import ArgumentParser+import Foundation+import SwiftInspectorCore++final class PropertyAnalyzerCommand: ParsableCommand {+ static var configuration = CommandConfiguration(+ commandName: "properties",+ abstract: "Finds property information for the provided type"+ )++ @Option(help: nameArgumentHelp)+ var name: String++ @Option(help: "The absolute path of the file to inspect")+ var path: String++ /// Runs the command+ func run() throws {+ let cachedSyntaxTree = CachedSyntaxTree()++ let analyzer = PropertyAnalyzer(typeName: name, cachedSyntaxTree: cachedSyntaxTree)+ let fileURL = URL(fileURLWithPath: path)+ if let propertyInformation = try analyzer.analyze(fileURL: fileURL) {+ print(outputString(from: propertyInformation))+ }+ }++ /// Validates if the arguments of this command are valid+ func validate() throws {+ guard !name.isEmpty else {+ throw InspectorError.emptyArgument(argumentName: "--name")+ }+ guard !path.isEmpty else {+ throw InspectorError.emptyArgument(argumentName: "--path")+ }+ let pathURL = URL(fileURLWithPath: path)+ guard FileManager.default.isSwiftFile(at: pathURL) else {+ throw InspectorError.invalidArgument(argumentName: "--path", value: path)+ }+ }++ private func outputString(from statement: TypeWithPropInfo) -> String {+ let propString = statement.properties.map { propInfo in+ "(\(propInfo.name)|\(propInfo.access)|\(propInfo.scope))"
I think returning the type is useful when parsing the result. That way you don't need to inspect both what you're passing and what the output is giving you. I've found it useful at least
comment created time in a month
Pull request review commentfdiaz/SwiftInspector
+// Created by Tyler Hedrick on 8/14/20.+//+// Distributed under the MIT License+//+// Permission is hereby granted, free of charge, to any person obtaining a copy+// of this software and associated documentation files (the "Software"), to deal+// in the Software without restriction, including without limitation the rights+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+// copies of the Software, and to permit persons to whom the Software is+// furnished to do so, subject to the following conditions:+//+// The above copyright notice and this permission notice shall be included in all+// copies or substantial portions of the Software.+//+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+// SOFTWARE.++import Foundation+import SwiftSyntax++// MARK: - TypeLocationAnalyzer++public final class PropertyAnalyzer: Analyzer {++ /// - Parameter typeName: The name of the type to get property information for+ /// - Parameter cachedSyntaxTree: The cached syntax tree to return the AST tree from+ public init(typeName: String, cachedSyntaxTree: CachedSyntaxTree = .init()) {+ self.typeName = typeName+ self.cachedSyntaxTree = cachedSyntaxTree+ }++ /// Finds the location(s) of the specified type name in the Swift file+ /// - Parameter fileURL: The fileURL where the Swift file is located+ public func analyze(fileURL: URL) throws -> TypeWithPropInfo? {+ let syntax: SourceFileSyntax = try cachedSyntaxTree.syntaxTree(for: fileURL)+ var result: TypeWithPropInfo?+ let visitor = TypeSyntaxVisitor() { [unowned self] typeWithPropInfo in+ guard self.typeName == typeWithPropInfo.name else { return }+ result = try? typeWithPropInfo.merge(with: result)+ }+ visitor.walk(syntax)+ return result+ }++ // MARK: Private++ private let cachedSyntaxTree: CachedSyntaxTree+ private let typeName: String+}++// MARK: - TypeLocationSyntaxVisitor++private final class TypeSyntaxVisitor: SyntaxVisitor {++ init(onNodeVisit: @escaping (_ info: TypeWithPropInfo) -> Void) {+ self.onNodeVisit = onNodeVisit+ }++ override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind {+ processNode(node, withName: node.identifier.text, members: node.members.members)+ return .visitChildren+ }++ override func visit(_ node: EnumDeclSyntax) -> SyntaxVisitorContinueKind {+ processNode(node, withName: node.identifier.text, members: node.members.members)+ return .visitChildren+ }++ override func visit(_ node: ProtocolDeclSyntax) -> SyntaxVisitorContinueKind {+ processNode(node, withName: node.identifier.text, members: node.members.members)+ return .visitChildren+ }++ override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind {+ processNode(node, withName: node.identifier.text, members: node.members.members)+ return .visitChildren+ }++ override func visit(_ node: ExtensionDeclSyntax) -> SyntaxVisitorContinueKind {+ if let typeIdentifier = node.extendedType.as(SimpleTypeIdentifierSyntax.self) {+ processNode(node, withName: typeIdentifier.name.text, members: node.members.members)+ }+ return .visitChildren+ }++ // MARK: Private++ private let onNodeVisit: (_ info: TypeWithPropInfo) -> Void++ private func processNode<Node>(_ node: Node, withName name: String, members: MemberDeclListSyntax) where Node: SyntaxProtocol {+ var result = [TypeWithPropInfo.PropInfo]()+ let propertyVisitor = PropertySyntaxVisitor(parentName: name) { info in+ result.append(info)+ }+ propertyVisitor.walk(node)+ onNodeVisit(.init(name: name, properties: result))+ }+}++// MARK: - PropertySyntaxVisitor++private final class PropertySyntaxVisitor: SyntaxVisitor {++ init(parentName: String, onNodeVisit: @escaping (_ info: TypeWithPropInfo.PropInfo) -> Void) {+ self.parentName = parentName+ self.onNodeVisit = onNodeVisit+ }++ override func visit(_ node: VariableDeclSyntax) -> SyntaxVisitorContinueKind {+ var leadingTrivia: Trivia? = node.leadingTrivia+ var access: TypeWithPropInfo.Access = .internal+ var scope: TypeWithPropInfo.Scope = .instance++ node.modifiers?.forEach { modifier in+ // leading trivia is on the first modifier, or the node itself if no modifiers are present+ leadingTrivia = leadingTrivia ?? modifier.leadingTrivia+ // This attempts to get any access information from the node's modifiers+ if let modifierAccess = TypeWithPropInfo.Access(rawValue: modifier.name.text) {+ access = modifierAccess+ }+ // this attempts to get any scope information from the node's modifiers+ if let modifierScope = TypeWithPropInfo.Scope(rawValue: modifier.name.text) {+ scope = modifierScope+ }+ }++ // in practice this `bindings` array only has 1 element+ node.bindings.forEach { binding in+ if let identifier = binding.pattern.as(IdentifierPatternSyntax.self) {+ if+ let typeAnnotation = binding.typeAnnotation,+ let simpleTypeIdentifier = typeAnnotation.type.as(SimpleTypeIdentifierSyntax.self)+ {+ onNodeVisit(.init(+ name: identifier.identifier.text,+ typeAnnotation: simpleTypeIdentifier.name.text,+ comment: comment(from: leadingTrivia),+ access: access,+ scope: scope))+ return+ }+ // no type annotation was found. In this case it's much more complicated to get+ // the type information for a variable.+ // TODO: Include logic to get types of any variable declaration
I think it makes sense to not pass the type here, This makes sense! Let's add this as a comment 🙏
comment created time in a month
Pull request review commentfdiaz/SwiftInspector
+// Created by Tyler Hedrick on 8/14/20.+//+// Distributed under the MIT License+//+// Permission is hereby granted, free of charge, to any person obtaining a copy+// of this software and associated documentation files (the "Software"), to deal+// in the Software without restriction, including without limitation the rights+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+// copies of the Software, and to permit persons to whom the Software is+// furnished to do so, subject to the following conditions:+//+// The above copyright notice and this permission notice shall be included in all+// copies or substantial portions of the Software.+//+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+// SOFTWARE.++import Foundation+import SwiftSyntax++// MARK: - TypeLocationAnalyzer++public final class PropertyAnalyzer: Analyzer {++ /// - Parameter typeName: The name of the type to get property information for+ /// - Parameter cachedSyntaxTree: The cached syntax tree to return the AST tree from+ public init(typeName: String, cachedSyntaxTree: CachedSyntaxTree = .init()) {+ self.typeName = typeName+ self.cachedSyntaxTree = cachedSyntaxTree+ }++ /// Finds the location(s) of the specified type name in the Swift file+ /// - Parameter fileURL: The fileURL where the Swift file is located+ public func analyze(fileURL: URL) throws -> TypeWithPropInfo? {+ let syntax: SourceFileSyntax = try cachedSyntaxTree.syntaxTree(for: fileURL)+ var result: TypeWithPropInfo?+ let visitor = TypeSyntaxVisitor() { [unowned self] typeWithPropInfo in+ guard self.typeName == typeWithPropInfo.name else { return }+ result = try? typeWithPropInfo.merge(with: result)+ }+ visitor.walk(syntax)+ return result+ }++ // MARK: Private++ private let cachedSyntaxTree: CachedSyntaxTree+ private let typeName: String+}++// MARK: - TypeLocationSyntaxVisitor++private final class TypeSyntaxVisitor: SyntaxVisitor {++ init(onNodeVisit: @escaping (_ info: TypeWithPropInfo) -> Void) {+ self.onNodeVisit = onNodeVisit+ }++ override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind {+ processNode(node, withName: node.identifier.text, members: node.members.members)+ return .visitChildren+ }++ override func visit(_ node: EnumDeclSyntax) -> SyntaxVisitorContinueKind {+ processNode(node, withName: node.identifier.text, members: node.members.members)+ return .visitChildren+ }++ override func visit(_ node: ProtocolDeclSyntax) -> SyntaxVisitorContinueKind {+ processNode(node, withName: node.identifier.text, members: node.members.members)+ return .visitChildren+ }++ override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind {+ processNode(node, withName: node.identifier.text, members: node.members.members)+ return .visitChildren+ }++ override func visit(_ node: ExtensionDeclSyntax) -> SyntaxVisitorContinueKind {+ if let typeIdentifier = node.extendedType.as(SimpleTypeIdentifierSyntax.self) {+ processNode(node, withName: typeIdentifier.name.text, members: node.members.members)+ }+ return .visitChildren+ }++ // MARK: Private++ private let onNodeVisit: (_ info: TypeWithPropInfo) -> Void++ private func processNode<Node>(_ node: Node, withName name: String, members: MemberDeclListSyntax) where Node: SyntaxProtocol {+ var result = [TypeWithPropInfo.PropInfo]()+ let propertyVisitor = PropertySyntaxVisitor(parentName: name) { info in+ result.append(info)+ }+ propertyVisitor.walk(node)+ onNodeVisit(.init(name: name, properties: result))+ }+}++// MARK: - PropertySyntaxVisitor++private final class PropertySyntaxVisitor: SyntaxVisitor {++ init(parentName: String, onNodeVisit: @escaping (_ info: TypeWithPropInfo.PropInfo) -> Void) {+ self.parentName = parentName+ self.onNodeVisit = onNodeVisit+ }++ override func visit(_ node: VariableDeclSyntax) -> SyntaxVisitorContinueKind {+ var leadingTrivia: Trivia? = node.leadingTrivia+ var access: TypeWithPropInfo.Access = .internal+ var scope: TypeWithPropInfo.Scope = .instance++ node.modifiers?.forEach { modifier in+ // leading trivia is on the first modifier, or the node itself if no modifiers are present+ leadingTrivia = leadingTrivia ?? modifier.leadingTrivia+ // This attempts to get any access information from the node's modifiers+ if let modifierAccess = TypeWithPropInfo.Access(rawValue: modifier.name.text) {+ access = modifierAccess+ }+ // this attempts to get any scope information from the node's modifiers+ if let modifierScope = TypeWithPropInfo.Scope(rawValue: modifier.name.text) {+ scope = modifierScope+ }+ }++ // in practice this `bindings` array only has 1 element
I don't think so. That'd be one variable declaration IIUC with two bindings but I haven't validated this. maybe we can add a test for this use case?
comment created time in a month
Pull request review commentfdiaz/SwiftInspector
+// Created by Tyler Hedrick on 3/27/20.+//+// Copyright (c) 2020 Tyler Hedrick+//+// Distributed under the MIT License+//+// Permission is hereby granted, free of charge, to any person obtaining a copy+// of this software and associated documentation files (the "Software"), to deal+// in the Software without restriction, including without limitation the rights+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+// copies of the Software, and to permit persons to whom the Software is+// furnished to do so, subject to the following conditions:+//+// The above copyright notice and this permission notice shall be included in all+// copies or substantial portions of the Software.+//+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+// SOFTWARE.++import Nimble+import Quick+import Foundation++@testable import SwiftInspectorCore++final class PropertyAnalyzerSpec: QuickSpec {+ override func spec() {+ var fileURL: URL!+ var sut = PropertyAnalyzer(typeName: "FakeType")++ beforeEach {+ sut = PropertyAnalyzer(typeName: "FakeType")+ }++ afterEach {+ guard let fileURL = fileURL else {+ return+ }+ try? Temporary.removeItem(at: fileURL)+ }++ describe("TypeWithPropInfo.merge(other:)") {+ let type1 = TypeWithPropInfo(+ name: "MyType",+ properties: [+ TypeWithPropInfo.PropInfo(name: "thing", typeAnnotation: "String", comment: "", access: .public, scope: .instance)+ ])+ let type2 = TypeWithPropInfo(+ name: "MyType",+ properties: [+ TypeWithPropInfo.PropInfo(name: "foo", typeAnnotation: "Int", comment: "", access: .public, scope: .instance)+ ])+ let type3 = TypeWithPropInfo(+ name: "AnotherType",+ properties: [+ TypeWithPropInfo.PropInfo(name: "foo", typeAnnotation: "Int", comment: "", access: .public, scope: .instance)+ ])++ context("when both types have the same name") {+ let result = try? type1.merge(with: type2)+ it("succeeds") {+ expect(result?.name) == "MyType"+ }++ it("has merged props") {+ let set = Set(result?.properties ?? [])+ let expectedSet: Set<TypeWithPropInfo.PropInfo> = [+ TypeWithPropInfo.PropInfo(name: "thing", typeAnnotation: "String", comment: "", access: .public, scope: .instance),+ TypeWithPropInfo.PropInfo(name: "foo", typeAnnotation: "Int", comment: "", access: .public, scope: .instance)+ ]+ expect(set) == expectedSet+ }+ }++ context("when the types don't match") {+ it("fails to merge and asserts") {+ expect { try type1.merge(with: type3) }.to(throwError())+ }+ }++ context("when the other type is nil") {+ it("returns the original") {+ expect(try? type1.merge(with: nil)) == type1+ }+ }+ }++ describe("analyze(fileURL:)") {+ context("when there are no properties") {+ beforeEach {+ let content = """+ public final class FakeType {}+ """+ fileURL = try? Temporary.makeFile(content: content)+ }++ it("returns type info with empty property list") {+ let result = try? sut.analyze(fileURL: fileURL)+ expect(result?.properties).to(beEmpty())+ }+ }++ context("when there is a property") {+ beforeEach {+ let content = """+ public final class FakeType {+ public var thing: String = "Hello, World"+ }+ """+ fileURL = try? Temporary.makeFile(content: content)+ }++ it("detects the type name") {+ let result = try? sut.analyze(fileURL: fileURL)+ expect(result?.name) == "FakeType"+ }++ it("returns nil if the type name is not present") {+ let sut = PropertyAnalyzer(typeName: "AnotherType")+ let result = try? sut.analyze(fileURL: fileURL)+ expect(result).to(beNil())+ }++ it("detects the properties") {+ let result = try? sut.analyze(fileURL: fileURL)+ expect(result?.properties) == [+ TypeWithPropInfo.PropInfo(+ name: "thing",+ typeAnnotation: "String",+ comment: "",+ access: .public,+ scope: .instance)+ ]+ }+ }++ context("when there is a property (struct)") {+ beforeEach {+ let content = """+ public struct FakeType {+ public var thing: String = "Hello, World"+ }+ """+ fileURL = try? Temporary.makeFile(content: content)+ }++ it("detects the type name") {+ let result = try? sut.analyze(fileURL: fileURL)+ expect(result?.name) == "FakeType"+ }++ it("detects the properties") {+ let result = try? sut.analyze(fileURL: fileURL)+ expect(result?.properties) == [+ TypeWithPropInfo.PropInfo(+ name: "thing",+ typeAnnotation: "String",+ comment: "",+ access: .public,+ scope: .instance)+ ]+ }+ }++ context("when there is a property (enum)") {+ beforeEach {+ let content = """+ public enum FakeType {+ public var thing: String = "Hello, World"+ }+ """+ fileURL = try? Temporary.makeFile(content: content)+ }++ it("detects the type name") {+ let result = try? sut.analyze(fileURL: fileURL)+ expect(result?.name) == "FakeType"+ }++ it("detects the properties") {+ let result = try? sut.analyze(fileURL: fileURL)+ expect(result?.properties) == [+ TypeWithPropInfo.PropInfo(+ name: "thing",+ typeAnnotation: "String",+ comment: "",+ access: .public,+ scope: .instance)+ ]+ }+ }++ context("when there is a property (protocol)") {+ beforeEach {+ let content = """+ public protocol FakeType {+ var thing: String { get }+ }+ """+ fileURL = try? Temporary.makeFile(content: content)+ }++ it("detects the type name") {+ let result = try? sut.analyze(fileURL: fileURL)+ expect(result?.name) == "FakeType"+ }++ it("detects the properties") {+ let result = try? sut.analyze(fileURL: fileURL)+ expect(result?.properties) == [+ TypeWithPropInfo.PropInfo(+ name: "thing",+ typeAnnotation: "String",+ comment: "",+ access: .internal,+ scope: .instance)+ ]+ }+ }++ context("when there is a property extension") {+ beforeEach {+ let content = """+ public class FakeType { }++ extension FakeType {+ var thing: String = "Hello, World"+ }+ """+ fileURL = try? Temporary.makeFile(content: content)+ }++ it("detects the type name") {+ let result = try? sut.analyze(fileURL: fileURL)+ expect(result?.name) == "FakeType"+ }++ it("detects the properties") {+ let result = try? sut.analyze(fileURL: fileURL)+ expect(result?.properties) == [+ TypeWithPropInfo.PropInfo(+ name: "thing",+ typeAnnotation: "String",+ comment: "",+ access: .internal,+ scope: .instance)+ ]+ }+ }++ context("when there are multiple properites") {+ beforeEach {+ let content = """+ public final class FakeType {+ public var thing: String = "Hello, World"+ var foo: Int = 4+ }+ """+ fileURL = try? Temporary.makeFile(content: content)+ }++ it("detects the properties") {+ let result = try? sut.analyze(fileURL: fileURL)+ let propSet = Set(result?.properties ?? [])+ let expectedPropSet: Set<TypeWithPropInfo.PropInfo> = [+ .init(name: "thing", typeAnnotation: "String", comment: "", access: .public, scope: .instance),+ .init(name: "foo", typeAnnotation: "Int", comment: "", access: .internal, scope: .instance)+ ]+ expect(propSet) == expectedPropSet+ }+ }++ context("when there is a static property") {+ beforeEach {+ let content = """+ public final class FakeType {+ public static var thing: String = "Hello, World"+ }+ """+ fileURL = try? Temporary.makeFile(content: content)+ }++ it("detects the property") {+ let result = try? sut.analyze(fileURL: fileURL)+ expect(result?.properties) == [+ TypeWithPropInfo.PropInfo(+ name: "thing",+ typeAnnotation: "String",+ comment: "",+ access: .public,+ scope: .static)+ ]+ }+ }++ context("when there is a private property") {
Let's also add a test for wwhen these are the other way: static private instead of private static
comment created time in a month
Pull request review commentfdiaz/SwiftInspector
+// Created by Tyler Hedrick on 8/14/20.+//+// Distributed under the MIT License+//+// Permission is hereby granted, free of charge, to any person obtaining a copy+// of this software and associated documentation files (the "Software"), to deal+// in the Software without restriction, including without limitation the rights+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+// copies of the Software, and to permit persons to whom the Software is+// furnished to do so, subject to the following conditions:+//+// The above copyright notice and this permission notice shall be included in all+// copies or substantial portions of the Software.+//+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+// SOFTWARE.++import Foundation+import SwiftSyntax++// MARK: - TypeLocationAnalyzer++public final class PropertyAnalyzer: Analyzer {++ /// - Parameter typeName: The name of the type to get property information for+ /// - Parameter cachedSyntaxTree: The cached syntax tree to return the AST tree from+ public init(typeName: String, cachedSyntaxTree: CachedSyntaxTree = .init()) {+ self.typeName = typeName+ self.cachedSyntaxTree = cachedSyntaxTree+ }++ /// Finds the location(s) of the specified type name in the Swift file+ /// - Parameter fileURL: The fileURL where the Swift file is located+ public func analyze(fileURL: URL) throws -> TypeWithPropInfo? {+ let syntax: SourceFileSyntax = try cachedSyntaxTree.syntaxTree(for: fileURL)+ var result: TypeWithPropInfo?+ let visitor = TypeSyntaxVisitor() { [unowned self] typeWithPropInfo in+ guard self.typeName == typeWithPropInfo.name else { return }+ result = try? typeWithPropInfo.merge(with: result)+ }+ visitor.walk(syntax)+ return result+ }++ // MARK: Private++ private let cachedSyntaxTree: CachedSyntaxTree+ private let typeName: String+}++// MARK: - TypeLocationSyntaxVisitor++private final class TypeSyntaxVisitor: SyntaxVisitor {++ init(onNodeVisit: @escaping (_ info: TypeWithPropInfo) -> Void) {+ self.onNodeVisit = onNodeVisit+ }++ override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind {+ processNode(node, withName: node.identifier.text, members: node.members.members)+ return .visitChildren+ }++ override func visit(_ node: EnumDeclSyntax) -> SyntaxVisitorContinueKind {+ processNode(node, withName: node.identifier.text, members: node.members.members)+ return .visitChildren+ }++ override func visit(_ node: ProtocolDeclSyntax) -> SyntaxVisitorContinueKind {+ processNode(node, withName: node.identifier.text, members: node.members.members)+ return .visitChildren+ }++ override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind {+ processNode(node, withName: node.identifier.text, members: node.members.members)+ return .visitChildren+ }++ override func visit(_ node: ExtensionDeclSyntax) -> SyntaxVisitorContinueKind {+ if let typeIdentifier = node.extendedType.as(SimpleTypeIdentifierSyntax.self) {+ processNode(node, withName: typeIdentifier.name.text, members: node.members.members)+ }+ return .visitChildren+ }++ // MARK: Private++ private let onNodeVisit: (_ info: TypeWithPropInfo) -> Void++ private func processNode<Node>(_ node: Node, withName name: String, members: MemberDeclListSyntax) where Node: SyntaxProtocol {+ var result = [TypeWithPropInfo.PropInfo]()+ let propertyVisitor = PropertySyntaxVisitor(parentName: name) { info in+ result.append(info)+ }+ propertyVisitor.walk(node)+ onNodeVisit(.init(name: name, properties: result))+ }+}++// MARK: - PropertySyntaxVisitor++private final class PropertySyntaxVisitor: SyntaxVisitor {++ init(parentName: String, onNodeVisit: @escaping (_ info: TypeWithPropInfo.PropInfo) -> Void) {+ self.parentName = parentName+ self.onNodeVisit = onNodeVisit+ }++ override func visit(_ node: VariableDeclSyntax) -> SyntaxVisitorContinueKind {+ var leadingTrivia: Trivia? = node.leadingTrivia+ var access: TypeWithPropInfo.Access = .internal+ var scope: TypeWithPropInfo.Scope = .instance++ node.modifiers?.forEach { modifier in+ // leading trivia is on the first modifier, or the node itself if no modifiers are present+ leadingTrivia = leadingTrivia ?? modifier.leadingTrivia+ // This attempts to get any access information from the node's modifiers+ if let modifierAccess = TypeWithPropInfo.Access(rawValue: modifier.name.text) {+ access = modifierAccess+ }+ // this attempts to get any scope information from the node's modifiers+ if let modifierScope = TypeWithPropInfo.Scope(rawValue: modifier.name.text) {+ scope = modifierScope+ }+ }++ // in practice this `bindings` array only has 1 element+ node.bindings.forEach { binding in+ if let identifier = binding.pattern.as(IdentifierPatternSyntax.self) {+ if+ let typeAnnotation = binding.typeAnnotation,+ let simpleTypeIdentifier = typeAnnotation.type.as(SimpleTypeIdentifierSyntax.self)+ {+ onNodeVisit(.init(+ name: identifier.identifier.text,+ typeAnnotation: simpleTypeIdentifier.name.text,+ comment: comment(from: leadingTrivia),+ access: access,+ scope: scope))+ return+ }+ // no type annotation was found. In this case it's much more complicated to get+ // the type information for a variable.+ // TODO: Include logic to get types of any variable declaration+ onNodeVisit(.init(+ name: identifier.identifier.text,+ typeAnnotation: nil,+ comment: comment(from: leadingTrivia),+ access: access,+ scope: scope))+ }+ }++ return .skipChildren+ }++ // MARK: Private++ private let parentName: String
nit: typeName?
comment created time in a month
Pull request review commentfdiaz/SwiftInspector
+// Created by Tyler Hedrick on 3/27/20.+//+// Copyright (c) 2020 Tyler Hedrick+//+// Distributed under the MIT License+//+// Permission is hereby granted, free of charge, to any person obtaining a copy+// of this software and associated documentation files (the "Software"), to deal+// in the Software without restriction, including without limitation the rights+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+// copies of the Software, and to permit persons to whom the Software is+// furnished to do so, subject to the following conditions:+//+// The above copyright notice and this permission notice shall be included in all+// copies or substantial portions of the Software.+//+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+// SOFTWARE.++import Nimble+import Quick+import Foundation++@testable import SwiftInspectorCore++final class PropertyAnalyzerSpec: QuickSpec {+ override func spec() {+ var fileURL: URL!+ var sut = PropertyAnalyzer(typeName: "FakeType")++ beforeEach {+ sut = PropertyAnalyzer(typeName: "FakeType")+ }++ afterEach {+ guard let fileURL = fileURL else {+ return+ }+ try? Temporary.removeItem(at: fileURL)+ }++ describe("TypeWithPropInfo.merge(other:)") {+ let type1 = TypeWithPropInfo(+ name: "MyType",+ properties: [+ TypeWithPropInfo.PropInfo(name: "thing", typeAnnotation: "String", comment: "", access: .public, scope: .instance)+ ])+ let type2 = TypeWithPropInfo(+ name: "MyType",+ properties: [+ TypeWithPropInfo.PropInfo(name: "foo", typeAnnotation: "Int", comment: "", access: .public, scope: .instance)+ ])+ let type3 = TypeWithPropInfo(+ name: "AnotherType",+ properties: [+ TypeWithPropInfo.PropInfo(name: "foo", typeAnnotation: "Int", comment: "", access: .public, scope: .instance)+ ])++ context("when both types have the same name") {+ let result = try? type1.merge(with: type2)+ it("succeeds") {+ expect(result?.name) == "MyType"+ }++ it("has merged props") {+ let set = Set(result?.properties ?? [])+ let expectedSet: Set<TypeWithPropInfo.PropInfo> = [+ TypeWithPropInfo.PropInfo(name: "thing", typeAnnotation: "String", comment: "", access: .public, scope: .instance),+ TypeWithPropInfo.PropInfo(name: "foo", typeAnnotation: "Int", comment: "", access: .public, scope: .instance)+ ]+ expect(set) == expectedSet+ }+ }++ context("when the types don't match") {+ it("fails to merge and asserts") {+ expect { try type1.merge(with: type3) }.to(throwError())+ }+ }++ context("when the other type is nil") {+ it("returns the original") {+ expect(try? type1.merge(with: nil)) == type1+ }+ }+ }++ describe("analyze(fileURL:)") {+ context("when there are no properties") {+ beforeEach {+ let content = """+ public final class FakeType {}+ """+ fileURL = try? Temporary.makeFile(content: content)+ }++ it("returns type info with empty property list") {+ let result = try? sut.analyze(fileURL: fileURL)+ expect(result?.properties).to(beEmpty())+ }+ }++ context("when there is a property") {+ beforeEach {+ let content = """+ public final class FakeType {+ public var thing: String = "Hello, World"+ }+ """+ fileURL = try? Temporary.makeFile(content: content)+ }++ it("detects the type name") {+ let result = try? sut.analyze(fileURL: fileURL)+ expect(result?.name) == "FakeType"+ }++ it("returns nil if the type name is not present") {+ let sut = PropertyAnalyzer(typeName: "AnotherType")+ let result = try? sut.analyze(fileURL: fileURL)+ expect(result).to(beNil())+ }++ it("detects the properties") {+ let result = try? sut.analyze(fileURL: fileURL)+ expect(result?.properties) == [+ TypeWithPropInfo.PropInfo(+ name: "thing",+ typeAnnotation: "String",+ comment: "",+ access: .public,+ scope: .instance)+ ]+ }+ }++ context("when there is a property (struct)") {+ beforeEach {+ let content = """+ public struct FakeType {+ public var thing: String = "Hello, World"+ }+ """+ fileURL = try? Temporary.makeFile(content: content)+ }++ it("detects the type name") {+ let result = try? sut.analyze(fileURL: fileURL)+ expect(result?.name) == "FakeType"+ }++ it("detects the properties") {+ let result = try? sut.analyze(fileURL: fileURL)+ expect(result?.properties) == [+ TypeWithPropInfo.PropInfo(+ name: "thing",+ typeAnnotation: "String",+ comment: "",+ access: .public,+ scope: .instance)+ ]+ }+ }++ context("when there is a property (enum)") {+ beforeEach {+ let content = """+ public enum FakeType {+ public var thing: String = "Hello, World"+ }+ """+ fileURL = try? Temporary.makeFile(content: content)+ }++ it("detects the type name") {+ let result = try? sut.analyze(fileURL: fileURL)+ expect(result?.name) == "FakeType"+ }++ it("detects the properties") {+ let result = try? sut.analyze(fileURL: fileURL)+ expect(result?.properties) == [+ TypeWithPropInfo.PropInfo(+ name: "thing",+ typeAnnotation: "String",+ comment: "",+ access: .public,+ scope: .instance)+ ]+ }+ }++ context("when there is a property (protocol)") {+ beforeEach {+ let content = """+ public protocol FakeType {+ var thing: String { get }+ }+ """+ fileURL = try? Temporary.makeFile(content: content)+ }++ it("detects the type name") {+ let result = try? sut.analyze(fileURL: fileURL)+ expect(result?.name) == "FakeType"+ }++ it("detects the properties") {+ let result = try? sut.analyze(fileURL: fileURL)+ expect(result?.properties) == [+ TypeWithPropInfo.PropInfo(+ name: "thing",+ typeAnnotation: "String",+ comment: "",+ access: .internal,+ scope: .instance)+ ]+ }+ }++ context("when there is a property extension") {+ beforeEach {+ let content = """+ public class FakeType { }++ extension FakeType {+ var thing: String = "Hello, World"+ }+ """+ fileURL = try? Temporary.makeFile(content: content)+ }++ it("detects the type name") {+ let result = try? sut.analyze(fileURL: fileURL)+ expect(result?.name) == "FakeType"+ }++ it("detects the properties") {+ let result = try? sut.analyze(fileURL: fileURL)+ expect(result?.properties) == [+ TypeWithPropInfo.PropInfo(+ name: "thing",+ typeAnnotation: "String",+ comment: "",+ access: .internal,+ scope: .instance)+ ]+ }+ }++ context("when there are multiple properites") {+ beforeEach {+ let content = """+ public final class FakeType {+ public var thing: String = "Hello, World"+ var foo: Int = 4+ }+ """+ fileURL = try? Temporary.makeFile(content: content)+ }++ it("detects the properties") {+ let result = try? sut.analyze(fileURL: fileURL)+ let propSet = Set(result?.properties ?? [])+ let expectedPropSet: Set<TypeWithPropInfo.PropInfo> = [+ .init(name: "thing", typeAnnotation: "String", comment: "", access: .public, scope: .instance),+ .init(name: "foo", typeAnnotation: "Int", comment: "", access: .internal, scope: .instance)+ ]+ expect(propSet) == expectedPropSet+ }+ }++ context("when there is a static property") {+ beforeEach {+ let content = """+ public final class FakeType {+ public static var thing: String = "Hello, World"+ }+ """+ fileURL = try? Temporary.makeFile(content: content)+ }++ it("detects the property") {+ let result = try? sut.analyze(fileURL: fileURL)+ expect(result?.properties) == [+ TypeWithPropInfo.PropInfo(+ name: "thing",+ typeAnnotation: "String",+ comment: "",+ access: .public,+ scope: .static)+ ]+ }+ }++ context("when there is a private property") {+ beforeEach {+ let content = """+ public final class FakeType {+ private static var thing: String = "Hello, World"+ }+ """+ fileURL = try? Temporary.makeFile(content: content)+ }++ it("detects the property") {+ let result = try? sut.analyze(fileURL: fileURL)+ expect(result?.properties) == [+ TypeWithPropInfo.PropInfo(+ name: "thing",+ typeAnnotation: "String",+ comment: "",+ access: .private,+ scope: .static)+ ]+ }+ }++ context("when there is a fileprivate property") {+ beforeEach {+ let content = """+ public final class FakeType {+ fileprivate static var thing: String = "Hello, World"+ }+ """+ fileURL = try? Temporary.makeFile(content: content)+ }++ it("detects the property") {+ let result = try? sut.analyze(fileURL: fileURL)+ expect(result?.properties) == [+ TypeWithPropInfo.PropInfo(+ name: "thing",+ typeAnnotation: "String",+ comment: "",+ access: .fileprivate,+ scope: .static)+ ]+ }+ }++ context("when there is no type annotation") {+ beforeEach {+ let content = """+ public final class FakeType {+ private static var thing = "Hello, World"+ }+ """+ fileURL = try? Temporary.makeFile(content: content)+ }++ it("detects the property with no type information") {+ let result = try? sut.analyze(fileURL: fileURL)+ expect(result?.properties) == [+ TypeWithPropInfo.PropInfo(+ name: "thing",+ typeAnnotation: nil,+ comment: "",+ access: .private,+ scope: .static)+ ]+ }+ }++ context("when there is a line comment") {+ beforeEach {+ let content = """+ public final class FakeType {+ // The thing+ public var thing: String = "Hello, World"+ }+ """+ fileURL = try? Temporary.makeFile(content: content)+ }++ it("detects the property with the comment") {+ let result = try? sut.analyze(fileURL: fileURL)+ expect(result?.properties) == [+ TypeWithPropInfo.PropInfo(+ name: "thing",+ typeAnnotation: "String",+ comment: "// The thing",+ access: .public,+ scope: .instance)+ ]+ }+ }++ context("when there is a doc line comment") {+ beforeEach {+ let content = """+ public final class FakeType {+ /// The thing+ public var thing: String = "Hello, World"+ }+ """+ fileURL = try? Temporary.makeFile(content: content)+ }++ it("detects the property with the comment") {+ let result = try? sut.analyze(fileURL: fileURL)+ expect(result?.properties) == [+ TypeWithPropInfo.PropInfo(+ name: "thing",+ typeAnnotation: "String",+ comment: "/// The thing",+ access: .public,+ scope: .instance)+ ]+ }+ }++ context("when there is a block comment") {+ beforeEach {+ let content = """+ public final class FakeType {+ /* The thing */+ public var thing: String = "Hello, World"+ }+ """+ fileURL = try? Temporary.makeFile(content: content)+ }++ it("detects the property with the comment") {+ let result = try? sut.analyze(fileURL: fileURL)+ expect(result?.properties) == [+ TypeWithPropInfo.PropInfo(+ name: "thing",+ typeAnnotation: "String",+ comment: "/* The thing */",+ access: .public,+ scope: .instance)+ ]+ }+ }++ context("when there is a doc block comment") {+ beforeEach {+ let content = """+ public final class FakeType {+ /** The thing */+ public var thing: String = "Hello, World"+ }+ """+ fileURL = try? Temporary.makeFile(content: content)+ }++ it("detects the property with the comment") {+ let result = try? sut.analyze(fileURL: fileURL)+ expect(result?.properties) == [+ TypeWithPropInfo.PropInfo(+ name: "thing",+ typeAnnotation: "String",+ comment: "/** The thing */",+ access: .public,+ scope: .instance)+ ]+ }+ }
Let's add a test for the public private(set) case too
comment created time in a month
Pull request review commentfdiaz/SwiftInspector
+// Created by Tyler Hedrick on 8/14/20.+//+// Distributed under the MIT License+//+// Permission is hereby granted, free of charge, to any person obtaining a copy+// of this software and associated documentation files (the "Software"), to deal+// in the Software without restriction, including without limitation the rights+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+// copies of the Software, and to permit persons to whom the Software is+// furnished to do so, subject to the following conditions:+//+// The above copyright notice and this permission notice shall be included in all+// copies or substantial portions of the Software.+//+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+// SOFTWARE.++import Foundation+import SwiftSyntax++// MARK: - TypeLocationAnalyzer++public final class PropertyAnalyzer: Analyzer {++ /// - Parameter typeName: The name of the type to get property information for+ /// - Parameter cachedSyntaxTree: The cached syntax tree to return the AST tree from+ public init(typeName: String, cachedSyntaxTree: CachedSyntaxTree = .init()) {+ self.typeName = typeName+ self.cachedSyntaxTree = cachedSyntaxTree+ }++ /// Finds the location(s) of the specified type name in the Swift file+ /// - Parameter fileURL: The fileURL where the Swift file is located+ public func analyze(fileURL: URL) throws -> TypeWithPropInfo? {+ let syntax: SourceFileSyntax = try cachedSyntaxTree.syntaxTree(for: fileURL)+ var result: TypeWithPropInfo?+ let visitor = TypeSyntaxVisitor() { [unowned self] typeWithPropInfo in+ guard self.typeName == typeWithPropInfo.name else { return }+ result = try? typeWithPropInfo.merge(with: result)+ }+ visitor.walk(syntax)+ return result+ }++ // MARK: Private++ private let cachedSyntaxTree: CachedSyntaxTree+ private let typeName: String+}++// MARK: - TypeLocationSyntaxVisitor++private final class TypeSyntaxVisitor: SyntaxVisitor {++ init(onNodeVisit: @escaping (_ info: TypeWithPropInfo) -> Void) {+ self.onNodeVisit = onNodeVisit+ }++ override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind {+ processNode(node, withName: node.identifier.text, members: node.members.members)+ return .visitChildren+ }++ override func visit(_ node: EnumDeclSyntax) -> SyntaxVisitorContinueKind {+ processNode(node, withName: node.identifier.text, members: node.members.members)+ return .visitChildren+ }++ override func visit(_ node: ProtocolDeclSyntax) -> SyntaxVisitorContinueKind {+ processNode(node, withName: node.identifier.text, members: node.members.members)+ return .visitChildren+ }++ override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind {+ processNode(node, withName: node.identifier.text, members: node.members.members)+ return .visitChildren+ }++ override func visit(_ node: ExtensionDeclSyntax) -> SyntaxVisitorContinueKind {+ if let typeIdentifier = node.extendedType.as(SimpleTypeIdentifierSyntax.self) {+ processNode(node, withName: typeIdentifier.name.text, members: node.members.members)+ }+ return .visitChildren+ }++ // MARK: Private++ private let onNodeVisit: (_ info: TypeWithPropInfo) -> Void++ private func processNode<Node>(_ node: Node, withName name: String, members: MemberDeclListSyntax) where Node: SyntaxProtocol {+ var result = [TypeWithPropInfo.PropInfo]()+ let propertyVisitor = PropertySyntaxVisitor(parentName: name) { info in+ result.append(info)+ }+ propertyVisitor.walk(node)+ onNodeVisit(.init(name: name, properties: result))+ }+}++// MARK: - PropertySyntaxVisitor++private final class PropertySyntaxVisitor: SyntaxVisitor {++ init(parentName: String, onNodeVisit: @escaping (_ info: TypeWithPropInfo.PropInfo) -> Void) {+ self.parentName = parentName+ self.onNodeVisit = onNodeVisit+ }++ override func visit(_ node: VariableDeclSyntax) -> SyntaxVisitorContinueKind {+ var leadingTrivia: Trivia? = node.leadingTrivia+ var access: TypeWithPropInfo.Access = .internal+ var scope: TypeWithPropInfo.Scope = .instance++ node.modifiers?.forEach { modifier in+ // leading trivia is on the first modifier, or the node itself if no modifiers are present+ leadingTrivia = leadingTrivia ?? modifier.leadingTrivia+ // This attempts to get any access information from the node's modifiers+ if let modifierAccess = TypeWithPropInfo.Access(rawValue: modifier.name.text) {
Should we be looking into DeclModifier syntax instead? I think this could break if we have an attribute like public private(set) since we'd be first assigning the modifier to public, but later re-assigning to private.
I think it'd be better to look at the tokens of each declaration modifier and look for the public, internal, etc keywords, then we could validate that we only have 1 token (so we make sure it's only public and not something like public(set)`?
I've found that assuming ordering when using the AST usually ends up biting me back. I think using the token names tends to be more robust and scales better.
comment created time in a month
Pull request review commentfdiaz/SwiftInspector
+// Created by Tyler Hedrick on 8/17/20.+//+// Copyright (c) 2020 Tyler Hedrick+//+// Distributed under the MIT License+//+// Permission is hereby granted, free of charge, to any person obtaining a copy+// of this software and associated documentation files (the "Software"), to deal+// in the Software without restriction, including without limitation the rights+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+// copies of the Software, and to permit persons to whom the Software is+// furnished to do so, subject to the following conditions:+//+// The above copyright notice and this permission notice shall be included in all+// copies or substantial portions of the Software.+//+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+// SOFTWARE.++import ArgumentParser+import Foundation+import SwiftInspectorCore++final class PropertyAnalyzerCommand: ParsableCommand {+ static var configuration = CommandConfiguration(+ commandName: "properties",+ abstract: "Finds property information for the provided type"+ )++ @Option(help: nameArgumentHelp)+ var name: String++ @Option(help: "The absolute path of the file to inspect")+ var path: String++ /// Runs the command+ func run() throws {+ let cachedSyntaxTree = CachedSyntaxTree()++ let analyzer = PropertyAnalyzer(typeName: name, cachedSyntaxTree: cachedSyntaxTree)+ let fileURL = URL(fileURLWithPath: path)+ if let propertyInformation = try analyzer.analyze(fileURL: fileURL) {+ print(outputString(from: propertyInformation))+ }+ }++ /// Validates if the arguments of this command are valid+ func validate() throws {+ guard !name.isEmpty else {+ throw InspectorError.emptyArgument(argumentName: "--name")+ }+ guard !path.isEmpty else {+ throw InspectorError.emptyArgument(argumentName: "--path")+ }+ let pathURL = URL(fileURLWithPath: path)+ guard FileManager.default.isSwiftFile(at: pathURL) else {+ throw InspectorError.invalidArgument(argumentName: "--path", value: path)+ }+ }++ private func outputString(from statement: TypeWithPropInfo) -> String {+ let propString = statement.properties.map { propInfo in+ "(\(propInfo.name)|\(propInfo.access)|\(propInfo.scope))"
Should we use spaces or commas to separate these and then new lines for each property instead? this would be the first command that uses| to separate fields
comment created time in a month
Pull request review commentfdiaz/SwiftInspector
+// Created by Tyler Hedrick on 8/14/20.+//+// Distributed under the MIT License+//+// Permission is hereby granted, free of charge, to any person obtaining a copy+// of this software and associated documentation files (the "Software"), to deal+// in the Software without restriction, including without limitation the rights+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+// copies of the Software, and to permit persons to whom the Software is+// furnished to do so, subject to the following conditions:+//+// The above copyright notice and this permission notice shall be included in all+// copies or substantial portions of the Software.+//+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+// SOFTWARE.++import Foundation+import SwiftSyntax++// MARK: - TypeLocationAnalyzer++public final class PropertyAnalyzer: Analyzer {++ /// - Parameter typeName: The name of the type to get property information for+ /// - Parameter cachedSyntaxTree: The cached syntax tree to return the AST tree from+ public init(typeName: String, cachedSyntaxTree: CachedSyntaxTree = .init()) {+ self.typeName = typeName+ self.cachedSyntaxTree = cachedSyntaxTree+ }++ /// Finds the location(s) of the specified type name in the Swift file+ /// - Parameter fileURL: The fileURL where the Swift file is located+ public func analyze(fileURL: URL) throws -> TypeWithPropInfo? {+ let syntax: SourceFileSyntax = try cachedSyntaxTree.syntaxTree(for: fileURL)+ var result: TypeWithPropInfo?+ let visitor = TypeSyntaxVisitor() { [unowned self] typeWithPropInfo in+ guard self.typeName == typeWithPropInfo.name else { return }+ result = try? typeWithPropInfo.merge(with: result)
Should we rethrow this error instead of ignoring it?
comment created time in a month
Pull request review commentfdiaz/SwiftInspector
+// Created by Tyler Hedrick on 8/14/20.+//+// Distributed under the MIT License+//+// Permission is hereby granted, free of charge, to any person obtaining a copy+// of this software and associated documentation files (the "Software"), to deal+// in the Software without restriction, including without limitation the rights+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+// copies of the Software, and to permit persons to whom the Software is+// furnished to do so, subject to the following conditions:+//+// The above copyright notice and this permission notice shall be included in all+// copies or substantial portions of the Software.+//+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+// SOFTWARE.++import Foundation+import SwiftSyntax++// MARK: - TypeLocationAnalyzer++public final class PropertyAnalyzer: Analyzer {++ /// - Parameter typeName: The name of the type to get property information for+ /// - Parameter cachedSyntaxTree: The cached syntax tree to return the AST tree from+ public init(typeName: String, cachedSyntaxTree: CachedSyntaxTree = .init()) {+ self.typeName = typeName+ self.cachedSyntaxTree = cachedSyntaxTree+ }++ /// Finds the location(s) of the specified type name in the Swift file+ /// - Parameter fileURL: The fileURL where the Swift file is located+ public func analyze(fileURL: URL) throws -> TypeWithPropInfo? {+ let syntax: SourceFileSyntax = try cachedSyntaxTree.syntaxTree(for: fileURL)+ var result: TypeWithPropInfo?+ let visitor = TypeSyntaxVisitor() { [unowned self] typeWithPropInfo in+ guard self.typeName == typeWithPropInfo.name else { return }+ result = try? typeWithPropInfo.merge(with: result)+ }+ visitor.walk(syntax)+ return result+ }++ // MARK: Private++ private let cachedSyntaxTree: CachedSyntaxTree+ private let typeName: String+}++// MARK: - TypeLocationSyntaxVisitor++private final class TypeSyntaxVisitor: SyntaxVisitor {++ init(onNodeVisit: @escaping (_ info: TypeWithPropInfo) -> Void) {+ self.onNodeVisit = onNodeVisit+ }++ override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind {+ processNode(node, withName: node.identifier.text, members: node.members.members)+ return .visitChildren+ }++ override func visit(_ node: EnumDeclSyntax) -> SyntaxVisitorContinueKind {+ processNode(node, withName: node.identifier.text, members: node.members.members)+ return .visitChildren+ }++ override func visit(_ node: ProtocolDeclSyntax) -> SyntaxVisitorContinueKind {+ processNode(node, withName: node.identifier.text, members: node.members.members)+ return .visitChildren+ }++ override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind {+ processNode(node, withName: node.identifier.text, members: node.members.members)+ return .visitChildren+ }++ override func visit(_ node: ExtensionDeclSyntax) -> SyntaxVisitorContinueKind {+ if let typeIdentifier = node.extendedType.as(SimpleTypeIdentifierSyntax.self) {+ processNode(node, withName: typeIdentifier.name.text, members: node.members.members)+ }+ return .visitChildren+ }++ // MARK: Private++ private let onNodeVisit: (_ info: TypeWithPropInfo) -> Void++ private func processNode<Node>(_ node: Node, withName name: String, members: MemberDeclListSyntax) where Node: SyntaxProtocol {+ var result = [TypeWithPropInfo.PropInfo]()+ let propertyVisitor = PropertySyntaxVisitor(parentName: name) { info in+ result.append(info)+ }+ propertyVisitor.walk(node)+ onNodeVisit(.init(name: name, properties: result))+ }+}++// MARK: - PropertySyntaxVisitor++private final class PropertySyntaxVisitor: SyntaxVisitor {++ init(parentName: String, onNodeVisit: @escaping (_ info: TypeWithPropInfo.PropInfo) -> Void) {+ self.parentName = parentName+ self.onNodeVisit = onNodeVisit+ }++ override func visit(_ node: VariableDeclSyntax) -> SyntaxVisitorContinueKind {+ var leadingTrivia: Trivia? = node.leadingTrivia+ var access: TypeWithPropInfo.Access = .internal+ var scope: TypeWithPropInfo.Scope = .instance++ node.modifiers?.forEach { modifier in+ // leading trivia is on the first modifier, or the node itself if no modifiers are present+ leadingTrivia = leadingTrivia ?? modifier.leadingTrivia+ // This attempts to get any access information from the node's modifiers+ if let modifierAccess = TypeWithPropInfo.Access(rawValue: modifier.name.text) {+ access = modifierAccess+ }+ // this attempts to get any scope information from the node's modifiers+ if let modifierScope = TypeWithPropInfo.Scope(rawValue: modifier.name.text) {+ scope = modifierScope+ }+ }++ // in practice this `bindings` array only has 1 element+ node.bindings.forEach { binding in+ if let identifier = binding.pattern.as(IdentifierPatternSyntax.self) {+ if+ let typeAnnotation = binding.typeAnnotation,+ let simpleTypeIdentifier = typeAnnotation.type.as(SimpleTypeIdentifierSyntax.self)+ {+ onNodeVisit(.init(+ name: identifier.identifier.text,+ typeAnnotation: simpleTypeIdentifier.name.text,+ comment: comment(from: leadingTrivia),+ access: access,+ scope: scope))+ return+ }+ // no type annotation was found. In this case it's much more complicated to get+ // the type information for a variable.+ // TODO: Include logic to get types of any variable declaration
What's an example of this use case?
comment created time in a month
Pull request review commentfdiaz/SwiftInspector
+// Created by Tyler Hedrick on 8/14/20.+//+// Distributed under the MIT License+//+// Permission is hereby granted, free of charge, to any person obtaining a copy+// of this software and associated documentation files (the "Software"), to deal+// in the Software without restriction, including without limitation the rights+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+// copies of the Software, and to permit persons to whom the Software is+// furnished to do so, subject to the following conditions:+//+// The above copyright notice and this permission notice shall be included in all+// copies or substantial portions of the Software.+//+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+// SOFTWARE.++import Foundation+import SwiftSyntax++// MARK: - TypeLocationAnalyzer++public final class PropertyAnalyzer: Analyzer {++ /// - Parameter typeName: The name of the type to get property information for+ /// - Parameter cachedSyntaxTree: The cached syntax tree to return the AST tree from+ public init(typeName: String, cachedSyntaxTree: CachedSyntaxTree = .init()) {+ self.typeName = typeName+ self.cachedSyntaxTree = cachedSyntaxTree+ }++ /// Finds the location(s) of the specified type name in the Swift file+ /// - Parameter fileURL: The fileURL where the Swift file is located+ public func analyze(fileURL: URL) throws -> TypeWithPropInfo? {+ let syntax: SourceFileSyntax = try cachedSyntaxTree.syntaxTree(for: fileURL)+ var result: TypeWithPropInfo?+ let visitor = TypeSyntaxVisitor() { [unowned self] typeWithPropInfo in+ guard self.typeName == typeWithPropInfo.name else { return }+ result = try? typeWithPropInfo.merge(with: result)+ }+ visitor.walk(syntax)+ return result+ }++ // MARK: Private++ private let cachedSyntaxTree: CachedSyntaxTree+ private let typeName: String+}++// MARK: - TypeLocationSyntaxVisitor++private final class TypeSyntaxVisitor: SyntaxVisitor {++ init(onNodeVisit: @escaping (_ info: TypeWithPropInfo) -> Void) {+ self.onNodeVisit = onNodeVisit+ }++ override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind {+ processNode(node, withName: node.identifier.text, members: node.members.members)+ return .visitChildren+ }++ override func visit(_ node: EnumDeclSyntax) -> SyntaxVisitorContinueKind {+ processNode(node, withName: node.identifier.text, members: node.members.members)+ return .visitChildren+ }++ override func visit(_ node: ProtocolDeclSyntax) -> SyntaxVisitorContinueKind {+ processNode(node, withName: node.identifier.text, members: node.members.members)+ return .visitChildren+ }++ override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind {+ processNode(node, withName: node.identifier.text, members: node.members.members)+ return .visitChildren+ }++ override func visit(_ node: ExtensionDeclSyntax) -> SyntaxVisitorContinueKind {+ if let typeIdentifier = node.extendedType.as(SimpleTypeIdentifierSyntax.self) {+ processNode(node, withName: typeIdentifier.name.text, members: node.members.members)+ }+ return .visitChildren+ }++ // MARK: Private++ private let onNodeVisit: (_ info: TypeWithPropInfo) -> Void++ private func processNode<Node>(_ node: Node, withName name: String, members: MemberDeclListSyntax) where Node: SyntaxProtocol {+ var result = [TypeWithPropInfo.PropInfo]()+ let propertyVisitor = PropertySyntaxVisitor(parentName: name) { info in+ result.append(info)+ }+ propertyVisitor.walk(node)+ onNodeVisit(.init(name: name, properties: result))+ }+}++// MARK: - PropertySyntaxVisitor++private final class PropertySyntaxVisitor: SyntaxVisitor {++ init(parentName: String, onNodeVisit: @escaping (_ info: TypeWithPropInfo.PropInfo) -> Void) {+ self.parentName = parentName+ self.onNodeVisit = onNodeVisit+ }++ override func visit(_ node: VariableDeclSyntax) -> SyntaxVisitorContinueKind {+ var leadingTrivia: Trivia? = node.leadingTrivia+ var access: TypeWithPropInfo.Access = .internal+ var scope: TypeWithPropInfo.Scope = .instance++ node.modifiers?.forEach { modifier in+ // leading trivia is on the first modifier, or the node itself if no modifiers are present+ leadingTrivia = leadingTrivia ?? modifier.leadingTrivia+ // This attempts to get any access information from the node's modifiers+ if let modifierAccess = TypeWithPropInfo.Access(rawValue: modifier.name.text) {+ access = modifierAccess+ }+ // this attempts to get any scope information from the node's modifiers+ if let modifierScope = TypeWithPropInfo.Scope(rawValue: modifier.name.text) {+ scope = modifierScope+ }+ }++ // in practice this `bindings` array only has 1 element
i don't think this is always true, I think we can have something like:
struct Foo {
let some: String, another: Int
}
And this will contain 2 bindings IIUC
comment created time in a month
Pull request review commentfdiaz/SwiftInspector
+// Created by Tyler Hedrick on 8/14/20.+//+// Distributed under the MIT License+//+// Permission is hereby granted, free of charge, to any person obtaining a copy+// of this software and associated documentation files (the "Software"), to deal+// in the Software without restriction, including without limitation the rights+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+// copies of the Software, and to permit persons to whom the Software is+// furnished to do so, subject to the following conditions:+//+// The above copyright notice and this permission notice shall be included in all+// copies or substantial portions of the Software.+//+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+// SOFTWARE.++import Foundation+import SwiftSyntax++// MARK: - TypeLocationAnalyzer++public final class PropertyAnalyzer: Analyzer {++ /// - Parameter typeName: The name of the type to get property information for+ /// - Parameter cachedSyntaxTree: The cached syntax tree to return the AST tree from+ public init(typeName: String, cachedSyntaxTree: CachedSyntaxTree = .init()) {+ self.typeName = typeName+ self.cachedSyntaxTree = cachedSyntaxTree+ }++ /// Finds the location(s) of the specified type name in the Swift file+ /// - Parameter fileURL: The fileURL where the Swift file is located+ public func analyze(fileURL: URL) throws -> TypeWithPropInfo? {+ let syntax: SourceFileSyntax = try cachedSyntaxTree.syntaxTree(for: fileURL)+ var result: TypeWithPropInfo?+ let visitor = TypeSyntaxVisitor() { [unowned self] typeWithPropInfo in+ guard self.typeName == typeWithPropInfo.name else { return }+ result = try? typeWithPropInfo.merge(with: result)+ }+ visitor.walk(syntax)+ return result+ }++ // MARK: Private++ private let cachedSyntaxTree: CachedSyntaxTree+ private let typeName: String+}++// MARK: - TypeLocationSyntaxVisitor++private final class TypeSyntaxVisitor: SyntaxVisitor {++ init(onNodeVisit: @escaping (_ info: TypeWithPropInfo) -> Void) {+ self.onNodeVisit = onNodeVisit+ }++ override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind {+ processNode(node, withName: node.identifier.text, members: node.members.members)+ return .visitChildren+ }++ override func visit(_ node: EnumDeclSyntax) -> SyntaxVisitorContinueKind {+ processNode(node, withName: node.identifier.text, members: node.members.members)+ return .visitChildren+ }++ override func visit(_ node: ProtocolDeclSyntax) -> SyntaxVisitorContinueKind {+ processNode(node, withName: node.identifier.text, members: node.members.members)+ return .visitChildren+ }++ override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind {+ processNode(node, withName: node.identifier.text, members: node.members.members)+ return .visitChildren+ }++ override func visit(_ node: ExtensionDeclSyntax) -> SyntaxVisitorContinueKind {+ if let typeIdentifier = node.extendedType.as(SimpleTypeIdentifierSyntax.self) {+ processNode(node, withName: typeIdentifier.name.text, members: node.members.members)+ }+ return .visitChildren+ }++ // MARK: Private++ private let onNodeVisit: (_ info: TypeWithPropInfo) -> Void++ private func processNode<Node>(_ node: Node, withName name: String, members: MemberDeclListSyntax) where Node: SyntaxProtocol {+ var result = [TypeWithPropInfo.PropInfo]()+ let propertyVisitor = PropertySyntaxVisitor(parentName: name) { info in+ result.append(info)+ }+ propertyVisitor.walk(node)+ onNodeVisit(.init(name: name, properties: result))+ }+}++// MARK: - PropertySyntaxVisitor++private final class PropertySyntaxVisitor: SyntaxVisitor {++ init(parentName: String, onNodeVisit: @escaping (_ info: TypeWithPropInfo.PropInfo) -> Void) {+ self.parentName = parentName+ self.onNodeVisit = onNodeVisit+ }++ override func visit(_ node: VariableDeclSyntax) -> SyntaxVisitorContinueKind {+ var leadingTrivia: Trivia? = node.leadingTrivia+ var access: TypeWithPropInfo.Access = .internal+ var scope: TypeWithPropInfo.Scope = .instance++ node.modifiers?.forEach { modifier in+ // leading trivia is on the first modifier, or the node itself if no modifiers are present+ leadingTrivia = leadingTrivia ?? modifier.leadingTrivia+ // This attempts to get any access information from the node's modifiers+ if let modifierAccess = TypeWithPropInfo.Access(rawValue: modifier.name.text) {+ access = modifierAccess+ }+ // this attempts to get any scope information from the node's modifiers+ if let modifierScope = TypeWithPropInfo.Scope(rawValue: modifier.name.text) {
same here, probably better to look for the static keyword in the tokens of the Decl modifier
comment created time in a month
Pull request review commentfdiaz/SwiftInspector
+// Created by Tyler Hedrick on 8/14/20.+//+// Distributed under the MIT License+//+// Permission is hereby granted, free of charge, to any person obtaining a copy+// of this software and associated documentation files (the "Software"), to deal+// in the Software without restriction, including without limitation the rights+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+// copies of the Software, and to permit persons to whom the Software is+// furnished to do so, subject to the following conditions:+//+// The above copyright notice and this permission notice shall be included in all+// copies or substantial portions of the Software.+//+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+// SOFTWARE.++import Foundation+import SwiftSyntax++// MARK: - TypeLocationAnalyzer++public final class PropertyAnalyzer: Analyzer {++ /// - Parameter typeName: The name of the type to get property information for+ /// - Parameter cachedSyntaxTree: The cached syntax tree to return the AST tree from+ public init(typeName: String, cachedSyntaxTree: CachedSyntaxTree = .init()) {+ self.typeName = typeName+ self.cachedSyntaxTree = cachedSyntaxTree+ }++ /// Finds the location(s) of the specified type name in the Swift file+ /// - Parameter fileURL: The fileURL where the Swift file is located+ public func analyze(fileURL: URL) throws -> TypeWithPropInfo? {+ let syntax: SourceFileSyntax = try cachedSyntaxTree.syntaxTree(for: fileURL)+ var result: TypeWithPropInfo?+ let visitor = TypeSyntaxVisitor() { [unowned self] typeWithPropInfo in+ guard self.typeName == typeWithPropInfo.name else { return }+ result = try? typeWithPropInfo.merge(with: result)+ }+ visitor.walk(syntax)+ return result+ }++ // MARK: Private++ private let cachedSyntaxTree: CachedSyntaxTree+ private let typeName: String+}++// MARK: - TypeLocationSyntaxVisitor++private final class TypeSyntaxVisitor: SyntaxVisitor {++ init(onNodeVisit: @escaping (_ info: TypeWithPropInfo) -> Void) {+ self.onNodeVisit = onNodeVisit+ }++ override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind {+ processNode(node, withName: node.identifier.text, members: node.members.members)+ return .visitChildren+ }++ override func visit(_ node: EnumDeclSyntax) -> SyntaxVisitorContinueKind {+ processNode(node, withName: node.identifier.text, members: node.members.members)+ return .visitChildren+ }++ override func visit(_ node: ProtocolDeclSyntax) -> SyntaxVisitorContinueKind {+ processNode(node, withName: node.identifier.text, members: node.members.members)+ return .visitChildren+ }++ override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind {+ processNode(node, withName: node.identifier.text, members: node.members.members)+ return .visitChildren+ }++ override func visit(_ node: ExtensionDeclSyntax) -> SyntaxVisitorContinueKind {+ if let typeIdentifier = node.extendedType.as(SimpleTypeIdentifierSyntax.self) {
Can you explain why we need this casting?
comment created time in a month
Pull request review commentfdiaz/SwiftInspector
+// Created by Tyler Hedrick on 8/14/20.+//+// Distributed under the MIT License+//+// Permission is hereby granted, free of charge, to any person obtaining a copy+// of this software and associated documentation files (the "Software"), to deal+// in the Software without restriction, including without limitation the rights+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+// copies of the Software, and to permit persons to whom the Software is+// furnished to do so, subject to the following conditions:+//+// The above copyright notice and this permission notice shall be included in all+// copies or substantial portions of the Software.+//+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+// SOFTWARE.++import Foundation+import SwiftSyntax++// MARK: - TypeLocationAnalyzer++public final class PropertyAnalyzer: Analyzer {++ /// - Parameter typeName: The name of the type to get property information for+ /// - Parameter cachedSyntaxTree: The cached syntax tree to return the AST tree from+ public init(typeName: String, cachedSyntaxTree: CachedSyntaxTree = .init()) {+ self.typeName = typeName+ self.cachedSyntaxTree = cachedSyntaxTree+ }++ /// Finds the location(s) of the specified type name in the Swift file+ /// - Parameter fileURL: The fileURL where the Swift file is located+ public func analyze(fileURL: URL) throws -> TypeWithPropInfo? {+ let syntax: SourceFileSyntax = try cachedSyntaxTree.syntaxTree(for: fileURL)+ var result: TypeWithPropInfo?+ let visitor = TypeSyntaxVisitor() { [unowned self] typeWithPropInfo in+ guard self.typeName == typeWithPropInfo.name else { return }+ result = try? typeWithPropInfo.merge(with: result)+ }+ visitor.walk(syntax)+ return result+ }++ // MARK: Private++ private let cachedSyntaxTree: CachedSyntaxTree+ private let typeName: String+}++// MARK: - TypeLocationSyntaxVisitor++private final class TypeSyntaxVisitor: SyntaxVisitor {++ init(onNodeVisit: @escaping (_ info: TypeWithPropInfo) -> Void) {+ self.onNodeVisit = onNodeVisit+ }++ override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind {+ processNode(node, withName: node.identifier.text, members: node.members.members)
Should we validate in here the name instead that after doing all the processing? That will likely improve the performance of this tool (since we won't be processing every type, only the types we care about)
I'mm thinking we should pass the type name to the TypeSyntaxVisitor and if the name is not the one expected on these methods we can do a guard and visit children immediately, and only process the node when the type name is the one we care about.
guard node.identifier.text == typeName else { return .visitChildren }
comment created time in a month
Pull request review commentfdiaz/SwiftInspector
+// Created by Tyler Hedrick on 8/14/20.+//+// Distributed under the MIT License+//+// Permission is hereby granted, free of charge, to any person obtaining a copy+// of this software and associated documentation files (the "Software"), to deal+// in the Software without restriction, including without limitation the rights+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+// copies of the Software, and to permit persons to whom the Software is+// furnished to do so, subject to the following conditions:+//+// The above copyright notice and this permission notice shall be included in all+// copies or substantial portions of the Software.+//+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+// SOFTWARE.++import Foundation+import SwiftSyntax++// MARK: - TypeLocationAnalyzer++public final class PropertyAnalyzer: Analyzer {++ /// - Parameter typeName: The name of the type to get property information for+ /// - Parameter cachedSyntaxTree: The cached syntax tree to return the AST tree from+ public init(typeName: String, cachedSyntaxTree: CachedSyntaxTree = .init()) {+ self.typeName = typeName+ self.cachedSyntaxTree = cachedSyntaxTree+ }++ /// Finds the location(s) of the specified type name in the Swift file+ /// - Parameter fileURL: The fileURL where the Swift file is located+ public func analyze(fileURL: URL) throws -> TypeWithPropInfo? {+ let syntax: SourceFileSyntax = try cachedSyntaxTree.syntaxTree(for: fileURL)+ var result: TypeWithPropInfo?+ let visitor = TypeSyntaxVisitor() { [unowned self] typeWithPropInfo in+ guard self.typeName == typeWithPropInfo.name else { return }+ result = try? typeWithPropInfo.merge(with: result)+ }+ visitor.walk(syntax)+ return result+ }++ // MARK: Private++ private let cachedSyntaxTree: CachedSyntaxTree+ private let typeName: String+}++// MARK: - TypeLocationSyntaxVisitor++private final class TypeSyntaxVisitor: SyntaxVisitor {++ init(onNodeVisit: @escaping (_ info: TypeWithPropInfo) -> Void) {+ self.onNodeVisit = onNodeVisit+ }++ override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind {+ processNode(node, withName: node.identifier.text, members: node.members.members)+ return .visitChildren+ }++ override func visit(_ node: EnumDeclSyntax) -> SyntaxVisitorContinueKind {+ processNode(node, withName: node.identifier.text, members: node.members.members)+ return .visitChildren+ }++ override func visit(_ node: ProtocolDeclSyntax) -> SyntaxVisitorContinueKind {+ processNode(node, withName: node.identifier.text, members: node.members.members)+ return .visitChildren+ }++ override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind {+ processNode(node, withName: node.identifier.text, members: node.members.members)+ return .visitChildren+ }++ override func visit(_ node: ExtensionDeclSyntax) -> SyntaxVisitorContinueKind {+ if let typeIdentifier = node.extendedType.as(SimpleTypeIdentifierSyntax.self) {+ processNode(node, withName: typeIdentifier.name.text, members: node.members.members)+ }+ return .visitChildren+ }++ // MARK: Private++ private let onNodeVisit: (_ info: TypeWithPropInfo) -> Void++ private func processNode<Node>(_ node: Node, withName name: String, members: MemberDeclListSyntax) where Node: SyntaxProtocol {+ var result = [TypeWithPropInfo.PropInfo]()+ let propertyVisitor = PropertySyntaxVisitor(parentName: name) { info in+ result.append(info)+ }+ propertyVisitor.walk(node)+ onNodeVisit(.init(name: name, properties: result))+ }+}++// MARK: - PropertySyntaxVisitor++private final class PropertySyntaxVisitor: SyntaxVisitor {++ init(parentName: String, onNodeVisit: @escaping (_ info: TypeWithPropInfo.PropInfo) -> Void) {+ self.parentName = parentName+ self.onNodeVisit = onNodeVisit+ }++ override func visit(_ node: VariableDeclSyntax) -> SyntaxVisitorContinueKind {+ var leadingTrivia: Trivia? = node.leadingTrivia+ var access: TypeWithPropInfo.Access = .internal+ var scope: TypeWithPropInfo.Scope = .instance++ node.modifiers?.forEach { modifier in+ // leading trivia is on the first modifier, or the node itself if no modifiers are present+ leadingTrivia = leadingTrivia ?? modifier.leadingTrivia+ // This attempts to get any access information from the node's modifiers+ if let modifierAccess = TypeWithPropInfo.Access(rawValue: modifier.name.text) {+ access = modifierAccess+ }+ // this attempts to get any scope information from the node's modifiers+ if let modifierScope = TypeWithPropInfo.Scope(rawValue: modifier.name.text) {+ scope = modifierScope+ }+ }++ // in practice this `bindings` array only has 1 element+ node.bindings.forEach { binding in+ if let identifier = binding.pattern.as(IdentifierPatternSyntax.self) {+ if+ let typeAnnotation = binding.typeAnnotation,+ let simpleTypeIdentifier = typeAnnotation.type.as(SimpleTypeIdentifierSyntax.self)+ {+ onNodeVisit(.init(+ name: identifier.identifier.text,+ typeAnnotation: simpleTypeIdentifier.name.text,+ comment: comment(from: leadingTrivia),+ access: access,+ scope: scope))+ return+ }+ // no type annotation was found. In this case it's much more complicated to get+ // the type information for a variable.+ // TODO: Include logic to get types of any variable declaration+ onNodeVisit(.init(+ name: identifier.identifier.text,+ typeAnnotation: nil,+ comment: comment(from: leadingTrivia),+ access: access,+ scope: scope))+ }+ }++ return .skipChildren+ }++ // MARK: Private++ private let parentName: String+ private let onNodeVisit: (_ info: TypeWithPropInfo.PropInfo) -> Void++ private func comment(from trivia: Trivia?) -> String {+ guard let trivia = trivia else { return "" }+ return trivia.compactMap { piece -> String? in+ switch piece {+ case .lineComment(let str): return str+ case .blockComment(let str): return str+ case .docLineComment(let str): return str+ case .docBlockComment(let str): return str+ default: return nil+ }+ }.joined(separator: "\n")+ }+}++// MARK: - TypeWithPropInfo++/// Information about a located type. Indices start with 0.
I don't think this comment is correct?
comment created time in a month
Pull request review commentfdiaz/SwiftInspector
+// Created by Tyler Hedrick on 8/14/20.+//+// Distributed under the MIT License+//+// Permission is hereby granted, free of charge, to any person obtaining a copy+// of this software and associated documentation files (the "Software"), to deal+// in the Software without restriction, including without limitation the rights+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+// copies of the Software, and to permit persons to whom the Software is+// furnished to do so, subject to the following conditions:+//+// The above copyright notice and this permission notice shall be included in all+// copies or substantial portions of the Software.+//+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+// SOFTWARE.++import Foundation+import SwiftSyntax++// MARK: - TypeLocationAnalyzer++public final class PropertyAnalyzer: Analyzer {++ /// - Parameter typeName: The name of the type to get property information for+ /// - Parameter cachedSyntaxTree: The cached syntax tree to return the AST tree from+ public init(typeName: String, cachedSyntaxTree: CachedSyntaxTree = .init()) {+ self.typeName = typeName+ self.cachedSyntaxTree = cachedSyntaxTree+ }++ /// Finds the location(s) of the specified type name in the Swift file+ /// - Parameter fileURL: The fileURL where the Swift file is located+ public func analyze(fileURL: URL) throws -> TypeWithPropInfo? {+ let syntax: SourceFileSyntax = try cachedSyntaxTree.syntaxTree(for: fileURL)+ var result: TypeWithPropInfo?+ let visitor = TypeSyntaxVisitor() { [unowned self] typeWithPropInfo in+ guard self.typeName == typeWithPropInfo.name else { return }+ result = try? typeWithPropInfo.merge(with: result)+ }+ visitor.walk(syntax)+ return result+ }++ // MARK: Private++ private let cachedSyntaxTree: CachedSyntaxTree+ private let typeName: String+}++// MARK: - TypeLocationSyntaxVisitor++private final class TypeSyntaxVisitor: SyntaxVisitor {++ init(onNodeVisit: @escaping (_ info: TypeWithPropInfo) -> Void) {+ self.onNodeVisit = onNodeVisit+ }++ override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind {+ processNode(node, withName: node.identifier.text, members: node.members.members)+ return .visitChildren+ }++ override func visit(_ node: EnumDeclSyntax) -> SyntaxVisitorContinueKind {+ processNode(node, withName: node.identifier.text, members: node.members.members)+ return .visitChildren+ }++ override func visit(_ node: ProtocolDeclSyntax) -> SyntaxVisitorContinueKind {+ processNode(node, withName: node.identifier.text, members: node.members.members)+ return .visitChildren+ }++ override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind {+ processNode(node, withName: node.identifier.text, members: node.members.members)+ return .visitChildren+ }++ override func visit(_ node: ExtensionDeclSyntax) -> SyntaxVisitorContinueKind {+ if let typeIdentifier = node.extendedType.as(SimpleTypeIdentifierSyntax.self) {+ processNode(node, withName: typeIdentifier.name.text, members: node.members.members)+ }+ return .visitChildren+ }++ // MARK: Private++ private let onNodeVisit: (_ info: TypeWithPropInfo) -> Void++ private func processNode<Node>(_ node: Node, withName name: String, members: MemberDeclListSyntax) where Node: SyntaxProtocol {+ var result = [TypeWithPropInfo.PropInfo]()+ let propertyVisitor = PropertySyntaxVisitor(parentName: name) { info in+ result.append(info)+ }+ propertyVisitor.walk(node)+ onNodeVisit(.init(name: name, properties: result))+ }+}++// MARK: - PropertySyntaxVisitor++private final class PropertySyntaxVisitor: SyntaxVisitor {++ init(parentName: String, onNodeVisit: @escaping (_ info: TypeWithPropInfo.PropInfo) -> Void) {+ self.parentName = parentName+ self.onNodeVisit = onNodeVisit+ }++ override func visit(_ node: VariableDeclSyntax) -> SyntaxVisitorContinueKind {+ var leadingTrivia: Trivia? = node.leadingTrivia+ var access: TypeWithPropInfo.Access = .internal+ var scope: TypeWithPropInfo.Scope = .instance++ node.modifiers?.forEach { modifier in+ // leading trivia is on the first modifier, or the node itself if no modifiers are present+ leadingTrivia = leadingTrivia ?? modifier.leadingTrivia+ // This attempts to get any access information from the node's modifiers+ if let modifierAccess = TypeWithPropInfo.Access(rawValue: modifier.name.text) {+ access = modifierAccess+ }+ // this attempts to get any scope information from the node's modifiers+ if let modifierScope = TypeWithPropInfo.Scope(rawValue: modifier.name.text) {+ scope = modifierScope+ }+ }++ // in practice this `bindings` array only has 1 element+ node.bindings.forEach { binding in+ if let identifier = binding.pattern.as(IdentifierPatternSyntax.self) {+ if+ let typeAnnotation = binding.typeAnnotation,+ let simpleTypeIdentifier = typeAnnotation.type.as(SimpleTypeIdentifierSyntax.self)+ {+ onNodeVisit(.init(+ name: identifier.identifier.text,+ typeAnnotation: simpleTypeIdentifier.name.text,+ comment: comment(from: leadingTrivia),+ access: access,+ scope: scope))+ return+ }+ // no type annotation was found. In this case it's much more complicated to get+ // the type information for a variable.+ // TODO: Include logic to get types of any variable declaration+ onNodeVisit(.init(+ name: identifier.identifier.text,+ typeAnnotation: nil,+ comment: comment(from: leadingTrivia),+ access: access,+ scope: scope))+ }+ }++ return .skipChildren+ }++ // MARK: Private++ private let parentName: String+ private let onNodeVisit: (_ info: TypeWithPropInfo.PropInfo) -> Void++ private func comment(from trivia: Trivia?) -> String {+ guard let trivia = trivia else { return "" }+ return trivia.compactMap { piece -> String? in+ switch piece {+ case .lineComment(let str): return str+ case .blockComment(let str): return str+ case .docLineComment(let str): return str+ case .docBlockComment(let str): return str+ default: return nil+ }+ }.joined(separator: "\n")+ }+}++// MARK: - TypeWithPropInfo++/// Information about a located type. Indices start with 0.+public struct TypeWithPropInfo: Hashable {++ public enum Access: String {+ case `public`+ case `internal`+ case `private`+ case `fileprivate`+ }++ public enum Scope: String {+ case instance+ case `static`+ }++ public struct PropInfo: Hashable {
PropertyInfo https://swift.org/documentation/api-design-guidelines/#promote-clear-usage ?
comment created time in a month
Pull request review commentfdiaz/SwiftInspector
+// Created by Tyler Hedrick on 8/14/20.+//+// Distributed under the MIT License+//+// Permission is hereby granted, free of charge, to any person obtaining a copy+// of this software and associated documentation files (the "Software"), to deal+// in the Software without restriction, including without limitation the rights+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+// copies of the Software, and to permit persons to whom the Software is+// furnished to do so, subject to the following conditions:+//+// The above copyright notice and this permission notice shall be included in all+// copies or substantial portions of the Software.+//+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+// SOFTWARE.++import Foundation+import SwiftSyntax++// MARK: - TypeLocationAnalyzer++public final class PropertyAnalyzer: Analyzer {++ /// - Parameter typeName: The name of the type to get property information for+ /// - Parameter cachedSyntaxTree: The cached syntax tree to return the AST tree from+ public init(typeName: String, cachedSyntaxTree: CachedSyntaxTree = .init()) {+ self.typeName = typeName+ self.cachedSyntaxTree = cachedSyntaxTree+ }++ /// Finds the location(s) of the specified type name in the Swift file+ /// - Parameter fileURL: The fileURL where the Swift file is located+ public func analyze(fileURL: URL) throws -> TypeWithPropInfo? {+ let syntax: SourceFileSyntax = try cachedSyntaxTree.syntaxTree(for: fileURL)+ var result: TypeWithPropInfo?+ let visitor = TypeSyntaxVisitor() { [unowned self] typeWithPropInfo in+ guard self.typeName == typeWithPropInfo.name else { return }+ result = try? typeWithPropInfo.merge(with: result)+ }+ visitor.walk(syntax)+ return result+ }++ // MARK: Private++ private let cachedSyntaxTree: CachedSyntaxTree+ private let typeName: String+}++// MARK: - TypeLocationSyntaxVisitor++private final class TypeSyntaxVisitor: SyntaxVisitor {++ init(onNodeVisit: @escaping (_ info: TypeWithPropInfo) -> Void) {+ self.onNodeVisit = onNodeVisit+ }++ override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind {+ processNode(node, withName: node.identifier.text, members: node.members.members)+ return .visitChildren+ }++ override func visit(_ node: EnumDeclSyntax) -> SyntaxVisitorContinueKind {+ processNode(node, withName: node.identifier.text, members: node.members.members)+ return .visitChildren+ }++ override func visit(_ node: ProtocolDeclSyntax) -> SyntaxVisitorContinueKind {+ processNode(node, withName: node.identifier.text, members: node.members.members)+ return .visitChildren+ }++ override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind {+ processNode(node, withName: node.identifier.text, members: node.members.members)+ return .visitChildren+ }++ override func visit(_ node: ExtensionDeclSyntax) -> SyntaxVisitorContinueKind {+ if let typeIdentifier = node.extendedType.as(SimpleTypeIdentifierSyntax.self) {+ processNode(node, withName: typeIdentifier.name.text, members: node.members.members)+ }+ return .visitChildren+ }++ // MARK: Private++ private let onNodeVisit: (_ info: TypeWithPropInfo) -> Void++ private func processNode<Node>(_ node: Node, withName name: String, members: MemberDeclListSyntax) where Node: SyntaxProtocol {+ var result = [TypeWithPropInfo.PropInfo]()+ let propertyVisitor = PropertySyntaxVisitor(parentName: name) { info in+ result.append(info)+ }+ propertyVisitor.walk(node)+ onNodeVisit(.init(name: name, properties: result))+ }+}++// MARK: - PropertySyntaxVisitor++private final class PropertySyntaxVisitor: SyntaxVisitor {++ init(parentName: String, onNodeVisit: @escaping (_ info: TypeWithPropInfo.PropInfo) -> Void) {+ self.parentName = parentName+ self.onNodeVisit = onNodeVisit+ }++ override func visit(_ node: VariableDeclSyntax) -> SyntaxVisitorContinueKind {+ var leadingTrivia: Trivia? = node.leadingTrivia+ var access: TypeWithPropInfo.Access = .internal+ var scope: TypeWithPropInfo.Scope = .instance++ node.modifiers?.forEach { modifier in+ // leading trivia is on the first modifier, or the node itself if no modifiers are present+ leadingTrivia = leadingTrivia ?? modifier.leadingTrivia+ // This attempts to get any access information from the node's modifiers+ if let modifierAccess = TypeWithPropInfo.Access(rawValue: modifier.name.text) {+ access = modifierAccess+ }+ // this attempts to get any scope information from the node's modifiers+ if let modifierScope = TypeWithPropInfo.Scope(rawValue: modifier.name.text) {+ scope = modifierScope+ }+ }++ // in practice this `bindings` array only has 1 element+ node.bindings.forEach { binding in+ if let identifier = binding.pattern.as(IdentifierPatternSyntax.self) {+ if+ let typeAnnotation = binding.typeAnnotation,+ let simpleTypeIdentifier = typeAnnotation.type.as(SimpleTypeIdentifierSyntax.self)+ {+ onNodeVisit(.init(+ name: identifier.identifier.text,+ typeAnnotation: simpleTypeIdentifier.name.text,+ comment: comment(from: leadingTrivia),+ access: access,+ scope: scope))+ return+ }+ // no type annotation was found. In this case it's much more complicated to get+ // the type information for a variable.+ // TODO: Include logic to get types of any variable declaration+ onNodeVisit(.init(+ name: identifier.identifier.text,+ typeAnnotation: nil,+ comment: comment(from: leadingTrivia),+ access: access,+ scope: scope))+ }+ }++ return .skipChildren+ }++ // MARK: Private++ private let parentName: String+ private let onNodeVisit: (_ info: TypeWithPropInfo.PropInfo) -> Void++ private func comment(from trivia: Trivia?) -> String {+ guard let trivia = trivia else { return "" }+ return trivia.compactMap { piece -> String? in+ switch piece {+ case .lineComment(let str): return str+ case .blockComment(let str): return str+ case .docLineComment(let str): return str+ case .docBlockComment(let str): return str+ default: return nil+ }+ }.joined(separator: "\n")+ }+}++// MARK: - TypeWithPropInfo++/// Information about a located type. Indices start with 0.+public struct TypeWithPropInfo: Hashable {++ public enum Access: String {+ case `public`+ case `internal`+ case `private`+ case `fileprivate`+ }++ public enum Scope: String {+ case instance+ case `static`+ }++ public struct PropInfo: Hashable {+ /// The name of the property+ public let name: String+ /// The Type annotation of the property if it's present+ public let typeAnnotation: String?+ /// Any comments associated with the property+ public let comment: String+ /// Access control of this property+ public let access: Access+ /// Scope of this property+ public let scope: Scope+ }++ /// The name of the type.+ public let name: String+ /// The properites on this type
typo: properties
comment created time in a month
Pull request review commentfdiaz/SwiftInspector
+// Created by Tyler Hedrick on 8/14/20.+//+// Distributed under the MIT License+//+// Permission is hereby granted, free of charge, to any person obtaining a copy+// of this software and associated documentation files (the "Software"), to deal+// in the Software without restriction, including without limitation the rights+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+// copies of the Software, and to permit persons to whom the Software is+// furnished to do so, subject to the following conditions:+//+// The above copyright notice and this permission notice shall be included in all+// copies or substantial portions of the Software.+//+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+// SOFTWARE.++import Foundation+import SwiftSyntax++// MARK: - TypeLocationAnalyzer++public final class PropertyAnalyzer: Analyzer {++ /// - Parameter typeName: The name of the type to get property information for+ /// - Parameter cachedSyntaxTree: The cached syntax tree to return the AST tree from+ public init(typeName: String, cachedSyntaxTree: CachedSyntaxTree = .init()) {+ self.typeName = typeName+ self.cachedSyntaxTree = cachedSyntaxTree+ }++ /// Finds the location(s) of the specified type name in the Swift file+ /// - Parameter fileURL: The fileURL where the Swift file is located+ public func analyze(fileURL: URL) throws -> TypeWithPropInfo? {+ let syntax: SourceFileSyntax = try cachedSyntaxTree.syntaxTree(for: fileURL)+ var result: TypeWithPropInfo?+ let visitor = TypeSyntaxVisitor() { [unowned self] typeWithPropInfo in+ guard self.typeName == typeWithPropInfo.name else { return }+ result = try? typeWithPropInfo.merge(with: result)+ }+ visitor.walk(syntax)+ return result+ }++ // MARK: Private++ private let cachedSyntaxTree: CachedSyntaxTree+ private let typeName: String+}++// MARK: - TypeLocationSyntaxVisitor++private final class TypeSyntaxVisitor: SyntaxVisitor {++ init(onNodeVisit: @escaping (_ info: TypeWithPropInfo) -> Void) {+ self.onNodeVisit = onNodeVisit+ }++ override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind {+ processNode(node, withName: node.identifier.text, members: node.members.members)+ return .visitChildren+ }++ override func visit(_ node: EnumDeclSyntax) -> SyntaxVisitorContinueKind {+ processNode(node, withName: node.identifier.text, members: node.members.members)+ return .visitChildren+ }++ override func visit(_ node: ProtocolDeclSyntax) -> SyntaxVisitorContinueKind {+ processNode(node, withName: node.identifier.text, members: node.members.members)+ return .visitChildren+ }++ override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind {+ processNode(node, withName: node.identifier.text, members: node.members.members)+ return .visitChildren+ }++ override func visit(_ node: ExtensionDeclSyntax) -> SyntaxVisitorContinueKind {+ if let typeIdentifier = node.extendedType.as(SimpleTypeIdentifierSyntax.self) {+ processNode(node, withName: typeIdentifier.name.text, members: node.members.members)+ }+ return .visitChildren+ }++ // MARK: Private++ private let onNodeVisit: (_ info: TypeWithPropInfo) -> Void++ private func processNode<Node>(_ node: Node, withName name: String, members: MemberDeclListSyntax) where Node: SyntaxProtocol {+ var result = [TypeWithPropInfo.PropInfo]()+ let propertyVisitor = PropertySyntaxVisitor(parentName: name) { info in+ result.append(info)+ }+ propertyVisitor.walk(node)+ onNodeVisit(.init(name: name, properties: result))+ }+}++// MARK: - PropertySyntaxVisitor++private final class PropertySyntaxVisitor: SyntaxVisitor {++ init(parentName: String, onNodeVisit: @escaping (_ info: TypeWithPropInfo.PropInfo) -> Void) {+ self.parentName = parentName+ self.onNodeVisit = onNodeVisit+ }++ override func visit(_ node: VariableDeclSyntax) -> SyntaxVisitorContinueKind {+ var leadingTrivia: Trivia? = node.leadingTrivia+ var access: TypeWithPropInfo.Access = .internal+ var scope: TypeWithPropInfo.Scope = .instance++ node.modifiers?.forEach { modifier in+ // leading trivia is on the first modifier, or the node itself if no modifiers are present+ leadingTrivia = leadingTrivia ?? modifier.leadingTrivia+ // This attempts to get any access information from the node's modifiers+ if let modifierAccess = TypeWithPropInfo.Access(rawValue: modifier.name.text) {+ access = modifierAccess+ }+ // this attempts to get any scope information from the node's modifiers+ if let modifierScope = TypeWithPropInfo.Scope(rawValue: modifier.name.text) {+ scope = modifierScope+ }+ }++ // in practice this `bindings` array only has 1 element+ node.bindings.forEach { binding in+ if let identifier = binding.pattern.as(IdentifierPatternSyntax.self) {+ if+ let typeAnnotation = binding.typeAnnotation,+ let simpleTypeIdentifier = typeAnnotation.type.as(SimpleTypeIdentifierSyntax.self)+ {+ onNodeVisit(.init(+ name: identifier.identifier.text,+ typeAnnotation: simpleTypeIdentifier.name.text,+ comment: comment(from: leadingTrivia),+ access: access,+ scope: scope))+ return+ }+ // no type annotation was found. In this case it's much more complicated to get+ // the type information for a variable.+ // TODO: Include logic to get types of any variable declaration+ onNodeVisit(.init(+ name: identifier.identifier.text,+ typeAnnotation: nil,+ comment: comment(from: leadingTrivia),+ access: access,+ scope: scope))+ }+ }++ return .skipChildren+ }++ // MARK: Private++ private let parentName: String+ private let onNodeVisit: (_ info: TypeWithPropInfo.PropInfo) -> Void++ private func comment(from trivia: Trivia?) -> String {+ guard let trivia = trivia else { return "" }+ return trivia.compactMap { piece -> String? in+ switch piece {+ case .lineComment(let str): return str+ case .blockComment(let str): return str+ case .docLineComment(let str): return str+ case .docBlockComment(let str): return str+ default: return nil+ }+ }.joined(separator: "\n")+ }+}++// MARK: - TypeWithPropInfo++/// Information about a located type. Indices start with 0.+public struct TypeWithPropInfo: Hashable {++ public enum Access: String {
nit: Let's name this AccessControl
comment created time in a month
Pull request review commentfdiaz/SwiftInspector
+// Created by Tyler Hedrick on 8/14/20.+//+// Distributed under the MIT License+//+// Permission is hereby granted, free of charge, to any person obtaining a copy+// of this software and associated documentation files (the "Software"), to deal+// in the Software without restriction, including without limitation the rights+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+// copies of the Software, and to permit persons to whom the Software is+// furnished to do so, subject to the following conditions:+//+// The above copyright notice and this permission notice shall be included in all+// copies or substantial portions of the Software.+//+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+// SOFTWARE.++import Foundation+import SwiftSyntax++// MARK: - TypeLocationAnalyzer++public final class PropertyAnalyzer: Analyzer {++ /// - Parameter typeName: The name of the type to get property information for+ /// - Parameter cachedSyntaxTree: The cached syntax tree to return the AST tree from+ public init(typeName: String, cachedSyntaxTree: CachedSyntaxTree = .init()) {+ self.typeName = typeName+ self.cachedSyntaxTree = cachedSyntaxTree+ }++ /// Finds the location(s) of the specified type name in the Swift file
I don't think this is correct?
comment created time in a month
Pull request review commentfdiaz/SwiftInspector
+// Created by Tyler Hedrick on 8/14/20.+//+// Distributed under the MIT License+//+// Permission is hereby granted, free of charge, to any person obtaining a copy+// of this software and associated documentation files (the "Software"), to deal+// in the Software without restriction, including without limitation the rights+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+// copies of the Software, and to permit persons to whom the Software is+// furnished to do so, subject to the following conditions:+//+// The above copyright notice and this permission notice shall be included in all+// copies or substantial portions of the Software.+//+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+// SOFTWARE.++import Foundation+import SwiftSyntax++// MARK: - TypeLocationAnalyzer++public final class PropertyAnalyzer: Analyzer {++ /// - Parameter typeName: The name of the type to get property information for+ /// - Parameter cachedSyntaxTree: The cached syntax tree to return the AST tree from+ public init(typeName: String, cachedSyntaxTree: CachedSyntaxTree = .init()) {+ self.typeName = typeName+ self.cachedSyntaxTree = cachedSyntaxTree+ }++ /// Finds the location(s) of the specified type name in the Swift file+ /// - Parameter fileURL: The fileURL where the Swift file is located+ public func analyze(fileURL: URL) throws -> TypeWithPropInfo? {+ let syntax: SourceFileSyntax = try cachedSyntaxTree.syntaxTree(for: fileURL)+ var result: TypeWithPropInfo?+ let visitor = TypeSyntaxVisitor() { [unowned self] typeWithPropInfo in+ guard self.typeName == typeWithPropInfo.name else { return }+ result = try? typeWithPropInfo.merge(with: result)+ }+ visitor.walk(syntax)+ return result+ }++ // MARK: Private++ private let cachedSyntaxTree: CachedSyntaxTree+ private let typeName: String+}++// MARK: - TypeLocationSyntaxVisitor++private final class TypeSyntaxVisitor: SyntaxVisitor {++ init(onNodeVisit: @escaping (_ info: TypeWithPropInfo) -> Void) {+ self.onNodeVisit = onNodeVisit+ }++ override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind {+ processNode(node, withName: node.identifier.text, members: node.members.members)+ return .visitChildren+ }++ override func visit(_ node: EnumDeclSyntax) -> SyntaxVisitorContinueKind {+ processNode(node, withName: node.identifier.text, members: node.members.members)+ return .visitChildren+ }++ override func visit(_ node: ProtocolDeclSyntax) -> SyntaxVisitorContinueKind {+ processNode(node, withName: node.identifier.text, members: node.members.members)+ return .visitChildren+ }++ override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind {+ processNode(node, withName: node.identifier.text, members: node.members.members)+ return .visitChildren+ }++ override func visit(_ node: ExtensionDeclSyntax) -> SyntaxVisitorContinueKind {+ if let typeIdentifier = node.extendedType.as(SimpleTypeIdentifierSyntax.self) {+ processNode(node, withName: typeIdentifier.name.text, members: node.members.members)+ }+ return .visitChildren+ }++ // MARK: Private++ private let onNodeVisit: (_ info: TypeWithPropInfo) -> Void++ private func processNode<Node>(_ node: Node, withName name: String, members: MemberDeclListSyntax) where Node: SyntaxProtocol {+ var result = [TypeWithPropInfo.PropInfo]()+ let propertyVisitor = PropertySyntaxVisitor(parentName: name) { info in+ result.append(info)+ }+ propertyVisitor.walk(node)+ onNodeVisit(.init(name: name, properties: result))+ }+}++// MARK: - PropertySyntaxVisitor++private final class PropertySyntaxVisitor: SyntaxVisitor {++ init(parentName: String, onNodeVisit: @escaping (_ info: TypeWithPropInfo.PropInfo) -> Void) {+ self.parentName = parentName+ self.onNodeVisit = onNodeVisit+ }++ override func visit(_ node: VariableDeclSyntax) -> SyntaxVisitorContinueKind {+ var leadingTrivia: Trivia? = node.leadingTrivia+ var access: TypeWithPropInfo.Access = .internal+ var scope: TypeWithPropInfo.Scope = .instance++ node.modifiers?.forEach { modifier in+ // leading trivia is on the first modifier, or the node itself if no modifiers are present+ leadingTrivia = leadingTrivia ?? modifier.leadingTrivia+ // This attempts to get any access information from the node's modifiers+ if let modifierAccess = TypeWithPropInfo.Access(rawValue: modifier.name.text) {+ access = modifierAccess+ }+ // this attempts to get any scope information from the node's modifiers+ if let modifierScope = TypeWithPropInfo.Scope(rawValue: modifier.name.text) {+ scope = modifierScope+ }+ }++ // in practice this `bindings` array only has 1 element+ node.bindings.forEach { binding in+ if let identifier = binding.pattern.as(IdentifierPatternSyntax.self) {+ if+ let typeAnnotation = binding.typeAnnotation,+ let simpleTypeIdentifier = typeAnnotation.type.as(SimpleTypeIdentifierSyntax.self)+ {+ onNodeVisit(.init(+ name: identifier.identifier.text,+ typeAnnotation: simpleTypeIdentifier.name.text,+ comment: comment(from: leadingTrivia),+ access: access,+ scope: scope))+ return+ }+ // no type annotation was found. In this case it's much more complicated to get+ // the type information for a variable.+ // TODO: Include logic to get types of any variable declaration+ onNodeVisit(.init(+ name: identifier.identifier.text,+ typeAnnotation: nil,+ comment: comment(from: leadingTrivia),+ access: access,+ scope: scope))+ }+ }++ return .skipChildren+ }++ // MARK: Private++ private let parentName: String+ private let onNodeVisit: (_ info: TypeWithPropInfo.PropInfo) -> Void++ private func comment(from trivia: Trivia?) -> String {+ guard let trivia = trivia else { return "" }+ return trivia.compactMap { piece -> String? in+ switch piece {+ case .lineComment(let str): return str+ case .blockComment(let str): return str+ case .docLineComment(let str): return str+ case .docBlockComment(let str): return str+ default: return nil+ }+ }.joined(separator: "\n")+ }+}++// MARK: - TypeWithPropInfo++/// Information about a located type. Indices start with 0.+public struct TypeWithPropInfo: Hashable {
TypeProperties instead?
comment created time in a month
Pull request review commentfdiaz/SwiftInspector
+// Created by Tyler Hedrick on 8/17/20.+//+// Copyright (c) 2020 Tyler Hedrick+//+// Distributed under the MIT License+//+// Permission is hereby granted, free of charge, to any person obtaining a copy+// of this software and associated documentation files (the "Software"), to deal+// in the Software without restriction, including without limitation the rights+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+// copies of the Software, and to permit persons to whom the Software is+// furnished to do so, subject to the following conditions:+//+// The above copyright notice and this permission notice shall be included in all+// copies or substantial portions of the Software.+//+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+// SOFTWARE.++import Nimble+import Quick+import Foundation++@testable import SwiftInspectorCore++final class PropertyAnalyzerCommandSpec: QuickSpec {+ override func spec() {+ describe("run") {++ context("with no arguments") {+ it("fails") {+ let result = try? TestTask.run(withArguments: ["properties"])+ expect(result?.didFail) == true+ }+ }++ context("when path is invalid") {+ it("fails when empty") {+ let result = try? TestTask.run(withArguments: ["properties", "--path", ""])+ expect(result?.didFail) == true+ }++ it("fails when not provided") {+ let result = try? TestTask.run(withArguments: ["properties", "--path"])+ expect(result?.didFail) == true+ }++ it("fails when it doesn't exist") {+ let result = try? TestTask.run(withArguments: ["properties", "--path", "/fake/path"])+ expect(result?.didFail) == true+ }++ it("fails when name is present but the file is invalid") {+ let result = try? TestTask.run(withArguments: ["properties", "--path", "/fake/path", "--name", "SomeType"])+ expect(result?.didFail) == true+ }+ }++ context("when name is passed") {+ var fileURL: URL!++ beforeEach {+ fileURL = try? Temporary.makeFile(content: """+ final class Some {+ public var thing: String = "Hello, World"+ }+ """)+ }++ afterEach {+ try? Temporary.removeItem(at: fileURL)+ }++ it("fails when name is empty") {+ let result = try? TestTask.run(withArguments: ["properties", "--path", fileURL.path, "--name", ""])+ expect(result?.didFail) == true+ }++ it("fails when name is not provided") {+ let result = try? TestTask.run(withArguments: ["properties", "--path", fileURL.path, "--name"])+ expect(result?.didFail) == true+ }++ it("fails when path is empty") {+ let result = try? TestTask.run(withArguments: ["properties", "--name", "Some"])+ expect(result?.didFail) == true+ }++ it("succeeds") {+ let result = try? TestTask.run(withArguments: ["properties", "--path", fileURL.path, "--name", "Some"])+ expect(result?.didSucceed) == true+ }++ it("returns the type name with property information including the name, access, and scope") {+ let result = try? TestTask.run(withArguments: ["properties", "--path", fileURL.path, "--name", "Some"])+ expect(result?.outputMessage).to(contain("Some:(thing|public|instance)"))
I'd suggest outputting something like:
Some thing public instance instead. This is a lot easier to parse as output and to provide as arguments for another tool.
I really need to come up with better documentation for these things.
comment created time in a month
push eventfdiaz/SwiftInspector
commit sha a173d02d773af433cc9e36c5797f58a5f7735445
Add types analyzer (#41) * Add types analyzer for a file * tests * add more tests for TypesCommand * address comments and support directories * remove comments from Command * Update Sources/SwiftInspectorCommands/Tests/TypesCommandSpec.swift Co-authored-by: Francisco Diaz <[email protected]> Co-authored-by: Francisco Diaz <[email protected]>
push time in a month
PR merged fdiaz/SwiftInspector
This adds an analyzer and a command for finding type information given a file. Here's what the help output looks like:
OVERVIEW: Finds information about types in a file
USAGE: SwiftInspector types --path <path>
OPTIONS:
--path <path> The absolute path to the file to inspect
-h, --help Show help information.
This will return data in the following format:
<Type Name>,<Type>
where the following code:
import Foundation
// Foo does foo things
public struct Foo {
}
would output
Foo,struct
pr closed time in a month
Pull request review commentfdiaz/SwiftInspector
+// Created by Tyler Hedrick on 8/13/20.+//+// Copyright (c) 2020 Tyler Hedrick+//+// Distributed under the MIT License+//+// Permission is hereby granted, free of charge, to any person obtaining a copy+// of this software and associated documentation files (the "Software"), to deal+// in the Software without restriction, including without limitation the rights+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+// copies of the Software, and to permit persons to whom the Software is+// furnished to do so, subject to the following conditions:+//+// The above copyright notice and this permission notice shall be included in all+// copies or substantial portions of the Software.+//+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+// SOFTWARE.++import Nimble+import Quick+import Foundation++@testable import SwiftInspectorCore++final class TypesCommandSpec: QuickSpec {+ override func spec() {+ var fileURL: URL!++ afterEach {+ guard let fileURL = fileURL else {+ return+ }+ try? Temporary.removeItem(at: fileURL)+ }+++ describe("run") {+ context("with no arguments") {+ it("fails") {+ let result = try? TestTask.run(withArguments: ["types"])+ expect(result?.didFail) == true+ }+ }++ context("when path is invalid") {+ it("fails when empty") {+ let result = try? TestTypesCommandTask.run(path: "")+ expect(result?.didFail) == true+ }++ it("fails when it doesn't exist") {+ let result = try? TestTypesCommandTask.run(path: "/fake/path")+ expect(result?.didFail) == true+ }+ }++ context("when path is valid") {+ it("succeeds") {+ fileURL = try? Temporary.makeFile(content: "struct Foo { }")+ let path = fileURL?.path ?? ""+ let result = try? TestTypesCommandTask.run(path: path)+ expect(result?.didSucceed) == true+ }++ it("runs and outputs the result without comment") {+ fileURL = try? Temporary.makeFile(content: """+ // This is a comment+ struct Foo { }+ """)+ let path = fileURL?.path ?? ""+ let result = try? TestTypesCommandTask.run(path: path)+ expect(result?.outputMessage).to(contain("Foo,struct"))
No worries, this was mostly a nit so let's keep it as is 😉
comment created time in a month
Pull request review commentfdiaz/SwiftInspector
+// Created by Tyler Hedrick on 8/12/20.+//+// Copyright (c) 2020 Tyler Hedrick+//+// Distributed under the MIT License+//+// Permission is hereby granted, free of charge, to any person obtaining a copy+// of this software and associated documentation files (the "Software"), to deal+// in the Software without restriction, including without limitation the rights+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+// copies of the Software, and to permit persons to whom the Software is+// furnished to do so, subject to the following conditions:+//+// The above copyright notice and this permission notice shall be included in all+// copies or substantial portions of the Software.+//+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+// SOFTWARE.+++import Foundation+import SwiftSyntax++public final class TypesAnalyzer: Analyzer {++ /// - Parameter cachedSyntaxTree: The cached syntax tree to return the AST tree from+ public init(cachedSyntaxTree: CachedSyntaxTree = .init()) {+ self.cachedSyntaxTree = cachedSyntaxTree+ }++ /// Analyzes the types located in the provided file+ /// - Parameter fileURL: The fileURL where the Swift file is located+ public func analyze(fileURL: URL) throws -> [TypeInfo] {+ let syntax: SourceFileSyntax = try cachedSyntaxTree.syntaxTree(for: fileURL)+ var result = [TypeInfo]()+ let visitor = TypeInfoSyntaxVisitor() { typeInfo in
Oh duh! 🤦
comment created time in a month
Pull request review commentfdiaz/SwiftInspector
+// Created by Tyler Hedrick on 8/13/20.+//+// Copyright (c) 2020 Tyler Hedrick+//+// Distributed under the MIT License+//+// Permission is hereby granted, free of charge, to any person obtaining a copy+// of this software and associated documentation files (the "Software"), to deal+// in the Software without restriction, including without limitation the rights+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+// copies of the Software, and to permit persons to whom the Software is+// furnished to do so, subject to the following conditions:+//+// The above copyright notice and this permission notice shall be included in all+// copies or substantial portions of the Software.+//+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+// SOFTWARE.++import Nimble+import Quick+import Foundation++@testable import SwiftInspectorCore++final class TypesCommandSpec: QuickSpec {+ override func spec() {+ var fileURL: URL!++ afterEach {+ guard let fileURL = fileURL else {+ return+ }+ try? Temporary.removeItem(at: fileURL)+ }+++ describe("run") {+ context("with no arguments") {+ it("fails") {+ let result = try? TestTask.run(withArguments: ["types"])+ expect(result?.didFail) == true+ }+ }++ context("when path is invalid") {+ it("fails when empty") {+ let result = try? TestTypesCommandTask.run(path: "")+ expect(result?.didFail) == true+ }++ it("fails when it doesn't exist") {+ let result = try? TestTypesCommandTask.run(path: "/fake/path")+ expect(result?.didFail) == true+ }+ }++ context("when path is valid") {
context("when path is valid file") {
comment created time in a month
Pull request review commentfdiaz/SwiftInspector
+// Created by Tyler Hedrick on 8/13/20.+//+// Copyright (c) 2020 Tyler Hedrick+//+// Distributed under the MIT License+//+// Permission is hereby granted, free of charge, to any person obtaining a copy+// of this software and associated documentation files (the "Software"), to deal+// in the Software without restriction, including without limitation the rights+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+// copies of the Software, and to permit persons to whom the Software is+// furnished to do so, subject to the following conditions:+//+// The above copyright notice and this permission notice shall be included in all+// copies or substantial portions of the Software.+//+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+// SOFTWARE.++import Nimble+import Quick+import Foundation++@testable import SwiftInspectorCore++final class TypesCommandSpec: QuickSpec {+ override func spec() {+ var fileURL: URL!++ afterEach {+ guard let fileURL = fileURL else {+ return+ }+ try? Temporary.removeItem(at: fileURL)+ }+++ describe("run") {+ context("with no arguments") {+ it("fails") {+ let result = try? TestTask.run(withArguments: ["types"])+ expect(result?.didFail) == true+ }+ }++ context("when path is invalid") {+ it("fails when empty") {+ let result = try? TestTypesCommandTask.run(path: "")+ expect(result?.didFail) == true+ }++ it("fails when it doesn't exist") {+ let result = try? TestTypesCommandTask.run(path: "/fake/path")+ expect(result?.didFail) == true+ }+ }++ context("when path is valid") {+ it("succeeds") {+ fileURL = try? Temporary.makeFile(content: "struct Foo { }")+ let path = fileURL?.path ?? ""+ let result = try? TestTypesCommandTask.run(path: path)+ expect(result?.didSucceed) == true+ }++ it("runs and outputs the result without comment") {+ fileURL = try? Temporary.makeFile(content: """+ // This is a comment+ struct Foo { }+ """)+ let path = fileURL?.path ?? ""+ let result = try? TestTypesCommandTask.run(path: path)+ expect(result?.outputMessage).to(contain("Foo,struct"))
We can refactor these 2 tests to put the common file in a beforeEach so this looks like:
var result: TaskStatus!
beforeEach {
fileURL = try? Temporary.makeFile(content: "struct Foo { }")
let path = fileURL?.path ?? ""
result = try? TestTypesCommandTask.run(path: path)
}
it("succeeds") {
expect(result?.didSucceed) == true
}
it("outputs the type name and Type") {
expect(result?.outputMessage).to(contain("Foo,struct"))
}
comment created time in a month
Pull request review commentfdiaz/SwiftInspector
final class TypesCommand: ParsableCommand { return "\(info.name),\(info.type),\(comment)" } }++private var commentFlagHelp = ArgumentHelp(+ "The granularity of the output",
"Whether to include any preceding comments on top of the type",
comment created time in a month
Pull request review commentfdiaz/SwiftInspector
final class TypesCommandSpec: QuickSpec { struct Foo { } """) let path = fileURL?.path ?? ""- let result = try? TestTask.run(withArguments: ["types", "--path", path])- expect(result!.outputMessage).to(contain("Foo,struct,// This is a comment"))+ let result = try? TestTypesCommandTask.run(path: path, arguments: ["--enable-include-comments"])+ expect(result?.outputMessage).to(contain("Foo,struct,// This is a comment"))+ }+ }++ context("when path is a folder") {+ var folderURL: URL!+ beforeEach {+ folderURL = try! Temporary.makeFolder()+ }+ afterEach {+ try! Temporary.removeItem(at: folderURL)+ }++ it("succeeds") {+ let result = try? TestTypesCommandTask.run(path: folderURL.path)++ expect(result?.didSucceed) == true+ }++ it("outputs the correct types") {+ let _ = try? Temporary.makeFile(+ content: """+ class Foo { }+ """,+ atPath: folderURL.path)++ let _ = try? Temporary.makeFile(+ content: """+ class Bar { }+ """,+ atPath: folderURL.path)++ let result = try? TestTypesCommandTask.run(path: folderURL.path)+ let outputMessageLines = result?.outputMessage?.split { $0.isNewline }+ expect(outputMessageLines).to(contain(["Foo,class", "Bar,class"])) } } } } }++private struct TestTypesCommandTask {
👍
comment created time in a month
Pull request review commentfdiaz/SwiftInspector
+// Created by Tyler Hedrick on 8/13/20.+//+// Copyright (c) 2020 Tyler Hedrick+//+// Distributed under the MIT License+//+// Permission is hereby granted, free of charge, to any person obtaining a copy+// of this software and associated documentation files (the "Software"), to deal+// in the Software without restriction, including without limitation the rights+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+// copies of the Software, and to permit persons to whom the Software is+// furnished to do so, subject to the following conditions:+//+// The above copyright notice and this permission notice shall be included in all+// copies or substantial portions of the Software.+//+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+// SOFTWARE.++import Nimble+import Quick+import Foundation++@testable import SwiftInspectorCore++final class TypesCommandSpec: QuickSpec {+ override func spec() {+ var fileURL: URL!++ afterEach {+ guard let fileURL = fileURL else {+ return+ }+ try? Temporary.removeItem(at: fileURL)+ }+++ describe("run") {+ context("with no arguments") {+ it("fails") {+ let result = try? TestTask.run(withArguments: ["types"])+ expect(result?.didFail) == true+ }+ }++ context("when path is invalid") {+ it("fails when empty") {+ let result = try? TestTask.run(withArguments: ["types", "--path", ""])+ expect(result?.didFail) == true+ }++ it("fails when it doesn't exist") {+ let result = try? TestTask.run(withArguments: ["types", "--path", "/fake/path"])+ expect(result?.didFail) == true+ }+ }++ context("when path is valid") {+ it("runs and outputs the result (without comment)") {+ fileURL = try? Temporary.makeFile(content: "struct Foo { }")+ let path = fileURL?.path ?? ""+ let result = try? TestTask.run(withArguments: ["types", "--path", path])+ expect(result!.outputMessage).to(contain("Foo,struct"))+ }++ it("runs and outputs the result (with comment)") {+ fileURL = try? Temporary.makeFile(content: """+ // This is a comment+ struct Foo { }+ """)+ let path = fileURL?.path ?? ""+ let result = try? TestTask.run(withArguments: ["types", "--path", path])+ expect(result!.outputMessage).to(contain("Foo,struct,// This is a comment"))
I'm curious about why we're adding comments too to the output? Curious about the use case.
comment created time in a month
Pull request review commentfdiaz/SwiftInspector
+// Created by Tyler Hedrick on 8/12/20.+//+// Copyright (c) 2020 Tyler Hedrick+//+// Distributed under the MIT License+//+// Permission is hereby granted, free of charge, to any person obtaining a copy+// of this software and associated documentation files (the "Software"), to deal+// in the Software without restriction, including without limitation the rights+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+// copies of the Software, and to permit persons to whom the Software is+// furnished to do so, subject to the following conditions:+//+// The above copyright notice and this permission notice shall be included in all+// copies or substantial portions of the Software.+//+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+// SOFTWARE.+++import Foundation+import SwiftSyntax++public final class TypesAnalyzer: Analyzer {++ /// - Parameter cachedSyntaxTree: The cached syntax tree to return the AST tree from+ public init(cachedSyntaxTree: CachedSyntaxTree = .init()) {+ self.cachedSyntaxTree = cachedSyntaxTree+ }++ /// Analyzes the types located in the provided file+ /// - Parameter fileURL: The fileURL where the Swift file is located+ public func analyze(fileURL: URL) throws -> [TypeInfo] {+ let syntax: SourceFileSyntax = try cachedSyntaxTree.syntaxTree(for: fileURL)+ var result = [TypeInfo]()+ let visitor = TypeInfoSyntaxVisitor() { typeInfo in
let visitor = TypeInfoSyntaxVisitor() { [unowned self] typeInfo in
comment created time in a month
Pull request review commentfdiaz/SwiftInspector
+// Created by Tyler Hedrick 8/13/20.+//+// Distributed under the MIT License+//+// Permission is hereby granted, free of charge, to any person obtaining a copy+// of this software and associated documentation files (the "Software"), to deal+// in the Software without restriction, including without limitation the rights+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+// copies of the Software, and to permit persons to whom the Software is+// furnished to do so, subject to the following conditions:+//+// The above copyright notice and this permission notice shall be included in all+// copies or substantial portions of the Software.+//+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+// SOFTWARE.++import Foundation+import Nimble+import Quick++@testable import SwiftInspectorCore++final class TypesAnalyzerSpec: QuickSpec {++ override func spec() {+ var fileURL: URL!+ var sut: TypesAnalyzer!++ beforeEach {+ sut = TypesAnalyzer()+ }++ afterEach {+ guard let fileURL = fileURL else {+ return+ }+ try? Temporary.removeItem(at: fileURL)+ }++ describe("analyze(fileURL:)") {+ context("there are no types present") {+ beforeEach {+ let content =+ """+ import Foundation+ """+ fileURL = try? Temporary.makeFile(content: content)+ }++ it("returns empty array") {+ let result = try? sut.analyze(fileURL: fileURL)+ expect(result).to(beEmpty())+ }+ }++ context("struct is present") {+ beforeEach {+ let content =+ """+ struct Foo { }+ """+ fileURL = try? Temporary.makeFile(content: content)+ }++ it("returns the type information for the struct") {+ let result = try? sut.analyze(fileURL: fileURL)+ expect(result).notTo(beEmpty())+ expect(result!.first!.name) == "Foo"+ expect(result!.first!.type) == .struct+ }+ }++ context("enum is present") {+ beforeEach {+ let content =+ """+ enum Foo { }+ """+ fileURL = try? Temporary.makeFile(content: content)+ }++ it("returns the type information for the enum") {+ let result = try? sut.analyze(fileURL: fileURL)+ expect(result).notTo(beEmpty())+ expect(result!.first!.name) == "Foo"+ expect(result!.first!.type) == .enum+ }+ }++ context("class is present") {+ beforeEach {+ let content =+ """+ class Foo { }+ """+ fileURL = try? Temporary.makeFile(content: content)+ }++ it("returns the type information for the class") {+ let result = try? sut.analyze(fileURL: fileURL)+ expect(result).notTo(beEmpty())+ expect(result!.first!.name) == "Foo"+ expect(result!.first!.type) == .class+ }+ }++ context("protocol is present") {+ beforeEach {+ let content =+ """+ protocol Foo { }+ """+ fileURL = try? Temporary.makeFile(content: content)+ }++ it("returns the type information for the protocol") {+ let result = try? sut.analyze(fileURL: fileURL)+ expect(result).notTo(beEmpty())+ expect(result!.first!.name) == "Foo"+ expect(result!.first!.type) == .protocol+ }+ }++ context("struct is present with comment") {+ beforeEach {+ let content =+ """+ // This is a comment+ struct Foo { }+ """+ fileURL = try? Temporary.makeFile(content: content)+ }++ it("returns the type information for the struct including the comment") {+ let result = try? sut.analyze(fileURL: fileURL)+ expect(result).notTo(beEmpty())+ expect(result!.first!.name) == "Foo"+ expect(result!.first!.type) == .struct+ expect(result!.first!.comment) == "// This is a comment"+ }+ }++ context("enum is present with comment") {+ beforeEach {+ let content =+ """+ // This is a comment+ enum Foo { }+ """+ fileURL = try? Temporary.makeFile(content: content)+ }++ it("returns the type information for the enum including the comment") {+ let result = try? sut.analyze(fileURL: fileURL)+ expect(result).notTo(beEmpty())+ expect(result!.first!.name) == "Foo"+ expect(result!.first!.type) == .enum+ expect(result!.first!.comment) == "// This is a comment"+ }+ }++ context("class is present with comment") {+ beforeEach {+ let content =+ """+ // This is a comment+ class Foo { }+ """+ fileURL = try? Temporary.makeFile(content: content)+ }++ it("returns the type information for the class including the comment") {+ let result = try? sut.analyze(fileURL: fileURL)+ expect(result).notTo(beEmpty())+ expect(result!.first!.name) == "Foo"+ expect(result!.first!.type) == .class+ expect(result!.first!.comment) == "// This is a comment"+ }+ }++ context("protocol is present with comment") {+ beforeEach {+ let content =+ """+ // This is a comment+ protocol Foo { }+ """+ fileURL = try? Temporary.makeFile(content: content)+ }++ it("returns the type information for the protocol including the comment") {+ let result = try? sut.analyze(fileURL: fileURL)+ expect(result).notTo(beEmpty())+ expect(result!.first!.name) == "Foo"+ expect(result!.first!.type) == .protocol+ expect(result!.first!.comment) == "// This is a comment"+ }+ }++ context("multiple types present") {+ beforeEach {+ let content =+ """+ // This is a comment+ protocol Foo { }++ // This is a different comment+ private final class Bar: Foo { }+ """+ fileURL = try? Temporary.makeFile(content: content)+ }++ it("returns the type information for both types including the comments") {+ let result = try? sut.analyze(fileURL: fileURL)+ expect(result).notTo(beEmpty())+ expect(result!.first!.name) == "Foo"+ expect(result!.first!.type) == .protocol+ expect(result!.first!.comment) == "// This is a comment"++ expect(result![1].name) == "Bar"+ expect(result![1].type) == .class+ expect(result![1].comment) == "// This is a different comment"+ }+ }
Let's remove the force unwrapping from these tests, we can just get the same behavior (without crashing) by using optional chaining instead.
comment created time in a month
Pull request review commentfdiaz/SwiftInspector
+// Created by Tyler Hedrick 8/13/20.+//+// Distributed under the MIT License+//+// Permission is hereby granted, free of charge, to any person obtaining a copy+// of this software and associated documentation files (the "Software"), to deal+// in the Software without restriction, including without limitation the rights+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+// copies of the Software, and to permit persons to whom the Software is+// furnished to do so, subject to the following conditions:+//+// The above copyright notice and this permission notice shall be included in all+// copies or substantial portions of the Software.+//+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+// SOFTWARE.++import Foundation+import Nimble+import Quick++@testable import SwiftInspectorCore++final class TypesAnalyzerSpec: QuickSpec {++ override func spec() {+ var fileURL: URL!+ var sut: TypesAnalyzer!++ beforeEach {+ sut = TypesAnalyzer()+ }++ afterEach {+ guard let fileURL = fileURL else {+ return+ }+ try? Temporary.removeItem(at: fileURL)+ }++ describe("analyze(fileURL:)") {+ context("there are no types present") {+ beforeEach {+ let content =+ """+ import Foundation+ """+ fileURL = try? Temporary.makeFile(content: content)+ }++ it("returns empty array") {+ let result = try? sut.analyze(fileURL: fileURL)+ expect(result).to(beEmpty())+ }+ }++ context("struct is present") {+ beforeEach {+ let content =+ """+ struct Foo { }+ """+ fileURL = try? Temporary.makeFile(content: content)+ }++ it("returns the type information for the struct") {+ let result = try? sut.analyze(fileURL: fileURL)+ expect(result).notTo(beEmpty())+ expect(result!.first!.name) == "Foo"+ expect(result!.first!.type) == .struct+ }+ }++ context("enum is present") {+ beforeEach {+ let content =+ """+ enum Foo { }+ """+ fileURL = try? Temporary.makeFile(content: content)+ }++ it("returns the type information for the enum") {+ let result = try? sut.analyze(fileURL: fileURL)+ expect(result).notTo(beEmpty())+ expect(result!.first!.name) == "Foo"+ expect(result!.first!.type) == .enum+ }+ }++ context("class is present") {+ beforeEach {+ let content =+ """+ class Foo { }+ """+ fileURL = try? Temporary.makeFile(content: content)+ }++ it("returns the type information for the class") {+ let result = try? sut.analyze(fileURL: fileURL)+ expect(result).notTo(beEmpty())+ expect(result!.first!.name) == "Foo"+ expect(result!.first!.type) == .class+ }
One of the cool things about Quick is that these no longer need to be one test. You can split them extremely easily into one assertion per test
context("class is present") {
var result: TypeInfo?
beforeEach {
let content =
"""
class Foo { }
"""
fileURL = try? Temporary.makeFile(content: content)
result = try? sut.analyze(fileURL: fileURL)
}
it("returns the type name") {
expect(result?.first?.name) == "Foo"
}
it("returns the type's Type") {
expect(result?.first?.type) == .class
}
it("returns the type comments as empty") {
expect(result?.first?.comment?.isEmpty) == true
}
comment created time in a month
Pull request review commentfdiaz/SwiftInspector
+// Created by Tyler Hedrick 8/13/20.+//+// Distributed under the MIT License+//+// Permission is hereby granted, free of charge, to any person obtaining a copy+// of this software and associated documentation files (the "Software"), to deal+// in the Software without restriction, including without limitation the rights+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+// copies of the Software, and to permit persons to whom the Software is+// furnished to do so, subject to the following conditions:+//+// The above copyright notice and this permission notice shall be included in all+// copies or substantial portions of the Software.+//+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+// SOFTWARE.++import Foundation+import Nimble+import Quick++@testable import SwiftInspectorCore++final class TypesAnalyzerSpec: QuickSpec {++ override func spec() {+ var fileURL: URL!+ var sut: TypesAnalyzer!++ beforeEach {+ sut = TypesAnalyzer()+ }++ afterEach {+ guard let fileURL = fileURL else {+ return+ }+ try? Temporary.removeItem(at: fileURL)+ }++ describe("analyze(fileURL:)") {+ context("there are no types present") {+ beforeEach {+ let content =+ """+ import Foundation+ """+ fileURL = try? Temporary.makeFile(content: content)+ }++ it("returns empty array") {+ let result = try? sut.analyze(fileURL: fileURL)+ expect(result).to(beEmpty())+ }+ }++ context("struct is present") {+ beforeEach {+ let content =+ """+ struct Foo { }+ """+ fileURL = try? Temporary.makeFile(content: content)+ }++ it("returns the type information for the struct") {+ let result = try? sut.analyze(fileURL: fileURL)+ expect(result).notTo(beEmpty())+ expect(result!.first!.name) == "Foo"+ expect(result!.first!.type) == .struct+ }+ }++ context("enum is present") {+ beforeEach {+ let content =+ """+ enum Foo { }+ """+ fileURL = try? Temporary.makeFile(content: content)+ }++ it("returns the type information for the enum") {+ let result = try? sut.analyze(fileURL: fileURL)+ expect(result).notTo(beEmpty())+ expect(result!.first!.name) == "Foo"+ expect(result!.first!.type) == .enum+ }+ }++ context("class is present") {+ beforeEach {+ let content =+ """+ class Foo { }+ """+ fileURL = try? Temporary.makeFile(content: content)+ }++ it("returns the type information for the class") {+ let result = try? sut.analyze(fileURL: fileURL)+ expect(result).notTo(beEmpty())+ expect(result!.first!.name) == "Foo"+ expect(result!.first!.type) == .class+ }+ }++ context("protocol is present") {+ beforeEach {+ let content =+ """+ protocol Foo { }+ """+ fileURL = try? Temporary.makeFile(content: content)+ }++ it("returns the type information for the protocol") {+ let result = try? sut.analyze(fileURL: fileURL)+ expect(result).notTo(beEmpty())+ expect(result!.first!.name) == "Foo"+ expect(result!.first!.type) == .protocol+ }+ }++ context("struct is present with comment") {+ beforeEach {+ let content =+ """+ // This is a comment+ struct Foo { }+ """+ fileURL = try? Temporary.makeFile(content: content)+ }++ it("returns the type information for the struct including the comment") {+ let result = try? sut.analyze(fileURL: fileURL)+ expect(result).notTo(beEmpty())+ expect(result!.first!.name) == "Foo"+ expect(result!.first!.type) == .struct+ expect(result!.first!.comment) == "// This is a comment"+ }+ }++ context("enum is present with comment") {+ beforeEach {+ let content =+ """+ // This is a comment+ enum Foo { }+ """+ fileURL = try? Temporary.makeFile(content: content)+ }++ it("returns the type information for the enum including the comment") {+ let result = try? sut.analyze(fileURL: fileURL)+ expect(result).notTo(beEmpty())+ expect(result!.first!.name) == "Foo"+ expect(result!.first!.type) == .enum+ expect(result!.first!.comment) == "// This is a comment"+ }+ }++ context("class is present with comment") {+ beforeEach {+ let content =+ """+ // This is a comment+ class Foo { }+ """+ fileURL = try? Temporary.makeFile(content: content)+ }++ it("returns the type information for the class including the comment") {+ let result = try? sut.analyze(fileURL: fileURL)+ expect(result).notTo(beEmpty())+ expect(result!.first!.name) == "Foo"+ expect(result!.first!.type) == .class+ expect(result!.first!.comment) == "// This is a comment"+ }+ }++ context("protocol is present with comment") {+ beforeEach {+ let content =+ """+ // This is a comment
Should we validate other types of comments? Like /// and /**
comment created time in a month
Pull request review commentfdiaz/SwiftInspector
+// Created by Tyler Hedrick 8/13/20.+//+// Distributed under the MIT License+//+// Permission is hereby granted, free of charge, to any person obtaining a copy+// of this software and associated documentation files (the "Software"), to deal+// in the Software without restriction, including without limitation the rights+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+// copies of the Software, and to permit persons to whom the Software is+// furnished to do so, subject to the following conditions:+//+// The above copyright notice and this permission notice shall be included in all+// copies or substantial portions of the Software.+//+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+// SOFTWARE.++import Foundation+import Nimble+import Quick++@testable import SwiftInspectorCore++final class TypesAnalyzerSpec: QuickSpec {++ override func spec() {+ var fileURL: URL!+ var sut: TypesAnalyzer!++ beforeEach {+ sut = TypesAnalyzer()+ }++ afterEach {+ guard let fileURL = fileURL else {+ return+ }+ try? Temporary.removeItem(at: fileURL)+ }++ describe("analyze(fileURL:)") {+ context("there are no types present") {+ beforeEach {+ let content =+ """+ import Foundation+ """+ fileURL = try? Temporary.makeFile(content: content)+ }++ it("returns empty array") {+ let result = try? sut.analyze(fileURL: fileURL)+ expect(result).to(beEmpty())+ }+ }++ context("struct is present") {+ beforeEach {+ let content =+ """+ struct Foo { }+ """+ fileURL = try? Temporary.makeFile(content: content)+ }++ it("returns the type information for the struct") {+ let result = try? sut.analyze(fileURL: fileURL)+ expect(result).notTo(beEmpty())+ expect(result!.first!.name) == "Foo"+ expect(result!.first!.type) == .struct
expect(result?.first?.type) == .struct
comment created time in a month
Pull request review commentfdiaz/SwiftInspector
+// Created by Tyler Hedrick 8/13/20.+//+// Distributed under the MIT License+//+// Permission is hereby granted, free of charge, to any person obtaining a copy+// of this software and associated documentation files (the "Software"), to deal+// in the Software without restriction, including without limitation the rights+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+// copies of the Software, and to permit persons to whom the Software is+// furnished to do so, subject to the following conditions:+//+// The above copyright notice and this permission notice shall be included in all+// copies or substantial portions of the Software.+//+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+// SOFTWARE.++import Foundation+import Nimble+import Quick++@testable import SwiftInspectorCore++final class TypesAnalyzerSpec: QuickSpec {++ override func spec() {+ var fileURL: URL!+ var sut: TypesAnalyzer!++ beforeEach {+ sut = TypesAnalyzer()+ }++ afterEach {+ guard let fileURL = fileURL else {+ return+ }+ try? Temporary.removeItem(at: fileURL)+ }++ describe("analyze(fileURL:)") {+ context("there are no types present") {+ beforeEach {+ let content =+ """+ import Foundation+ """+ fileURL = try? Temporary.makeFile(content: content)+ }++ it("returns empty array") {+ let result = try? sut.analyze(fileURL: fileURL)+ expect(result).to(beEmpty())+ }+ }++ context("struct is present") {+ beforeEach {+ let content =+ """+ struct Foo { }+ """+ fileURL = try? Temporary.makeFile(content: content)+ }++ it("returns the type information for the struct") {+ let result = try? sut.analyze(fileURL: fileURL)+ expect(result).notTo(beEmpty())+ expect(result!.first!.name) == "Foo"+ expect(result!.first!.type) == .struct+ }+ }++ context("enum is present") {+ beforeEach {+ let content =+ """+ enum Foo { }+ """+ fileURL = try? Temporary.makeFile(content: content)+ }++ it("returns the type information for the enum") {+ let result = try? sut.analyze(fileURL: fileURL)+ expect(result).notTo(beEmpty())+ expect(result!.first!.name) == "Foo"+ expect(result!.first!.type) == .enum
expect(result?.first?.type) == .enum
comment created time in a month
Pull request review commentfdiaz/SwiftInspector
+// Created by Tyler Hedrick on 8/13/20.+//+// Copyright (c) 2020 Tyler Hedrick+//+// Distributed under the MIT License+//+// Permission is hereby granted, free of charge, to any person obtaining a copy+// of this software and associated documentation files (the "Software"), to deal+// in the Software without restriction, including without limitation the rights+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+// copies of the Software, and to permit persons to whom the Software is+// furnished to do so, subject to the following conditions:+//+// The above copyright notice and this permission notice shall be included in all+// copies or substantial portions of the Software.+//+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+// SOFTWARE.++import Nimble+import Quick+import Foundation++@testable import SwiftInspectorCore++final class TypesCommandSpec: QuickSpec {+ override func spec() {+ var fileURL: URL!++ afterEach {+ guard let fileURL = fileURL else {+ return+ }+ try? Temporary.removeItem(at: fileURL)+ }+++ describe("run") {+ context("with no arguments") {+ it("fails") {+ let result = try? TestTask.run(withArguments: ["types"])+ expect(result?.didFail) == true+ }+ }++ context("when path is invalid") {+ it("fails when empty") {+ let result = try? TestTask.run(withArguments: ["types", "--path", ""])+ expect(result?.didFail) == true+ }++ it("fails when it doesn't exist") {+ let result = try? TestTask.run(withArguments: ["types", "--path", "/fake/path"])+ expect(result?.didFail) == true+ }+ }++ context("when path is valid") {+ it("runs and outputs the result (without comment)") {+ fileURL = try? Temporary.makeFile(content: "struct Foo { }")+ let path = fileURL?.path ?? ""+ let result = try? TestTask.run(withArguments: ["types", "--path", path])+ expect(result!.outputMessage).to(contain("Foo,struct"))
expect(result?.outputMessage).to(contain("Foo,struct"))
comment created time in a month
Pull request review commentfdiaz/SwiftInspector
+// Created by Tyler Hedrick 8/13/20.+//+// Distributed under the MIT License+//+// Permission is hereby granted, free of charge, to any person obtaining a copy+// of this software and associated documentation files (the "Software"), to deal+// in the Software without restriction, including without limitation the rights+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+// copies of the Software, and to permit persons to whom the Software is+// furnished to do so, subject to the following conditions:+//+// The above copyright notice and this permission notice shall be included in all+// copies or substantial portions of the Software.+//+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+// SOFTWARE.++import Foundation+import Nimble+import Quick++@testable import SwiftInspectorCore++final class TypesAnalyzerSpec: QuickSpec {++ override func spec() {+ var fileURL: URL!+ var sut: TypesAnalyzer!++ beforeEach {+ sut = TypesAnalyzer()+ }++ afterEach {+ guard let fileURL = fileURL else {+ return+ }+ try? Temporary.removeItem(at: fileURL)+ }++ describe("analyze(fileURL:)") {+ context("there are no types present") {+ beforeEach {+ let content =+ """+ import Foundation+ """+ fileURL = try? Temporary.makeFile(content: content)+ }++ it("returns empty array") {+ let result = try? sut.analyze(fileURL: fileURL)+ expect(result).to(beEmpty())+ }+ }++ context("struct is present") {+ beforeEach {+ let content =+ """+ struct Foo { }+ """+ fileURL = try? Temporary.makeFile(content: content)+ }++ it("returns the type information for the struct") {+ let result = try? sut.analyze(fileURL: fileURL)+ expect(result).notTo(beEmpty())+ expect(result!.first!.name) == "Foo"+ expect(result!.first!.type) == .struct+ }+ }++ context("enum is present") {+ beforeEach {+ let content =+ """+ enum Foo { }+ """+ fileURL = try? Temporary.makeFile(content: content)+ }++ it("returns the type information for the enum") {+ let result = try? sut.analyze(fileURL: fileURL)+ expect(result).notTo(beEmpty())+ expect(result!.first!.name) == "Foo"
expect(result?.first?.name) == "Foo"
comment created time in a month
Pull request review commentfdiaz/SwiftInspector
+// Created by Tyler Hedrick on 8/12/20.+//+// Copyright (c) 2020 Tyler Hedrick+//+// Distributed under the MIT License+//+// Permission is hereby granted, free of charge, to any person obtaining a copy+// of this software and associated documentation files (the "Software"), to deal+// in the Software without restriction, including without limitation the rights+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+// copies of the Software, and to permit persons to whom the Software is+// furnished to do so, subject to the following conditions:+//+// The above copyright notice and this permission notice shall be included in all+// copies or substantial portions of the Software.+//+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+// SOFTWARE.++import ArgumentParser+import Foundation+import SwiftInspectorCore++final class TypesCommand: ParsableCommand {+ static var configuration = CommandConfiguration(+ commandName: "types",+ abstract: "Finds information about types in a file"+ )++ @Option(help: "The absolute path to the file to inspect")+ var path: String++ /// Runs the command+ func run() throws {+ let cachedSyntaxTree = CachedSyntaxTree()+ let analyzer = TypesAnalyzer(cachedSyntaxTree: cachedSyntaxTree)+ let fileURL = URL(fileURLWithPath: path)+ let outputArray = try FileManager.default.swiftFiles(at: fileURL)
Seems like you set up this to work with folders 😬 this is gonna make https://github.com/fdiaz/SwiftInspector/issues/42 a lot easier to adopt. I think you can actually just change the documentation on path on the command and add a couple of tests and we should be good to go to support both a single swift file or a folder.
comment created time in a month
Pull request review commentfdiaz/SwiftInspector
+// Created by Tyler Hedrick 8/13/20.+//+// Distributed under the MIT License+//+// Permission is hereby granted, free of charge, to any person obtaining a copy+// of this software and associated documentation files (the "Software"), to deal+// in the Software without restriction, including without limitation the rights+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+// copies of the Software, and to permit persons to whom the Software is+// furnished to do so, subject to the following conditions:+//+// The above copyright notice and this permission notice shall be included in all+// copies or substantial portions of the Software.+//+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+// SOFTWARE.++import Foundation+import Nimble+import Quick++@testable import SwiftInspectorCore++final class TypesAnalyzerSpec: QuickSpec {++ override func spec() {+ var fileURL: URL!+ var sut: TypesAnalyzer!++ beforeEach {+ sut = TypesAnalyzer()+ }++ afterEach {+ guard let fileURL = fileURL else {+ return+ }+ try? Temporary.removeItem(at: fileURL)+ }++ describe("analyze(fileURL:)") {+ context("there are no types present") {+ beforeEach {+ let content =+ """+ import Foundation+ """+ fileURL = try? Temporary.makeFile(content: content)+ }++ it("returns empty array") {+ let result = try? sut.analyze(fileURL: fileURL)+ expect(result).to(beEmpty())+ }+ }++ context("struct is present") {+ beforeEach {+ let content =+ """+ struct Foo { }+ """+ fileURL = try? Temporary.makeFile(content: content)+ }++ it("returns the type information for the struct") {+ let result = try? sut.analyze(fileURL: fileURL)+ expect(result).notTo(beEmpty())+ expect(result!.first!.name) == "Foo"
expect(result?.first?.name) == "Foo"
comment created time in a month
Pull request review commentfdiaz/SwiftInspector
+// Created by Tyler Hedrick on 8/13/20.+//+// Copyright (c) 2020 Tyler Hedrick+//+// Distributed under the MIT License+//+// Permission is hereby granted, free of charge, to any person obtaining a copy+// of this software and associated documentation files (the "Software"), to deal+// in the Software without restriction, including without limitation the rights+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+// copies of the Software, and to permit persons to whom the Software is+// furnished to do so, subject to the following conditions:+//+// The above copyright notice and this permission notice shall be included in all+// copies or substantial portions of the Software.+//+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+// SOFTWARE.++import Nimble+import Quick+import Foundation++@testable import SwiftInspectorCore++final class TypesCommandSpec: QuickSpec {+ override func spec() {+ var fileURL: URL!++ afterEach {+ guard let fileURL = fileURL else {+ return+ }+ try? Temporary.removeItem(at: fileURL)+ }+++ describe("run") {+ context("with no arguments") {+ it("fails") {+ let result = try? TestTask.run(withArguments: ["types"])+ expect(result?.didFail) == true+ }+ }++ context("when path is invalid") {+ it("fails when empty") {+ let result = try? TestTask.run(withArguments: ["types", "--path", ""])+ expect(result?.didFail) == true+ }++ it("fails when it doesn't exist") {+ let result = try? TestTask.run(withArguments: ["types", "--path", "/fake/path"])+ expect(result?.didFail) == true+ }+ }++ context("when path is valid") {+ it("runs and outputs the result (without comment)") {+ fileURL = try? Temporary.makeFile(content: "struct Foo { }")+ let path = fileURL?.path ?? ""+ let result = try? TestTask.run(withArguments: ["types", "--path", path])+ expect(result!.outputMessage).to(contain("Foo,struct"))+ }++ it("runs and outputs the result (with comment)") {+ fileURL = try? Temporary.makeFile(content: """+ // This is a comment+ struct Foo { }+ """)+ let path = fileURL?.path ?? ""+ let result = try? TestTask.run(withArguments: ["types", "--path", path])+ expect(result!.outputMessage).to(contain("Foo,struct,// This is a comment"))
expect(result?.outputMessage).to(contain("Foo,struct,// This is a comment"))
comment created time in a month
Pull request review commentfdiaz/SwiftInspector
+// Created by Tyler Hedrick on 8/13/20.+//+// Copyright (c) 2020 Tyler Hedrick+//+// Distributed under the MIT License+//+// Permission is hereby granted, free of charge, to any person obtaining a copy+// of this software and associated documentation files (the "Software"), to deal+// in the Software without restriction, including without limitation the rights+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+// copies of the Software, and to permit persons to whom the Software is+// furnished to do so, subject to the following conditions:+//+// The above copyright notice and this permission notice shall be included in all+// copies or substantial portions of the Software.+//+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+// SOFTWARE.++import Nimble+import Quick+import Foundation++@testable import SwiftInspectorCore++final class TypesCommandSpec: QuickSpec {+ override func spec() {+ describe("run") {++ context("with no arguments") {+ it("fails") {+ let result = try? TestTask.run(withArguments: ["types"])+ expect(result?.didFail) == true+ }+ }++ context("when path is invalid") {+ it("fails when empty") {+ let result = try? TestTask.run(withArguments: ["types", "--path", ""])+ expect(result?.didFail) == true+ }++ it("fails when it doesn't exist") {+ let result = try? TestTask.run(withArguments: ["types", "--path", "/fake/path"])+ expect(result?.didFail) == true+ }
Let's add a success case here
comment created time in a month
issue openedfdiaz/SwiftInspector
Make types command work with folders
This PR https://github.com/fdiaz/SwiftInspector/pull/41 introduced the types command, but it only works today with files.
Let's extend it to work with folders too.
created time in a month
issue openedfdiaz/SwiftInspector
Document what our stance on the format of the output is
When contributing it's not clear what the output of a new command should look like. We should write a document that states our reasoning behind output of the tool
created time in a month
startedtuist/tuist
started time in 2 months
issue closedairbnb/swift
Place function/type attributes on the line above the declaration
This rule is missing autocorrect. Following this PR.
-
<a id='attributes-on-prev-line'></a>(<a href='#attributes-on-prev-line'>link</a>) Place function/type attributes on the line above the declaration.
<details>
// WRONG @objc class Spaceship: NSObject { @discardableResult func fly() -> Bool { } } // RIGHT @objc class Spaceship: NSObject { @discardableResult func fly() -> Bool { } }</details>
closed time in 2 months
fdiazissue commentairbnb/swift
Place function/type attributes on the line above the declaration
Added in https://github.com/airbnb/swift/pull/101
comment created time in 2 months
issue closedairbnb/swift
Multi-line arrays should have each bracket on a separate line
This rule is missing autocorrect. Following this PR.
-
<a id='multi-line-array'></a>(<a href='#multi-line-array'>link</a>) Multi-line arrays should have each bracket on a separate line. Put the opening and closing brackets on separate lines from any of the elements of the array. Also add a trailing comma on the last element.
<details>
// WRONG let rowContent = [listingUrgencyDatesRowContent(), listingUrgencyBookedRowContent(), listingUrgencyBookedShortRowContent()] let rowContent = [ listingUrgencyDatesRowContent(), listingUrgencyBookedRowContent(), listingUrgencyBookedShortRowContent() ] // RIGHT let rowContent = [ listingUrgencyDatesRowContent(), listingUrgencyBookedRowContent(), listingUrgencyBookedShortRowContent(), ]</details>
closed time in 2 months
fdiazissue commentairbnb/swift
Multi-line arrays should have each bracket on a separate line
Added in https://github.com/airbnb/swift/pull/101
comment created time in 2 months
issue closedairbnb/swift
Long function invocations should also break on each argument
This rule is missing autocorrect. Following this PR.
-
<a id='long-function-invocation'></a>(<a href='#long-function-invocation'>link</a>) Long function invocations should also break on each argument. Put the closing parenthesis on the last line of the invocation.
<details>
universe.generateStars( at: location, count: 5, color: starColor, withAverageDistance: 4) universe.generate( 5, .stars, at: location)</details>
closed time in 2 months
fdiazissue commentairbnb/swift
Long function invocations should also break on each argument
Added in https://github.com/airbnb/swift/pull/101
comment created time in 2 months
issue closedairbnb/swift
This rule is missing autocorrect. Following this PR.
-
<a id='long-function-declaration'></a>(<a href='#long-function-declaration'>link</a>) Separate long function declarations with line breaks before each argument label and before the return signature. Put the open curly brace on the next line so the first executable line doesn't look like it's another parameter.
<details>
class Universe { // WRONG func generateStars(at location: Point, count: Int, color: StarColor, withAverageDistance averageDistance: Float) -> String { // This is too long and will probably auto-wrap in a weird way } // WRONG func generateStars(at location: Point, count: Int, color: StarColor, withAverageDistance averageDistance: Float) -> String { // Xcode indents all the arguments } // WRONG func generateStars( at location: Point, count: Int, color: StarColor, withAverageDistance averageDistance: Float) -> String { populateUniverse() // this line blends in with the argument list } // WRONG func generateStars( at location: Point, count: Int, color: StarColor, withAverageDistance averageDistance: Float) throws -> String { populateUniverse() // this line blends in with the argument list } // RIGHT func generateStars( at location: Point, count: Int, color: StarColor, withAverageDistance averageDistance: Float) -> String { populateUniverse() } // RIGHT func generateStars( at location: Point, count: Int, color: StarColor, withAverageDistance averageDistance: Float) throws -> String { populateUniverse() } }</details>
closed time in 2 months
fdiazissue commentairbnb/swift
Added in https://github.com/airbnb/swift/pull/101
comment created time in 2 months
Pull request review commentairbnb/swift
Reintroduce old wrapping rules
_You can enable the following settings in Xcode by running [this script](resourc </details> +* <a id='long-function-declaration'></a>(<a href='#long-function-declaration'>link</a>) **Separate [long](https://github.com/airbnb/swift#column-width) function declarations with line breaks before each argument label and before the return signature.** Put the open curly brace on the next line so the first executable line doesn't look like it's another parameter. [](https://github.com/nicklockwood/SwiftFormat/blob/master/Rules.md#wrapArguments) [](https://github.com/nicklockwood/SwiftFormat/blob/master/Rules.md#wrapArguments)
Let's move this down to the Functions section
comment created time in 2 months
Pull request review commentairbnb/swift
Reintroduce old wrapping rules
_You can enable the following settings in Xcode by running [this script](resourc </details> +* <a id='long-function-declaration'></a>(<a href='#long-function-declaration'>link</a>) **Separate [long](https://github.com/airbnb/swift#column-width) function declarations with line breaks before each argument label and before the return signature.** Put the open curly brace on the next line so the first executable line doesn't look like it's another parameter. [](https://github.com/nicklockwood/SwiftFormat/blob/master/Rules.md#wrapArguments) [](https://github.com/nicklockwood/SwiftFormat/blob/master/Rules.md#wrapArguments)
The second badge name needs to be updated:
* <a id='long-function-declaration'></a>(<a href='#long-function-declaration'>link</a>) **Separate [long](https://github.com/airbnb/swift#column-width) function declarations with line breaks before each argument label and before the return signature.** Put the open curly brace on the next line so the first executable line doesn't look like it's another parameter. [](https://github.com/nicklockwood/SwiftFormat/blob/master/Rules.md#wrapArguments) [](https://github.com/nicklockwood/SwiftFormat/blob/master/Rules.md#wrapArguments)
comment created time in 2 months
Pull request review commentairbnb/swift
Reintroduce old wrapping rules
_You can enable the following settings in Xcode by running [this script](resourc ``` </details>+ +* <a id='attributes-on-prev-line'></a>(<a href='#attributes-on-prev-line'>link</a>) **Place function/type attributes on the line above the declaration**. [](https://github.com/nicklockwood/SwiftFormat/blob/master/Rules.md#wrapAttributes)
Let's move this down to Functions
comment created time in 2 months
Pull request review commentairbnb/swift
Reintroduce old wrapping rules
_You can enable the following settings in Xcode by running [this script](resourc </details> +* <a id='long-function-declaration'></a>(<a href='#long-function-declaration'>link</a>) **Separate [long](https://github.com/airbnb/swift#column-width) function declarations with line breaks before each argument label and before the return signature.** Put the open curly brace on the next line so the first executable line doesn't look like it's another parameter. [](https://github.com/nicklockwood/SwiftFormat/blob/master/Rules.md#wrapArguments) [](https://github.com/nicklockwood/SwiftFormat/blob/master/Rules.md#wrapArguments)++ <details>++ ```swift+ class Universe {++ // WRONG+ func generateStars(at location: Point, count: Int, color: StarColor, withAverageDistance averageDistance: Float) -> String {+ // This is too long and will probably auto-wrap in a weird way+ }++ // WRONG+ func generateStars(at location: Point,+ count: Int,+ color: StarColor,+ withAverageDistance averageDistance: Float) -> String+ {+ // Xcode indents all the arguments+ }++ // WRONG+ func generateStars(+ at location: Point,+ count: Int,+ color: StarColor,+ withAverageDistance averageDistance: Float) -> String {+ populateUniverse() // this line blends in with the argument list+ }++ // WRONG+ func generateStars(+ at location: Point,+ count: Int,+ color: StarColor,+ withAverageDistance averageDistance: Float) throws+ -> String {+ populateUniverse() // this line blends in with the argument list+ }++ // RIGHT+ func generateStars(+ at location: Point,+ count: Int,+ color: StarColor,+ withAverageDistance averageDistance: Float) + -> String+ {+ populateUniverse()+ }++ // RIGHT+ func generateStars(+ at location: Point,+ count: Int,+ color: StarColor,+ withAverageDistance averageDistance: Float) + throws -> String+ {+ populateUniverse()+ }+ }+ ```++ </details>++* <a id='long-function-invocation'></a>(<a href='#long-function-invocation'>link</a>) **[Long](https://github.com/airbnb/swift#column-width) function invocations should also break on each argument.** Put the closing parenthesis on the last line of the invocation. [](https://github.com/nicklockwood/SwiftFormat/blob/master/Rules.md#wrapArguments)++ <details>++ ```swift+ universe.generateStars(+ at: location,+ count: 5,+ color: starColor,+ withAverageDistance: 4)++ universe.generate(+ 5,+ .stars,+ at: location)+ ```++ </details>+ + * <a id='multi-line-array'></a>(<a href='#multi-line-array'>link</a>) **Multi-line arrays should have each bracket on a separate line.** Put the opening and closing brackets on separate lines from any of the elements of the array. Also add a trailing comma on the last element. [](https://github.com/nicklockwood/SwiftFormat/blob/master/Rules.md#wrapArguments)
* <a id='multi-line-array'></a>(<a href='#multi-line-array'>link</a>) **Multi-line arrays should have each bracket on a separate line.** Put the opening and closing brackets on separate lines from any of the elements of the array. Also add a trailing comma on the last element. [](https://github.com/nicklockwood/SwiftFormat/blob/master/Rules.md#wrapArguments)
comment created time in 2 months
issue commentairbnb/ResilientDecoding
Support passthrough conformances for encoding and equatable
This is very similar to https://github.com/airbnb/ResilientDecoding/pull/38
cc @GeorgeLyon
comment created time in 2 months
push eventfdiaz/SwiftInspector
commit sha 56d6325e17d9b07ce0450bd05a5c7f13ad3f5b42
Update README (#39)
push time in 2 months
PR merged fdiaz/SwiftInspector
Following https://github.com/fdiaz/SwiftInspector/issues/38 we're renaming master -> main.
pr closed time in 2 months
push eventfdiaz/SwiftInspector
commit sha ff62d994e4cf89f856336fcd684230c0272b6cf1
Update README
push time in 2 months