File: WithAttributesSyntaxAdditions.swift

package info (click to toggle)
swiftlang 6.0.3-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 2,519,992 kB
  • sloc: cpp: 9,107,863; ansic: 2,040,022; asm: 1,135,751; python: 296,500; objc: 82,456; f90: 60,502; lisp: 34,951; pascal: 19,946; sh: 18,133; perl: 7,482; ml: 4,937; javascript: 4,117; makefile: 3,840; awk: 3,535; xml: 914; fortran: 619; cs: 573; ruby: 573
file content (142 lines) | stat: -rw-r--r-- 5,611 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2023 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 Swift project authors
//

import SwiftSyntax
import SwiftSyntaxMacros

extension WithAttributesSyntax {
  /// The set of availability attributes on this instance.
  var availabilityAttributes: [AttributeSyntax] {
    attributes.lazy
      .compactMap { attribute in
        if case let .attribute(attribute) = attribute {
          return attribute
        }
        return nil
      }.filter { attribute in
        if case .availability = attribute.arguments {
          return true
        }
        return false
      }
  }

  /// Get the set of version-based availability constraints on this instance.
  ///
  /// - Parameters:
  ///   - whenKeyword: The keyword to filter the result by, such as
  ///     `.introduced` or `.deprecated`. If `.introduced` is specified, then
  ///     shorthand `@available` attributes such as `@available(macOS 999.0, *)`
  ///     are included.
  ///
  /// - Returns: An array of structures describing the version-based
  ///   availability constraints on this instance, such as `("macOS", 999.0)`.
  ///
  /// The values in the resulting array can be used to construct expressions
  /// such as `if #available(macOS 999.0, *)`.
  func availability(when whenKeyword: Keyword) -> [Availability] {
    availabilityAttributes.flatMap { attribute -> [Availability] in
      guard case let .availability(specList) = attribute.arguments else {
        return []
      }

      let entries = specList.map(\.argument)

      // First, find the message (if any) to apply to any values produced from
      // this spec list.
      let message = entries.lazy.compactMap { entry in
          if case let .availabilityLabeledArgument(argument) = entry,
             argument.label.tokenKind == .keyword(.message),
             case let .string(message) = argument.value {
            return message
          }
          return nil
        }.first

      var lastPlatformName: TokenSyntax? = nil
      var asteriskEncountered = false
      return entries.compactMap { entry in
        switch entry {
        case let .availabilityVersionRestriction(restriction) where whenKeyword == .introduced:
          return Availability(attribute: attribute, platformName: restriction.platform, version: restriction.version, message: message)
        case let .token(token):
          if case .identifier = token.tokenKind {
            lastPlatformName = token
          } else if case let .binaryOperator(op) = token.tokenKind, op == "*" {
            asteriskEncountered = true
            // It is syntactically valid to specify a platform name without a
            // version in an availability declaration, and it's used to resolve
            // a custom availability definition specified via the
            // `-define-availability` compiler flag. So if there was a "last"
            // platform name and we encounter an asterisk token, append it as an
            // `Availability` with a `nil` version.
            if let lastPlatformName, whenKeyword == .introduced {
              return Availability(attribute: attribute, platformName: lastPlatformName, version: nil, message: message)
            }
          } else if case let .keyword(keyword) = token.tokenKind, keyword == whenKeyword, asteriskEncountered {
            // Match the "always this availability" construct, i.e.
            // `@available(*, deprecated)` and `@available(*, unavailable)`.
            return Availability(attribute: attribute, platformName: lastPlatformName, version: nil, message: message)
          }
        case let .availabilityLabeledArgument(argument):
          if argument.label.tokenKind == .keyword(whenKeyword), case let .version(version) = argument.value {
            return Availability(attribute: attribute, platformName: lastPlatformName, version: version, message: message)
          }
        default:
          break
        }

        return nil
      }
    }
  }

  /// The first `@available(*, noasync)` or `@_unavailableFromAsync` attribute
  /// on this instance, if any.
  var noasyncAttribute: AttributeSyntax? {
    availability(when: .noasync).first?.attribute ?? attributes.lazy
      .compactMap { attribute in
        if case let .attribute(attribute) = attribute {
          return attribute
        }
        return nil
      }.first { $0.attributeNameText == "_unavailableFromAsync" }
  }

  /// Find all attributes on this node, if any, with the given name.
  ///
  /// - Parameters:
  ///   - name: The name of the attribute to look for.
  ///   - moduleName: The name of the module that declares the attribute named
  ///     `name`.
  ///
  /// - Returns: An array of `AttributeSyntax` corresponding to the attached
  ///   `@Test` attributes, or the empty array if none is attached.
  func attributes(named name: String, inModuleNamed moduleName: String = "Testing") -> [AttributeSyntax] {
    attributes.lazy.compactMap { attribute in
      if case let .attribute(attribute) = attribute {
        return attribute
      }
      return nil
    }.filter {
      $0.attributeName.isNamed(name, inModuleNamed: moduleName)
    }
  }
}

extension AttributeSyntax {
  /// The text of this attribute's name.
  var attributeNameText: String {
    attributeName
      .tokens(viewMode: .fixedUp)
      .map(\.textWithoutBackticks)
      .joined()
  }
}