Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions Sources/SwiftTreeSitter/Node.swift
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,29 @@ extension Node {

return Node(internalNode: n, internalTree: internalTree)
}

/// The text of the node
public var text: String? {
// Tree.source holds the original source text. It can be empty if the parse
// was performed without a backing string; handle that gracefully.
let source = self.internalTree.source
guard source != nil else { return nil }

// byteRange is in UTF-16LE bytes. Convert to UTF-16 code unit offsets.
let lowerUnits = Int(byteRange.lowerBound / 2)
let upperUnits = Int(byteRange.upperBound / 2)

// Clamp to valid bounds of the source’s UTF-16 view.
let utf16Count = source!.utf16.count
let startUnits = max(0, min(lowerUnits, utf16Count))
let endUnits = max(startUnits, min(upperUnits, utf16Count))

// Convert UTF-16 code unit offsets to String.Index and slice.
let startIndex = String.Index(utf16Offset: startUnits, in: source!)
let endIndex = String.Index(utf16Offset: endUnits, in: source!)

return String(source![startIndex..<endIndex])
}
}

extension Node {
Expand Down
8 changes: 4 additions & 4 deletions Sources/SwiftTreeSitter/Parser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,8 @@ extension Parser {
public typealias ReadBlock = (Int, Point) -> Data?
public typealias DataSnapshotProvider = @Sendable (Int, Point) -> Data?

public func parse(_ string: String) -> MutableTree? {
guard let data = string.data(using: encoding) else { return nil }
public func parse(_ source: String) -> MutableTree? {
guard let data = source.data(using: encoding) else { return nil }

let dataLength = data.count

Expand All @@ -112,7 +112,7 @@ extension Parser {
return ts_parser_parse_string_encoding(internalParser, nil, ptr, UInt32(dataLength), TSInputEncodingUTF16LE)
})

return optionalTreePtr.flatMap({ MutableTree(internalTree: $0) })
return optionalTreePtr.flatMap({ MutableTree(internalTree: $0,source: source) })
}

public func parse(tree: Tree?, encoding: TSInputEncoding = TSInputEncodingUTF16LE, readBlock: ReadBlock) -> MutableTree? {
Expand All @@ -127,7 +127,7 @@ extension Parser {
return nil
}

return MutableTree(internalTree: newTree)
return MutableTree(internalTree: newTree,source: tree?.source)
}
}

Expand Down
9 changes: 6 additions & 3 deletions Sources/SwiftTreeSitter/Tree.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@ import TreeSitter
/// An immutable tree-sitter tree structure.
public final class Tree: Sendable {
private let internalTreePointer: SendableOpaquePointer

let source:String?

init(internalTree: OpaquePointer) {
init(internalTree: OpaquePointer,source:String? = nil) {
self.internalTreePointer = SendableOpaquePointer(internalTree)
self.source = source
}

deinit {
Expand Down Expand Up @@ -131,8 +134,8 @@ extension Tree {
public final class MutableTree {
let tree: Tree

init(internalTree: OpaquePointer) {
self.tree = Tree(internalTree: internalTree)
init(internalTree: OpaquePointer,source: String?) {
self.tree = Tree(internalTree: internalTree,source: source)
}

init(tree: Tree) {
Expand Down
73 changes: 64 additions & 9 deletions Tests/SwiftTreeSitterTests/NodeTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,79 @@ import SwiftTreeSitter
import TestTreeSitterSwift

final class NodeTests: XCTestCase {
#if !os(WASI)
func testTreeNodeLifecycle() throws {
#if !os(WASI)
func testTreeNodeLifecycle() throws {
let language = Language(language: tree_sitter_swift())

let text = """
func main() {
}
"""

let parser = Parser()
try parser.setLanguage(language)

var tree: MutableTree? = try XCTUnwrap(parser.parse(text))
let root = try XCTUnwrap(tree?.rootNode)

tree = nil

XCTAssertTrue(root.childCount != 0)
}
#endif

func testTreeNodeText() throws {
let language = Language(language: tree_sitter_swift())

let text = """
func main() {
}
"""
func greet(name: String){
print("hello,\(name)")
}
greet("world")
"""

let parser = Parser()
try parser.setLanguage(language)

var tree: MutableTree? = try XCTUnwrap(parser.parse(text))
let tree: MutableTree? = try XCTUnwrap(parser.parse(text))
let root = try XCTUnwrap(tree?.rootNode)

tree = nil
// function_declaration
let function_declaration_node = try XCTUnwrap(root.child(at: 0))

for i in (0..<function_declaration_node.namedChildCount) {
let node = try XCTUnwrap(function_declaration_node.namedChild(at: i))
let nodeType = try XCTUnwrap(node.nodeType)
let nodeText = try XCTUnwrap(node.text)

switch nodeType {
case "simple_identifier":
XCTAssertEqual(nodeText, "greet")
case "parameter":
XCTAssertEqual(nodeText, "name: String")
default:
break
}
}

// call_expression
let call_expression_node = try XCTUnwrap(root.child(at: 1))
let call_expression_text = try XCTUnwrap(call_expression_node.text)
XCTAssertEqual(call_expression_text, "greet(\"world\")")

for i in (0..<call_expression_node.namedChildCount) {
let node = try XCTUnwrap(call_expression_node.namedChild(at: i))
let nodeType = try XCTUnwrap(node.nodeType)
let nodeText = try XCTUnwrap(node.text)

XCTAssertTrue(root.childCount != 0)
switch nodeType {
case "simple_identifier":
XCTAssertEqual(nodeText, "greet")
case "call_suffix":
XCTAssertEqual(nodeText, "(\"world\")")
default:
break
}
}
}
#endif
}