Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 51 additions & 30 deletions Sources/SwiftWarningControl/WarningControlDeclSyntax.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,47 +10,68 @@
//
//===----------------------------------------------------------------------===//

import SwiftSyntax
@_spi(ExperimentalLanguageFeatures) import SwiftSyntax

extension AttributeSyntax {
var warningGroupControl: (DiagnosticGroupIdentifier, WarningGroupControl)? {
// `@warn` attributes
guard attributeName.as(IdentifierTypeSyntax.self)?.name.text == "warn"
else {
return nil
}

// First argument is the unquoted diagnostic group identifier
guard
let diagnosticGroupID = arguments?
.as(LabeledExprListSyntax.self)?.first?.expression
.as(DeclReferenceExprSyntax.self)?.baseName.text
else {
return nil
}

// Second argument is the `as: ` behavior control specifier
guard
let asParamExprSyntax = arguments?.as(LabeledExprListSyntax.self)?
.dropFirst().first
else {
return nil
}
guard
asParamExprSyntax.label?.text == "as",
let controlText = asParamExprSyntax
.expression.as(DeclReferenceExprSyntax.self)?
.baseName.text,
let control = WarningGroupControl(rawValue: controlText)
else {
return nil
}

return (DiagnosticGroupIdentifier(diagnosticGroupID), control)
}
}

extension WithAttributesSyntax {
/// Compute a dictionary of all `@warn` diagnostic group behavior controls
/// specified on this warning control declaration scope.
var allWarningGroupControls: [(DiagnosticGroupIdentifier, WarningGroupControl)] {
attributes.reduce(into: [(DiagnosticGroupIdentifier, WarningGroupControl)]()) { result, attr in
// `@warn` attributes
guard case .attribute(let attributeSyntax) = attr,
attributeSyntax.attributeName.as(IdentifierTypeSyntax.self)?.name.text == "warn"
else {
return
}

// First argument is the unquoted diagnostic group identifier
guard
let diagnosticGroupID = attributeSyntax.arguments?
.as(LabeledExprListSyntax.self)?.first?.expression
.as(DeclReferenceExprSyntax.self)?.baseName.text
let warningGroupControl = attributeSyntax.warningGroupControl
else {
return
}
result.append(warningGroupControl)
}
}
}

// Second argument is the `as: ` behavior control specifier
guard
let asParamExprSyntax = attributeSyntax
.arguments?.as(LabeledExprListSyntax.self)?
.dropFirst().first
else {
return
}
guard
asParamExprSyntax.label?.text == "as",
let controlText = asParamExprSyntax
.expression.as(DeclReferenceExprSyntax.self)?
.baseName.text,
let control = WarningGroupControl(rawValue: controlText)
else {
return
}
result.append((DiagnosticGroupIdentifier(diagnosticGroupID), control))
extension UsingDeclSyntax {
var warningControl: (DiagnosticGroupIdentifier, WarningGroupControl)? {
guard case .attribute(let attributeSyntax) = self.specifier,
let warningGroupControl = attributeSyntax.warningGroupControl
else {
return nil
}
return warningGroupControl
}
}
19 changes: 15 additions & 4 deletions Sources/SwiftWarningControl/WarningControlRegionBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
//
//===----------------------------------------------------------------------===//

import SwiftSyntax
@_spi(ExperimentalLanguageFeatures) import SwiftSyntax

/// Compute the full set of warning control regions in this syntax node
extension SyntaxProtocol {
Expand Down Expand Up @@ -73,14 +73,25 @@ private class WarningControlRegionVisitor: SyntaxAnyVisitor {
}

override func visitAny(_ node: Syntax) -> SyntaxVisitorContinueKind {
if let withAttributesSyntax = node.asProtocol(WithAttributesSyntax.self) {
tree.addWarningControlRegions(for: withAttributesSyntax)
}
// Handle file-scoped `using` declarations before the `containingPosition`
// check since they may only appear in top-level code and may affect
// warning group control of all positions in this source file.
if let usingAttributedSyntax = node.as(UsingDeclSyntax.self),
let usingWarningControl = usingAttributedSyntax.warningControl
{
tree.addRootWarningGroupControls(controls: [usingWarningControl])
}
// Skip all declarations which do not contain the specified
// `containingPosition`.
if let containingPosition,
node.isProtocol(DeclSyntaxProtocol.self),
!node.range.contains(containingPosition)
{
return .skipChildren
}
if let withAttributesSyntax = node.asProtocol(WithAttributesSyntax.self) {
tree.addWarningControlRegions(for: withAttributesSyntax)
}
return .visitChildren
}
}
6 changes: 6 additions & 0 deletions Sources/SwiftWarningControl/WarningControlRegions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,12 @@ public struct WarningControlRegionTree {
insertIntoSubtree(newNode, parent: rootRegionNode)
}

/// Add warning control regions to the tree root node.
/// For example, controls corresponding to a top-level `using @warn()` statement.
mutating func addRootWarningGroupControls(controls: [(DiagnosticGroupIdentifier, WarningGroupControl)]) {
addWarningGroupControls(range: rootRegionNode.range, controls: controls)
}

/// Insert a region node into the appropriate position in a subtree.
/// During top-down traversal of the syntax tree, nodes that are visited
/// later should never contain any of the previously visited nodes,
Expand Down
33 changes: 33 additions & 0 deletions Tests/SwiftParserTest/DeclarationTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3693,6 +3693,39 @@ final class UsingDeclarationTests: ParserTestCase {
)
)

assertParse(
"using @warn(DiagGroupID, as: warning)",
substructure: UsingDeclSyntax(
usingKeyword: .keyword(.using),
specifier: .attribute(
AttributeSyntax(
attributeName: IdentifierTypeSyntax(
name: .identifier("warn")
),
leftParen: .leftParenToken(),
arguments: .argumentList(
LabeledExprListSyntax([
LabeledExprSyntax(
expression: DeclReferenceExprSyntax(
baseName: .identifier("DiagGroupID")
),
trailingComma: .commaToken()
),
LabeledExprSyntax(
label: .identifier("as"),
colon: .colonToken(),
expression: DeclReferenceExprSyntax(
baseName: .identifier("warning")
)
),
])
),
rightParen: .rightParenToken()
)
)
)
)

assertParse(
"""
nonisolated
Expand Down
37 changes: 33 additions & 4 deletions Tests/SwiftWarningControlTest/WarningControlTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
//
//===----------------------------------------------------------------------===//

import SwiftParser
import SwiftSyntax
@_spi(ExperimentalLanguageFeatures) import SwiftParser
@_spi(ExperimentalLanguageFeatures) import SwiftSyntax
@_spi(ExperimentalLanguageFeatures) import SwiftWarningControl
import XCTest
import _SwiftSyntaxGenericTestSupport
Expand Down Expand Up @@ -387,6 +387,34 @@ public class WarningGroupControlTests: XCTestCase {
]
)
}

func testFileScopeUsingWarningGroupControl() throws {
try assertWarningGroupControl(
"""
0️⃣let x = 1
@warn(GroupID, as: warning)
struct Bar {
1️⃣let y = 1
}
struct Foo {
@warn(GroupID, as: ignored)
var property: Int {
2️⃣return 11
}
}
using @warn(GroupID, as: error)
3️⃣let k = 1
""",
experimentalFeatures: [.defaultIsolationPerFile],
diagnosticGroupID: "GroupID",
states: [
"0️⃣": .error,
"1️⃣": .warning,
"2️⃣": .ignored,
"3️⃣": .error
]
)
}
}

/// Assert that the various marked positions in the source code have the
Expand All @@ -395,15 +423,16 @@ private func assertWarningGroupControl(
_ markedSource: String,
globalControls: [(DiagnosticGroupIdentifier, WarningGroupControl)] = [],
groupInheritanceTree: DiagnosticGroupInheritanceTree? = nil,
experimentalFeatures: Parser.ExperimentalFeatures? = nil,
diagnosticGroupID: DiagnosticGroupIdentifier,
states: [String: WarningGroupControl?],
file: StaticString = #filePath,
line: UInt = #line
) throws {
// Pull out the markers that we'll use to dig out nodes to query.
let (markerLocations, source) = extractMarkers(markedSource)

var parser = Parser(source)
let experimentalFeatures = experimentalFeatures ?? Parser.ExperimentalFeatures(rawValue: 0)
var parser = Parser(source, experimentalFeatures: experimentalFeatures)
let tree = SourceFileSyntax.parse(from: &parser)
for (marker, location) in markerLocations {
guard let expectedState = states[marker] else {
Expand Down
Loading