Add "Toggle disabled test" code action#2541
Conversation
Add a syntactic code action that toggles test functions between enabled and disabled states. For Swift Testing, this adds or removes the .disabled() trait on the @test attribute. For XCTest, this inserts or removes a leading throw XCTSkip("Disabled") statement and adjusts throws accordingly. Resolves swiftlang#2524
|
@swift-ci Please test |
| return declRef.baseName.text == "XCTSkip" | ||
| } | ||
| if let memberAccess = functionCall.calledExpression.as(MemberAccessExprSyntax.self) { | ||
| return memberAccess.declName.baseName.text == "XCTSkip" |
There was a problem hiding this comment.
Is this handling module qualified calls i.e. XCTests.XCTSkip(...). If so, could you add test cases?
| // Walk up to find the enclosing MemberBlockSyntax, then check its parent. | ||
| var node: Syntax? = Syntax(funcDecl).parent | ||
| while let current = node { | ||
| if let memberBlock = current.as(MemberBlockSyntax.self) { | ||
| let parent = memberBlock.parent | ||
| return parent?.is(ClassDeclSyntax.self) == true | ||
| || parent?.is(ExtensionDeclSyntax.self) == true | ||
| } | ||
| // Stop at code blocks to avoid matching nested functions. | ||
| if current.is(CodeBlockSyntax.self) { | ||
| return false | ||
| } | ||
| node = current.parent | ||
| } | ||
| return false |
There was a problem hiding this comment.
| // Walk up to find the enclosing MemberBlockSyntax, then check its parent. | |
| var node: Syntax? = Syntax(funcDecl).parent | |
| while let current = node { | |
| if let memberBlock = current.as(MemberBlockSyntax.self) { | |
| let parent = memberBlock.parent | |
| return parent?.is(ClassDeclSyntax.self) == true | |
| || parent?.is(ExtensionDeclSyntax.self) == true | |
| } | |
| // Stop at code blocks to avoid matching nested functions. | |
| if current.is(CodeBlockSyntax.self) { | |
| return false | |
| } | |
| node = current.parent | |
| } | |
| return false | |
| funcDecl.findParentOfSelf( | |
| ofType: Syntax.self, | |
| stoppingIf: { $0.is(CodeBlockSyntax.self) }, | |
| matching: { $0.is(ClassDeclSyntax.self) || $0.is(ExtensionDeclSyntax.self) } | |
| ) != nil |
| let name = funcDecl.name.text | ||
| guard name.hasPrefix("test") else { | ||
| return nil | ||
| } | ||
|
|
||
| // XCTest methods must be inside a class or extension. | ||
| guard isDirectClassOrExtensionMember(funcDecl) else { | ||
| return nil | ||
| } |
There was a problem hiding this comment.
It's a bit unfortunate that this is essentially duplicating the logic in SyntacticSwiftXCTestScanner, but I don't feel it's worth to reuse that for this simple task... fine for now
|
|
||
| /// Infer the indentation used inside a code block by examining the first | ||
| /// statement's leading trivia, or falling back to 4 spaces. | ||
| private static func inferIndentation(of body: CodeBlockSyntax) -> String { |
There was a problem hiding this comment.
Could you use BasicFormat.inferIndentation(of:) from SwiftBasicFormat module?
| return true | ||
| } | ||
| // Don't descend into nested functions or closures. | ||
| if node.is(FunctionDeclSyntax.self) || node.is(ClosureExprSyntax.self) { |
There was a problem hiding this comment.
I feel we can skip any DeclSyntax
| if node.is(FunctionDeclSyntax.self) || node.is(ClosureExprSyntax.self) { | |
| if node.is(DeclSyntax.self) || node.is(ClosureExprSyntax.self) { |
| guard | ||
| let funcDecl = scope.innermostNodeContainingRange?.findParentOfSelf( | ||
| ofType: FunctionDeclSyntax.self, | ||
| stoppingIf: { $0.is(CodeBlockSyntax.self) } |
There was a problem hiding this comment.
This action is active at anywhere in the function signature including the attributes, but not in the body.
Maybe we might want to adjust it in the future, but I think it's fine for now. Could you add comment to clarify the cursor positions it's intended to be active?
Add a syntactic code action to toggle test functions between enabled and disabled states.
For Swift Testing, activating the action on a
@Testfunction adds.disabled()to the attribute (and removes it when the test is already disabled). For XCTest, it inserts a leadingthrow XCTSkip("Disabled")statement and addsthrowsto the signature (and reverses both when enabling).Conditional disables such as
.disabled(if: condition)are intentionally not treated as unconditional - the action will not offer "Enable test" for those.Resolves #2524