mirror of
https://github.com/nicklockwood/SwiftFormat.git
synced 2026-06-16 10:34:34 +00:00
Migrate environmentEntry rule to DeclarationV2
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user