From 0b890bb7f667cd31168eed87ff56f0aa242f4fba Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Tue, 7 May 2024 10:38:05 -0700 Subject: [PATCH 1/7] =?UTF-8?q?Move=20`assertStringsEqualWithDiff`=20into?= =?UTF-8?q?=20a=20module=20that=20doesn=E2=80=99t=20depend=20on=20Foundati?= =?UTF-8?q?on=20or=20XCTest?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This allows us to use these functions from a framework-agnostic `SwiftSyntaxMacroTestSupport` module. All changes are in underscored modules and thus don’t have any API impact. --- Package.swift | 13 +- .../AssertEqualWithDiff.swift | 109 +++----------- .../AssertEqualWithDiff.swift | 134 ++++++++++++++++++ .../String+TrimmingTrailingWhitespace.swift | 11 +- Tests/SwiftParserTest/Assertions.swift | 8 +- 5 files changed, 182 insertions(+), 93 deletions(-) create mode 100644 Sources/_SwiftSyntaxTestSupportFrameworkAgnostic/AssertEqualWithDiff.swift rename Sources/{_SwiftSyntaxTestSupport => _SwiftSyntaxTestSupportFrameworkAgnostic}/String+TrimmingTrailingWhitespace.swift (68%) diff --git a/Package.swift b/Package.swift index ec5c6f7b3be..2c47bf4491f 100644 --- a/Package.swift +++ b/Package.swift @@ -40,7 +40,18 @@ let package = Package( .target( name: "_SwiftSyntaxTestSupport", - dependencies: ["SwiftBasicFormat", "SwiftSyntax", "SwiftSyntaxBuilder", "SwiftSyntaxMacroExpansion"] + dependencies: [ + "_SwiftSyntaxTestSupportFrameworkAgnostic", + "SwiftBasicFormat", + "SwiftSyntax", + "SwiftSyntaxBuilder", + "SwiftSyntaxMacroExpansion", + ] + ), + + .target( + name: "_SwiftSyntaxTestSupportFrameworkAgnostic", + dependencies: [] ), .testTarget( diff --git a/Sources/_SwiftSyntaxTestSupport/AssertEqualWithDiff.swift b/Sources/_SwiftSyntaxTestSupport/AssertEqualWithDiff.swift index 6fb8244b8b7..2242d257cde 100644 --- a/Sources/_SwiftSyntaxTestSupport/AssertEqualWithDiff.swift +++ b/Sources/_SwiftSyntaxTestSupport/AssertEqualWithDiff.swift @@ -13,9 +13,11 @@ #if swift(>=6) public import Foundation private import XCTest +private import _SwiftSyntaxTestSupportFrameworkAgnostic #else import Foundation import XCTest +import _SwiftSyntaxTestSupportFrameworkAgnostic #endif /// Asserts that the two strings are equal, providing Unix `diff`-style output if they are not. @@ -37,16 +39,21 @@ public func assertStringsEqualWithDiff( file: StaticString = #filePath, line: UInt = #line ) { - if actual == expected { - return - } - failStringsEqualWithDiff( + let location = TestFailureLocation( + fileID: "", // Not used in the failure handler + filePath: file, + line: line, + column: 0 // Not used in the failure handler + ) + return _SwiftSyntaxTestSupportFrameworkAgnostic.assertStringsEqualWithDiff( actual, expected, message, additionalInfo: additionalInfo(), - file: file, - line: line + location: location, + failureHandler: { + XCTFail($0.message, file: $0.location.filePath, line: $0.location.line) + } ) } @@ -73,89 +80,19 @@ public func assertDataEqualWithDiff( return } - // NOTE: Converting to `Stirng` here looses invalid UTF8 sequence difference, - // but at least we can see something is different. - failStringsEqualWithDiff( - String(decoding: actual, as: UTF8.self), - String(decoding: expected, as: UTF8.self), + let actualString = String(decoding: actual, as: UTF8.self) + let expectedString = String(decoding: expected, as: UTF8.self) + + if actualString == expectedString { + XCTFail("Actual differs from expected data but underlying strings are equivalent", file: file, line: line) + } + + assertStringsEqualWithDiff( + actualString, + expectedString, message, additionalInfo: additionalInfo(), file: file, line: line ) } - -/// `XCTFail` with `diff`-style output. -public func failStringsEqualWithDiff( - _ actual: String, - _ expected: String, - _ message: String = "", - additionalInfo: @autoclosure () -> String? = nil, - file: StaticString = #filePath, - line: UInt = #line -) { - let stringComparison: String - - // Use `CollectionDifference` on supported platforms to get `diff`-like line-based output. On - // older platforms, fall back to simple string comparison. - if #available(macOS 10.15, *) { - let actualLines = actual.components(separatedBy: .newlines) - let expectedLines = expected.components(separatedBy: .newlines) - - let difference = actualLines.difference(from: expectedLines) - - var result = "" - - var insertions = [Int: String]() - var removals = [Int: String]() - - for change in difference { - switch change { - case .insert(let offset, let element, _): - insertions[offset] = element - case .remove(let offset, let element, _): - removals[offset] = element - } - } - - var expectedLine = 0 - var actualLine = 0 - - while expectedLine < expectedLines.count || actualLine < actualLines.count { - if let removal = removals[expectedLine] { - result += "–\(removal)\n" - expectedLine += 1 - } else if let insertion = insertions[actualLine] { - result += "+\(insertion)\n" - actualLine += 1 - } else { - result += " \(expectedLines[expectedLine])\n" - expectedLine += 1 - actualLine += 1 - } - } - - stringComparison = result - } else { - // Fall back to simple message on platforms that don't support CollectionDifference. - stringComparison = """ - Expected: - \(expected) - - Actual: - \(actual) - """ - } - - var fullMessage = """ - \(message.isEmpty ? "Actual output does not match the expected" : message) - \(stringComparison) - """ - if let additional = additionalInfo() { - fullMessage = """ - \(fullMessage) - \(additional) - """ - } - XCTFail(fullMessage, file: file, line: line) -} diff --git a/Sources/_SwiftSyntaxTestSupportFrameworkAgnostic/AssertEqualWithDiff.swift b/Sources/_SwiftSyntaxTestSupportFrameworkAgnostic/AssertEqualWithDiff.swift new file mode 100644 index 00000000000..c874acbde94 --- /dev/null +++ b/Sources/_SwiftSyntaxTestSupportFrameworkAgnostic/AssertEqualWithDiff.swift @@ -0,0 +1,134 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +/// Defines the location at which the a test failure should be anchored. This is typically the location where the +/// assertion function is called. +public struct TestFailureLocation { + public let fileID: StaticString + public let filePath: StaticString + public let line: UInt + public let column: UInt + + public init( + fileID: StaticString, + filePath: StaticString, + line: UInt, + column: UInt + ) { + self.fileID = fileID + self.filePath = filePath + self.line = line + self.column = column + } +} + +/// Defines the details of a test failure, consisting of a message and the location at which the l +public struct TestFailureSpec { + public let message: String + public let location: TestFailureLocation +} + +/// Asserts that the two strings are equal, providing Unix `diff`-style output if they are not. +/// +/// - Parameters: +/// - actual: The actual string. +/// - expected: The expected string. +/// - message: An optional description of the failure. +/// - additionalInfo: Additional information about the failed test case that will be printed after the diff +/// - file: The file in which failure occurred. Defaults to the file name of the test case in +/// which this function was called. +/// - line: The line number on which failure occurred. Defaults to the line number on which this +/// function was called. +public func assertStringsEqualWithDiff( + _ actual: String, + _ expected: String, + _ message: String = "", + additionalInfo: @autoclosure () -> String? = nil, + location: TestFailureLocation, + failureHandler: (TestFailureSpec) -> Void +) { + if actual == expected { + return + } + failStringsEqualWithDiff( + actual, + expected, + message, + additionalInfo: additionalInfo(), + location: location, + failureHandler: failureHandler + ) +} + +/// `XCTFail` with `diff`-style output. +public func failStringsEqualWithDiff( + _ actual: String, + _ expected: String, + _ message: String = "", + additionalInfo: @autoclosure () -> String? = nil, + location: TestFailureLocation, + failureHandler: (TestFailureSpec) -> Void +) { + let stringComparison: String + + // Use `CollectionDifference` on supported platforms to get `diff`-like line-based output. On + // older platforms, fall back to simple string comparison. + let actualLines = actual.split(separator: "\n") + let expectedLines = expected.split(separator: "\n") + + let difference = actualLines.difference(from: expectedLines) + + var result = "" + + var insertions = [Int: Substring]() + var removals = [Int: Substring]() + + for change in difference { + switch change { + case .insert(let offset, let element, _): + insertions[offset] = element + case .remove(let offset, let element, _): + removals[offset] = element + } + } + + var expectedLine = 0 + var actualLine = 0 + + while expectedLine < expectedLines.count || actualLine < actualLines.count { + if let removal = removals[expectedLine] { + result += "–\(removal)\n" + expectedLine += 1 + } else if let insertion = insertions[actualLine] { + result += "+\(insertion)\n" + actualLine += 1 + } else { + result += " \(expectedLines[expectedLine])\n" + expectedLine += 1 + actualLine += 1 + } + } + + stringComparison = result + + var fullMessage = """ + \(message.isEmpty ? "Actual output does not match the expected" : message) + \(stringComparison) + """ + if let additional = additionalInfo() { + fullMessage = """ + \(fullMessage) + \(additional) + """ + } + failureHandler(TestFailureSpec(message: fullMessage, location: location)) +} diff --git a/Sources/_SwiftSyntaxTestSupport/String+TrimmingTrailingWhitespace.swift b/Sources/_SwiftSyntaxTestSupportFrameworkAgnostic/String+TrimmingTrailingWhitespace.swift similarity index 68% rename from Sources/_SwiftSyntaxTestSupport/String+TrimmingTrailingWhitespace.swift rename to Sources/_SwiftSyntaxTestSupportFrameworkAgnostic/String+TrimmingTrailingWhitespace.swift index 4c70a3ac654..b7ac4edb2a4 100644 --- a/Sources/_SwiftSyntaxTestSupport/String+TrimmingTrailingWhitespace.swift +++ b/Sources/_SwiftSyntaxTestSupportFrameworkAgnostic/String+TrimmingTrailingWhitespace.swift @@ -15,7 +15,14 @@ extension String { public func trimmingTrailingWhitespace() -> String { return self - .replacingOccurrences(of: "[ ]+\\n", with: "\n", options: .regularExpression) - .trimmingCharacters(in: [" "]) + .split(separator: "\n", omittingEmptySubsequences: false) + .map { $0.dropLast(while: { $0 == " " }) } + .joined(separator: "\n") + } +} + +fileprivate extension Substring { + func dropLast(while predicate: (Character) -> Bool) -> String { + return String(self.reversed().drop(while: predicate).reversed()) } } diff --git a/Tests/SwiftParserTest/Assertions.swift b/Tests/SwiftParserTest/Assertions.swift index 8514e75ed56..8cce493526a 100644 --- a/Tests/SwiftParserTest/Assertions.swift +++ b/Tests/SwiftParserTest/Assertions.swift @@ -99,7 +99,7 @@ private func assertTokens( } if actualLexeme.leadingTriviaText != expectedLexeme.leadingTrivia { - failStringsEqualWithDiff( + assertStringsEqualWithDiff( String(syntaxText: actualLexeme.leadingTriviaText), String(syntaxText: expectedLexeme.leadingTrivia), "Leading trivia does not match", @@ -109,7 +109,7 @@ private func assertTokens( } if actualLexeme.tokenText.debugDescription != expectedLexeme.tokenText.debugDescription { - failStringsEqualWithDiff( + assertStringsEqualWithDiff( actualLexeme.tokenText.debugDescription, expectedLexeme.tokenText.debugDescription, "Token text does not match", @@ -119,7 +119,7 @@ private func assertTokens( } if actualLexeme.trailingTriviaText != expectedLexeme.trailingTrivia { - failStringsEqualWithDiff( + assertStringsEqualWithDiff( String(syntaxText: actualLexeme.trailingTriviaText), String(syntaxText: expectedLexeme.trailingTrivia), "Trailing trivia does not match", @@ -402,7 +402,7 @@ func assertDiagnostic( line: spec.line ) } else if spec.fixIts != diag.fixIts.map(\.message.message) { - failStringsEqualWithDiff( + assertStringsEqualWithDiff( diag.fixIts.map(\.message.message).joined(separator: "\n"), spec.fixIts.joined(separator: "\n"), file: spec.file, From 9204b6b594970e956dac06f21e67fa42970d4a0d Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Tue, 7 May 2024 11:12:10 -0700 Subject: [PATCH 2/7] =?UTF-8?q?Add=20a=20platform-agnostic=20macro=20testi?= =?UTF-8?q?ng=20module=20that=20doesn=E2=80=99t=20depend=20on=20XCTest=20o?= =?UTF-8?q?r=20Foundation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit rdar://119519713 --- Package.swift | 14 + .../Assertions.swift | 652 ++++++++++++++++++ .../String+TrimmingTrailingWhitespace.swift | 6 +- 3 files changed, 669 insertions(+), 3 deletions(-) create mode 100644 Sources/SwiftSyntaxMacrosTestSupportFrameworkAgnostic/Assertions.swift diff --git a/Package.swift b/Package.swift index 2c47bf4491f..fc9b04bb8a5 100644 --- a/Package.swift +++ b/Package.swift @@ -227,6 +227,20 @@ let package = Package( ] ), + // MARK: SwiftSyntaxMacrosTestSupportFrameworkAgnostic + + .target( + name: "SwiftSyntaxMacrosTestSupportFrameworkAgnostic", + dependencies: [ + "_SwiftSyntaxTestSupportFrameworkAgnostic", + "SwiftDiagnostics", + "SwiftIDEUtils", + "SwiftParser", + "SwiftSyntaxMacros", + "SwiftSyntaxMacroExpansion", + ] + ), + .testTarget( name: "SwiftSyntaxMacrosTestSupportTests", dependencies: ["SwiftDiagnostics", "SwiftSyntax", "SwiftSyntaxMacros", "SwiftSyntaxMacrosTestSupport"] diff --git a/Sources/SwiftSyntaxMacrosTestSupportFrameworkAgnostic/Assertions.swift b/Sources/SwiftSyntaxMacrosTestSupportFrameworkAgnostic/Assertions.swift new file mode 100644 index 00000000000..6f26a6de4a0 --- /dev/null +++ b/Sources/SwiftSyntaxMacrosTestSupportFrameworkAgnostic/Assertions.swift @@ -0,0 +1,652 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +#if swift(>=6.0) +import SwiftBasicFormat +public import SwiftDiagnostics +@_spi(FixItApplier) import SwiftIDEUtils +import SwiftParser +import SwiftParserDiagnostics +public import SwiftSyntax +public import SwiftSyntaxMacroExpansion +private import SwiftSyntaxMacros +private import _SwiftSyntaxTestSupportFrameworkAgnostic +#else +import SwiftBasicFormat +import SwiftDiagnostics +@_spi(FixItApplier) import SwiftIDEUtils +import SwiftParser +import SwiftParserDiagnostics +import SwiftSyntax +import SwiftSyntaxMacroExpansion +import SwiftSyntaxMacros +import _SwiftSyntaxTestSupportFrameworkAgnostic +#endif + +/// Defines the location at which the a test failure should be anchored. This is typically the location where the +/// assertion function is called. +public struct TestFailureLocation { + public let fileID: StaticString + public let filePath: StaticString + public let line: UInt + public let column: UInt + + public init( + fileID: StaticString, + filePath: StaticString, + line: UInt, + column: UInt + ) { + self.fileID = fileID + self.filePath = filePath + self.line = line + self.column = column + } + + fileprivate init(underlying: _SwiftSyntaxTestSupportFrameworkAgnostic.TestFailureLocation) { + self.init( + fileID: underlying.fileID, + filePath: underlying.filePath, + line: underlying.line, + column: underlying.column + ) + } + + /// This type is intentionally different to `_SwiftSyntaxTestSupportFrameworkAgnostic.TestFailureLocation` so we can + /// import `_SwiftSyntaxTestSupportFrameworkAgnostic` privately and don't expose its internal types. + fileprivate var underlying: _SwiftSyntaxTestSupportFrameworkAgnostic.TestFailureLocation { + _SwiftSyntaxTestSupportFrameworkAgnostic.TestFailureLocation( + fileID: self.fileID, + filePath: self.filePath, + line: self.line, + column: self.column + ) + } +} + +/// Defines the details of a test failure, consisting of a message and the location at which the l +public struct TestFailureSpec { + public let message: String + public let location: TestFailureLocation + + public init(message: String, location: TestFailureLocation) { + self.message = message + self.location = location + } + + fileprivate init(underlying: _SwiftSyntaxTestSupportFrameworkAgnostic.TestFailureSpec) { + self.init( + message: underlying.message, + location: TestFailureLocation(underlying: underlying.location) + ) + } +} + +// MARK: - Note + +/// Describes a diagnostic note that tests expect to be created by a macro expansion. +public struct NoteSpec { + /// The expected message of the note + public let message: String + + /// The line to which the note is expected to point + public let line: Int + + /// The column to which the note is expected to point + public let column: Int + + /// The file and line at which this ``NoteSpec`` was created, so that assertion failures can be reported at its location. + internal let failureLocation: TestFailureLocation + + /// Creates a new ``NoteSpec`` that describes a note tests are expecting to be generated by a macro expansion. + /// + /// - Parameters: + /// - message: The expected message of the note + /// - line: The line to which the note is expected to point + /// - column: The column to which the note is expected to point + /// - originatorFile: The file at which this ``NoteSpec`` was created, so that assertion failures can be reported at its location. + /// - originatorLine: The line at which this ``NoteSpec`` was created, so that assertion failures can be reported at its location. + public init( + message: String, + line: Int, + column: Int, + originatorFileID: StaticString = #fileID, + originatorFile: StaticString = #filePath, + originatorLine: UInt = #line, + originatorColumn: UInt = #column + ) { + self.message = message + self.line = line + self.column = column + self.failureLocation = TestFailureLocation( + fileID: originatorFileID, + filePath: originatorFile, + line: originatorLine, + column: originatorColumn + ) + } +} + +func assertNote( + _ note: Note, + in expansionContext: BasicMacroExpansionContext, + expected spec: NoteSpec, + failureHandler: (TestFailureSpec) -> Void +) { + assertStringsEqualWithDiff( + note.message, + spec.message, + "message of note does not match", + location: spec.failureLocation.underlying, + failureHandler: { failureHandler(TestFailureSpec(underlying: $0)) } + ) + let location = expansionContext.location(for: note.position, anchoredAt: note.node, fileName: "") + if location.line != spec.line { + failureHandler( + TestFailureSpec( + message: "line of note \(location.line) does not match expected line \(spec.line)", + location: spec.failureLocation + ) + ) + } + if location.column != spec.column { + failureHandler( + TestFailureSpec( + message: "column of note \(location.column) does not match expected column \(spec.column)", + location: spec.failureLocation + ) + ) + } +} + +// MARK: - Fix-It + +/// Describes a Fix-It that tests expect to be created by a macro expansion. +/// +/// Currently, it only compares the message of the Fix-It. In the future, it might +/// also compare the expected changes that should be performed by the Fix-It. +public struct FixItSpec { + /// The expected message of the Fix-It + public let message: String + + /// The file and line at which this ``FixItSpec`` was created, so that assertion failures can be reported at its location. + internal let failureLocation: TestFailureLocation + + /// Creates a new ``FixItSpec`` that describes a Fix-It tests are expecting to be generated by a macro expansion. + /// + /// - Parameters: + /// - message: The expected message of the note + /// - originatorFile: The file at which this ``NoteSpec`` was created, so that assertion failures can be reported at its location. + /// - originatorLine: The line at which this ``NoteSpec`` was created, so that assertion failures can be reported at its location. + public init( + message: String, + originatorFileID: StaticString = #fileID, + originatorFile: StaticString = #filePath, + originatorLine: UInt = #line, + originatorColumn: UInt = #column + ) { + self.message = message + self.failureLocation = TestFailureLocation( + fileID: originatorFileID, + filePath: originatorFile, + line: originatorLine, + column: originatorColumn + ) + } +} + +func assertFixIt( + _ fixIt: FixIt, + expected spec: FixItSpec, + failureHandler: (TestFailureSpec) -> Void +) { + assertStringsEqualWithDiff( + fixIt.message.message, + spec.message, + "message of Fix-It does not match", + location: spec.failureLocation.underlying, + failureHandler: { failureHandler(TestFailureSpec(underlying: $0)) } + ) +} + +// MARK: - Diagnostic + +/// Describes a diagnostic that tests expect to be created by a macro expansion. +public struct DiagnosticSpec { + /// If not `nil`, the ID, which the diagnostic is expected to have. + public let id: MessageID? + + /// The expected message of the diagnostic + public let message: String + + /// The line to which the diagnostic is expected to point + public let line: Int + + /// The column to which the diagnostic is expected to point + public let column: Int + + /// The expected severity of the diagnostic + public let severity: DiagnosticSeverity + + /// If not `nil`, the text fragments the diagnostic is expected to highlight + public let highlights: [String]? + + /// The notes that are expected to be attached to the diagnostic + public let notes: [NoteSpec] + + /// The messages of the Fix-Its the diagnostic is expected to produce + public let fixIts: [FixItSpec] + + /// The file and line at which this ``NoteSpec`` was created, so that assertion failures can be reported at its location. + internal let failureLocation: TestFailureLocation + + /// Creates a new ``DiagnosticSpec`` that describes a diagnostic tests are expecting to be generated by a macro expansion. + /// + /// - Parameters: + /// - id: If not `nil`, the ID, which the diagnostic is expected to have. + /// - message: The expected message of the diagnostic + /// - line: The line to which the diagnostic is expected to point + /// - column: The column to which the diagnostic is expected to point + /// - severity: The expected severity of the diagnostic + /// - highlights: If not empty, the text fragments the diagnostic is expected to highlight + /// - notes: The notes that are expected to be attached to the diagnostic + /// - fixIts: The messages of the Fix-Its the diagnostic is expected to produce + /// - originatorFile: The file at which this ``NoteSpec`` was created, so that assertion failures can be reported at its location. + /// - originatorLine: The line at which this ``NoteSpec`` was created, so that assertion failures can be reported at its location. + public init( + id: MessageID? = nil, + message: String, + line: Int, + column: Int, + severity: DiagnosticSeverity = .error, + highlights: [String]? = nil, + notes: [NoteSpec] = [], + fixIts: [FixItSpec] = [], + originatorFileID: StaticString = #fileID, + originatorFile: StaticString = #filePath, + originatorLine: UInt = #line, + originatorColumn: UInt = #column + ) { + self.id = id + self.message = message + self.line = line + self.column = column + self.severity = severity + self.highlights = highlights + self.notes = notes + self.fixIts = fixIts + self.failureLocation = TestFailureLocation( + fileID: originatorFileID, + filePath: originatorFile, + line: originatorLine, + column: originatorColumn + ) + } +} + +extension DiagnosticSpec { + @available(*, deprecated, message: "Use highlights instead") + public var highlight: String? { + guard let highlights else { + return nil + } + return highlights.joined(separator: " ") + } + + // swift-format-ignore + @available(*, deprecated, message: "Use init(id:message:line:column:severity:highlights:notes:fixIts:originatorFile:originatorLine:) instead") + @_disfavoredOverload + public init( + id: MessageID? = nil, + message: String, + line: Int, + column: Int, + severity: DiagnosticSeverity = .error, + highlight: String? = nil, + notes: [NoteSpec] = [], + fixIts: [FixItSpec] = [], + originatorFile: StaticString = #filePath, + originatorLine: UInt = #line + ) { + self.init( + id: id, + message: message, + line: line, + column: column, + severity: severity, + highlights: highlight.map { [$0] }, + notes: notes, + fixIts: fixIts + ) + } +} + +func assertDiagnostic( + _ diag: Diagnostic, + in expansionContext: BasicMacroExpansionContext, + expected spec: DiagnosticSpec, + failureHandler: (TestFailureSpec) -> Void +) { + if let id = spec.id, diag.diagnosticID != id { + failureHandler( + TestFailureSpec( + message: "diagnostic ID \(diag.diagnosticID) does not match expected id \(id)", + location: spec.failureLocation + ) + ) + } + assertStringsEqualWithDiff( + diag.message, + spec.message, + "message does not match", + location: spec.failureLocation.underlying, + failureHandler: { failureHandler(TestFailureSpec(underlying: $0)) } + ) + let location = expansionContext.location(for: diag.position, anchoredAt: diag.node, fileName: "") + if location.line != spec.line { + failureHandler( + TestFailureSpec( + message: "line \(location.line) does not match expected line \(spec.line)", + location: spec.failureLocation + ) + ) + } + if location.column != spec.column { + failureHandler( + TestFailureSpec( + message: "column \(location.column) does not match expected column \(spec.column)", + location: spec.failureLocation + ) + ) + } + + if spec.severity != diag.diagMessage.severity { + failureHandler( + TestFailureSpec( + message: "severity \(diag.diagMessage.severity) does not match expected severity \(spec.severity)", + location: spec.failureLocation + ) + ) + } + + if let highlights = spec.highlights { + if diag.highlights.count != highlights.count { + failureHandler( + TestFailureSpec( + message: """ + Expected \(highlights.count) highlights but received \(diag.highlights.count): + \(diag.highlights.map(\.trimmedDescription).joined(separator: "\n")) + """, + location: spec.failureLocation + ) + ) + } else { + for (actual, expected) in zip(diag.highlights, highlights) { + assertStringsEqualWithDiff( + actual.trimmedDescription, + expected, + "highlight does not match", + location: spec.failureLocation.underlying, + failureHandler: { failureHandler(TestFailureSpec(underlying: $0)) } + ) + } + } + } + if diag.notes.count != spec.notes.count { + failureHandler( + TestFailureSpec( + message: """ + Expected \(spec.notes.count) notes but received \(diag.notes.count): + \(diag.notes.map(\.debugDescription).joined(separator: "\n")) + """, + location: spec.failureLocation + ) + ) + } else { + for (note, expectedNote) in zip(diag.notes, spec.notes) { + assertNote(note, in: expansionContext, expected: expectedNote, failureHandler: failureHandler) + } + } + if diag.fixIts.count != spec.fixIts.count { + failureHandler( + TestFailureSpec( + message: """ + Expected \(spec.fixIts.count) Fix-Its but received \(diag.fixIts.count): + \(diag.fixIts.map(\.message.message).joined(separator: "\n")) + """, + location: spec.failureLocation + ) + ) + } else { + for (fixIt, expectedFixIt) in zip(diag.fixIts, spec.fixIts) { + assertFixIt(fixIt, expected: expectedFixIt, failureHandler: failureHandler) + } + } +} + +/// Assert that expanding the given macros in the original source produces +/// the given expanded source code. +/// +/// - Parameters: +/// - originalSource: The original source code, which is expected to contain +/// macros in various places (e.g., `#stringify(x + y)`). +/// - expectedExpandedSource: The source code that we expect to see after +/// performing macro expansion on the original source. +/// - diagnostics: The diagnostics when expanding any macro +/// - macros: The macros that should be expanded, provided as a dictionary +/// mapping macro names (e.g., `"stringify"`) to implementation types +/// (e.g., `StringifyMacro.self`). +/// - testModuleName: The name of the test module to use. +/// - testFileName: The name of the test file name to use. +/// - indentationWidth: The indentation width used in the expansion. +/// +/// - SeeAlso: ``assertMacroExpansion(_:expandedSource:diagnostics:macroSpecs:applyFixIts:fixedSource:testModuleName:testFileName:indentationWidth:file:line:)`` +/// to also specify the list of conformances passed to the macro expansion. +public func assertMacroExpansion( + _ originalSource: String, + expandedSource expectedExpandedSource: String, + diagnostics: [DiagnosticSpec] = [], + macros: [String: Macro.Type], + applyFixIts: [String]? = nil, + fixedSource expectedFixedSource: String? = nil, + testModuleName: String = "TestModule", + testFileName: String = "test.swift", + indentationWidth: Trivia = .spaces(4), + failureHandler: (TestFailureSpec) -> Void, + fileID: StaticString = #fileID, + filePath: StaticString = #filePath, + line: UInt = #line, + column: UInt = #column +) { + let specs = macros.mapValues { MacroSpec(type: $0) } + assertMacroExpansion( + originalSource, + expandedSource: expectedExpandedSource, + diagnostics: diagnostics, + macroSpecs: specs, + applyFixIts: applyFixIts, + fixedSource: expectedFixedSource, + testModuleName: testModuleName, + testFileName: testFileName, + indentationWidth: indentationWidth, + failureHandler: failureHandler, + fileID: fileID, + filePath: filePath, + line: line, + column: column + ) +} + +/// Assert that expanding the given macros in the original source produces +/// the given expanded source code. +/// +/// - Parameters: +/// - originalSource: The original source code, which is expected to contain +/// macros in various places (e.g., `#stringify(x + y)`). +/// - expectedExpandedSource: The source code that we expect to see after +/// performing macro expansion on the original source. +/// - diagnostics: The diagnostics when expanding any macro +/// - macroSpecs: The macros that should be expanded, provided as a dictionary +/// mapping macro names (e.g., `"CodableMacro"`) to specification with macro type +/// (e.g., `CodableMacro.self`) and a list of conformances macro provides +/// (e.g., `["Decodable", "Encodable"]`). +/// - applyFixIts: If specified, filters the Fix-Its that are applied to generate `fixedSource` to only those whose message occurs in this array. If `nil`, all Fix-Its from the diagnostics are applied. +/// - fixedSource: If specified, asserts that the source code after applying Fix-Its matches this string. +/// - testModuleName: The name of the test module to use. +/// - testFileName: The name of the test file name to use. +/// - indentationWidth: The indentation width used in the expansion. +public func assertMacroExpansion( + _ originalSource: String, + expandedSource expectedExpandedSource: String, + diagnostics: [DiagnosticSpec] = [], + macroSpecs: [String: MacroSpec], + applyFixIts: [String]? = nil, + fixedSource expectedFixedSource: String? = nil, + testModuleName: String = "TestModule", + testFileName: String = "test.swift", + indentationWidth: Trivia = .spaces(4), + failureHandler: (TestFailureSpec) -> Void, + fileID: StaticString = #fileID, + filePath: StaticString = #filePath, + line: UInt = #line, + column: UInt = #column +) { + let failureLocation = TestFailureLocation(fileID: fileID, filePath: filePath, line: line, column: column) + // Parse the original source file. + let origSourceFile = Parser.parse(source: originalSource) + + // Expand all macros in the source. + let context = BasicMacroExpansionContext( + sourceFiles: [origSourceFile: .init(moduleName: testModuleName, fullFilePath: testFileName)] + ) + + func contextGenerator(_ syntax: Syntax) -> BasicMacroExpansionContext { + return BasicMacroExpansionContext(sharingWith: context, lexicalContext: syntax.allMacroLexicalContexts()) + } + + let expandedSourceFile = origSourceFile.expand( + macroSpecs: macroSpecs, + contextGenerator: contextGenerator, + indentationWidth: indentationWidth + ) + let diags = ParseDiagnosticsGenerator.diagnostics(for: expandedSourceFile) + if !diags.isEmpty { + failureHandler( + TestFailureSpec( + message: """ + Expanded source should not contain any syntax errors, but contains: + \(DiagnosticsFormatter.annotatedSource(tree: expandedSourceFile, diags: diags)) + + Expanded syntax tree was: + \(expandedSourceFile.debugDescription) + """, + location: failureLocation + ) + ) + } + + assertStringsEqualWithDiff( + expandedSourceFile.description.droppingLast(while: \.isNewline), + expectedExpandedSource.droppingLast(while: \.isNewline), + "Macro expansion did not produce the expected expanded source", + additionalInfo: """ + Actual expanded source: + \(expandedSourceFile) + """, + location: failureLocation.underlying, + failureHandler: { failureHandler(TestFailureSpec(underlying: $0)) } + ) + + if context.diagnostics.count != diagnostics.count { + failureHandler( + TestFailureSpec( + message: """ + Expected \(diagnostics.count) diagnostics but received \(context.diagnostics.count): + \(context.diagnostics.map(\.debugDescription).joined(separator: "\n")) + """, + location: failureLocation + ) + ) + } else { + for (actualDiag, expectedDiag) in zip(context.diagnostics, diagnostics) { + assertDiagnostic(actualDiag, in: context, expected: expectedDiag, failureHandler: failureHandler) + } + } + + // Applying Fix-Its + if let expectedFixedSource = expectedFixedSource { + let messages = applyFixIts ?? context.diagnostics.compactMap { $0.fixIts.first?.message.message } + + let edits = + context.diagnostics + .flatMap(\.fixIts) + .filter { messages.contains($0.message.message) } + .flatMap { $0.changes } + .map { $0.edit(in: context) } + + let fixedTree = FixItApplier.apply(edits: edits, to: origSourceFile) + let fixedTreeDescription = fixedTree.description + assertStringsEqualWithDiff( + fixedTreeDescription.trimmingTrailingWhitespace(), + expectedFixedSource.trimmingTrailingWhitespace(), + location: failureLocation.underlying, + failureHandler: { failureHandler(TestFailureSpec(underlying: $0)) } + ) + } +} + +fileprivate extension FixIt.Change { + /// Returns the edit for this change, translating positions from detached nodes + /// to the corresponding locations in the original source file based on + /// `expansionContext`. + /// + /// - SeeAlso: `FixIt.Change.edit` + func edit(in expansionContext: BasicMacroExpansionContext) -> SourceEdit { + switch self { + case .replace(let oldNode, let newNode): + let start = expansionContext.position(of: oldNode.position, anchoredAt: oldNode) + let end = expansionContext.position(of: oldNode.endPosition, anchoredAt: oldNode) + return SourceEdit( + range: start.. AbsolutePosition { + let location = self.location(for: position, anchoredAt: Syntax(node), fileName: "") + return AbsolutePosition(utf8Offset: location.offset) + } +} diff --git a/Sources/_SwiftSyntaxTestSupportFrameworkAgnostic/String+TrimmingTrailingWhitespace.swift b/Sources/_SwiftSyntaxTestSupportFrameworkAgnostic/String+TrimmingTrailingWhitespace.swift index b7ac4edb2a4..31d659a801c 100644 --- a/Sources/_SwiftSyntaxTestSupportFrameworkAgnostic/String+TrimmingTrailingWhitespace.swift +++ b/Sources/_SwiftSyntaxTestSupportFrameworkAgnostic/String+TrimmingTrailingWhitespace.swift @@ -16,13 +16,13 @@ extension String { return self .split(separator: "\n", omittingEmptySubsequences: false) - .map { $0.dropLast(while: { $0 == " " }) } + .map { $0.droppingLast(while: \.isWhitespace) } .joined(separator: "\n") } } -fileprivate extension Substring { - func dropLast(while predicate: (Character) -> Bool) -> String { +public extension StringProtocol { + func droppingLast(while predicate: (Character) -> Bool) -> String { return String(self.reversed().drop(while: predicate).reversed()) } } From beb322f76301e9a485efafe8efa2491b275ff8f4 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Tue, 7 May 2024 11:22:31 -0700 Subject: [PATCH 3/7] Recore `SwiftSyntaxMacrosTestSupport` on top of `SwiftSyntaxMacrosTestSupportFrameworkAgnostic` --- Package.swift | 5 + Release Notes/600.md | 4 + .../Assertions.swift | 468 +----------------- .../Assertions.swift | 53 -- 4 files changed, 31 insertions(+), 499 deletions(-) diff --git a/Package.swift b/Package.swift index fc9b04bb8a5..45af8ded625 100644 --- a/Package.swift +++ b/Package.swift @@ -27,6 +27,10 @@ let package = Package( .library(name: "SwiftSyntaxMacros", targets: ["SwiftSyntaxMacros"]), .library(name: "SwiftSyntaxMacroExpansion", targets: ["SwiftSyntaxMacroExpansion"]), .library(name: "SwiftSyntaxMacrosTestSupport", targets: ["SwiftSyntaxMacrosTestSupport"]), + .library( + name: "SwiftSyntaxMacrosTestSupportFrameworkAgnostic", + targets: ["SwiftSyntaxMacrosTestSupportFrameworkAgnostic"] + ), ], targets: [ // MARK: - Internal helper targets @@ -223,6 +227,7 @@ let package = Package( "SwiftIDEUtils", "SwiftParser", "SwiftSyntaxMacros", + "SwiftSyntaxMacrosTestSupportFrameworkAgnostic", "SwiftSyntaxMacroExpansion", ] ), diff --git a/Release Notes/600.md b/Release Notes/600.md index ee4e5e5a8c2..acb19781251 100644 --- a/Release Notes/600.md +++ b/Release Notes/600.md @@ -95,6 +95,10 @@ - Description: `Range` gained a few convenience functions inspired from `ByteSourceRange`: `init(position:length:)`, `length`, `overlapsOrTouches` - Pull request: https://github.com/apple/swift-syntax/pull/2587 +- `SwiftSyntaxMacrosTestSupportFrameworkAgnostic` + - Description: A version of the `SwiftSyntaxMacrosTestSupport` module that doesn't depend on `Foundation` or `XCTest` and can thus be used to write macro tests using `swift-testing`. Since swift-syntax can't depend on swift-testing (which would incur a circular dependency since swift-testing depends on swift-syntax), users need to manually specify a failure handler like the following, that fails the swift-testing test: `Issue.record(Comment(rawValue: $0.message), fileID: $0.location.fileID.description, filePath: $0.location.filePath.description, line: Int($0.location.line), column: Int($0.location.column))` + - Pull request: https://github.com/apple/swift-syntax/pull/2647 + ## API Behavior Changes ## Deprecations diff --git a/Sources/SwiftSyntaxMacrosTestSupport/Assertions.swift b/Sources/SwiftSyntaxMacrosTestSupport/Assertions.swift index a107165c32b..de90bbe1f3b 100644 --- a/Sources/SwiftSyntaxMacrosTestSupport/Assertions.swift +++ b/Sources/SwiftSyntaxMacrosTestSupport/Assertions.swift @@ -11,339 +11,23 @@ //===----------------------------------------------------------------------===// #if swift(>=6.0) -import SwiftBasicFormat -public import SwiftDiagnostics -@_spi(FixItApplier) import SwiftIDEUtils -import SwiftParser -import SwiftParserDiagnostics public import SwiftSyntax public import SwiftSyntaxMacroExpansion public import SwiftSyntaxMacros -import _SwiftSyntaxTestSupport +public import SwiftSyntaxMacrosTestSupportFrameworkAgnostic private import XCTest #else -import SwiftBasicFormat -import SwiftDiagnostics -@_spi(FixItApplier) import SwiftIDEUtils -import SwiftParser -import SwiftParserDiagnostics import SwiftSyntax import SwiftSyntaxMacroExpansion import SwiftSyntaxMacros -import _SwiftSyntaxTestSupport +import SwiftSyntaxMacrosTestSupportFrameworkAgnostic import XCTest #endif -// MARK: - Note - -/// Describes a diagnostic note that tests expect to be created by a macro expansion. -public struct NoteSpec { - /// The expected message of the note - public let message: String - - /// The line to which the note is expected to point - public let line: Int - - /// The column to which the note is expected to point - public let column: Int - - /// The file and line at which this ``NoteSpec`` was created, so that assertion failures can be reported at its location. - internal let originatorFile: StaticString - internal let originatorLine: UInt - - /// Creates a new ``NoteSpec`` that describes a note tests are expecting to be generated by a macro expansion. - /// - /// - Parameters: - /// - message: The expected message of the note - /// - line: The line to which the note is expected to point - /// - column: The column to which the note is expected to point - /// - originatorFile: The file at which this ``NoteSpec`` was created, so that assertion failures can be reported at its location. - /// - originatorLine: The line at which this ``NoteSpec`` was created, so that assertion failures can be reported at its location. - public init( - message: String, - line: Int, - column: Int, - originatorFile: StaticString = #filePath, - originatorLine: UInt = #line - ) { - self.message = message - self.line = line - self.column = column - self.originatorFile = originatorFile - self.originatorLine = originatorLine - } -} - -func assertNote( - _ note: Note, - in expansionContext: BasicMacroExpansionContext, - expected spec: NoteSpec -) { - assertStringsEqualWithDiff( - note.message, - spec.message, - "message of note does not match", - file: spec.originatorFile, - line: spec.originatorLine - ) - let location = expansionContext.location(for: note.position, anchoredAt: note.node, fileName: "") - XCTAssertEqual( - location.line, - spec.line, - "line of note does not match", - file: spec.originatorFile, - line: spec.originatorLine - ) - XCTAssertEqual( - location.column, - spec.column, - "column of note does not match", - file: spec.originatorFile, - line: spec.originatorLine - ) -} - -// MARK: - Fix-It - -/// Describes a Fix-It that tests expect to be created by a macro expansion. -/// -/// Currently, it only compares the message of the Fix-It. In the future, it might -/// also compare the expected changes that should be performed by the Fix-It. -public struct FixItSpec { - /// The expected message of the Fix-It - public let message: String - - /// The file and line at which this ``NoteSpec`` was created, so that assertion failures can be reported at its location. - internal let originatorFile: StaticString - internal let originatorLine: UInt - - /// Creates a new ``FixItSpec`` that describes a Fix-It tests are expecting to be generated by a macro expansion. - /// - /// - Parameters: - /// - message: The expected message of the note - /// - originatorFile: The file at which this ``NoteSpec`` was created, so that assertion failures can be reported at its location. - /// - originatorLine: The line at which this ``NoteSpec`` was created, so that assertion failures can be reported at its location. - public init( - message: String, - originatorFile: StaticString = #filePath, - originatorLine: UInt = #line - ) { - self.message = message - self.originatorFile = originatorFile - self.originatorLine = originatorLine - } -} - -func assertFixIt( - _ fixIt: FixIt, - expected spec: FixItSpec -) { - assertStringsEqualWithDiff( - fixIt.message.message, - spec.message, - "message of Fix-It does not match", - file: spec.originatorFile, - line: spec.originatorLine - ) -} - -// MARK: - Diagnostic - -/// Describes a diagnostic that tests expect to be created by a macro expansion. -public struct DiagnosticSpec { - /// If not `nil`, the ID, which the diagnostic is expected to have. - public let id: MessageID? - - /// The expected message of the diagnostic - public let message: String - - /// The line to which the diagnostic is expected to point - public let line: Int - - /// The column to which the diagnostic is expected to point - public let column: Int - - /// The expected severity of the diagnostic - public let severity: DiagnosticSeverity - - /// If not `nil`, the text fragments the diagnostic is expected to highlight - public let highlights: [String]? - - /// The notes that are expected to be attached to the diagnostic - public let notes: [NoteSpec] - - /// The messages of the Fix-Its the diagnostic is expected to produce - public let fixIts: [FixItSpec] - - /// The file and line at which this ``NoteSpec`` was created, so that assertion failures can be reported at its location. - internal let originatorFile: StaticString - internal let originatorLine: UInt - - /// Creates a new ``DiagnosticSpec`` that describes a diagnostic tests are expecting to be generated by a macro expansion. - /// - /// - Parameters: - /// - id: If not `nil`, the ID, which the diagnostic is expected to have. - /// - message: The expected message of the diagnostic - /// - line: The line to which the diagnostic is expected to point - /// - column: The column to which the diagnostic is expected to point - /// - severity: The expected severity of the diagnostic - /// - highlights: If not empty, the text fragments the diagnostic is expected to highlight - /// - notes: The notes that are expected to be attached to the diagnostic - /// - fixIts: The messages of the Fix-Its the diagnostic is expected to produce - /// - originatorFile: The file at which this ``NoteSpec`` was created, so that assertion failures can be reported at its location. - /// - originatorLine: The line at which this ``NoteSpec`` was created, so that assertion failures can be reported at its location. - public init( - id: MessageID? = nil, - message: String, - line: Int, - column: Int, - severity: DiagnosticSeverity = .error, - highlights: [String]? = nil, - notes: [NoteSpec] = [], - fixIts: [FixItSpec] = [], - originatorFile: StaticString = #filePath, - originatorLine: UInt = #line - ) { - self.id = id - self.message = message - self.line = line - self.column = column - self.severity = severity - self.highlights = highlights - self.notes = notes - self.fixIts = fixIts - self.originatorFile = originatorFile - self.originatorLine = originatorLine - } -} - -extension DiagnosticSpec { - @available(*, deprecated, message: "Use highlights instead") - public var highlight: String? { - guard let highlights else { - return nil - } - return highlights.joined(separator: " ") - } - - // swift-format-ignore - @available(*, deprecated, message: "Use init(id:message:line:column:severity:highlights:notes:fixIts:originatorFile:originatorLine:) instead") - @_disfavoredOverload - public init( - id: MessageID? = nil, - message: String, - line: Int, - column: Int, - severity: DiagnosticSeverity = .error, - highlight: String? = nil, - notes: [NoteSpec] = [], - fixIts: [FixItSpec] = [], - originatorFile: StaticString = #filePath, - originatorLine: UInt = #line - ) { - self.init( - id: id, - message: message, - line: line, - column: column, - severity: severity, - highlights: highlight.map { [$0] }, - notes: notes, - fixIts: fixIts - ) - } -} - -func assertDiagnostic( - _ diag: Diagnostic, - in expansionContext: BasicMacroExpansionContext, - expected spec: DiagnosticSpec -) { - if let id = spec.id { - XCTAssertEqual( - diag.diagnosticID, - id, - "diagnostic ID does not match", - file: spec.originatorFile, - line: spec.originatorLine - ) - } - assertStringsEqualWithDiff( - diag.message, - spec.message, - "message does not match", - file: spec.originatorFile, - line: spec.originatorLine - ) - let location = expansionContext.location(for: diag.position, anchoredAt: diag.node, fileName: "") - XCTAssertEqual(location.line, spec.line, "line does not match", file: spec.originatorFile, line: spec.originatorLine) - XCTAssertEqual( - location.column, - spec.column, - "column does not match", - file: spec.originatorFile, - line: spec.originatorLine - ) - - XCTAssertEqual( - spec.severity, - diag.diagMessage.severity, - "severity does not match", - file: spec.originatorFile, - line: spec.originatorLine - ) - - if let highlights = spec.highlights { - if diag.highlights.count != highlights.count { - XCTFail( - """ - Expected \(highlights.count) highlights but received \(diag.highlights.count): - \(diag.highlights.map(\.trimmedDescription).joined(separator: "\n")) - """, - file: spec.originatorFile, - line: spec.originatorLine - ) - } else { - for (actual, expected) in zip(diag.highlights, highlights) { - assertStringsEqualWithDiff( - actual.trimmedDescription, - expected, - "highlight does not match", - file: spec.originatorFile, - line: spec.originatorLine - ) - } - } - } - if diag.notes.count != spec.notes.count { - XCTFail( - """ - Expected \(spec.notes.count) notes but received \(diag.notes.count): - \(diag.notes.map(\.debugDescription).joined(separator: "\n")) - """, - file: spec.originatorFile, - line: spec.originatorLine - ) - } else { - for (note, expectedNote) in zip(diag.notes, spec.notes) { - assertNote(note, in: expansionContext, expected: expectedNote) - } - } - if diag.fixIts.count != spec.fixIts.count { - XCTFail( - """ - Expected \(spec.fixIts.count) Fix-Its but received \(diag.fixIts.count): - \(diag.fixIts.map(\.message.message).joined(separator: "\n")) - """, - file: spec.originatorFile, - line: spec.originatorLine - ) - } else { - for (fixIt, expectedFixIt) in zip(diag.fixIts, spec.fixIts) { - assertFixIt(fixIt, expected: expectedFixIt) - } - } -} +// Re-export the spec types from `SwiftSyntaxMacrosTestSupportFrameworkAgnostic`. +public typealias NoteSpec = SwiftSyntaxMacrosTestSupportFrameworkAgnostic.NoteSpec +public typealias FixItSpec = SwiftSyntaxMacrosTestSupportFrameworkAgnostic.FixItSpec +public typealias DiagnosticSpec = SwiftSyntaxMacrosTestSupportFrameworkAgnostic.DiagnosticSpec /// Assert that expanding the given macros in the original source produces /// the given expanded source code. @@ -421,130 +105,22 @@ public func assertMacroExpansion( file: StaticString = #filePath, line: UInt = #line ) { - // Parse the original source file. - let origSourceFile = Parser.parse(source: originalSource) - - // Expand all macros in the source. - let context = BasicMacroExpansionContext( - sourceFiles: [origSourceFile: .init(moduleName: testModuleName, fullFilePath: testFileName)] - ) - - func contextGenerator(_ syntax: Syntax) -> BasicMacroExpansionContext { - return BasicMacroExpansionContext(sharingWith: context, lexicalContext: syntax.allMacroLexicalContexts()) - } - - let expandedSourceFile = origSourceFile.expand( + SwiftSyntaxMacrosTestSupportFrameworkAgnostic.assertMacroExpansion( + originalSource, + expandedSource: expectedExpandedSource, + diagnostics: diagnostics, macroSpecs: macroSpecs, - contextGenerator: contextGenerator, - indentationWidth: indentationWidth - ) - let diags = ParseDiagnosticsGenerator.diagnostics(for: expandedSourceFile) - if !diags.isEmpty { - XCTFail( - """ - Expanded source should not contain any syntax errors, but contains: - \(DiagnosticsFormatter.annotatedSource(tree: expandedSourceFile, diags: diags)) - - Expanded syntax tree was: - \(expandedSourceFile.debugDescription) - """, - file: file, - line: line - ) - } - - assertStringsEqualWithDiff( - expandedSourceFile.description.trimmingCharacters(in: .newlines), - expectedExpandedSource.trimmingCharacters(in: .newlines), - "Macro expansion did not produce the expected expanded source", - additionalInfo: """ - Actual expanded source: - \(expandedSourceFile) - """, - file: file, - line: line + applyFixIts: applyFixIts, + fixedSource: expectedFixedSource, + testModuleName: testModuleName, + testFileName: testFileName, + indentationWidth: indentationWidth, + failureHandler: { + XCTFail($0.message, file: $0.location.filePath, line: $0.location.line) + }, + fileID: "", // Not used in the failure handler + filePath: file, + line: line, + column: 0 // Not used in the failure handler ) - - if context.diagnostics.count != diagnostics.count { - XCTFail( - """ - Expected \(diagnostics.count) diagnostics but received \(context.diagnostics.count): - \(context.diagnostics.map(\.debugDescription).joined(separator: "\n")) - """, - file: file, - line: line - ) - } else { - for (actualDiag, expectedDiag) in zip(context.diagnostics, diagnostics) { - assertDiagnostic(actualDiag, in: context, expected: expectedDiag) - } - } - - // Applying Fix-Its - if let expectedFixedSource = expectedFixedSource { - let messages = applyFixIts ?? context.diagnostics.compactMap { $0.fixIts.first?.message.message } - - let edits = - context.diagnostics - .flatMap(\.fixIts) - .filter { messages.contains($0.message.message) } - .flatMap { $0.changes } - .map { $0.edit(in: context) } - - let fixedTree = FixItApplier.apply(edits: edits, to: origSourceFile) - let fixedTreeDescription = fixedTree.description - assertStringsEqualWithDiff( - fixedTreeDescription.trimmingTrailingWhitespace(), - expectedFixedSource.trimmingTrailingWhitespace(), - file: file, - line: line - ) - } -} - -fileprivate extension FixIt.Change { - /// Returns the edit for this change, translating positions from detached nodes - /// to the corresponding locations in the original source file based on - /// `expansionContext`. - /// - /// - SeeAlso: `FixIt.Change.edit` - func edit(in expansionContext: BasicMacroExpansionContext) -> SourceEdit { - switch self { - case .replace(let oldNode, let newNode): - let start = expansionContext.position(of: oldNode.position, anchoredAt: oldNode) - let end = expansionContext.position(of: oldNode.endPosition, anchoredAt: oldNode) - return SourceEdit( - range: start.. AbsolutePosition { - let location = self.location(for: position, anchoredAt: Syntax(node), fileName: "") - return AbsolutePosition(utf8Offset: location.offset) - } } diff --git a/Sources/SwiftSyntaxMacrosTestSupportFrameworkAgnostic/Assertions.swift b/Sources/SwiftSyntaxMacrosTestSupportFrameworkAgnostic/Assertions.swift index 6f26a6de4a0..e89695f2d91 100644 --- a/Sources/SwiftSyntaxMacrosTestSupportFrameworkAgnostic/Assertions.swift +++ b/Sources/SwiftSyntaxMacrosTestSupportFrameworkAgnostic/Assertions.swift @@ -433,59 +433,6 @@ func assertDiagnostic( } } -/// Assert that expanding the given macros in the original source produces -/// the given expanded source code. -/// -/// - Parameters: -/// - originalSource: The original source code, which is expected to contain -/// macros in various places (e.g., `#stringify(x + y)`). -/// - expectedExpandedSource: The source code that we expect to see after -/// performing macro expansion on the original source. -/// - diagnostics: The diagnostics when expanding any macro -/// - macros: The macros that should be expanded, provided as a dictionary -/// mapping macro names (e.g., `"stringify"`) to implementation types -/// (e.g., `StringifyMacro.self`). -/// - testModuleName: The name of the test module to use. -/// - testFileName: The name of the test file name to use. -/// - indentationWidth: The indentation width used in the expansion. -/// -/// - SeeAlso: ``assertMacroExpansion(_:expandedSource:diagnostics:macroSpecs:applyFixIts:fixedSource:testModuleName:testFileName:indentationWidth:file:line:)`` -/// to also specify the list of conformances passed to the macro expansion. -public func assertMacroExpansion( - _ originalSource: String, - expandedSource expectedExpandedSource: String, - diagnostics: [DiagnosticSpec] = [], - macros: [String: Macro.Type], - applyFixIts: [String]? = nil, - fixedSource expectedFixedSource: String? = nil, - testModuleName: String = "TestModule", - testFileName: String = "test.swift", - indentationWidth: Trivia = .spaces(4), - failureHandler: (TestFailureSpec) -> Void, - fileID: StaticString = #fileID, - filePath: StaticString = #filePath, - line: UInt = #line, - column: UInt = #column -) { - let specs = macros.mapValues { MacroSpec(type: $0) } - assertMacroExpansion( - originalSource, - expandedSource: expectedExpandedSource, - diagnostics: diagnostics, - macroSpecs: specs, - applyFixIts: applyFixIts, - fixedSource: expectedFixedSource, - testModuleName: testModuleName, - testFileName: testFileName, - indentationWidth: indentationWidth, - failureHandler: failureHandler, - fileID: fileID, - filePath: filePath, - line: line, - column: column - ) -} - /// Assert that expanding the given macros in the original source produces /// the given expanded source code. /// From 178dc7a345fd57aee5925e766013b58e820e60db Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Wed, 8 May 2024 11:47:07 -0700 Subject: [PATCH 4/7] Address review comments --- Sources/SwiftSyntaxMacrosTestSupport/Assertions.swift | 4 +++- .../Assertions.swift | 7 ++++--- .../AssertEqualWithDiff.swift | 3 ++- .../String+TrimmingTrailingWhitespace.swift | 1 - Tests/SwiftParserTest/translated/RecoveryTests.swift | 2 +- 5 files changed, 10 insertions(+), 7 deletions(-) diff --git a/Sources/SwiftSyntaxMacrosTestSupport/Assertions.swift b/Sources/SwiftSyntaxMacrosTestSupport/Assertions.swift index de90bbe1f3b..8a4af759a7d 100644 --- a/Sources/SwiftSyntaxMacrosTestSupport/Assertions.swift +++ b/Sources/SwiftSyntaxMacrosTestSupport/Assertions.swift @@ -70,7 +70,9 @@ public func assertMacroExpansion( fixedSource: expectedFixedSource, testModuleName: testModuleName, testFileName: testFileName, - indentationWidth: indentationWidth + indentationWidth: indentationWidth, + file: file, + line: line ) } diff --git a/Sources/SwiftSyntaxMacrosTestSupportFrameworkAgnostic/Assertions.swift b/Sources/SwiftSyntaxMacrosTestSupportFrameworkAgnostic/Assertions.swift index e89695f2d91..0d6887cd013 100644 --- a/Sources/SwiftSyntaxMacrosTestSupportFrameworkAgnostic/Assertions.swift +++ b/Sources/SwiftSyntaxMacrosTestSupportFrameworkAgnostic/Assertions.swift @@ -73,7 +73,8 @@ public struct TestFailureLocation { } } -/// Defines the details of a test failure, consisting of a message and the location at which the l +/// Defines the details of a test failure, consisting of a message and the location at which the test failure should be +/// shown. public struct TestFailureSpec { public let message: String public let location: TestFailureLocation @@ -502,8 +503,8 @@ public func assertMacroExpansion( } assertStringsEqualWithDiff( - expandedSourceFile.description.droppingLast(while: \.isNewline), - expectedExpandedSource.droppingLast(while: \.isNewline), + expandedSourceFile.description.drop(while: \.isNewline).droppingLast(while: \.isNewline), + expectedExpandedSource.drop(while: \.isNewline).droppingLast(while: \.isNewline), "Macro expansion did not produce the expected expanded source", additionalInfo: """ Actual expanded source: diff --git a/Sources/_SwiftSyntaxTestSupportFrameworkAgnostic/AssertEqualWithDiff.swift b/Sources/_SwiftSyntaxTestSupportFrameworkAgnostic/AssertEqualWithDiff.swift index c874acbde94..d23a3031538 100644 --- a/Sources/_SwiftSyntaxTestSupportFrameworkAgnostic/AssertEqualWithDiff.swift +++ b/Sources/_SwiftSyntaxTestSupportFrameworkAgnostic/AssertEqualWithDiff.swift @@ -31,7 +31,8 @@ public struct TestFailureLocation { } } -/// Defines the details of a test failure, consisting of a message and the location at which the l +/// Defines the details of a test failure, consisting of a message and the location at which the test failure should be +/// shown. public struct TestFailureSpec { public let message: String public let location: TestFailureLocation diff --git a/Sources/_SwiftSyntaxTestSupportFrameworkAgnostic/String+TrimmingTrailingWhitespace.swift b/Sources/_SwiftSyntaxTestSupportFrameworkAgnostic/String+TrimmingTrailingWhitespace.swift index 31d659a801c..2134b5632c5 100644 --- a/Sources/_SwiftSyntaxTestSupportFrameworkAgnostic/String+TrimmingTrailingWhitespace.swift +++ b/Sources/_SwiftSyntaxTestSupportFrameworkAgnostic/String+TrimmingTrailingWhitespace.swift @@ -11,7 +11,6 @@ //===----------------------------------------------------------------------===// extension String { - // This implementation is really slow; to use it outside a test it should be optimized. public func trimmingTrailingWhitespace() -> String { return self diff --git a/Tests/SwiftParserTest/translated/RecoveryTests.swift b/Tests/SwiftParserTest/translated/RecoveryTests.swift index c158d4beba1..6a9c96a1452 100644 --- a/Tests/SwiftParserTest/translated/RecoveryTests.swift +++ b/Tests/SwiftParserTest/translated/RecoveryTests.swift @@ -2454,7 +2454,7 @@ final class RecoveryTests: ParserTestCase { DiagnosticSpec(message: "unexpected code '!=baz' in parameter clause"), ], fixedSource: """ - func foo1(bar: <#type#>!=baz) {} + func foo1(bar: <#type#>!=baz) {} """ ) } From 7c119539a060f0b162b5fff8b52aebb25893e8a4 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Wed, 8 May 2024 15:43:02 -0700 Subject: [PATCH 5/7] Make unsigned and static properties in `TestFailureLocation` SPI Instead, expose `String` and `Int` variants publicly that can be passed directly in to `Issue.record` from `swift-testing`. --- Release Notes/600.md | 2 +- .../Assertions.swift | 6 ++-- .../Assertions.swift | 31 ++++++++++++------- 3 files changed, 23 insertions(+), 16 deletions(-) diff --git a/Release Notes/600.md b/Release Notes/600.md index acb19781251..56e665b704d 100644 --- a/Release Notes/600.md +++ b/Release Notes/600.md @@ -96,7 +96,7 @@ - Pull request: https://github.com/apple/swift-syntax/pull/2587 - `SwiftSyntaxMacrosTestSupportFrameworkAgnostic` - - Description: A version of the `SwiftSyntaxMacrosTestSupport` module that doesn't depend on `Foundation` or `XCTest` and can thus be used to write macro tests using `swift-testing`. Since swift-syntax can't depend on swift-testing (which would incur a circular dependency since swift-testing depends on swift-syntax), users need to manually specify a failure handler like the following, that fails the swift-testing test: `Issue.record(Comment(rawValue: $0.message), fileID: $0.location.fileID.description, filePath: $0.location.filePath.description, line: Int($0.location.line), column: Int($0.location.column))` + - Description: A version of the `SwiftSyntaxMacrosTestSupport` module that doesn't depend on `Foundation` or `XCTest` and can thus be used to write macro tests using `swift-testing`. Since swift-syntax can't depend on swift-testing (which would incur a circular dependency since swift-testing depends on swift-syntax), users need to manually specify a failure handler like the following, that fails the swift-testing test: `Issue.record("\($0.message)", fileID: $0.location.fileID, filePath: $0.location.filePath, line: $0.location.line, column: $0.location.column)` - Pull request: https://github.com/apple/swift-syntax/pull/2647 ## API Behavior Changes diff --git a/Sources/SwiftSyntaxMacrosTestSupport/Assertions.swift b/Sources/SwiftSyntaxMacrosTestSupport/Assertions.swift index 8a4af759a7d..3c85dbb1403 100644 --- a/Sources/SwiftSyntaxMacrosTestSupport/Assertions.swift +++ b/Sources/SwiftSyntaxMacrosTestSupport/Assertions.swift @@ -14,13 +14,13 @@ public import SwiftSyntax public import SwiftSyntaxMacroExpansion public import SwiftSyntaxMacros -public import SwiftSyntaxMacrosTestSupportFrameworkAgnostic +@_spi(XCTestFailureLocation) public import SwiftSyntaxMacrosTestSupportFrameworkAgnostic private import XCTest #else import SwiftSyntax import SwiftSyntaxMacroExpansion import SwiftSyntaxMacros -import SwiftSyntaxMacrosTestSupportFrameworkAgnostic +@_spi(XCTestFailureLocation) import SwiftSyntaxMacrosTestSupportFrameworkAgnostic import XCTest #endif @@ -118,7 +118,7 @@ public func assertMacroExpansion( testFileName: testFileName, indentationWidth: indentationWidth, failureHandler: { - XCTFail($0.message, file: $0.location.filePath, line: $0.location.line) + XCTFail($0.message, file: $0.location.staticFilePath, line: $0.location.unsignedLine) }, fileID: "", // Not used in the failure handler filePath: file, diff --git a/Sources/SwiftSyntaxMacrosTestSupportFrameworkAgnostic/Assertions.swift b/Sources/SwiftSyntaxMacrosTestSupportFrameworkAgnostic/Assertions.swift index 0d6887cd013..05585c07c7e 100644 --- a/Sources/SwiftSyntaxMacrosTestSupportFrameworkAgnostic/Assertions.swift +++ b/Sources/SwiftSyntaxMacrosTestSupportFrameworkAgnostic/Assertions.swift @@ -35,10 +35,17 @@ import _SwiftSyntaxTestSupportFrameworkAgnostic /// Defines the location at which the a test failure should be anchored. This is typically the location where the /// assertion function is called. public struct TestFailureLocation { - public let fileID: StaticString - public let filePath: StaticString - public let line: UInt - public let column: UInt + @_spi(XCTestFailureLocation) public let staticFileID: StaticString + public var fileID: String { staticFileID.description } + + @_spi(XCTestFailureLocation) public let staticFilePath: StaticString + public var filePath: String { staticFilePath.description } + + @_spi(XCTestFailureLocation) public let unsignedLine: UInt + public var line: Int { Int(unsignedLine) } + + @_spi(XCTestFailureLocation) public let unsignedColumn: UInt + public var column: Int { Int(unsignedColumn) } public init( fileID: StaticString, @@ -46,10 +53,10 @@ public struct TestFailureLocation { line: UInt, column: UInt ) { - self.fileID = fileID - self.filePath = filePath - self.line = line - self.column = column + self.staticFileID = fileID + self.staticFilePath = filePath + self.unsignedLine = line + self.unsignedColumn = column } fileprivate init(underlying: _SwiftSyntaxTestSupportFrameworkAgnostic.TestFailureLocation) { @@ -65,10 +72,10 @@ public struct TestFailureLocation { /// import `_SwiftSyntaxTestSupportFrameworkAgnostic` privately and don't expose its internal types. fileprivate var underlying: _SwiftSyntaxTestSupportFrameworkAgnostic.TestFailureLocation { _SwiftSyntaxTestSupportFrameworkAgnostic.TestFailureLocation( - fileID: self.fileID, - filePath: self.filePath, - line: self.line, - column: self.column + fileID: self.staticFileID, + filePath: self.staticFilePath, + line: self.unsignedLine, + column: self.unsignedColumn ) } } From 537e1a2de2f3d58d31a7733100953959157e15c2 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Wed, 8 May 2024 15:43:28 -0700 Subject: [PATCH 6/7] Rename module from `FrameworkAgnostic` to `Generic` --- Package.swift | 16 ++++++++-------- Release Notes/600.md | 2 +- .../Assertions.swift | 16 ++++++++-------- .../Assertions.swift | 14 +++++++------- .../AssertEqualWithDiff.swift | 0 .../String+TrimmingTrailingWhitespace.swift | 0 .../AssertEqualWithDiff.swift | 6 +++--- 7 files changed, 27 insertions(+), 27 deletions(-) rename Sources/{SwiftSyntaxMacrosTestSupportFrameworkAgnostic => SwiftSyntaxMacrosGenericTestSupport}/Assertions.swift (96%) rename Sources/{_SwiftSyntaxTestSupportFrameworkAgnostic => _SwiftSyntaxGenericTestSupport}/AssertEqualWithDiff.swift (100%) rename Sources/{_SwiftSyntaxTestSupportFrameworkAgnostic => _SwiftSyntaxGenericTestSupport}/String+TrimmingTrailingWhitespace.swift (100%) diff --git a/Package.swift b/Package.swift index 45af8ded625..d2dbfe765b9 100644 --- a/Package.swift +++ b/Package.swift @@ -28,8 +28,8 @@ let package = Package( .library(name: "SwiftSyntaxMacroExpansion", targets: ["SwiftSyntaxMacroExpansion"]), .library(name: "SwiftSyntaxMacrosTestSupport", targets: ["SwiftSyntaxMacrosTestSupport"]), .library( - name: "SwiftSyntaxMacrosTestSupportFrameworkAgnostic", - targets: ["SwiftSyntaxMacrosTestSupportFrameworkAgnostic"] + name: "SwiftSyntaxMacrosGenericTestSupport", + targets: ["SwiftSyntaxMacrosGenericTestSupport"] ), ], targets: [ @@ -45,7 +45,7 @@ let package = Package( .target( name: "_SwiftSyntaxTestSupport", dependencies: [ - "_SwiftSyntaxTestSupportFrameworkAgnostic", + "_SwiftSyntaxGenericTestSupport", "SwiftBasicFormat", "SwiftSyntax", "SwiftSyntaxBuilder", @@ -54,7 +54,7 @@ let package = Package( ), .target( - name: "_SwiftSyntaxTestSupportFrameworkAgnostic", + name: "_SwiftSyntaxGenericTestSupport", dependencies: [] ), @@ -227,17 +227,17 @@ let package = Package( "SwiftIDEUtils", "SwiftParser", "SwiftSyntaxMacros", - "SwiftSyntaxMacrosTestSupportFrameworkAgnostic", + "SwiftSyntaxMacrosGenericTestSupport", "SwiftSyntaxMacroExpansion", ] ), - // MARK: SwiftSyntaxMacrosTestSupportFrameworkAgnostic + // MARK: SwiftSyntaxMacrosGenericTestSupport .target( - name: "SwiftSyntaxMacrosTestSupportFrameworkAgnostic", + name: "SwiftSyntaxMacrosGenericTestSupport", dependencies: [ - "_SwiftSyntaxTestSupportFrameworkAgnostic", + "_SwiftSyntaxGenericTestSupport", "SwiftDiagnostics", "SwiftIDEUtils", "SwiftParser", diff --git a/Release Notes/600.md b/Release Notes/600.md index 56e665b704d..f0e16afdce1 100644 --- a/Release Notes/600.md +++ b/Release Notes/600.md @@ -95,7 +95,7 @@ - Description: `Range` gained a few convenience functions inspired from `ByteSourceRange`: `init(position:length:)`, `length`, `overlapsOrTouches` - Pull request: https://github.com/apple/swift-syntax/pull/2587 -- `SwiftSyntaxMacrosTestSupportFrameworkAgnostic` +- `SwiftSyntaxMacrosGenericTestSupport` - Description: A version of the `SwiftSyntaxMacrosTestSupport` module that doesn't depend on `Foundation` or `XCTest` and can thus be used to write macro tests using `swift-testing`. Since swift-syntax can't depend on swift-testing (which would incur a circular dependency since swift-testing depends on swift-syntax), users need to manually specify a failure handler like the following, that fails the swift-testing test: `Issue.record("\($0.message)", fileID: $0.location.fileID, filePath: $0.location.filePath, line: $0.location.line, column: $0.location.column)` - Pull request: https://github.com/apple/swift-syntax/pull/2647 diff --git a/Sources/SwiftSyntaxMacrosTestSupportFrameworkAgnostic/Assertions.swift b/Sources/SwiftSyntaxMacrosGenericTestSupport/Assertions.swift similarity index 96% rename from Sources/SwiftSyntaxMacrosTestSupportFrameworkAgnostic/Assertions.swift rename to Sources/SwiftSyntaxMacrosGenericTestSupport/Assertions.swift index 05585c07c7e..8f204aafbf3 100644 --- a/Sources/SwiftSyntaxMacrosTestSupportFrameworkAgnostic/Assertions.swift +++ b/Sources/SwiftSyntaxMacrosGenericTestSupport/Assertions.swift @@ -19,7 +19,7 @@ import SwiftParserDiagnostics public import SwiftSyntax public import SwiftSyntaxMacroExpansion private import SwiftSyntaxMacros -private import _SwiftSyntaxTestSupportFrameworkAgnostic +private import _SwiftSyntaxGenericTestSupport #else import SwiftBasicFormat import SwiftDiagnostics @@ -29,7 +29,7 @@ import SwiftParserDiagnostics import SwiftSyntax import SwiftSyntaxMacroExpansion import SwiftSyntaxMacros -import _SwiftSyntaxTestSupportFrameworkAgnostic +import _SwiftSyntaxGenericTestSupport #endif /// Defines the location at which the a test failure should be anchored. This is typically the location where the @@ -59,7 +59,7 @@ public struct TestFailureLocation { self.unsignedColumn = column } - fileprivate init(underlying: _SwiftSyntaxTestSupportFrameworkAgnostic.TestFailureLocation) { + fileprivate init(underlying: _SwiftSyntaxGenericTestSupport.TestFailureLocation) { self.init( fileID: underlying.fileID, filePath: underlying.filePath, @@ -68,10 +68,10 @@ public struct TestFailureLocation { ) } - /// This type is intentionally different to `_SwiftSyntaxTestSupportFrameworkAgnostic.TestFailureLocation` so we can - /// import `_SwiftSyntaxTestSupportFrameworkAgnostic` privately and don't expose its internal types. - fileprivate var underlying: _SwiftSyntaxTestSupportFrameworkAgnostic.TestFailureLocation { - _SwiftSyntaxTestSupportFrameworkAgnostic.TestFailureLocation( + /// This type is intentionally different to `_SwiftSyntaxGenericTestSupport.TestFailureLocation` so we can + /// import `_SwiftSyntaxGenericTestSupport` privately and don't expose its internal types. + fileprivate var underlying: _SwiftSyntaxGenericTestSupport.TestFailureLocation { + _SwiftSyntaxGenericTestSupport.TestFailureLocation( fileID: self.staticFileID, filePath: self.staticFilePath, line: self.unsignedLine, @@ -91,7 +91,7 @@ public struct TestFailureSpec { self.location = location } - fileprivate init(underlying: _SwiftSyntaxTestSupportFrameworkAgnostic.TestFailureSpec) { + fileprivate init(underlying: _SwiftSyntaxGenericTestSupport.TestFailureSpec) { self.init( message: underlying.message, location: TestFailureLocation(underlying: underlying.location) diff --git a/Sources/SwiftSyntaxMacrosTestSupport/Assertions.swift b/Sources/SwiftSyntaxMacrosTestSupport/Assertions.swift index 3c85dbb1403..50296b267ab 100644 --- a/Sources/SwiftSyntaxMacrosTestSupport/Assertions.swift +++ b/Sources/SwiftSyntaxMacrosTestSupport/Assertions.swift @@ -14,20 +14,20 @@ public import SwiftSyntax public import SwiftSyntaxMacroExpansion public import SwiftSyntaxMacros -@_spi(XCTestFailureLocation) public import SwiftSyntaxMacrosTestSupportFrameworkAgnostic +@_spi(XCTestFailureLocation) public import SwiftSyntaxMacrosGenericTestSupport private import XCTest #else import SwiftSyntax import SwiftSyntaxMacroExpansion import SwiftSyntaxMacros -@_spi(XCTestFailureLocation) import SwiftSyntaxMacrosTestSupportFrameworkAgnostic +@_spi(XCTestFailureLocation) import SwiftSyntaxMacrosGenericTestSupport import XCTest #endif -// Re-export the spec types from `SwiftSyntaxMacrosTestSupportFrameworkAgnostic`. -public typealias NoteSpec = SwiftSyntaxMacrosTestSupportFrameworkAgnostic.NoteSpec -public typealias FixItSpec = SwiftSyntaxMacrosTestSupportFrameworkAgnostic.FixItSpec -public typealias DiagnosticSpec = SwiftSyntaxMacrosTestSupportFrameworkAgnostic.DiagnosticSpec +// Re-export the spec types from `SwiftSyntaxMacrosGenericTestSupport`. +public typealias NoteSpec = SwiftSyntaxMacrosGenericTestSupport.NoteSpec +public typealias FixItSpec = SwiftSyntaxMacrosGenericTestSupport.FixItSpec +public typealias DiagnosticSpec = SwiftSyntaxMacrosGenericTestSupport.DiagnosticSpec /// Assert that expanding the given macros in the original source produces /// the given expanded source code. @@ -107,7 +107,7 @@ public func assertMacroExpansion( file: StaticString = #filePath, line: UInt = #line ) { - SwiftSyntaxMacrosTestSupportFrameworkAgnostic.assertMacroExpansion( + SwiftSyntaxMacrosGenericTestSupport.assertMacroExpansion( originalSource, expandedSource: expectedExpandedSource, diagnostics: diagnostics, diff --git a/Sources/_SwiftSyntaxTestSupportFrameworkAgnostic/AssertEqualWithDiff.swift b/Sources/_SwiftSyntaxGenericTestSupport/AssertEqualWithDiff.swift similarity index 100% rename from Sources/_SwiftSyntaxTestSupportFrameworkAgnostic/AssertEqualWithDiff.swift rename to Sources/_SwiftSyntaxGenericTestSupport/AssertEqualWithDiff.swift diff --git a/Sources/_SwiftSyntaxTestSupportFrameworkAgnostic/String+TrimmingTrailingWhitespace.swift b/Sources/_SwiftSyntaxGenericTestSupport/String+TrimmingTrailingWhitespace.swift similarity index 100% rename from Sources/_SwiftSyntaxTestSupportFrameworkAgnostic/String+TrimmingTrailingWhitespace.swift rename to Sources/_SwiftSyntaxGenericTestSupport/String+TrimmingTrailingWhitespace.swift diff --git a/Sources/_SwiftSyntaxTestSupport/AssertEqualWithDiff.swift b/Sources/_SwiftSyntaxTestSupport/AssertEqualWithDiff.swift index 2242d257cde..99f451a555a 100644 --- a/Sources/_SwiftSyntaxTestSupport/AssertEqualWithDiff.swift +++ b/Sources/_SwiftSyntaxTestSupport/AssertEqualWithDiff.swift @@ -13,11 +13,11 @@ #if swift(>=6) public import Foundation private import XCTest -private import _SwiftSyntaxTestSupportFrameworkAgnostic +private import _SwiftSyntaxGenericTestSupport #else import Foundation import XCTest -import _SwiftSyntaxTestSupportFrameworkAgnostic +import _SwiftSyntaxGenericTestSupport #endif /// Asserts that the two strings are equal, providing Unix `diff`-style output if they are not. @@ -45,7 +45,7 @@ public func assertStringsEqualWithDiff( line: line, column: 0 // Not used in the failure handler ) - return _SwiftSyntaxTestSupportFrameworkAgnostic.assertStringsEqualWithDiff( + return _SwiftSyntaxGenericTestSupport.assertStringsEqualWithDiff( actual, expected, message, From 769a78b03a1e8dcece17395223efc962dc2cd9f3 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Mon, 13 May 2024 08:31:06 -0700 Subject: [PATCH 7/7] Shrink dependency list of `SwiftSyntaxMacrosTestSupport` --- Package.swift | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/Package.swift b/Package.swift index d2dbfe765b9..351928bd19d 100644 --- a/Package.swift +++ b/Package.swift @@ -222,13 +222,10 @@ let package = Package( .target( name: "SwiftSyntaxMacrosTestSupport", dependencies: [ - "_SwiftSyntaxTestSupport", - "SwiftDiagnostics", - "SwiftIDEUtils", - "SwiftParser", + "SwiftSyntax", + "SwiftSyntaxMacroExpansion", "SwiftSyntaxMacros", "SwiftSyntaxMacrosGenericTestSupport", - "SwiftSyntaxMacroExpansion", ] ),