Skip to content

Commit 4cc19ed

Browse files
committed
WIP: add assertClassification for ClassificationTests
1 parent 8b77cb6 commit 4cc19ed

File tree

3 files changed

+146
-170
lines changed

3 files changed

+146
-170
lines changed

Sources/_SwiftSyntaxTestSupport/IncrementalParseTestUtils.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ public func assertIncrementalParse(
125125
}
126126
}
127127

128-
fileprivate func byteSourceRange(for substring: String, in sourceString: String, after: String.Index) -> ByteSourceRange? {
128+
public func byteSourceRange(for substring: String, in sourceString: String, after: String.Index) -> ByteSourceRange? {
129129
if let range = sourceString[after...].range(of: substring) {
130130
return ByteSourceRange(
131131
offset: sourceString.utf8.distance(from: sourceString.startIndex, to: range.lowerBound),
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
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 XCTest
14+
import SwiftIDEUtils
15+
import SwiftParser
16+
import SwiftSyntax
17+
import _SwiftSyntaxTestSupport
18+
19+
func assertClassification(
20+
_ source: String,
21+
expectedClassificationSpec: [ClassificationSpec] = []
22+
) {
23+
let tree = Parser.parse(source: source)
24+
assertClassificationImpl(source: source, classifications: tree.classifications.filter { $0.kind != .none }, expectedClassificationSpec: expectedClassificationSpec)
25+
}
26+
27+
func assertClassification(
28+
_ source: String,
29+
in range: ByteSourceRange,
30+
expectedClassificationSpec: [ClassificationSpec] = []
31+
) {
32+
let tree = Parser.parse(source: source)
33+
assertClassificationImpl(source: source, classifications: tree.classifications.filter { $0.kind != .none && ($0.endOffset > range.offset) && ($0.offset < range.endOffset) }, expectedClassificationSpec: expectedClassificationSpec)
34+
}
35+
36+
37+
func assertTokensClassification(
38+
_ source: String,
39+
expectedClassificationSpec: [ClassificationSpec]
40+
) {
41+
let tree = Parser.parse(source: source)
42+
assertClassificationImpl(source: source, classifications: tree.tokens(viewMode: .sourceAccurate).map { $0.tokenClassification }.filter { $0.kind != .none }, expectedClassificationSpec: expectedClassificationSpec)
43+
}
44+
45+
func assertClassificationImpl(
46+
source: String,
47+
classifications: [SyntaxClassifiedRange],
48+
expectedClassificationSpec: [ClassificationSpec] = []
49+
) {
50+
let classifications = Array(classifications)
51+
52+
XCTAssertEqual(expectedClassificationSpec.count, classifications.count)
53+
54+
var lastRangeUpperBound = source.startIndex
55+
for (index, spec) in expectedClassificationSpec.enumerated() {
56+
let classification = classifications[index]
57+
guard let range = byteSourceRange(for: spec.source, in: source, after: lastRangeUpperBound) else {
58+
XCTFail("Fail to find string in original source,", file: spec.file, line: spec.line)
59+
continue
60+
}
61+
62+
XCTAssertEqual(
63+
range,
64+
classification.range,
65+
"""
66+
Expected \(range) but received \(classification.range)
67+
""",
68+
file: spec.file,
69+
line: spec.line
70+
)
71+
72+
XCTAssertEqual(
73+
spec.kind,
74+
classification.kind,
75+
"""
76+
Expected \(spec.kind) syntax classification kind but received \(classification.kind)
77+
""",
78+
file: spec.file,
79+
line: spec.line
80+
)
81+
82+
lastRangeUpperBound = source.index(source.startIndex, offsetBy: range.endOffset)
83+
}
84+
}
85+
86+
public struct ClassificationSpec {
87+
let source: String
88+
let kind: SyntaxClassification
89+
let file: StaticString
90+
let line: UInt
91+
92+
public init(
93+
source: String,
94+
kind: SyntaxClassification,
95+
file: StaticString = #file,
96+
line: UInt = #line
97+
) {
98+
self.source = source
99+
self.kind = kind
100+
self.file = file
101+
self.line = line
102+
}
103+
}

Tests/SwiftIDEUtilsTest/ClassificationTests.swift

Lines changed: 42 additions & 169 deletions
Original file line numberDiff line numberDiff line change
@@ -23,177 +23,50 @@ public class ClassificationTests: XCTestCase {
2323
// blah.
2424
let x/*yo*/ = 0
2525
"""
26-
let tree = Parser.parse(source: source)
27-
do {
28-
let classif = Array(tree.classifications)
29-
XCTAssertEqual(classif.count, 8)
30-
guard classif.count == 8 else {
31-
return
32-
}
33-
XCTAssertEqual(classif[0].kind, .lineComment)
34-
XCTAssertEqual(classif[0].range, ByteSourceRange(offset: 0, length: 8))
35-
XCTAssertEqual(classif[1].kind, .none)
36-
XCTAssertEqual(classif[1].range, ByteSourceRange(offset: 8, length: 1))
37-
XCTAssertEqual(classif[2].kind, .keyword)
38-
XCTAssertEqual(classif[2].range, ByteSourceRange(offset: 9, length: 3))
39-
XCTAssertEqual(classif[3].kind, .none)
40-
XCTAssertEqual(classif[3].range, ByteSourceRange(offset: 12, length: 1))
41-
XCTAssertEqual(classif[4].kind, .identifier)
42-
XCTAssertEqual(classif[4].range, ByteSourceRange(offset: 13, length: 1))
43-
XCTAssertEqual(classif[5].kind, .blockComment)
44-
XCTAssertEqual(classif[5].range, ByteSourceRange(offset: 14, length: 6))
45-
XCTAssertEqual(classif[6].kind, .none)
46-
XCTAssertEqual(classif[6].range, ByteSourceRange(offset: 20, length: 3))
47-
XCTAssertEqual(classif[7].kind, .integerLiteral)
48-
XCTAssertEqual(classif[7].range, ByteSourceRange(offset: 23, length: 1))
49-
}
50-
do {
51-
let classif = Array(tree.classifications(in: ByteSourceRange(offset: 7, length: 8)))
52-
XCTAssertEqual(classif.count, 6)
53-
guard classif.count == 6 else {
54-
return
55-
}
56-
XCTAssertEqual(classif[0].kind, .lineComment)
57-
XCTAssertEqual(classif[0].range, ByteSourceRange(offset: 0, length: 8))
58-
XCTAssertEqual(classif[1].kind, .none)
59-
XCTAssertEqual(classif[1].range, ByteSourceRange(offset: 8, length: 1))
60-
XCTAssertEqual(classif[2].kind, .keyword)
61-
XCTAssertEqual(classif[2].range, ByteSourceRange(offset: 9, length: 3))
62-
XCTAssertEqual(classif[3].kind, .none)
63-
XCTAssertEqual(classif[3].range, ByteSourceRange(offset: 12, length: 1))
64-
XCTAssertEqual(classif[4].kind, .identifier)
65-
XCTAssertEqual(classif[4].range, ByteSourceRange(offset: 13, length: 1))
66-
XCTAssertEqual(classif[5].kind, .blockComment)
67-
XCTAssertEqual(classif[5].range, ByteSourceRange(offset: 14, length: 6))
68-
}
69-
do {
70-
let classif = Array(tree.classifications(in: ByteSourceRange(offset: 21, length: 1)))
71-
XCTAssertEqual(classif.count, 1)
72-
guard classif.count == 1 else {
73-
return
74-
}
75-
XCTAssertEqual(classif[0].kind, .none)
76-
XCTAssertEqual(classif[0].range, ByteSourceRange(offset: 21, length: 2))
77-
}
78-
do {
79-
let pattern = (tree.statements[0].item.as(VariableDeclSyntax.self)!).bindings[0].pattern
80-
XCTAssertEqual(pattern.description, "x/*yo*/ ")
81-
// Classify with a relative range inside this node.
82-
let classif = Array(pattern.classifications(in: ByteSourceRange(offset: 5, length: 2)))
83-
XCTAssertEqual(classif.count, 2)
84-
guard classif.count == 2 else {
85-
return
86-
}
87-
XCTAssertEqual(classif[0].kind, .blockComment)
88-
XCTAssertEqual(classif[0].range, ByteSourceRange(offset: 14, length: 6))
89-
XCTAssertEqual(classif[1].kind, .none)
90-
XCTAssertEqual(classif[1].range, ByteSourceRange(offset: 20, length: 1))
91-
92-
do {
93-
let singleClassif = pattern.classification(at: 5)
94-
XCTAssertEqual(singleClassif, classif[0])
95-
}
96-
do {
97-
let singleClassif = pattern.classification(at: AbsolutePosition(utf8Offset: 19))
98-
XCTAssertEqual(singleClassif, classif[0])
99-
}
100-
}
101-
102-
do {
103-
let source = "func foo() {}"
104-
let tree = Parser.parse(source: source)
105-
// For `classification(at:)` there's an initial walk to find the token that
106-
// the offset is contained in and the classified ranges are processed from that
107-
// token. That means that a `none` classified range would be restricted inside
108-
// the token range.
109-
let classif = tree.classification(at: 11)!
110-
XCTAssertEqual(classif.kind, .none)
111-
XCTAssertEqual(classif.range, ByteSourceRange(offset: 11, length: 1))
112-
}
113-
}
114-
115-
public func testTokenClassification() {
116-
let source = "let x: Int"
117-
let tree = Parser.parse(source: source)
118-
do {
119-
let tokens = Array(tree.tokens(viewMode: .sourceAccurate))
120-
XCTAssertEqual(tokens.count, 5)
121-
guard tokens.count == 5 else {
122-
return
123-
}
124-
let classif = tokens.map { $0.tokenClassification }
125-
XCTAssertEqual(classif[0].kind, .keyword)
126-
XCTAssertEqual(classif[0].range, ByteSourceRange(offset: 0, length: 3))
127-
XCTAssertEqual(classif[1].kind, .identifier)
128-
XCTAssertEqual(classif[1].range, ByteSourceRange(offset: 4, length: 1))
129-
XCTAssertEqual(classif[2].kind, .none)
130-
XCTAssertEqual(classif[2].range, ByteSourceRange(offset: 5, length: 1))
131-
XCTAssertEqual(classif[3].kind, .typeIdentifier)
132-
XCTAssertEqual(classif[3].range, ByteSourceRange(offset: 7, length: 3))
133-
XCTAssertEqual(classif[4].kind, .none)
134-
XCTAssertEqual(classif[4].range, ByteSourceRange(offset: 10, length: 0))
135-
}
136-
do {
137-
let tok = tree.lastToken(viewMode: .sourceAccurate)!.previousToken(viewMode: .sourceAccurate)!
138-
XCTAssertEqual("\(tok)", "Int")
139-
let classif = Array(tok.classifications).first!
140-
XCTAssertEqual(classif.kind, .typeIdentifier)
141-
}
26+
27+
assertClassification(
28+
source, expectedClassificationSpec: [
29+
.init(source: "// blah.", kind: .lineComment),
30+
.init(source: "let", kind: .keyword),
31+
.init(source: "x", kind: .identifier),
32+
.init(source: "/*yo*/", kind: .blockComment),
33+
.init(source: "0", kind: .integerLiteral)
34+
])
35+
36+
assertClassification(
37+
source, in: ByteSourceRange(offset: 7, length: 8), expectedClassificationSpec: [
38+
.init(source: "// blah.", kind: .lineComment),
39+
.init(source: "let", kind: .keyword),
40+
.init(source: "x", kind: .identifier),
41+
.init(source: "/*yo*/", kind: .blockComment),
42+
])
43+
44+
assertClassification(
45+
source, in: ByteSourceRange(offset: 21, length: 2)
46+
)
47+
48+
assertClassification("x/*yo*/ ", in: ByteSourceRange(offset: 5, length: 2), expectedClassificationSpec: [.init(source: "/*yo*/", kind: .blockComment)])
14249
}
14350

14451
public func testOperatorTokenClassification() {
145-
do {
146-
let source = "let x: Int = 4 + 5 / 6"
147-
let tree = Parser.parse(source: source)
148-
149-
let tokens = Array(tree.tokens(viewMode: .sourceAccurate))
150-
XCTAssertEqual(tokens.count, 11)
151-
guard tokens.count == 11 else {
152-
return
153-
}
154-
let classif = tokens.map { $0.tokenClassification }
155-
XCTAssertEqual(classif[0].kind, .keyword)
156-
XCTAssertEqual(classif[0].range, ByteSourceRange(offset: 0, length: 3))
157-
XCTAssertEqual(classif[1].kind, .identifier)
158-
XCTAssertEqual(classif[1].range, ByteSourceRange(offset: 4, length: 1))
159-
XCTAssertEqual(classif[2].kind, .none)
160-
XCTAssertEqual(classif[2].range, ByteSourceRange(offset: 5, length: 1))
161-
XCTAssertEqual(classif[3].kind, .typeIdentifier)
162-
XCTAssertEqual(classif[3].range, ByteSourceRange(offset: 7, length: 3))
163-
XCTAssertEqual(classif[4].kind, .none)
164-
XCTAssertEqual(classif[4].range, ByteSourceRange(offset: 11, length: 1))
165-
XCTAssertEqual(classif[5].kind, .integerLiteral)
166-
XCTAssertEqual(classif[5].range, ByteSourceRange(offset: 13, length: 1))
167-
XCTAssertEqual(classif[6].kind, .operatorIdentifier)
168-
XCTAssertEqual(classif[6].range, ByteSourceRange(offset: 15, length: 1))
169-
XCTAssertEqual(classif[7].kind, .integerLiteral)
170-
XCTAssertEqual(classif[7].range, ByteSourceRange(offset: 17, length: 1))
171-
XCTAssertEqual(classif[8].kind, .operatorIdentifier)
172-
XCTAssertEqual(classif[8].range, ByteSourceRange(offset: 19, length: 1))
173-
XCTAssertEqual(classif[9].kind, .integerLiteral)
174-
XCTAssertEqual(classif[9].range, ByteSourceRange(offset: 21, length: 1))
175-
XCTAssertEqual(classif[10].kind, .none)
176-
XCTAssertEqual(classif[10].range, ByteSourceRange(offset: 22, length: 0))
177-
}
178-
179-
do {
180-
let source = "infix operator *--*"
181-
let tree = Parser.parse(source: source)
182-
183-
let tokens = Array(tree.tokens(viewMode: .sourceAccurate))
184-
XCTAssertEqual(tokens.count, 4)
185-
guard tokens.count == 4 else {
186-
return
187-
}
188-
let classif = tokens.map { $0.tokenClassification }
189-
XCTAssertEqual(classif[0].kind, .keyword)
190-
XCTAssertEqual(classif[0].range, ByteSourceRange(offset: 0, length: 5))
191-
XCTAssertEqual(classif[1].kind, .keyword)
192-
XCTAssertEqual(classif[1].range, ByteSourceRange(offset: 6, length: 8))
193-
XCTAssertEqual(classif[2].kind, .operatorIdentifier)
194-
XCTAssertEqual(classif[2].range, ByteSourceRange(offset: 15, length: 4))
195-
XCTAssertEqual(classif[3].kind, .none)
196-
XCTAssertEqual(classif[3].range, ByteSourceRange(offset: 19, length: 0))
197-
}
52+
assertTokensClassification(
53+
"""
54+
let x: Int = 4 + 5 / 6
55+
""", expectedClassificationSpec: [
56+
.init(source: "let", kind: .keyword),
57+
.init(source: "x", kind: .identifier),
58+
.init(source: "Int", kind: .typeIdentifier),
59+
.init(source: "4", kind: .integerLiteral),
60+
.init(source: "+", kind: .operatorIdentifier),
61+
.init(source: "5", kind: .integerLiteral),
62+
.init(source: "/", kind: .operatorIdentifier),
63+
.init(source: "6", kind: .integerLiteral)
64+
])
65+
66+
assertTokensClassification("infix operator *--*", expectedClassificationSpec: [
67+
.init(source: "infix", kind: .keyword),
68+
.init(source: "operator", kind: .keyword),
69+
.init(source: "*--*", kind: .operatorIdentifier)
70+
])
19871
}
19972
}

0 commit comments

Comments
 (0)