diff --git a/Sources/SwiftParserDiagnostics/MultiLineStringLiteralDiagnosticsGenerator.swift b/Sources/SwiftParserDiagnostics/MultiLineStringLiteralDiagnosticsGenerator.swift index 9a0a2a5e7fd..2739d2aad3d 100644 --- a/Sources/SwiftParserDiagnostics/MultiLineStringLiteralDiagnosticsGenerator.swift +++ b/Sources/SwiftParserDiagnostics/MultiLineStringLiteralDiagnosticsGenerator.swift @@ -61,7 +61,7 @@ final class MultiLineStringLiteralIndentatinDiagnosticsGenerator: SyntaxVisitor let indentationStartIndex = tokenLeadingTrivia.pieces.lastIndex(where: { $0.isNewline })?.advanced(by: 1) ?? tokenLeadingTrivia.startIndex let preIndentationTrivia = Trivia(pieces: tokenLeadingTrivia[0.. String { if let firstLetter = self.first { return firstLetter.uppercased() + self.dropFirst() @@ -19,6 +22,9 @@ extension String { } } + /// Replace the first occurance of `character` with `replacement`. + /// + /// If `character` does not occur in this string, no change is made. func replacingFirstOccurence(of character: Character, with replacement: Character) -> String { guard let match = self.firstIndex(of: character) else { return self @@ -26,6 +32,9 @@ extension String { return self[startIndex.. String { guard let match = self.lastIndex(of: character) else { return self diff --git a/Sources/SwiftSyntax/BumpPtrAllocator.swift b/Sources/SwiftSyntax/BumpPtrAllocator.swift index b9be43fbc34..e5c4d5830b8 100644 --- a/Sources/SwiftSyntax/BumpPtrAllocator.swift +++ b/Sources/SwiftSyntax/BumpPtrAllocator.swift @@ -10,7 +10,9 @@ // //===----------------------------------------------------------------------===// -/// Bump-pointer allocation. +/// A `BumpPtrAllocator` that allocates `slabSize` at a time. +/// Once all memory in a slab has been used, it allocates a new slab and no +/// memory allocations are necessary until that slab is completely filled up. @_spi(RawSyntax) @_spi(Testing) public class BumpPtrAllocator { typealias Slab = UnsafeMutableRawBufferPointer @@ -35,6 +37,9 @@ public class BumpPtrAllocator { private var customSizeSlabs: [Slab] private var _totalBytesAllocated: Int + /// Construct a new ``BumpPtrAllocator`` that allocates `slabSize` at a time. + /// Once all memory in a slab has been used, it allocates a new slab and no + /// memory allocations are necessary until that slab is completely filled up. public init(slabSize: Int) { self.slabSize = slabSize slabs = [] diff --git a/Sources/SwiftSyntax/IncrementalParseTransition.swift b/Sources/SwiftSyntax/IncrementalParseTransition.swift index a5cf01bcb22..a967a0294bc 100644 --- a/Sources/SwiftSyntax/IncrementalParseTransition.swift +++ b/Sources/SwiftSyntax/IncrementalParseTransition.swift @@ -206,6 +206,8 @@ public struct IncrementalParseLookup { fileprivate let transition: IncrementalParseTransition fileprivate var cursor: SyntaxCursor + /// Create a new `IncrementalParseLookup` that can look nodes up based on the + /// given ``IncrementalParseTransition``. public init(transition: IncrementalParseTransition) { self.transition = transition self.cursor = .init(root: transition.previousTree.data) diff --git a/Sources/SwiftSyntax/Raw/RawSyntax.swift b/Sources/SwiftSyntax/Raw/RawSyntax.swift index 55c2eea80a2..68ced4fa8ae 100644 --- a/Sources/SwiftSyntax/Raw/RawSyntax.swift +++ b/Sources/SwiftSyntax/Raw/RawSyntax.swift @@ -342,10 +342,12 @@ extension RawSyntax { } extension RawSyntax { + @_spi(RawSyntax) public func toOpaque() -> UnsafeRawPointer { UnsafeRawPointer(pointer) } + @_spi(RawSyntax) public static func fromOpaque(_ pointer: UnsafeRawPointer) -> RawSyntax { Self(pointer: pointer.assumingMemoryBound(to: RawSyntaxData.self)) } @@ -866,6 +868,7 @@ extension RawSyntax: CustomDebugStringConvertible { target.write(")") } + @_spi(RawSyntax) public var debugDescription: String { var string = "" debugWrite(to: &string, indent: 0, withChildren: false) @@ -874,6 +877,7 @@ extension RawSyntax: CustomDebugStringConvertible { } extension RawSyntax: CustomReflectable { + @_spi(RawSyntax) public var customMirror: Mirror { let mirrorChildren: [Any] diff --git a/Sources/SwiftSyntax/Raw/RawSyntaxNodeProtocol.swift b/Sources/SwiftSyntax/Raw/RawSyntaxNodeProtocol.swift index d8671dda88e..a358d9103c1 100644 --- a/Sources/SwiftSyntax/Raw/RawSyntaxNodeProtocol.swift +++ b/Sources/SwiftSyntax/Raw/RawSyntaxNodeProtocol.swift @@ -58,15 +58,19 @@ public extension RawSyntaxNodeProtocol { /// `RawSyntax` itself conforms to `RawSyntaxNodeProtocol`. extension RawSyntax: RawSyntaxNodeProtocol { + @_spi(RawSyntax) public static func isKindOf(_ raw: RawSyntax) -> Bool { return true } + + @_spi(RawSyntax) public var raw: RawSyntax { self } init(raw: RawSyntax) { self = raw } + @_spi(RawSyntax) public init(_ other: some RawSyntaxNodeProtocol) { self.init(raw: other.raw) } @@ -75,6 +79,7 @@ extension RawSyntax: RawSyntaxNodeProtocol { #if swift(<5.8) // Cherry-pick this function from SE-0370 extension Slice { + @_spi(RawSyntax) @inlinable public func initialize( from source: S @@ -90,6 +95,7 @@ extension Slice { @_spi(RawSyntax) public struct RawTokenSyntax: RawSyntaxNodeProtocol { + @_spi(RawSyntax) public typealias SyntaxType = TokenSyntax @_spi(RawSyntax) @@ -97,10 +103,12 @@ public struct RawTokenSyntax: RawSyntaxNodeProtocol { return raw.tokenView! } + @_spi(RawSyntax) public static func isKindOf(_ raw: RawSyntax) -> Bool { return raw.kind == .token } + @_spi(RawSyntax) public var raw: RawSyntax init(raw: RawSyntax) { @@ -112,43 +120,53 @@ public struct RawTokenSyntax: RawSyntaxNodeProtocol { self.raw = raw } + @_spi(RawSyntax) public init?(_ other: some RawSyntaxNodeProtocol) { guard Self.isKindOf(other.raw) else { return nil } self.init(unchecked: other.raw) } + @_spi(RawSyntax) public var tokenKind: RawTokenKind { return tokenView.rawKind } + @_spi(RawSyntax) public var tokenText: SyntaxText { return tokenView.rawText } + @_spi(RawSyntax) public var byteLength: Int { return raw.byteLength } + @_spi(RawSyntax) public var presence: SourcePresence { tokenView.presence } + @_spi(RawSyntax) public var isMissing: Bool { presence == .missing } + @_spi(RawSyntax) public var leadingTriviaByteLength: Int { return tokenView.leadingTriviaByteLength } + @_spi(RawSyntax) public var leadingTriviaPieces: [RawTriviaPiece] { tokenView.leadingRawTriviaPieces } + @_spi(RawSyntax) public var trailingTriviaByteLength: Int { return tokenView.trailingTriviaByteLength } + @_spi(RawSyntax) public var trailingTriviaPieces: [RawTriviaPiece] { tokenView.trailingRawTriviaPieces } diff --git a/Sources/SwiftSyntax/SourceLength.swift b/Sources/SwiftSyntax/SourceLength.swift index 1b12ec0a01a..1879bb7eb7f 100644 --- a/Sources/SwiftSyntax/SourceLength.swift +++ b/Sources/SwiftSyntax/SourceLength.swift @@ -13,6 +13,7 @@ /// The length a syntax node spans in the source code. From any AbsolutePosition /// you reach a node's end location by adding its UTF-8 length. public struct SourceLength: Comparable { + /// The length in bytes when the text is represented as UTF-8. public let utf8Length: Int /// Construct the source length of a given text @@ -20,6 +21,7 @@ public struct SourceLength: Comparable { self.utf8Length = text.utf8.count } + /// Construct a source length that takes up `utf8Length` bytes when represented as UTF-8. public init(utf8Length: Int) { self.utf8Length = utf8Length } @@ -28,6 +30,7 @@ public struct SourceLength: Comparable { public static let zero: SourceLength = SourceLength(utf8Length: 0) + /// Returns `true` if `lhs` is shorter than `rhs`. public static func < (lhs: SourceLength, rhs: SourceLength) -> Bool { return lhs.utf8Length < rhs.utf8Length } @@ -38,15 +41,18 @@ public struct SourceLength: Comparable { return SourceLength(utf8Length: utf8Length) } + /// Extend `lhs` by `rhs` bytes. public static func += (lhs: inout SourceLength, rhs: SourceLength) { lhs = lhs + rhs } + /// Return a source length that's `rhs` bytes shorter than `lhs`. public static func - (lhs: SourceLength, rhs: SourceLength) -> SourceLength { let utf8Length = lhs.utf8Length - rhs.utf8Length return SourceLength(utf8Length: utf8Length) } + /// Change `lhs` to be `rhs` bytes shorter than `lhs`. public static func -= (lhs: inout SourceLength, rhs: SourceLength) { lhs = lhs - rhs } @@ -62,10 +68,12 @@ extension AbsolutePosition { return AbsolutePosition(utf8Offset: utf8Offset) } + /// Advance `lhs` by `rhs`, i.e. changing it to the position `rhs` bytes after `lhs`. public static func += (lhs: inout AbsolutePosition, rhs: SourceLength) { lhs = lhs + rhs } + /// Return the position `rhs` bytes before `lhs`. public static func - ( lhs: AbsolutePosition, rhs: SourceLength @@ -74,6 +82,7 @@ extension AbsolutePosition { return AbsolutePosition(utf8Offset: utf8Offset) } + /// Reverse `lhs` by `rhs`, i.e. changing it `lhs` to the position `rhs` bytes before `lhs`. public static func -= (lhs: inout AbsolutePosition, rhs: SourceLength) { lhs = lhs - rhs } diff --git a/Sources/SwiftSyntax/SourceLocation.swift b/Sources/SwiftSyntax/SourceLocation.swift index b961e2658b7..80b421ce7b4 100644 --- a/Sources/SwiftSyntax/SourceLocation.swift +++ b/Sources/SwiftSyntax/SourceLocation.swift @@ -26,11 +26,27 @@ public struct SourceLocation: Hashable, Codable, CustomDebugStringConvertible { /// The file in which this location resides. public let file: String + /// Returns the location as `:` for debugging purposes. + /// Do not rely on this output being stable. public var debugDescription: String { // Print file name? return "\(line):\(column)" } + /// Create a new source location at the specified `line` and `column` in `file`. + /// + /// - Parameters: + /// - line: 1-based, i.e. the first line in the file has line number 1 + /// - column: The UTF-8 byte offset of the location with its line, i.e. the + /// number of bytes all characters in the line before the location + /// occupy when encoded as UTF-8. 1-based, i.e. the leftmost + /// column in the file has column 1. + /// - offset: The UTF-8 offset of the location within the entire file, i.e. + /// the number of bytes all source code before the location + /// occupies when encoded as UTF-8. 0-based, i.e. the first + /// location in the source file has `offset` 0. + /// - file: A string describing the name of the file in which this location + /// is contained. public init(line: Int, column: Int, offset: Int, file: String) { self.line = line self.offset = offset @@ -43,15 +59,22 @@ public struct SourceLocation: Hashable, Codable, CustomDebugStringConvertible { public struct SourceRange: Hashable, Codable, CustomDebugStringConvertible { /// The beginning location in the source range. + /// + /// This location is included in the range public let start: SourceLocation /// The beginning location in the source range. + /// + /// This location is no longer part of the range public let end: SourceLocation + /// A description describing this range for debugging purposes, don't rely on it. public var debugDescription: String { return "(\(start.debugDescription),\(end.debugDescription))" } + /// Construct a new source range, starting at `start` (inclusive) and ending + /// at `end` (exclusive). public init(start: SourceLocation, end: SourceLocation) { self.start = start self.end = end diff --git a/Sources/SwiftSyntax/Syntax.swift b/Sources/SwiftSyntax/Syntax.swift index 239c7a44bba..410f83308e5 100644 --- a/Sources/SwiftSyntax/Syntax.swift +++ b/Sources/SwiftSyntax/Syntax.swift @@ -26,6 +26,7 @@ public enum SyntaxNodeStructure { /// The node can contain a single node with one of the listed types. case choices([SyntaxChoice]) + /// Convenience property that’s `true` if `self` is the `layout` case. public var isLayout: Bool { switch self { case .layout: return true @@ -33,6 +34,7 @@ public enum SyntaxNodeStructure { } } + /// Convenience property that’s `true` if `self` is the `collection` case. public var isCollection: Bool { switch self { case .collection: return true @@ -40,6 +42,7 @@ public enum SyntaxNodeStructure { } } + /// Convenience property that’s `true` if `self` is the `choices` case. public var isChoices: Bool { switch self { case .choices: return true @@ -54,6 +57,9 @@ public enum SyntaxNodeStructure { public struct Syntax: SyntaxProtocol, SyntaxHashable { let data: SyntaxData + /// Needed for the conformance to ``SyntaxProtocol``. + /// + /// Kind of non-sensical on `Syntax` since this just returns `self`. public var _syntaxNode: Syntax { return self } @@ -72,10 +78,12 @@ public struct Syntax: SyntaxProtocol, SyntaxHashable { self = syntax._syntaxNode } + /// Creates a new `Syntax` node from any concrete node that conforms to `SyntaxProtocol`. public init(fromProtocol syntax: SyntaxProtocol) { self = syntax._syntaxNode } + /// Same as ``init(fromProtocol:)`` but returns `nil` if `syntax` is `nil`. public init?(fromProtocol syntax: SyntaxProtocol?) { guard let syntax = syntax else { return nil } self = syntax._syntaxNode @@ -95,16 +103,29 @@ public struct Syntax: SyntaxProtocol, SyntaxHashable { return self.raw.kind.syntaxNodeType.init(self)! } + /// Add the hash value of this node’s ID to `hasher`. public func hash(into hasher: inout Hasher) { return data.nodeId.hash(into: &hasher) } + /// Returns `true` if `rhs` and `lhs` have the same ID. + /// + /// Note `lhs` and `rhs` might have the same contents even if their IDs are + /// different. For example two different `FunctionDeclSyntax` nodes in the + /// might have the exact same contents but if they occur at a different + /// location in the source file, they have different IDs. + /// + /// Also note that the ID of a syntax node changes when it is anchored in a + /// different syntax tree. Modifying any node in the syntax tree a node is + /// contained in generates a copy of that tree and thus changes the IDs of all + /// nodes in the tree, not just the modified node's children. public static func == (lhs: Syntax, rhs: Syntax) -> Bool { return lhs.data.nodeId == rhs.data.nodeId } } extension Syntax: Identifiable { + /// `SyntaxIdentifier` uniquely identifies a node. public typealias ID = SyntaxIdentifier } @@ -215,10 +236,19 @@ extension SyntaxProtocol { return Syntax(raw: self.raw).cast(Self.self) } + /// The kind of the syntax node, e.g. if it is a `functionDecl`. + /// + /// If you want to check for a node's kind and cast the node to that type, see + /// `self.as(FunctionDeclSyntax.self)` or `self.as(SyntaxEnum.self)`. public var kind: SyntaxKind { return raw.kind } + /// The dynamic metatype of this node. You almost always want to prefer this + /// over `type(of: self)` because if `self` is a `DeclSyntax` representing a + /// `FunctionDeclSyntax`, `type(of: self)` will return `DeclSyntax`, while + /// `syntaxNodeType` looks at the dynamic kind of this node and returns + /// `FunctionDeclSyntax`. public var syntaxNodeType: SyntaxProtocol.Type { return self.raw.kind.syntaxNodeType } @@ -680,6 +710,7 @@ public protocol SyntaxChildChoices: SyntaxProtocol {} /// Sequence of tokens that are part of the provided Syntax node. public struct TokenSequence: Sequence { + /// Iterates over a ``TokenSequence``. public struct Iterator: IteratorProtocol { var nextToken: TokenSyntax? /// The last token to iterate (inclusive). @@ -692,6 +723,7 @@ public struct TokenSequence: Sequence { self.viewMode = viewMode } + /// Return the next element in a ``TokenSequence``. public mutating func next() -> TokenSyntax? { guard let token = self.nextToken else { return nil } if nextToken == endToken { @@ -706,21 +738,28 @@ public struct TokenSequence: Sequence { let node: Syntax let viewMode: SyntaxTreeViewMode + /// Construct a `TokenSequence` that walks all tokens in `node` in source order, + /// recursively walking into child nodes. + /// + /// All nodes that are not visible in the given `viewMode` are skipped. public init(_ node: Syntax, viewMode: SyntaxTreeViewMode) { self.node = node self.viewMode = viewMode } + /// Create an iterator that iterates over all the tokens in the sequence. public func makeIterator() -> Iterator { return Iterator(node.firstToken(viewMode: viewMode), endToken: node.lastToken(viewMode: viewMode), viewMode: viewMode) } + /// Iterate the tokens in reverse order. public func reversed() -> ReversedTokenSequence { return ReversedTokenSequence(node, viewMode: viewMode) } } extension TokenSequence: CustomReflectable { + /// A custom mirror for ``TokenSequence`` that shows all elements in the sequence. public var customMirror: Mirror { return Mirror(self, unlabeledChildren: self.map { $0 }) } @@ -728,6 +767,7 @@ extension TokenSequence: CustomReflectable { /// Reverse sequence of tokens that are part of the provided Syntax node. public struct ReversedTokenSequence: Sequence { + /// Iterates over a ``ReversedTokenSequence``. public struct Iterator: IteratorProtocol { var nextToken: TokenSyntax? let startPosition: AbsolutePosition @@ -739,6 +779,8 @@ public struct ReversedTokenSequence: Sequence { self.viewMode = viewMode } + /// Returns the next element in a ``ReversedTokenSequence``, i.e. the one + /// that occured before the current token in source order. public mutating func next() -> TokenSyntax? { guard let token = self.nextToken else { return nil } self.nextToken = token.previousToken(viewMode: viewMode) @@ -753,25 +795,34 @@ public struct ReversedTokenSequence: Sequence { let node: Syntax let viewMode: SyntaxTreeViewMode + /// Construct a ``TokenSequence`` that walks all tokens in `node` in reverse + /// source order, recursively walking into child nodes. + /// + /// All nodes that are not visible in the given `viewMode` are skipped. public init(_ node: Syntax, viewMode: SyntaxTreeViewMode) { self.node = node self.viewMode = viewMode } + /// Create an iterator that iterates over all the tokens in the sequence. public func makeIterator() -> Iterator { return Iterator(node.lastToken(viewMode: viewMode), startPosition: node.position, viewMode: viewMode) } + /// Iterate over the tokens in source order. public func reversed() -> TokenSequence { return TokenSequence(node, viewMode: viewMode) } } extension ReversedTokenSequence: CustomReflectable { + /// A custom mirror for ``ReversedTokenSequence`` that shows all elements in the sequence. public var customMirror: Mirror { return Mirror(self, unlabeledChildren: self.map { $0 }) } } +/// `SyntaxNode` used to be a pervasive type name in SwiftSyntax that has been +/// replaced by the ``Syntax`` type. @available(*, unavailable, message: "use 'Syntax' instead") public struct SyntaxNode {} diff --git a/Sources/SwiftSyntax/SyntaxArena.swift b/Sources/SwiftSyntax/SyntaxArena.swift index f3c9ca1226c..1cf0f829bd6 100644 --- a/Sources/SwiftSyntax/SyntaxArena.swift +++ b/Sources/SwiftSyntax/SyntaxArena.swift @@ -10,6 +10,31 @@ // //===----------------------------------------------------------------------===// +/// A syntax arena owns the memory for all syntax nodes within it. +/// +/// The following is only relevant if you are accessing the raw syntax tree using +/// ``RawSyntax`` nodes. When working with syntax trees using SwiftSyntax’s API, +/// the usage of a ``SyntaxArena`` is transparent. +/// +/// Contrary to Swift’s usual memory model, syntax node's are not individually +/// reference-counted. Instead, they live in an arena. That arena allocates a +/// chunk of memory at a time, which it can then use to store syntax nodes in. +/// This way, only a single memory allocation needs to be performed for multiple +/// syntax nodes and since memory allocations have a non-trivial cost, this +/// signficiantly speeds up parsing. +/// +/// As a consequence, syntax nodes cannot be freed individually but the memory +/// will get freed once the owning ``SyntaxArena`` gets freed. Thus, it needs to +/// be manually ensured that the ``SyntaxArena`` is not deallocated before any +/// of its nodes are being accessed. The ``SyntaxData`` type ensures this as +/// follows: Each node retains its parent ``SyntaxData``, thus keeping it alive. +/// The tree’s root keeps the ``SyntaxArena`` it is contained in alive. And if +/// any children of this tree are allocated within a different ``SyntaxArena``, +/// the root arena keeps those arenas alive via the `childRefs` property. +/// +/// As an added benefit of the ``SyntaxArena``, `RawSyntax` nodes don’t need to +/// be reference-counted, further improving the performance of ``SwiftSyntax`` +/// when worked with at that level. public class SyntaxArena { /// Bump-pointer allocator for all "intern" methods. fileprivate let allocator: BumpPtrAllocator @@ -25,6 +50,7 @@ public class SyntaxArena { private var hasParent: Bool #endif + /// Construct a new ``SyntaxArena`` in which syntax nodes can be allocated. public convenience init() { self.init(slabSize: 128) } diff --git a/Sources/SwiftSyntax/SyntaxChildren.swift b/Sources/SwiftSyntax/SyntaxChildren.swift index 5db5b440c94..ff0b68faae2 100644 --- a/Sources/SwiftSyntax/SyntaxChildren.swift +++ b/Sources/SwiftSyntax/SyntaxChildren.swift @@ -51,6 +51,7 @@ struct SyntaxChildrenIndexData: Comparable { /// An index in a syntax children collection. public struct SyntaxChildrenIndex: Comparable, ExpressibleByNilLiteral { + /// Construct the `endIndex` of a ``SyntaxChildren`` collection. public init(nilLiteral: ()) { self.data = nil } @@ -84,6 +85,7 @@ public struct SyntaxChildrenIndex: Comparable, ExpressibleByNilLiteral { self.data = SyntaxChildrenIndexData(absoluteSyntaxInfo) } + /// Returns `true` if `lhs` occurs before `rhs`. public static func < (lhs: SyntaxChildrenIndex, rhs: SyntaxChildrenIndex) -> Bool { @@ -423,7 +425,10 @@ struct NonNilRawSyntaxChildren: BidirectionalCollection { /// Collection that contains the present child `Syntax` nodes of the given node. public struct SyntaxChildren: BidirectionalCollection { + /// ``SyntaxChildren`` is indexed by ``SyntaxChildrenIndex``. public typealias Index = SyntaxChildrenIndex + + /// ``SyntaxChildren`` contains ``Syntax`` nodes. public typealias Element = Syntax /// The collection that contains the raw child nodes. `Syntax` nodes are @@ -433,19 +438,25 @@ public struct SyntaxChildren: BidirectionalCollection { /// The parent node of the children. Used to build the `Syntax` nodes. private let parent: Syntax + /// The index of the first child in this collection. public var startIndex: SyntaxChildrenIndex { return rawChildren.startIndex } + + /// The index that’s one after the last element in the collection. public var endIndex: SyntaxChildrenIndex { return rawChildren.endIndex } + /// The index for the child that’s after the child at `index`. public func index(after index: SyntaxChildrenIndex) -> SyntaxChildrenIndex { return rawChildren.index(after: index) } + /// The index for the child that’s before the child at `index`. public func index(before index: SyntaxChildrenIndex) -> SyntaxChildrenIndex { return rawChildren.index(before: index) } - public subscript(position: SyntaxChildrenIndex) -> Syntax { - let child = rawChildren[position] + /// The syntax node at the given `index` + public subscript(index: SyntaxChildrenIndex) -> Syntax { + let child = rawChildren[index] let data = SyntaxData(child, parent: parent) return Syntax(data) } diff --git a/Sources/SwiftSyntax/SyntaxData.swift b/Sources/SwiftSyntax/SyntaxData.swift index f4b1da68079..b8ddc7f641e 100644 --- a/Sources/SwiftSyntax/SyntaxData.swift +++ b/Sources/SwiftSyntax/SyntaxData.swift @@ -87,12 +87,23 @@ public struct SyntaxIndexInTree: Comparable, Hashable { self.indexInTree = indexInTree } + /// Returns `true` if `lhs` occurs before `rhs` in the tree. public static func < (lhs: SyntaxIndexInTree, rhs: SyntaxIndexInTree) -> Bool { return lhs.indexInTree < rhs.indexInTree } } /// Provides a stable and unique identity for `Syntax` nodes. +/// +/// Note that two nodes might have the same contents even if their IDs are +/// different. For example two different `FunctionDeclSyntax` nodes in the +/// might have the exact same contents but if they occur at a different +/// location in the source file, they have different IDs. +/// +/// Also note that the ID of a syntax node changes when it is anchored in a +/// different syntax tree. Modifying any node in the syntax tree a node is +/// contained in generates a copy of that tree and thus changes the IDs of all +/// nodes in the tree, not just the modified node's children. public struct SyntaxIdentifier: Hashable { /// Unique value for the root node. /// diff --git a/Sources/SwiftSyntax/SyntaxText.swift b/Sources/SwiftSyntax/SyntaxText.swift index 0c9cf962f1b..271c4381039 100644 --- a/Sources/SwiftSyntax/SyntaxText.swift +++ b/Sources/SwiftSyntax/SyntaxText.swift @@ -37,10 +37,15 @@ public struct SyntaxText { var buffer: UnsafeBufferPointer + /// Construct a ``SyntaxText`` whose text is represented by the given `buffer`. public init(buffer: UnsafeBufferPointer) { self.buffer = buffer } + /// Construct a ``SyntaxText`` whose text is represented by the memory starting + /// at `baseAddress` and ranging `count` bytes. + /// + /// If count is not zero, `baseAddress` must not be `nil`. public init(baseAddress: UnsafePointer?, count: Int) { precondition( count == 0 || baseAddress != nil, @@ -142,19 +147,29 @@ public struct SyntaxText { /// `SyntaxText` is a collection of `UInt8`. extension SyntaxText: RandomAccessCollection { + /// SyntaxText operates on bytes and each byte is represented by a `UInt8`. public typealias Element = UInt8 + + /// `SyntaxText` is a continuous memory region that can be accessed by an integer. public typealias Index = Int + + /// `Slice` represents a part of a ``SyntaxText``. public typealias SubSequence = Slice + /// The index of the first byte in ``SyntaxText`` public var startIndex: Index { buffer.startIndex } + + /// The index one after the last byte in ``SyntaxText``. public var endIndex: Index { buffer.endIndex } - public subscript(position: Index) -> Element { - get { return buffer[position] } + /// Access the byte at `index`. + public subscript(index: Index) -> Element { + get { return buffer[index] } } } extension SyntaxText: Hashable { + /// Returns `true` if `lhs` and `rhs` contain the same bytes. public static func == (lhs: SyntaxText, rhs: SyntaxText) -> Bool { if lhs.buffer.count != rhs.buffer.count { return false @@ -174,22 +189,40 @@ extension SyntaxText: Hashable { return compareMemory(lBase, rBase, lhs.count) } + /// Hash the contents of this ``SyntaxText`` into `hasher`. public func hash(into hasher: inout Hasher) { hasher.combine(bytes: .init(buffer)) } } extension SyntaxText: ExpressibleByStringLiteral { + /// We can always safely create ``SyntaxText`` from a ``StaticString`` because + /// ``StaticString`` is guaranteed to be alive for the entire execution + /// duration of the process. public init(stringLiteral value: StaticString) { self.init(value) } + + /// We can always safely create ``SyntaxText`` from a ``StaticString`` because + /// ``StaticString`` is guaranteed to be alive for the entire execution + /// duration of the process. public init(unicodeScalarLiteral value: StaticString) { self.init(value) } + + /// We can always safely create ``SyntaxText`` from a ``StaticString`` because + /// ``StaticString`` is guaranteed to be alive for the entire execution + /// duration of the process. public init(extendedGraphemeClusterLiteral value: StaticString) { self.init(value) } } extension SyntaxText: CustomStringConvertible { + /// The contents of this ``SyntaxText`` as a ``String``. + /// + /// Note that ``SyntaxText`` can represent invalid Unicode, while ``String`` + /// cannot, so if this text contains invalid UTF-8, the conversion is lossy. public var description: String { String(syntaxText: self) } } extension SyntaxText: CustomDebugStringConvertible { + /// The string value of this text, which may be lossy if the text contains + /// invalid Unicode. public var debugDescription: String { description.debugDescription } } diff --git a/Sources/SwiftSyntax/TokenDiagnostic.swift b/Sources/SwiftSyntax/TokenDiagnostic.swift index e6040d90b95..8351b3fa855 100644 --- a/Sources/SwiftSyntax/TokenDiagnostic.swift +++ b/Sources/SwiftSyntax/TokenDiagnostic.swift @@ -19,6 +19,7 @@ public struct TokenDiagnostic: Hashable { case error } + /// Each diagnostic kind is uniquely represented by a value in this enum. public enum Kind { // Please order these alphabetically @@ -53,19 +54,71 @@ public struct TokenDiagnostic: Hashable { case unicodeCurlyQuote case unprintableAsciiCharacter case unterminatedBlockComment + + /// The severity of the diagnostic, i.e. whether it’s a warning or error. + var severity: Severity { + switch self { + case .editorPlaceholder: return .error + case .equalMustHaveConsistentWhitespaceOnBothSides: return .error + case .expectedBinaryExponentInHexFloatLiteral: return .error + case .expectedClosingBraceInUnicodeEscape: return .error + case .expectedDigitInFloatLiteral: return .error + case .expectedHexCodeInUnicodeEscape: return .error + case .expectedHexDigitInHexLiteral: return .error + case .insufficientIndentationInMultilineStringLiteral: return .error + case .invalidBinaryDigitInIntegerLiteral: return .error + case .invalidCharacter: return .error + case .invalidDecimalDigitInIntegerLiteral: return .error + case .invalidEscapeSequenceInStringLiteral: return .error + case .invalidFloatingPointExponentCharacter: return .error + case .invalidFloatingPointExponentDigit: return .error + case .invalidHexDigitInIntegerLiteral: return .error + case .invalidIdentifierStartCharacter: return .error + case .invalidNumberOfHexDigitsInUnicodeEscape: return .error + case .invalidOctalDigitInIntegerLiteral: return .error + case .invalidUtf8: return .error + case .multilineRegexClosingNotOnNewline: return .error + case .nonBreakingSpace: return .warning + case .nulCharacter: return .warning + case .sourceConflictMarker: return .error + case .spaceAtEndOfRegexLiteral: return .error + case .spaceAtStartOfRegexLiteral: return .error + case .tokenDiagnosticOffsetOverflow: return .error + case .unexpectedBlockCommentEnd: return .error + case .unicodeCurlyQuote: return .error + case .unprintableAsciiCharacter: return .error + case .unterminatedBlockComment: return .error + } + } } + /// The unique kind of this diagnostic. + /// + /// This kind determines the message that will be shown by the diagnostic. public let kind: Kind /// The offset at which the error is, in bytes relative to the token's leading /// trivia start (i.e. relative to the token's `position`) public let byteOffset: UInt16 + /// Construct a diagnostic of the given `kind` that is anchored at `byteOffset`, + /// measured in UTF-8 bytes relative to the leading trivia start of the token + /// this diagnostic will be attached to. public init(_ kind: Kind, byteOffset: UInt16) { self.kind = kind self.byteOffset = byteOffset } + /// Construct a diagnostic of the given `kind` that is anchored at `byteOffset`, + /// measured in UTF-8 bytes relative to the leading trivia start of the token + /// this diagnostic will be attached to. + /// + /// Since the offset within the token is represented by 16 bits only, + /// diagnostics that are more than 2^16 bytes from the token's start cannot + /// be represented. In that case, emit a `tokenDiagnosticOffsetOverflow` + /// diagnostic at the token’s start. 2^16 are quite a lot of characters for + /// a single token (even when we include comments as trivia), so we don’t + /// expect to hit this case in the vast majority. public init(_ kind: Kind, byteOffset: Int) { precondition(byteOffset >= 0) // `type(of: self.byteOffset).max` gets optimized to a constant @@ -98,38 +151,8 @@ public struct TokenDiagnostic: Hashable { } } + /// The severity of the diagnostic, i.e. whether it’s a warning or error. public var severity: Severity { - switch kind { - case .editorPlaceholder: return .error - case .equalMustHaveConsistentWhitespaceOnBothSides: return .error - case .expectedBinaryExponentInHexFloatLiteral: return .error - case .expectedClosingBraceInUnicodeEscape: return .error - case .expectedDigitInFloatLiteral: return .error - case .expectedHexCodeInUnicodeEscape: return .error - case .expectedHexDigitInHexLiteral: return .error - case .insufficientIndentationInMultilineStringLiteral: return .error - case .invalidBinaryDigitInIntegerLiteral: return .error - case .invalidCharacter: return .error - case .invalidDecimalDigitInIntegerLiteral: return .error - case .invalidEscapeSequenceInStringLiteral: return .error - case .invalidFloatingPointExponentCharacter: return .error - case .invalidFloatingPointExponentDigit: return .error - case .invalidHexDigitInIntegerLiteral: return .error - case .invalidIdentifierStartCharacter: return .error - case .invalidNumberOfHexDigitsInUnicodeEscape: return .error - case .invalidOctalDigitInIntegerLiteral: return .error - case .invalidUtf8: return .error - case .multilineRegexClosingNotOnNewline: return .error - case .nonBreakingSpace: return .warning - case .nulCharacter: return .warning - case .sourceConflictMarker: return .error - case .spaceAtEndOfRegexLiteral: return .error - case .spaceAtStartOfRegexLiteral: return .error - case .tokenDiagnosticOffsetOverflow: return .error - case .unexpectedBlockCommentEnd: return .error - case .unicodeCurlyQuote: return .error - case .unprintableAsciiCharacter: return .error - case .unterminatedBlockComment: return .error - } + return kind.severity } } diff --git a/Sources/SwiftSyntax/TokenSyntax.swift b/Sources/SwiftSyntax/TokenSyntax.swift index 838daeff2d1..3c7af91bacf 100644 --- a/Sources/SwiftSyntax/TokenSyntax.swift +++ b/Sources/SwiftSyntax/TokenSyntax.swift @@ -14,13 +14,20 @@ /// A Syntax node representing a single token. public struct TokenSyntax: SyntaxProtocol, SyntaxHashable { + /// The ``Syntax`` node that provides the underlying data. + /// + /// Don’t access this. Use `Syntax(token)` instead. public let _syntaxNode: Syntax + /// The ``RawSyntaxTokenView`` of this token that allows accessing raw + /// properties of the token. @_spi(RawSyntax) public var tokenView: RawSyntaxTokenView { return raw.tokenView! } + /// If `node` is a token, return the ``TokenSyntax`` that represents it. + /// Otherwise, return `nil`. public init?(_ node: S) { guard node.raw.kind == .token else { return nil } self._syntaxNode = node._syntaxNode @@ -34,6 +41,8 @@ public struct TokenSyntax: SyntaxProtocol, SyntaxHashable { self._syntaxNode = Syntax(data) } + /// Construct a new token with the given `kind`, `leadingTrivia`, + /// `trailingTrivia` and `presence`. public init( _ kind: TokenKind, leadingTrivia: Trivia = [], @@ -54,6 +63,7 @@ public struct TokenSyntax: SyntaxProtocol, SyntaxHashable { self.init(data) } + /// Whether the token is present or missing. public var presence: SourcePresence { get { return tokenView.presence @@ -125,6 +135,12 @@ public struct TokenSyntax: SyntaxProtocol, SyntaxHashable { return raw.totalLength } + /// A token by itself has no structure, so we represent its structure by an + /// empty layout node. + /// + /// Every syntax node that contains a token will have a + /// ``SyntaxNodeStructure.SyntaxChoices.choices`` case for the token and those + /// choices represent the token kinds the token might have. public static var structure: SyntaxNodeStructure { return .layout([]) } @@ -136,6 +152,8 @@ public struct TokenSyntax: SyntaxProtocol, SyntaxHashable { } extension TokenSyntax: CustomReflectable { + /// A custom mirror that shows the token properties in a better for, making + /// the debug output of the token easier to read. public var customMirror: Mirror { return Mirror( self, diff --git a/Sources/SwiftSyntax/Trivia.swift b/Sources/SwiftSyntax/Trivia.swift index 581eede2b4a..d35a700480c 100644 --- a/Sources/SwiftSyntax/Trivia.swift +++ b/Sources/SwiftSyntax/Trivia.swift @@ -18,6 +18,8 @@ public enum TriviaPosition { /// A collection of leading or trailing trivia. This is the main data structure /// for thinking about trivia. public struct Trivia { + /// The pieces this trivia consists of. Each ``TriviaPiece`` can represent + /// multiple characters, such as an entire comment or 4 spaces. public let pieces: [TriviaPiece] /// Creates Trivia with the provided underlying pieces. @@ -30,11 +32,13 @@ public struct Trivia { pieces.isEmpty } + /// The length of all the pieces in this ``Trivia``. public var sourceLength: SourceLength { return pieces.map({ $0.sourceLength }).reduce(.zero, +) } - /// Get the byteSize of this trivia + /// Get the number of bytes this trivia needs to be represented as UTF-8. + @available(*, deprecated, renamed: "sourceLength.utf8Length") public var byteSize: Int { return sourceLength.utf8Length } @@ -87,18 +91,22 @@ public struct Trivia { extension Trivia: Equatable {} extension Trivia: Collection { + /// The index of the first ``TriviaPiece`` within this trivia. public var startIndex: Int { return pieces.startIndex } + /// The index one after the last ``TriviaPiece`` within this trivia. public var endIndex: Int { return pieces.endIndex } - public func index(after i: Int) -> Int { - return pieces.index(after: i) + /// The index of the trivia piece after the piece at `index`. + public func index(after index: Int) -> Int { + return pieces.index(after: index) } + /// The ``TriviaPiece`` at `index`. public subscript(_ index: Int) -> TriviaPiece { return pieces[index] } @@ -123,6 +131,7 @@ extension Trivia: TextOutputStreamable { } extension Trivia: CustomStringConvertible { + /// The trivia’s representation in source code. public var description: String { var description = "" self.write(to: &description) @@ -131,6 +140,9 @@ extension Trivia: CustomStringConvertible { } extension Trivia: CustomDebugStringConvertible { + /// A debug description that shows the individual trivia pieces. + /// + /// Do not rely on this output being stable. public var debugDescription: String { if count == 1, let first { return first.debugDescription @@ -172,12 +184,16 @@ extension Trivia { } extension RawTriviaPiece: TextOutputStreamable { + /// Write the source representation of this trivia piece to `target`. public func write(to target: inout some TextOutputStream) { TriviaPiece(raw: self).write(to: &target) } } extension RawTriviaPiece: CustomDebugStringConvertible { + /// A debug description of this trivia piece. + /// + /// Do not rely on this output being stable. public var debugDescription: String { TriviaPiece(raw: self).debugDescription } diff --git a/Sources/SwiftSyntax/Utils.swift b/Sources/SwiftSyntax/Utils.swift index e7b5055fbe9..1ab3148771f 100644 --- a/Sources/SwiftSyntax/Utils.swift +++ b/Sources/SwiftSyntax/Utils.swift @@ -84,6 +84,11 @@ public struct SourceEdit: Equatable { } extension RawUnexpectedNodesSyntax { + /// Construct a ``RawUnexpectedNodesSyntax``with the given `elements`. + /// + /// If `isMaximumNestingLevelOverflow` is `true`, the node has the + /// `isMaximumNestingLevelOverflow` error bit set, indicating that the parser + /// overflowed its maximum nesting level and thus aborted parsing. public init(elements: [RawSyntax], isMaximumNestingLevelOverflow: Bool, arena: __shared SyntaxArena) { let raw = RawSyntax.makeLayout( kind: .unexpectedNodes, diff --git a/Tests/SwiftSyntaxTest/AbsolutePositionTests.swift b/Tests/SwiftSyntaxTest/AbsolutePositionTests.swift index dbaa22781c5..4b3a12a3742 100644 --- a/Tests/SwiftSyntaxTest/AbsolutePositionTests.swift +++ b/Tests/SwiftSyntaxTest/AbsolutePositionTests.swift @@ -89,7 +89,8 @@ public class AbsolutePositionTests: XCTestCase { XCTAssertEqual(2, state.trailingTrivia.count) XCTAssertEqual( state.byteSize, - state.leadingTrivia.byteSize + state.trailingTrivia.byteSize + state.leadingTrivia.sourceLength.utf8Length + + state.trailingTrivia.sourceLength.utf8Length + state.byteSizeAfterTrimmingTrivia ) XCTAssertFalse(root.statements.isImplicit)