Skip to content
This repository has been archived by the owner on Sep 20, 2022. It is now read-only.

Add support for parameter names #136

Merged
merged 2 commits into from Sep 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion Sources/SwiftInspectorAnalyzers/InitializerAnalyzer.swift
Expand Up @@ -83,7 +83,7 @@ public final class InitializerAnalyzer: Analyzer {

private func findParameter(from node: FunctionParameterSyntax) -> InitializerStatement.Parameter {
let name: String
// If the initializer contains a different parameter and an argument label we want to check first
// If the initializer contains a different parameter name and an argument label we want to check first
// for the parameter. e.g. init(some another: String) -- the parameter would be `another`
if let secondName = node.secondName {
name = secondName.text
Expand Down
30 changes: 19 additions & 11 deletions Sources/SwiftInspectorVisitors/FunctionDeclarationVisitor.swift
Expand Up @@ -15,7 +15,7 @@ public final class FunctionDeclarationVisitor: SyntaxVisitor {
let info = FunctionDeclarationInfo(
modifiers: modifiersVisitor.modifiers,
name: name,
arguments: functionSignatureVisitor.arguments,
parameters: functionSignatureVisitor.parameters,
returnType: functionSignatureVisitor.returnType)
functionDeclarations.append(info)
return .skipChildren
Expand Down Expand Up @@ -52,41 +52,49 @@ public final class FunctionDeclarationVisitor: SyntaxVisitor {
fileprivate final class FunctionSignatureVisitor: SyntaxVisitor {
override func visit(_ node: FunctionParameterSyntax) -> SyntaxVisitorContinueKind {
guard
let argumentLabelName = node.firstName?.text,
let firstMember = node.firstName?.text,
let type = node.type?.typeDescription
else { return .skipChildren }

appendArgument(argumentLabelName: argumentLabelName, type: type)
let secondMember = node.secondName?.text

appendParameter(firstMember: firstMember, secondMember: secondMember, type: type)
return .skipChildren
}
override func visit(_ node: ReturnClauseSyntax) -> SyntaxVisitorContinueKind {
returnType = node.returnType.typeDescription
return .skipChildren
}

fileprivate func appendArgument(argumentLabelName: String, type: TypeDescription) {
var arguments = self.arguments ?? []
arguments.append(.init(argumentLabelName: argumentLabelName, type: type))
self.arguments = arguments
fileprivate func appendParameter(firstMember: String, secondMember: String?, type: TypeDescription) {
var parameters = self.parameters ?? []
// Each function parameter has both an argument label and a parameter name.
// The argument label is used when calling the function; each argument is written in the function call with its argument label before it.
// The parameter name is used in the implementation of the function. By default, parameters use their parameter name as their argument label.
let argumentLabel = firstMember
let parameterName = secondMember ?? firstMember
parameters.append(.init(argumentLabelName: argumentLabel, parameterName: parameterName, type: type))
self.parameters = parameters
}

fileprivate var arguments: [FunctionDeclarationInfo.ArgumentInfo]?
fileprivate var parameters: [FunctionDeclarationInfo.ParameterInfo]?
fileprivate var returnType: TypeDescription?
}

public struct FunctionDeclarationInfo: Codable, Hashable {
public let modifiers: Modifiers
public let name: String
public let arguments: [ArgumentInfo]?
public let parameters: [ParameterInfo]?
public let returnType: TypeDescription?

/// A convenience for creating a selector string that can be reference in Objective-C code.
public var selectorName: String {
"\(name)(\((arguments ?? []).map { "\($0.argumentLabelName):" }.joined()))"
"\(name)(\((parameters ?? []).map { "\($0.argumentLabelName):" }.joined()))"
}

public struct ArgumentInfo: Codable, Hashable {
public struct ParameterInfo: Codable, Hashable {
public let argumentLabelName: String
public let parameterName: String
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was going to say that we should make the argument label optional but since Swift stresses both exist I think this is fine.
Screen Shot 2022-09-16 at 2 37 30 PM

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Per my realization at the end of #136 (comment) I think making the parameterName optional would be helpful.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just pushed a new commit that hopefully will help clarify a bit f4c285d

public let type: TypeDescription
}
}
Expand Up @@ -33,8 +33,9 @@ final class FunctionDeclarationVisitorSpec: QuickSpec {
expect(self.sut.functionDeclarations.first?.name) == "greet"
}
it("finds the function parameter") {
expect(self.sut.functionDeclarations.first?.arguments?.first?.argumentLabelName) == "person"
expect(self.sut.functionDeclarations.first?.arguments?.first?.type) == .simple(name: "String")
expect(self.sut.functionDeclarations.first?.parameters?.first?.argumentLabelName) == "person"
expect(self.sut.functionDeclarations.first?.parameters?.first?.parameterName) == "person"
expect(self.sut.functionDeclarations.first?.parameters?.first?.type) == .simple(name: "String")
}
it("finds the return type") {
expect(self.sut.functionDeclarations.first?.returnType) == TypeDescription.simple(name: "String")
Expand All @@ -59,8 +60,9 @@ final class FunctionDeclarationVisitorSpec: QuickSpec {
expect(self.sut.functionDeclarations.first?.name) == "printWithoutCounting"
}
it("finds the function parameter") {
expect(self.sut.functionDeclarations.first?.arguments?.first?.argumentLabelName) == "string"
expect(self.sut.functionDeclarations.first?.arguments?.first?.type) == .simple(name: "String")
expect(self.sut.functionDeclarations.first?.parameters?.first?.argumentLabelName) == "string"
expect(self.sut.functionDeclarations.first?.parameters?.first?.parameterName) == "string"
expect(self.sut.functionDeclarations.first?.parameters?.first?.type) == .simple(name: "String")
}
it("sets the return type as nil") {
expect(self.sut.functionDeclarations.first?.returnType).to(beNil())
Expand Down Expand Up @@ -94,8 +96,9 @@ final class FunctionDeclarationVisitorSpec: QuickSpec {
expect(self.sut.functionDeclarations.first?.name) == "minMax"
}
it("finds the function parameter") {
expect(self.sut.functionDeclarations.first?.arguments?.first?.argumentLabelName) == "array"
expect(self.sut.functionDeclarations.first?.arguments?.first?.type) == .array(element: .simple(name: "Int"))
expect(self.sut.functionDeclarations.first?.parameters?.first?.argumentLabelName) == "array"
expect(self.sut.functionDeclarations.first?.parameters?.first?.parameterName) == "array"
expect(self.sut.functionDeclarations.first?.parameters?.first?.type) == .array(element: .simple(name: "Int"))
}
it("finds the tuple return value") {
expect(self.sut.functionDeclarations.first?.returnType) == .tuple([.simple(name: "Int"), .simple(name: "Int")])
Expand Down Expand Up @@ -134,16 +137,16 @@ final class FunctionDeclarationVisitorSpec: QuickSpec {
let firstFunction = self.sut.functionDeclarations.first
expect(firstFunction?.modifiers) == Modifiers.public
expect(firstFunction?.name) == "minMax"
expect(firstFunction?.arguments?.first?.argumentLabelName) == "array"
expect(firstFunction?.arguments?.first?.type) == .array(element: .simple(name: "Int"))
expect(firstFunction?.parameters?.first?.argumentLabelName) == "array"
expect(firstFunction?.parameters?.first?.type) == .array(element: .simple(name: "Int"))
expect(firstFunction?.returnType) == .tuple([.simple(name: "Int"), .simple(name: "Int")])
}
it("finds the last function") {
let lastFunction = self.sut.functionDeclarations.last
expect(lastFunction?.modifiers) == [.private, .static]
expect(lastFunction?.name) == "printWithoutCounting"
expect(lastFunction?.arguments?.first?.argumentLabelName) == "string"
expect(lastFunction?.arguments?.first?.type) == .simple(name: "String")
expect(lastFunction?.parameters?.first?.argumentLabelName) == "string"
expect(lastFunction?.parameters?.first?.type) == .simple(name: "String")
expect(lastFunction?.returnType).to(beNil())
}
}
Expand All @@ -159,12 +162,12 @@ final class FunctionDeclarationVisitorSpec: QuickSpec {
try? self.sut.walkContent(content)
}

it("finds no arguments") {
expect(self.sut.functionDeclarations.first?.arguments).to(beNil())
it("finds no parameters") {
expect(self.sut.functionDeclarations.first?.parameters).to(beNil())
}
}

context("function with no argument label") {
context("function with empty argument label") {
beforeEach {
let content = """
func print(_ string: String) {
Expand All @@ -175,8 +178,11 @@ final class FunctionDeclarationVisitorSpec: QuickSpec {
try? self.sut.walkContent(content)
}

it("finds the parameter label") {
expect(self.sut.functionDeclarations.first?.arguments?.first?.argumentLabelName) == "_"
it("finds the argument label") {
expect(self.sut.functionDeclarations.first?.parameters?.first?.argumentLabelName) == "_"
}
it("finds the parameter name") {
expect(self.sut.functionDeclarations.first?.parameters?.first?.parameterName) == "string"
}
it("calculates the selectorName") {
expect(self.sut.functionDeclarations.first?.selectorName) == "print(_:)"
Expand All @@ -194,18 +200,21 @@ final class FunctionDeclarationVisitorSpec: QuickSpec {
try? self.sut.walkContent(content)
}

it("finds the parameter label") {
expect(self.sut.functionDeclarations.first?.arguments?.first?.argumentLabelName) == "argumentLabelName"
it("finds the argument label") {
expect(self.sut.functionDeclarations.first?.parameters?.first?.argumentLabelName) == "argumentLabelName"
}
it("calculates the selectorName") {
expect(self.sut.functionDeclarations.first?.selectorName) == "append(argumentLabelName:)"
}
it("finds the parameter name") {
expect(self.sut.functionDeclarations.first?.parameters?.first?.parameterName) == "parameterName"
}
}

context("function with multiple parameters") {
beforeEach {
let content = """
func append(argumentLabelName parameterName: String, type: TypeDescription) {
func append(argumentLabelName parameterName: String, type secondParameterName: TypeDescription) {
// ...
}
"""
Expand All @@ -214,11 +223,15 @@ final class FunctionDeclarationVisitorSpec: QuickSpec {
}

it("finds the correct number of parameters") {
expect(self.sut.functionDeclarations.first?.arguments?.count) == 2
expect(self.sut.functionDeclarations.first?.parameters?.count) == 2
}
it("finds the argument labels") {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add a test somewhere for a case where there is no argument label and only a parameter name?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that's possible, the argument label is always present IIUC?

Each function parameter has both an argument label and a parameter name. The argument label is used when calling the function; each argument is written in the function call with its argument label before it. The parameter name is used in the implementation of the function.

I think you're referring to having the argument label be the same as the parameter name, which I think we already have tests for?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just pushed a new commit that hopefully will help clarify a bit f4c285d

expect(self.sut.functionDeclarations.first?.parameters?.first?.argumentLabelName) == "argumentLabelName"
expect(self.sut.functionDeclarations.first?.parameters?.last?.argumentLabelName) == "type"
}
it("finds the parameter labels") {
expect(self.sut.functionDeclarations.first?.arguments?.first?.argumentLabelName) == "argumentLabelName"
expect(self.sut.functionDeclarations.first?.arguments?.last?.argumentLabelName) == "type"
it("finds the parameter names") {
expect(self.sut.functionDeclarations.first?.parameters?.first?.parameterName) == "parameterName"
expect(self.sut.functionDeclarations.first?.parameters?.last?.parameterName) == "secondParameterName"
}
it("calculates the selectorName") {
expect(self.sut.functionDeclarations.first?.selectorName) == "append(argumentLabelName:type:)"
Expand Down