@@ -21,22 +21,6 @@ public enum TestStyle {
2121 public static let swiftTesting = " swift-testing "
2222}
2323
24- public struct AnnotatedTestItem : Sendable {
25- /// The test item to be annotated
26- public var testItem : TestItem
27-
28- /// Whether the `TestItem` is an extension.
29- public var isExtension : Bool
30-
31- public init (
32- testItem: TestItem ,
33- isExtension: Bool
34- ) {
35- self . testItem = testItem
36- self . isExtension = isExtension
37- }
38- }
39-
4024fileprivate extension SymbolOccurrence {
4125 /// Assuming that this is a symbol occurrence returned by the index, return whether it can constitute the definition
4226 /// of a test case.
@@ -352,117 +336,6 @@ extension SourceKitLSPServer {
352336 }
353337}
354338
355- /// Scans a source file for `XCTestCase` classes and test methods.
356- ///
357- /// The syntax visitor scans from class and extension declarations that could be `XCTestCase` classes or extensions
358- /// thereof. It then calls into `findTestMethods` to find the actual test methods.
359- final class SyntacticSwiftXCTestScanner : SyntaxVisitor {
360- /// The document snapshot of the syntax tree that is being walked.
361- private var snapshot : DocumentSnapshot
362-
363- /// The workspace symbols representing the found `XCTestCase` subclasses and test methods.
364- private var result : [ AnnotatedTestItem ] = [ ]
365-
366- private init ( snapshot: DocumentSnapshot ) {
367- self . snapshot = snapshot
368- super. init ( viewMode: . fixedUp)
369- }
370-
371- public static func findTestSymbols(
372- in snapshot: DocumentSnapshot ,
373- syntaxTreeManager: SyntaxTreeManager
374- ) async -> [ AnnotatedTestItem ] {
375- guard snapshot. text. contains ( " XCTestCase " ) || snapshot. text. contains ( " test " ) else {
376- // If the file contains tests that can be discovered syntactically, it needs to have a class inheriting from
377- // `XCTestCase` or a function starting with `test`.
378- // This is intended to filter out files that obviously do not contain tests.
379- return [ ]
380- }
381- let syntaxTree = await syntaxTreeManager. syntaxTree ( for: snapshot)
382- let visitor = SyntacticSwiftXCTestScanner ( snapshot: snapshot)
383- visitor. walk ( syntaxTree)
384- return visitor. result
385- }
386-
387- private func findTestMethods( in members: MemberBlockItemListSyntax , containerName: String ) -> [ TestItem ] {
388- return members. compactMap { ( member) -> TestItem ? in
389- guard let function = member. decl. as ( FunctionDeclSyntax . self) else {
390- return nil
391- }
392- guard function. name. text. starts ( with: " test " ) else {
393- return nil
394- }
395- guard function. modifiers. map ( \. name. tokenKind) . allSatisfy ( { $0 != . keyword( . static) && $0 != . keyword( . class) } )
396- else {
397- // Test methods can't be static.
398- return nil
399- }
400- guard function. signature. returnClause == nil , function. signature. parameterClause. parameters. isEmpty else {
401- // Test methods can't have a return type or have parameters.
402- // Technically we are also filtering out functions that have an explicit `Void` return type here but such
403- // declarations are probably less common than helper functions that start with `test` and have a return type.
404- return nil
405- }
406- let range = snapshot. absolutePositionRange (
407- of: function. positionAfterSkippingLeadingTrivia..< function. endPositionBeforeTrailingTrivia
408- )
409-
410- return TestItem (
411- id: " \( containerName) / \( function. name. text) () " ,
412- label: " \( function. name. text) () " ,
413- disabled: false ,
414- style: TestStyle . xcTest,
415- location: Location ( uri: snapshot. uri, range: range) ,
416- children: [ ] ,
417- tags: [ ]
418- )
419- }
420- }
421-
422- override func visit( _ node: ClassDeclSyntax ) -> SyntaxVisitorContinueKind {
423- guard let inheritedTypes = node. inheritanceClause? . inheritedTypes, let superclass = inheritedTypes. first else {
424- // The class has no superclass and thus can't inherit from XCTestCase.
425- // Continue scanning its children in case it has a nested subclass that inherits from XCTestCase.
426- return . visitChildren
427- }
428- let superclassName = superclass. type. as ( IdentifierTypeSyntax . self) ? . name. text
429- if superclassName == " NSObject " {
430- // We know that the class can't be an subclass of `XCTestCase` so don't visit it.
431- // We can't explicitly check for the `XCTestCase` superclass because the class might inherit from a class that in
432- // turn inherits from `XCTestCase`. Resolving that inheritance hierarchy would be semantic.
433- return . visitChildren
434- }
435- let testMethods = findTestMethods ( in: node. memberBlock. members, containerName: node. name. text)
436- guard !testMethods. isEmpty || superclassName == " XCTestCase " else {
437- // Don't report a test class if it doesn't contain any test methods.
438- return . visitChildren
439- }
440- let range = snapshot. absolutePositionRange (
441- of: node. positionAfterSkippingLeadingTrivia..< node. endPositionBeforeTrailingTrivia
442- )
443- let testItem = AnnotatedTestItem (
444- testItem: TestItem (
445- id: node. name. text,
446- label: node. name. text,
447- disabled: false ,
448- style: TestStyle . xcTest,
449- location: Location ( uri: snapshot. uri, range: range) ,
450- children: testMethods,
451- tags: [ ]
452- ) ,
453- isExtension: false
454- )
455- result. append ( testItem)
456- return . visitChildren
457- }
458-
459- override func visit( _ node: ExtensionDeclSyntax ) -> SyntaxVisitorContinueKind {
460- result += findTestMethods ( in: node. memberBlock. members, containerName: node. extendedType. trimmedDescription)
461- . map { AnnotatedTestItem ( testItem: $0, isExtension: true ) }
462- return . visitChildren
463- }
464- }
465-
466339extension TestItem {
467340 /// Use out-of-date semantic information to filter syntactic symbols.
468341 ///
@@ -506,7 +379,7 @@ extension AnnotatedTestItem {
506379 }
507380}
508381
509- extension Array < AnnotatedTestItem > {
382+ fileprivate extension Array < AnnotatedTestItem > {
510383 /// When the test scanners discover tests in extensions they are captured in their own parent `TestItem`, not the
511384 /// `TestItem` generated from the class/struct's definition. This is largely because of the syntatic nature of the
512385 /// test scanners as they are today, which only know about tests within the context of the current file. Extensions
0 commit comments