Skip to content

Commit

Permalink
[Swift APIView] Create intermediate models and SwiftAPIViewTests pack…
Browse files Browse the repository at this point in the history
…age (#3069)

* Initial work.

* Refactoring progress.

* Refactoring progress.

* More refactoring.

* Fixes and extract tests to new test project.

* Address fixes.

* More updates. Add operator and precedence group tests.

* Fix navigation. Add attribute tests.

* Fix issues with enums. Add enum test file.

* TypeModel improvements.

* Add FunctionsTestFile
  • Loading branch information
tjprescott authored Apr 21, 2022
1 parent fd168df commit fe55757
Show file tree
Hide file tree
Showing 71 changed files with 5,314 additions and 2,045 deletions.
3 changes: 3 additions & 0 deletions src/swift/SwiftAPIView.xcworkspace/contents.xcworkspacedata

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -57,27 +57,19 @@
</CommandLineArgument>
<CommandLineArgument
argument = "--source=/Users/travisprescott/repos/communication-ui-library-ios/AzureCommunicationUI"
isEnabled = "YES">
isEnabled = "NO">
</CommandLineArgument>
<CommandLineArgument
argument = "--package-version=0.0.0"
isEnabled = "YES">
</CommandLineArgument>
<CommandLineArgument
argument = "--package-name=AzureCommunicationUI"
argument = "--package-name=SwiftAPIViewTests"
isEnabled = "YES">
</CommandLineArgument>
<CommandLineArgument
argument = "--source=/Users/travisprescott/repos/MixedCodeDemo/Sources"
isEnabled = "NO">
</CommandLineArgument>
<CommandLineArgument
argument = "--source=/Users/travisprescott/Documents/AzureCommunicationCalling.framework"
isEnabled = "NO">
</CommandLineArgument>
<CommandLineArgument
argument = "--source=/Users/travisprescott/repos/azure-sdk-tools/src/swift/SwiftAPIViewCore/TestFiles/TestFile2.swifttxt"
isEnabled = "NO">
argument = "--source=/Users/travisprescott/repos/azure-sdk-tools/src/swift/SwiftAPIViewTests/Sources"
isEnabled = "YES">
</CommandLineArgument>
</CommandLineArguments>
<LocationScenarioReference
Expand Down
19 changes: 9 additions & 10 deletions src/swift/SwiftAPIViewCore/Sources/APIViewManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public class APIViewManager {

// MARK: Properties

public var config = APIViewConfiguration()
var config = APIViewConfiguration()

var mode: APIViewManagerMode

Expand All @@ -82,19 +82,19 @@ public class APIViewManager {
guard let sourceUrl = URL(string: config.sourcePath) else {
SharedLogger.fail("usage error: source path was invalid.")
}
let tokenFile = try buildTokenFile(from: sourceUrl)
let apiView = try createApiView(from: sourceUrl)

switch mode {
case .commandLine:
save(tokenFile: tokenFile)
save(apiView: apiView)
return ""
case .testing:
return tokenFile.text
return apiView.text
}
}

/// Persist the token file to disk
func save(tokenFile: TokenFile) {
func save(apiView: APIViewModel) {
let destUrl: URL
if let destPath = config.destPath {
destUrl = URL(fileURLWithPath: destPath)
Expand All @@ -108,7 +108,7 @@ public class APIViewManager {
do {
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let tokenData = try encoder.encode(tokenFile)
let tokenData = try encoder.encode(apiView)
try tokenData.write(to: destUrl)
} catch {
SharedLogger.fail(error.localizedDescription)
Expand Down Expand Up @@ -187,7 +187,7 @@ public class APIViewManager {
return filePaths
}

func buildTokenFile(from sourceUrl: URL) throws -> TokenFile {
func createApiView(from sourceUrl: URL) throws -> APIViewModel {
SharedLogger.debug("URL: \(sourceUrl.absoluteString)")
var packageName: String?
var packageVersion: String?
Expand Down Expand Up @@ -236,8 +236,7 @@ public class APIViewManager {
config.packageName = packageName!
config.packageVersion = packageVersion!
let apiViewName = "\(packageName!) (version \(packageVersion!))"
let tokenFile = TokenFile(name: apiViewName, packageName: packageName!, versionString: packageVersion!)
tokenFile.process(statements: Array(statements.values))
return tokenFile
let apiView = APIViewModel(name: apiViewName, packageName: packageName!, versionString: packageVersion!, statements: Array(statements.values))
return apiView
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,50 @@ import AST
import Foundation

extension Declaration {

/// Convert a declaration to a Tokenizable instance.
func toTokenizable(withParent parent: Linkable) -> Tokenizable? {
switch self {
case let self as ClassDeclaration:
return ClassModel(from: self, parent: parent)
case let self as ConstantDeclaration:
return ConstantModel(from: self, parent: parent)
case let self as EnumDeclaration:
return EnumModel(from: self, parent: parent)
case let self as ExtensionDeclaration:
return ExtensionModel(from: self, parent: parent)
case let self as FunctionDeclaration:
return FunctionModel(from: self, parent: parent)
case let self as InitializerDeclaration:
return InitializerModel(from: self, parent: parent)
case let self as ProtocolDeclaration:
return ProtocolModel(from: self, parent: parent)
case let self as StructDeclaration:
return StructModel(from: self, parent: parent)
case let self as TypealiasDeclaration:
return TypealiasModel(from: self, parent: parent)
case let self as VariableDeclaration:
return VariableModel(from: self, parent: parent)
case _ as ImportDeclaration:
// Imports are no-op
return nil
case _ as DeinitializerDeclaration:
// Deinitializers are never public
return nil
case let self as SubscriptDeclaration:
return SubscriptModel(from: self, parent: parent)
case let self as PrecedenceGroupDeclaration:
// precedence groups are always public
return PrecedenceGroupModel(from: self, parent: parent)
case let self as OperatorDeclaration:
// operators are always public
return OperatorModel(from: self, parent: parent)
default:
SharedLogger.fail("Unsupported declaration: \(self)")
}
}

/// Returns the access level, if found, for the declaration.
var accessLevel: AccessLevelModifier? {
switch self {
case let decl as ClassDeclaration:
Expand All @@ -48,27 +92,16 @@ extension Declaration {
return decl.modifiers.accessLevel
case let decl as SubscriptDeclaration:
return decl.modifiers.accessLevel
case let decl as ExtensionDeclaration:
return decl.accessLevelModifier
case let decl as ProtocolDeclaration:
return decl.accessLevelModifier
default:
return nil
}
}
}

extension DeclarationModifiers {

var accessLevel: AccessLevelModifier? {
for modifier in self {
switch modifier {
case let .accessLevel(value):
return value
default:
continue
}
}
return nil
}
}

extension InitializerDeclaration {
var fullName: String {
var value = "init("
Expand Down Expand Up @@ -126,38 +159,3 @@ extension ProtocolDeclaration.MethodMember {
return value.replacingOccurrences(of: " ", with: "")
}
}

extension OperatorDeclaration {
var `operator`: String {
switch self.kind {
case let .infix(opName, _):
return opName
case let .postfix(opName):
return opName
case let .prefix(opName):
return opName
}
}
}

extension ExtensionDeclaration {

var isPublic: Bool {
let publicModifiers: [AccessLevelModifier] = [.public, .open]
return publicModifiers.contains(self.accessLevelModifier ?? .internal)
}

var hasPublicMembers: Bool {
let publicModifiers: [AccessLevelModifier] = [.public, .open]
for member in self.members {
switch member {
case let .declaration(decl):
guard let accessLevel = decl.accessLevel else { continue }
if publicModifiers.contains(accessLevel) { return true }
case .compilerControl(_):
break
}
}
return false
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// --------------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The MIT License (MIT)
//
// 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 AST
import Foundation

extension DeclarationModifiers {

var accessLevel: AccessLevelModifier? {
for modifier in self {
switch modifier {
case let .accessLevel(value):
return value
default:
continue
}
}
return nil
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// --------------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The MIT License (MIT)
//
// 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 AST
import Foundation

extension OperatorDeclaration {
var `operator`: String {
switch self.kind {
case let .infix(opName, _):
return opName
case let .postfix(opName):
return opName
case let .prefix(opName):
return opName
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,34 +27,28 @@
import AST
import Foundation


extension PatternInitializer {
var name: String {
if let ident = self.pattern as? IdentifierPattern {
return ident.identifier.textDescription
}
SharedLogger.fail("Unable to extract name from \(self)")
var name: String? {
return (self.pattern as? IdentifierPattern)?.identifier.textDescription
}

var typeModel: TypeModel? {
if case let typeAnno as IdentifierPattern = pattern,
let typeInfo = typeAnno.typeAnnotation?.type {
return TypeModel(from: typeInfo)
return typeInfo.toTokenizable()
}
if case let literalExpression as LiteralExpression = initializerExpression {
return TypeModel(name: literalExpression.kind.textDescription)
return TypeIdentifierModel(name: literalExpression.kind.textDescription)
}
if case let functionExpression as FunctionCallExpression = initializerExpression {
return TypeModel(name: functionExpression.postfixExpression.textDescription)
return TypeIdentifierModel(name: functionExpression.postfixExpression.textDescription)
}
SharedLogger.fail("Unsupported pattern: \(self)")
return nil
}

var defaultValue: String? {
guard let initExpr = initializerExpression else { return nil }
if case is LiteralExpression = initExpr {
return initExpr.textDescription
}
// ignore function expressions
return nil
// TODO: This only works for literal expressions. What about closures, etc? Do we care?
return (initializerExpression as? LiteralExpression)?.textDescription
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// --------------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The MIT License (MIT)
//
// 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 AST
import Foundation

extension ThrowsKind: Tokenizable {
func tokenize(apiview a: APIViewModel) {
switch self {
case .throwing, .rethrowing:
a.keyword("throws", prefixSpace: true, postfixSpace: true)
case .nothrowing:
return
}
}
}
Loading

0 comments on commit fe55757

Please sign in to comment.