Skip to content

Commit 692ab16

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

File tree

2 files changed

+115
-38
lines changed

2 files changed

+115
-38
lines changed
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
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)
59+
} catch {
60+
XCTFail("Matching for a subtree failed with error: \(error)")
61+
}
62+
63+
// Re-used nodes
64+
// The range of node should ignore Trivia
65+
XCTAssertEqual(reusedNodesCollector.rangeAndNodes.count, expectedReusedNodes.count)
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 find \(targetNode.sourceString) in:
77+
\(reusedNodesCollector.rangeAndNodes.map({"\($0.0): \($0.1.description)"}).joined(separator: "\n"))
78+
"""
79+
)
80+
return
81+
}
82+
83+
XCTAssertEqual(targetNode.kind, reusedNode.kind)
84+
}
85+
}
86+
87+
private func getByteSourceRange(for subString: String, in sourceString: String) -> ByteSourceRange? {
88+
89+
if let range = sourceString.range(of: subString) {
90+
91+
return ByteSourceRange(
92+
offset: sourceString.utf8.distance(from: sourceString.startIndex, to: range.lowerBound),
93+
length: sourceString.utf8.distance(from: range.lowerBound, to: range.upperBound))
94+
}
95+
return nil
96+
}
97+
98+
public struct ReusedNodeSpec {
99+
public let sourceString: String
100+
101+
public let kind: SyntaxKind
102+
103+
public init(
104+
sourceString: String,
105+
kind: SyntaxKind
106+
) {
107+
self.sourceString = sourceString
108+
self.kind = kind
109+
}
110+
}

Tests/SwiftParserTest/IncrementalParsingTests.swift

Lines changed: 5 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,8 @@ public class IncrementalParsingTests: XCTestCase {
1919

2020
public func testIncrementalInvalid() {
2121
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)
22+
23+
assertIncrementalParse(source)
3224
}
3325

3426
public func testReusedNode() throws {
@@ -40,33 +32,8 @@ public class IncrementalParsingTests: XCTestCase {
4032
struct B {}
4133
"""
4234

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)
35+
assertIncrementalParse(source, reusedNodes: [
36+
ReusedNodeSpec(sourceString: "struct B {}", kind: .codeBlockItem)
37+
])
7138
}
7239
}

0 commit comments

Comments
 (0)