diff --git a/Release Notes/601.md b/Release Notes/601.md index eee0be858b2..b7e30fc4553 100644 --- a/Release Notes/601.md +++ b/Release Notes/601.md @@ -2,6 +2,11 @@ ## New APIs +- `FunctionDecl`, `InitializerDecl`, `SubscriptDecl`, `EnumCaseElement`, and `MacroDecl` gained a new computed property: `symbolName` + - Description: The `symbolName` property provides a string representation of the declaration's name along with its parameter labels. For example, a function `func greet(name: String)` will have the symbol name `greet(name:)`. + - Issue: https://github.com/apple/swift-syntax/issues/2488 + - Pull Request: https://github.com/apple/swift-syntax/pull/2583 + ## API Behavior Changes ## Deprecations diff --git a/Release Notes/610.md b/Release Notes/610.md deleted file mode 100644 index 09d33d65f12..00000000000 --- a/Release Notes/610.md +++ /dev/null @@ -1,20 +0,0 @@ -# Swift Syntax 610 Release Notes - -## New APIs - -## API Behavior Changes - -## Deprecations - -## API-Incompatible Changes - -## Template - -- *Affected API or two word description* - - Description: *A 1-2 sentence description of the new/modified API* - - Issue: *If an issue exists for this change, a link to the issue* - - Pull Request: *Link to the pull request(s) that introduces this change* - - Migration steps: Steps that adopters of swift-syntax should take to move to the new API (required for deprecations and API-incompatible changes). - - Notes: *In case of deprecations or API-incompatible changes, the reason why this change was made and the suggested alternative* - -*Insert entries in chronological order, with newer entries at the bottom* diff --git a/Sources/SwiftSyntax/SymbolName.swift b/Sources/SwiftSyntax/SymbolName.swift new file mode 100644 index 00000000000..ee595c36a95 --- /dev/null +++ b/Sources/SwiftSyntax/SymbolName.swift @@ -0,0 +1,139 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +extension FunctionDeclSyntax { + /// The symbol name of the function declaration. + /// + /// The symbol name is a string representation of the function name along with its parameter labels. + /// For example, a function `func greet(name: String)` will have the symbol name `greet(name:)`. + public var symbolName: String { + SwiftSyntax.symbolName( + fromBaseName: name.text, + parameters: signature.parameterClause + ) + } +} + +extension InitializerDeclSyntax { + /// The symbol name of the initializer declaration. + /// + /// The symbol name is a string representation of the initializer along with its parameter labels. + /// For example, an initializer `init(name: String)` will have the symbol name `init(name:)`. + public var symbolName: String { + SwiftSyntax.symbolName( + fromBaseName: initKeyword.text, + parameters: signature.parameterClause + ) + } +} + +extension SubscriptDeclSyntax { + /// The symbol name of the subscript declaration. + /// + /// The symbol name is a string representation of the subscript along with its parameter labels. + /// For example, a subscript `subscript(index: Int)` will have the symbol name `subscript(_:)`. + public var symbolName: String { + SwiftSyntax.symbolName( + fromBaseName: subscriptKeyword.text, + parameters: parameterClause, + isForSubscript: true + ) + } +} + +extension EnumCaseElementSyntax { + /// The symbol name of the enum case element. + /// + /// The symbol name is a string representation of the enum case name along with its associated value labels (if any). + /// For example, an enum case `case foo(bar: Int)` will have the symbol name `foo(bar:)`. + public var symbolName: String { + let caseName = name.text + + guard let associatedValues = parameterClause else { + return caseName + } + + let argumentLabels = associatedValues.parameters + .map { parameter in + guard let firstName = parameter.firstName else { + return "_:" + } + + return firstName.text + ":" + } + .joined() + + return "\(caseName)(\(argumentLabels))" + } +} + +extension MacroDeclSyntax { + /// The symbol name of the macro declaration. + /// + /// The symbol name is a string representation of the macro name along with its parameter labels. + /// For example, a macro `macro greet(name: String)` will have the symbol name `greet(name:)`. + public var symbolName: String { + SwiftSyntax.symbolName( + fromBaseName: name.text, + parameters: signature.parameterClause + ) + } +} + +/// Generates the symbol name by combining the base name and parameter labels. +/// +/// If the parameter has two names (e.g., `external internal: Int`), the first name is considered the argument label. +/// If the parameter has only one name and it's not a subscript parameter, it is considered the argument label. +/// +/// - Parameters: +/// - baseName: The base name of the symbol (e.g., function name, initializer, subscript). +/// - parameters: The function parameter clause containing the parameter labels. +/// - Returns: The symbol name with the base name and parameter labels combined. +private func symbolName( + fromBaseName baseName: String, + parameters: FunctionParameterClauseSyntax, + isForSubscript: Bool = false +) -> String { + let argumentLabels = parameters.parameters + .map { parameter in + let argumentLabelText = parameter.argumentName(isForSubscript: isForSubscript) ?? "_" + return argumentLabelText + ":" + } + .joined() + + return "\(baseName)(\(argumentLabels))" +} + +extension FunctionParameterSyntax { + /// The argument name (label) of the function parameter. + fileprivate func argumentName(isForSubscript: Bool = false) -> String? { + // If we have two names, the first one is the argument label + if secondName != nil { + return firstName.asIdentifierToken + } + + if isForSubscript { + return nil + } + + return firstName.asIdentifierToken + } +} + +extension TokenSyntax { + fileprivate var asIdentifierToken: String? { + switch tokenKind { + case .identifier: return self.text + default: return nil + } + } +}