Skip to content

Commit d74d81e

Browse files
committed
Add utils for incremental parse test
1 parent 56ef324 commit d74d81e

File tree

2 files changed

+123
-45
lines changed

2 files changed

+123
-45
lines changed
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import SwiftSyntax
14+
import SwiftParser
15+
import XCTest
16+
17+
/// This function is used to verify the correctness of incremental parsing
18+
/// containing three parts:
19+
/// 1. Round-trip on the incrementally parsed syntax tree.
20+
/// 2. verify that incrementally parsing the edited source base on the original source produces the same syntax tree as reparsing the post-edit file from scratch.
21+
/// 3. verify the reused nodes fall into expectations.
22+
public func assertIncrementalParse(
23+
_ source: String,
24+
reusedNodes expectedReusedNodes: [ReusedNodeSpec] = [],
25+
file: StaticString = #file,
26+
line: UInt = #line
27+
) {
28+
let (concurrentEdits, originalSource, editedSource) = getEditsAndSources(source)
29+
30+
let originalString = String(originalSource)
31+
let editedString = String(editedSource)
32+
33+
let originalTree = Parser.parse(source: originalString)
34+
35+
let reusedNodesCollector = IncrementalParseReusedNodeCollector()
36+
let transition = IncrementalParseTransition(previousTree: originalTree, edits: concurrentEdits, reusedNodeDelegate: reusedNodesCollector)
37+
38+
let newTree = Parser.parse(source: String(editedSource))
39+
let incrementallyParsedNewTree = Parser.parse(source: editedString, parseTransition: transition)
40+
41+
// Round-trip
42+
assertStringsEqualWithDiff(
43+
editedString,
44+
"\(incrementallyParsedNewTree)",
45+
additionalInfo: """
46+
Source failed to round-trip when parsing incrementally
47+
48+
Actual syntax tree:
49+
\(incrementallyParsedNewTree.debugDescription)
50+
""",
51+
file: file,
52+
line: line
53+
)
54+
55+
// Substructure
56+
let subtreeMatcher = SubtreeMatcher(Syntax(incrementallyParsedNewTree), markers: [:])
57+
do {
58+
try subtreeMatcher.assertSameStructure(Syntax(newTree), includeTrivia: true, file: file, line: line)
59+
} catch {
60+
XCTFail("Matching for a subtree failed with error: \(error)", file: file, line: line)
61+
}
62+
63+
// Re-used nodes
64+
// The range of node should ignore Trivia
65+
XCTAssertEqual(reusedNodesCollector.rangeAndNodes.count, expectedReusedNodes.count, file: file, line: line)
66+
67+
for targetNode in expectedReusedNodes {
68+
guard let range = getByteSourceRange(for: targetNode.sourceString, in: originalString) else {
69+
XCTFail("Fail to find \(targetNode.sourceString) in \(originalString), please check test setting.")
70+
return
71+
}
72+
73+
guard let reusedNode = reusedNodesCollector.rangeAndNodes.first(where: { $0.0 == range })?.1 else {
74+
XCTFail(
75+
"""
76+
Fail to match the range of \(targetNode.sourceString) in:
77+
\(reusedNodesCollector.rangeAndNodes.map({"\($0.0): \($0.1.description)"}).joined(separator: "\n"))
78+
""",
79+
file: file, line: line
80+
)
81+
return
82+
}
83+
84+
XCTAssertEqual(targetNode.kind, reusedNode.kind, file: file, line: line)
85+
}
86+
}
87+
88+
private func getByteSourceRange(for subString: String, in sourceString: String) -> ByteSourceRange? {
89+
90+
if let range = sourceString.range(of: subString) {
91+
92+
return ByteSourceRange(
93+
offset: sourceString.utf8.distance(from: sourceString.startIndex, to: range.lowerBound),
94+
length: sourceString.utf8.distance(from: range.lowerBound, to: range.upperBound))
95+
}
96+
return nil
97+
}
98+
99+
public struct ReusedNodeSpec {
100+
public let sourceString: String
101+
102+
public let kind: SyntaxKind
103+
104+
public init(
105+
sourceString: String,
106+
kind: SyntaxKind
107+
) {
108+
self.sourceString = sourceString
109+
self.kind = kind
110+
}
111+
}

Tests/SwiftParserTest/IncrementalParsingTests.swift

Lines changed: 12 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -18,55 +18,22 @@ import _SwiftSyntaxTestSupport
1818
public class IncrementalParsingTests: XCTestCase {
1919

2020
public func testIncrementalInvalid() {
21-
let source = "struct A⏩️⏸️A⏪️ { func f() {"
22-
let (concurrentEdits, originalSubString, editedSubString) = getEditsAndSources(source)
23-
24-
let originalSource = String(originalSubString)
25-
let editedSource = String(editedSubString)
26-
27-
var tree = Parser.parse(source: originalSource)
28-
29-
let lookup = IncrementalParseTransition(previousTree: tree, edits: concurrentEdits)
30-
tree = Parser.parse(source: editedSource, parseTransition: lookup)
31-
XCTAssertEqual("\(tree)", editedSource)
21+
assertIncrementalParse(
22+
"""
23+
struct A⏩️⏸️A⏪️ { func f() {
24+
"""
25+
)
3226
}
3327

34-
public func testReusedNode() throws {
35-
try XCTSkipIf(true, "Swift parser does not handle node reuse yet")
36-
37-
let source =
28+
public func testReusedNode() {
29+
XCTExpectFailure()
30+
assertIncrementalParse(
3831
"""
3932
struct A⏩️⏸️A⏪️ {}
4033
struct B {}
41-
"""
42-
43-
let (concurrentEdits, originalSubString, editedSubString) = getEditsAndSources(source)
44-
45-
let originalSource = String(originalSubString)
46-
let editedSource = String(editedSubString)
47-
48-
let origTree = Parser.parse(source: originalSource)
49-
let reusedNodeCollector = IncrementalParseReusedNodeCollector()
50-
let transition = IncrementalParseTransition(previousTree: origTree, edits: concurrentEdits, reusedNodeDelegate: reusedNodeCollector)
51-
let newTree = Parser.parse(source: editedSource, parseTransition: transition)
52-
XCTAssertEqual("\(newTree)", editedSource)
53-
54-
let origStructB = origTree.statements[1]
55-
let newStructB = newTree.statements[1]
56-
XCTAssertEqual("\(origStructB)", "\nstruct B {}")
57-
XCTAssertEqual("\(newStructB)", "\nstruct B {}")
58-
XCTAssertNotEqual(origStructB, newStructB)
59-
60-
XCTAssertEqual(reusedNodeCollector.rangeAndNodes.count, 1)
61-
if reusedNodeCollector.rangeAndNodes.count != 1 { return }
62-
let (reusedRange, reusedNode) = reusedNodeCollector.rangeAndNodes[0]
63-
XCTAssertEqual("\(reusedNode)", "\nstruct B {}")
64-
65-
XCTAssertEqual(newStructB.byteRange, reusedRange)
66-
XCTAssertEqual(origStructB.id, reusedNode.id)
67-
XCTAssertEqual(origStructB, reusedNode.as(CodeBlockItemSyntax.self))
68-
XCTAssertTrue(reusedNode.is(CodeBlockItemSyntax.self))
69-
XCTAssertEqual(origStructB, reusedNode.as(CodeBlockItemSyntax.self)!)
70-
XCTAssertEqual(origStructB.parent!.id, reusedNode.parent!.id)
34+
""",
35+
reusedNodes: [
36+
ReusedNodeSpec(sourceString: "struct B {}", kind: .codeBlockItem)
37+
])
7138
}
7239
}

0 commit comments

Comments
 (0)