Migrate environmentEntry rule to DeclarationV2

This commit is contained in:
Cal Stephens
2025-05-12 20:18:43 -07:00
parent 9dad26e2ac
commit f0ce5c8773
6 changed files with 224 additions and 173 deletions
+3 -17
View File
@@ -1,5 +1,5 @@
//
// DeclarationHelpers.swift
// DeclarationV1.swift
// SwiftFormat
//
// Created by Cal Stephens on 7/20/24.
@@ -162,23 +162,9 @@ enum Declaration: Hashable {
var isStoredProperty: Bool {
guard keyword == "let" || keyword == "var" else { return false }
// If this property has a body, then it's a stored property
// if and only if the declaration body has a `didSet` or `willSet` keyword,
// based on the grammar for a variable declaration:
// https://docs.swift.org/swift-book/ReferenceManual/Declarations.html#grammar_variable-declaration
let formatter = Formatter(tokens)
if let keywordIndex = formatter.index(of: .keyword(keyword), after: -1),
let startOfPropertyBody = formatter.startOfPropertyBody(
at: keywordIndex,
endOfPropertyIndex: formatter.tokens.count
),
let nextToken = formatter.next(.nonSpaceOrCommentOrLinebreak, after: startOfPropertyBody)
{
return [.identifier("willSet"), .identifier("didSet")].contains(nextToken)
}
// Otherwise, if the property doesn't have a body, then it must not be a computed property.
return true
guard let keywordIndex = formatter.index(of: .keyword(keyword), after: -1) else { return false }
return formatter.isStoredProperty(atIntroducerIndex: keywordIndex)
}
/// The original index of this declaration's primary keyword in the given formatter
+62 -3
View File
@@ -70,7 +70,12 @@ extension DeclarationV2 {
var name: String? {
formatter.declarationName(keywordIndex: keywordIndex)
}
/// A `Hashable` reference to this declaration.
var identity: AnyHashable {
ObjectIdentifier(self)
}
/// The child declarations of this declaration's body, if present.
@_disfavoredOverload
var body: [DeclarationV2]? {
@@ -83,6 +88,45 @@ extension DeclarationV2 {
return conditionalCompilation.body
}
}
/// The modifiers before this declaration's keyword, including any attributes.
var modifiers: [String] {
var allModifiers = [String]()
_ = formatter.modifiersForDeclaration(at: keywordIndex, contains: { _, modifier in
allModifiers.append(modifier)
return false
})
return allModifiers
}
/// Whether or not this declaration represents a stored instance property
var isStoredInstanceProperty: Bool {
// A static property is not an instance property
!modifiers.contains("static") && isStoredProperty
}
/// Whether or not this declaration represents a static stored property
var isStaticStoredProperty: Bool {
modifiers.contains("static") && isStoredProperty
}
/// Whether or not this declaration represents a stored property
var isStoredProperty: Bool {
formatter.isStoredProperty(atIntroducerIndex: keywordIndex)
}
/// Full information about this `let` or `var` property declaration.
var asPropertyDeclaration: Formatter.PropertyDeclaration? {
guard keyword == "let" || keyword == "var" else { return nil }
return formatter.parsePropertyDeclaration(atIntroducerIndex: keywordIndex)
}
/// Removes this declaration from the source file.
/// After this point, this declaration reference is no longer valid.
func remove() {
formatter.unregisterDeclaration(self)
formatter.removeTokens(in: range)
}
}
/// A simple declaration without any child declarations, representing a property, function, etc.
@@ -188,12 +232,27 @@ extension DeclarationV2 {
/// Adds the given visibility keyword to the given declaration,
/// replacing any existing visibility keyword.
func add(_ visibilityKeyword: Visibility) {
func addVisibility(_ visibilityKeyword: Visibility) {
formatter.addDeclarationVisibility(visibilityKeyword, declarationKeywordIndex: keywordIndex)
}
/// Removes the given visibility keyword from the given declaration
func remove(_ visibilityKeyword: Visibility) {
func removeVisibility(_ visibilityKeyword: Visibility) {
formatter.removeDeclarationVisibility(visibilityKeyword, declarationKeywordIndex: keywordIndex)
}
}
/// We want to avoid including a Hashable requirement on DeclarationV2,
/// so instead you can use this container type if you need a Hashable declaration.
/// Uses reference identity of the `DeclarationV2` class value.
struct HashableDeclaration: Hashable {
let declaration: DeclarationV2
static func == (lhs: HashableDeclaration, rhs: HashableDeclaration) -> Bool {
lhs.declaration === rhs.declaration
}
func hash(into hasher: inout Hasher) {
hasher.combine(ObjectIdentifier(declaration))
}
}
+12
View File
@@ -745,6 +745,18 @@ public extension Formatter {
internal func registerDeclaration(_ declaration: DeclarationV2) {
activeDeclarations.append(WeakDeclarationReference(declaration: declaration))
}
/// Unregisters the given declaration and any children so it will no longer be notified of modifications.
internal func unregisterDeclaration(_ declaration: DeclarationV2) {
var declarationsToRemove = Set([declaration.identity])
declaration.body?.forEachRecursiveDeclaration { childDeclaration in
declarationsToRemove.insert(childDeclaration.identity)
}
activeDeclarations.removeAll(where: { declaration in
declarationsToRemove.contains(declaration.declaration?.identity)
})
}
}
extension String {
+75 -50
View File
@@ -872,54 +872,20 @@ extension Formatter {
/// Whether or not this property at the given introducer index (either `var` or `let`)
/// is a stored property or a computed property.
func isStoredProperty(atIntroducerIndex introducerIndex: Int) -> Bool {
assert(["let", "var"].contains(tokens[introducerIndex].string))
var parseIndex = introducerIndex
// All properties have the property name after the introducer
if let propertyNameIndex = index(of: .nonSpaceOrCommentOrLinebreak, after: parseIndex),
tokens[propertyNameIndex].isIdentifierOrKeyword
{
parseIndex = propertyNameIndex
guard let property = parsePropertyDeclaration(atIntroducerIndex: introducerIndex) else {
return false
}
// Properties have an optional `: TypeName` component
if let typeAnnotationStartIndex = index(of: .nonSpaceOrCommentOrLinebreak, after: parseIndex),
tokens[typeAnnotationStartIndex] == .delimiter(":"),
let startOfTypeIndex = index(of: .nonSpaceOrComment, after: typeAnnotationStartIndex),
let typeRange = parseType(at: startOfTypeIndex)?.range
{
parseIndex = typeRange.upperBound
}
// Properties have an optional `= expression` component
if let assignmentIndex = index(of: .nonSpaceOrCommentOrLinebreak, after: parseIndex),
tokens[assignmentIndex] == .operator("=", .infix)
{
// If the type has an assignment operator, it's guaranteed to be a stored property.
return true
}
// Finally, properties have an optional `{` body
if let startOfBody = index(of: .nonSpaceOrCommentOrLinebreak, after: parseIndex),
tokens[startOfBody] == .startOfScope("{")
{
// If this property has a body, then its a stored property if and only if the body
// has a `didSet` or `willSet` keyword, based on the grammar for a variable declaration.
if let nextToken = next(.nonSpaceOrCommentOrLinebreak, after: startOfBody),
[.identifier("willSet"), .identifier("didSet")].contains(nextToken)
{
return true
} else {
return false
}
}
// If the property declaration isn't followed by a `{ ... }` block,
// then it's definitely a stored property and not a computed property.
// If this property doesn't have a body, then it's definitely a stored property.
guard let bodyRange = property.body?.range,
let firstTokenInBody = token(at: bodyRange.lowerBound)
else {
return true
}
// If this property has a body, then its a stored property if and only if the body
// has a `didSet` or `willSet` keyword, based on the grammar for a variable declaration.
return [.identifier("willSet"), .identifier("didSet")].contains(firstTokenInBody)
}
/// Determine if next line after this token should be indented
@@ -1644,6 +1610,17 @@ extension Formatter {
return startIndex ... endOfExpression
}
/// Whether or not the body within this scope is a single expression
func scopeBodyIsSingleExpression(at startOfScopeIndex: Int) -> Bool {
guard let endOfScopeIndex = endOfScope(at: startOfScopeIndex),
startOfScopeIndex + 1 != endOfScopeIndex,
let firstTokenInBody = index(of: .nonSpaceOrCommentOrLinebreak, after: startOfScopeIndex + 1),
let expressionRange = parseExpressionRange(startingAt: firstTokenInBody, allowConditionalExpressions: true)
else { return false }
return index(of: .nonSpaceOrCommentOrLinebreak, after: expressionRange.upperBound) == endOfScopeIndex
}
/// Whether or not the comment starting at the given index is a doc comment
func isDocComment(startOfComment: Int) -> Bool {
let commentToken = tokens[startOfComment]
@@ -1688,22 +1665,43 @@ extension Formatter {
}
}
/// A property of the format `(let|var) identifier: Type = expression`.
/// - `: Type` and `= expression` elements are optional
/// A property of the format `(let|var) identifier: Type = expression { ... }`.
/// - `: Type`, `= expression`, and the following `{ ... }` body are optional
struct PropertyDeclaration {
/// The start index for this propery's list of modifiers.
/// If there are no modifiers, `startOfModifiersIndex` is just `introducerIndex`.
let startOfModifiersIndex: Int
/// The index of the `let` or `var` keyword
let introducerIndex: Int
/// The identifier / name of this propery.
let identifier: String
/// The index of this property's identifier / name.
let identifierIndex: Int
/// Information about the property's type definition, if written explicitly.
let type: (colonIndex: Int, name: String, range: ClosedRange<Int>)?
/// Information about the value following the propery's `=` token, if present.
let value: (assignmentIndex: Int, expressionRange: ClosedRange<Int>)?
/// Information about the body following the property, which can include
/// `get`, `set`, `willSet`, `didSet` blocks, or just a single getter.
/// - `scopeRange` is the range starting at the `{` token and ending at the `}` token.
/// - `range` is the range of tokens inside the body scope.
let body: (scopeRange: ClosedRange<Int>, range: ClosedRange<Int>)?
var range: ClosedRange<Int> {
if let value = value {
return introducerIndex ... value.expressionRange.upperBound
if let bodyScopeRange = body?.scopeRange {
return startOfModifiersIndex ... bodyScopeRange.upperBound
} else if let value = value {
return startOfModifiersIndex ... value.expressionRange.upperBound
} else if let type = type {
return introducerIndex ... type.range.upperBound
return startOfModifiersIndex ... type.range.upperBound
} else {
return introducerIndex ... identifierIndex
return startOfModifiersIndex ... identifierIndex
}
}
}
@@ -1746,12 +1744,39 @@ extension Formatter {
)
}
let endOfTypeOrIdentifierOrValue = valueInformation?.expressionRange.upperBound ?? endOfTypeOrIdentifier
var body: (scopeRange: ClosedRange<Int>, range: ClosedRange<Int>)?
if let startOfBodyIndex = index(of: .nonSpaceOrCommentOrLinebreak, after: endOfTypeOrIdentifierOrValue),
tokens[startOfBodyIndex] == .startOfScope("{"),
let endOfScope = endOfScope(at: startOfBodyIndex)
{
let bodyRange = startOfBodyIndex ... endOfScope
let rangeInsideBody: ClosedRange<Int>
if startOfBodyIndex + 1 != endOfScope {
if let firstTokenInBody = index(of: .nonSpaceOrCommentOrLinebreak, after: startOfBodyIndex + 1),
let lastTokenInBody = index(of: .nonSpaceOrCommentOrLinebreak, before: endOfScope)
{
rangeInsideBody = firstTokenInBody ... lastTokenInBody
} else {
rangeInsideBody = startOfBodyIndex + 1 ... endOfScope - 1
}
} else {
rangeInsideBody = bodyRange
}
body = (bodyRange, rangeInsideBody)
}
return PropertyDeclaration(
startOfModifiersIndex: startOfModifiers(at: introducerIndex, includingAttributes: true),
introducerIndex: introducerIndex,
identifier: propertyIdentifier.string,
identifierIndex: propertyIdentifierIndex,
type: typeInformation,
value: valueInformation
value: valueInformation,
body: body
)
}
+67 -98
View File
@@ -12,10 +12,10 @@ public extension FormatRule {
// The @Entry macro is only available in Xcode 16 therefore this rule requires the same Xcode version to work.
guard formatter.options.swiftVersion >= "6.0" else { return }
let declarations = formatter.parseDeclarations()
let declarations = formatter.parseDeclarationsV2()
// Find all structs that conform to `EnvironmentKey`
let environmentKeys = Dictionary(uniqueKeysWithValues: formatter.findAllEnvironmentKeys(declarations).map { ($0.key, $0) })
let environmentKeys = formatter.findAllEnvironmentKeys(declarations)
// Find all `EnvironmentValues` properties
let environmentValuesProperties = formatter.findAllEnvironmentValuesProperties(declarations, referencing: environmentKeys)
@@ -24,8 +24,12 @@ public extension FormatRule {
formatter.modifyEnvironmentValuesProperties(environmentValuesProperties)
// Remove `EnvironmentKey`s
let updatedEnvironmentKeys = Set(environmentValuesProperties.map(\.key))
formatter.removeEnvironmentKeys(updatedEnvironmentKeys)
for environmentValuesProperty in environmentValuesProperties {
if let environmentKey = environmentKeys[environmentValuesProperty.key] {
environmentKey.declaration.remove()
}
}
} examples: {
"""
```diff
@@ -49,95 +53,81 @@ public extension FormatRule {
struct EnvironmentKey {
let key: String
let declaration: Declaration
let defaultValueTokens: ArraySlice<Token>?
let isMultilineDefaultValue: Bool
let declaration: DeclarationV2
let defaultValueTokens: [Token]?
}
struct EnvironmentValueProperty {
let key: String
let associatedEnvironmentKey: EnvironmentKey
let declaration: Declaration
let declaration: DeclarationV2
}
extension Formatter {
func findAllEnvironmentKeys(_ declarations: [Declaration]) -> [EnvironmentKey] {
declarations.compactMap { declaration -> EnvironmentKey? in
guard declaration.keyword == "struct" || declaration.keyword == "enum",
declaration.openTokens.contains(.identifier("EnvironmentKey")),
let keyName = declaration.openTokens.first(where: \.isIdentifier),
let structDeclarationBody = declaration.body,
structDeclarationBody.count == 1,
let defaultValueDeclaration = structDeclarationBody.first(where: {
func findAllEnvironmentKeys(_ declarations: [DeclarationV2]) -> [String: EnvironmentKey] {
var environmentKeys = [String: EnvironmentKey]()
for declaration in declarations {
guard let typeDeclaration = declaration as? TypeDeclaration,
typeDeclaration.keyword == "struct" || typeDeclaration.keyword == "enum",
typeDeclaration.conformances.contains(where: { $0.conformance == "EnvironmentKey" }),
let keyName = typeDeclaration.name,
typeDeclaration.body.count == 1,
let defaultValueDeclaration = typeDeclaration.body.first(where: {
($0.keyword == "var" || $0.keyword == "let") && $0.name == "defaultValue"
}),
let (defaultValueTokens, isMultiline) = findEnvironmentKeyDefaultValue(defaultValueDeclaration)
else { return nil }
return EnvironmentKey(
key: keyName.string,
declaration: declaration,
defaultValueTokens: defaultValueTokens,
isMultilineDefaultValue: isMultiline
})
else { continue }
environmentKeys[keyName] = EnvironmentKey(
key: keyName,
declaration: typeDeclaration,
defaultValueTokens: findEnvironmentKeyDefaultValue(defaultValueDeclaration)
)
}
return environmentKeys
}
func findEnvironmentKeyDefaultValue(_ defaultValueDeclaration: Declaration) -> (tokens: ArraySlice<Token>?, isMultiline: Bool)? {
if defaultValueDeclaration.isStaticStoredProperty,
let equalsIndex = index(of: .operator("=", .infix), after: defaultValueDeclaration.originalRange.lowerBound),
equalsIndex <= defaultValueDeclaration.originalRange.upperBound,
let valueStartIndex = index(of: .nonSpaceOrCommentOrLinebreak, after: equalsIndex),
let valueEndIndex = index(of: .nonSpaceOrCommentOrLinebreak, before: defaultValueDeclaration.originalRange.upperBound)
{
// Default value is stored property, not computed (e.g. static var defaultValue: Bool = false)
return (tokens[valueStartIndex ... valueEndIndex], false)
} else if let valueEndOfScopeIndex = endOfScope(at: defaultValueDeclaration.originalRange.upperBound - 1),
let valueStartOfScopeIndex = startOfScope(at: valueEndOfScopeIndex),
let valueStartIndex = index(of: .nonSpaceOrCommentOrLinebreak, after: valueStartOfScopeIndex),
let valueEndIndex = index(of: .nonSpaceOrCommentOrLinebreak, before: valueEndOfScopeIndex)
{
let defaultValueDeclarations = parseDeclarations(in: valueStartIndex ... valueEndIndex)
let isMultilineDeclaration = defaultValueDeclarations.count > 1
if defaultValueDeclarations.count <= 1 {
if defaultValueDeclarations.first?.name == "defaultValue" {
// Default value is implicitly `nil` (e.g. static var defaultValue: Bool?)
return (nil, false)
} else {
// Default value is a computed property with a single value (e.g. static var defaultValue: Bool { false })
return (tokens[valueStartIndex ... valueEndIndex], isMultilineDeclaration)
}
func findEnvironmentKeyDefaultValue(_ defaultValueDeclaration: DeclarationV2) -> [Token]? {
guard let property = defaultValueDeclaration.asPropertyDeclaration else { return nil }
if let valueRange = property.value?.expressionRange {
return Array(tokens[valueRange])
}
else if let body = property.body {
// If the body contains multiple expressions, the final output will need to be wrapped
// in an immediately-executed closure.
if !scopeBodyIsSingleExpression(at: body.scopeRange.lowerBound) {
let existingBodyScope = Array(tokens[body.scopeRange])
return existingBodyScope + [.startOfScope("("), .endOfScope(")")]
} else {
// Default value is a multiline computed property:
// ```
// static var defaultValue: Bool {
// let computedValue = compute()
// return computedValue
// }
// ```
return (tokens[valueStartOfScopeIndex ... valueEndOfScopeIndex], isMultilineDeclaration)
return Array(tokens[body.range])
}
} else { return nil }
}
return nil
}
func findAllEnvironmentValuesProperties(_ declarations: [Declaration], referencing environmentKeys: [String: EnvironmentKey])
-> [EnvironmentValueProperty]
{
func findAllEnvironmentValuesProperties(
_ declarations: [DeclarationV2],
referencing environmentKeys: [String: EnvironmentKey]
) -> [EnvironmentValueProperty] {
declarations
.filter {
$0.keyword == "extension" && $0.openTokens.contains(.identifier("EnvironmentValues"))
$0.keyword == "extension" && $0.name == "EnvironmentValues"
}.compactMap { environmentValuesDeclaration -> [EnvironmentValueProperty]? in
environmentValuesDeclaration.body?.compactMap { propertyDeclaration -> (EnvironmentValueProperty)? in
guard propertyDeclaration.isSimpleDeclaration,
propertyDeclaration.keyword == "var",
guard propertyDeclaration.keyword == "var",
let key = propertyDeclaration.tokens.first(where: { environmentKeys[$0.string] != nil })?.string,
let environmentKey = environmentKeys[key]
else { return nil }
// Ensure the property has a setter and a getter, this can avoid edge cases where
// a property references a `EnvironmentKey` and consumes it to perform some computation.
let propertyFormatter = Formatter(propertyDeclaration.tokens)
guard let indexOfSetter = propertyDeclaration.tokens.firstIndex(where: { $0 == .identifier("set") }),
propertyFormatter.isAccessorKeyword(at: indexOfSetter)
guard let bodyRange = propertyDeclaration.asPropertyDeclaration?.body?.range,
let indexOfSetter = index(of: .identifier("set"), in: Range(bodyRange)),
isAccessorKeyword(at: indexOfSetter)
else { return nil }
return EnvironmentValueProperty(
@@ -150,46 +140,25 @@ extension Formatter {
}
func modifyEnvironmentValuesProperties(_ environmentValuesPropertiesDeclarations: [EnvironmentValueProperty]) {
// Loop the collection in reverse to avoid invalidating the declaration indexes as we modify the property
for envProperty in environmentValuesPropertiesDeclarations.reversed() {
let propertyDeclaration = envProperty.declaration
guard let propertyBodyStartIndex = index(of: .startOfScope("{"), after: propertyDeclaration.originalRange.lowerBound),
let propertyBodyEndIndex = endOfScope(at: propertyBodyStartIndex),
let propertyStartIndex = index(of: .nonSpaceOrCommentOrLinebreak, after: propertyDeclaration.originalRange.lowerBound)
else {
continue
}
for envProperty in environmentValuesPropertiesDeclarations {
guard let propertyDeclaration = envProperty.declaration.asPropertyDeclaration,
let bodyScopeRange = propertyDeclaration.body?.scopeRange
else { continue }
// Remove `EnvironmentValues.property` getter and setters
if let nonSpaceTokenIndexBeforeBody = index(of: .nonSpaceOrLinebreak, before: propertyBodyStartIndex), nonSpaceTokenIndexBeforeBody != propertyBodyStartIndex {
if let nonSpaceTokenIndexBeforeBody = index(of: .nonSpaceOrLinebreak, before: bodyScopeRange.lowerBound), nonSpaceTokenIndexBeforeBody != bodyScopeRange.lowerBound {
// There are some spaces between the property body and the property type definition, we should remove the extra spaces.
let propertyBodyStartIndex = nonSpaceTokenIndexBeforeBody + 1
removeTokens(in: propertyBodyStartIndex ... propertyBodyEndIndex)
removeTokens(in: nonSpaceTokenIndexBeforeBody + 1 ... bodyScopeRange.upperBound)
} else {
removeTokens(in: propertyBodyStartIndex ... propertyBodyEndIndex)
removeTokens(in: bodyScopeRange)
}
// Add `EnvironmentKey.defaultValue` to `EnvironmentValues property`
if let defaultValueTokens = envProperty.associatedEnvironmentKey.defaultValueTokens {
var defaultValueTokens = [.space(" "), .operator("=", .infix), .space(" ")] + defaultValueTokens
if envProperty.associatedEnvironmentKey.isMultilineDefaultValue {
defaultValueTokens.append(contentsOf: [.endOfScope("("), .endOfScope(")")])
}
insert(defaultValueTokens, at: endOfLine(at: propertyStartIndex))
let defaultValueTokens = [.space(" "), .operator("=", .infix), .space(" ")] + defaultValueTokens
insert(defaultValueTokens, at: endOfLine(at: propertyDeclaration.range.lowerBound))
}
// Add @Entry Macro
insert([.identifier("@Entry"), .space(" ")], at: propertyStartIndex)
}
}
func removeEnvironmentKeys(_ updatedEnvironmentKeys: Set<String>) {
guard !updatedEnvironmentKeys.isEmpty else { return }
// After modifying the EnvironmentValues properties, parse declarations again to delete the Environment keys in their new position.
let repositionedEnvironmentKeys = findAllEnvironmentKeys(parseDeclarations())
// Loop the collection in reverse to avoid invalidating the declaration indexes as we remove EnvironmentKey
for declaration in repositionedEnvironmentKeys.reversed() where updatedEnvironmentKeys.contains(declaration.key) {
removeTokens(in: declaration.declaration.originalRange)
insert([.identifier("@Entry"), .space(" ")], at: propertyDeclaration.range.lowerBound)
}
}
}
+5 -5
View File
@@ -74,7 +74,7 @@ public extension FormatRule {
if memberVisibility != extensionVisibility,
!(memberVisibility == .internal && visibilityKeyword == nil)
{
extensionDeclaration.add(memberVisibility)
extensionDeclaration.addVisibility(memberVisibility)
}
extensionDeclaration.body.forEachRecursiveDeclarationExcludingTypeBodies { bodyDeclaration in
@@ -82,11 +82,11 @@ public extension FormatRule {
let visibility = bodyDeclaration.visibility()
if memberVisibility > visibility ?? extensionVisibility ?? .internal {
if visibility == nil {
bodyDeclaration.add(.internal)
bodyDeclaration.addVisibility(.internal)
}
return
}
bodyDeclaration.remove(memberVisibility)
bodyDeclaration.removeVisibility(memberVisibility)
}
// Move the extension's visibility keyword to each individual declaration
@@ -95,7 +95,7 @@ public extension FormatRule {
guard let extensionVisibility = extensionVisibility else { return }
// Remove the visibility keyword from the extension declaration itself
extensionDeclaration.remove(visibilityKeyword!)
extensionDeclaration.removeVisibility(visibilityKeyword!)
// And apply the extension's visibility to each of its child declarations
// that don't have an explicit visibility keyword
@@ -103,7 +103,7 @@ public extension FormatRule {
if bodyDeclaration.visibility() == nil {
// If there was no explicit visibility keyword, then this declaration
// was using the visibility of the extension itself.
bodyDeclaration.add(extensionVisibility)
bodyDeclaration.addVisibility(extensionVisibility)
}
}
}