Skip to content

Commit 0186aab

Browse files
committed
refactoring swift code validation
1 parent 3ac0c94 commit 0186aab

6 files changed

Lines changed: 254 additions & 192 deletions

File tree

Tests/SyntaxDocTests/DocumentationExampleTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ internal struct DocumentationExampleTests {
5454
let results = try await testHarness.validateExamplesInFile(macroTutorialFile)
5555

5656
// Macro examples should compile (though they may not execute without full macro setup)
57-
let compileResults = results.filter { $0.testType == .compilation }
57+
let compileResults = results.filter { $0.testType == .parsing }
5858
#expect(
5959
compileResults.allSatisfy { $0.success }, "All macro examples should compile successfully")
6060
}
Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
import Foundation
2+
import SwiftParser
3+
import SwiftSyntax
4+
5+
/// Protocol defining the parameters required for Swift code validation
6+
internal protocol SwiftValidationParameters {
7+
/// The Swift code to validate
8+
var code: String { get }
9+
10+
/// The file URL where the code was found
11+
var fileURL: URL { get }
12+
13+
/// The line number where the code was found
14+
var lineNumber: Int { get }
15+
}
16+
17+
18+
/// Parameters for validating code blocks with additional metadata
19+
internal struct CodeBlockValidationParameters: SwiftValidationParameters, Sendable {
20+
internal let code: String
21+
internal let fileURL: URL
22+
internal let lineNumber: Int
23+
internal let blockIndex: Int
24+
internal let blockType: CodeBlockType
25+
26+
internal init(
27+
code: String,
28+
fileURL: URL,
29+
lineNumber: Int,
30+
blockIndex: Int,
31+
blockType: CodeBlockType
32+
) {
33+
self.code = code
34+
self.fileURL = fileURL
35+
self.lineNumber = lineNumber
36+
self.blockIndex = blockIndex
37+
self.blockType = blockType
38+
}
39+
}
40+
41+
/// Result of Swift code validation
42+
internal struct SyntaxValidationResult {
43+
internal let success: Bool
44+
internal let error: String?
45+
}
46+
47+
/// Validates Swift code examples for syntax correctness
48+
internal class SwiftCodeValidator {
49+
50+
// MARK: - Public Methods
51+
52+
/// Validates a Swift code example
53+
/// - Parameter parameters: The validation parameters containing code, file URL, and line number
54+
/// - Returns: A ValidationResult indicating success or failure
55+
internal func validateSwiftExample(
56+
_ parameters: any SwiftValidationParameters
57+
) -> ValidationResult {
58+
let code = parameters.code
59+
let fileURL = parameters.fileURL
60+
let lineNumber = parameters.lineNumber
61+
do {
62+
// Create a temporary Swift file for testing
63+
let tempFile = try createTemporarySwiftFile(with: code)
64+
defer { try? FileManager.default.removeItem(at: tempFile) }
65+
66+
// First, try to validate the syntax
67+
let validationResult = try validateSwiftSyntax(tempFile)
68+
69+
if !validationResult.success {
70+
return ValidationResult(
71+
success: false,
72+
fileURL: fileURL,
73+
lineNumber: lineNumber,
74+
testType: .parsing,
75+
error: "Syntax validation failed: \(validationResult.error ?? "Unknown error")"
76+
)
77+
}
78+
79+
// If syntax validation succeeded, return success
80+
// Note: This only validates syntax, not full compilation or execution
81+
return ValidationResult(
82+
success: true,
83+
fileURL: fileURL,
84+
lineNumber: lineNumber,
85+
testType: .parsing,
86+
error: nil
87+
)
88+
} catch {
89+
return ValidationResult(
90+
success: false,
91+
fileURL: fileURL,
92+
lineNumber: lineNumber,
93+
testType: .parsing,
94+
error: "Syntax validation setup failed: \(error.localizedDescription)"
95+
)
96+
}
97+
}
98+
99+
/// Convenience method for backward compatibility
100+
/// - Parameters:
101+
/// - code: The Swift code to validate
102+
/// - fileURL: The file URL where the code was found
103+
/// - lineNumber: The line number where the code was found
104+
/// - Returns: A ValidationResult indicating success or failure
105+
// internal func validateSwiftExample(
106+
// _ code: String,
107+
// fileURL: URL,
108+
// lineNumber: Int
109+
// ) async -> ValidationResult {
110+
// let parameters = SwiftValidationParametersImpl(
111+
// code: code,
112+
// fileURL: fileURL,
113+
// lineNumber: lineNumber
114+
// )
115+
// return await validateSwiftExample(parameters)
116+
// }
117+
118+
// MARK: - Private Methods
119+
120+
/// Creates a temporary Swift file with proper imports and structure
121+
private func createTemporarySwiftFile(with code: String) throws -> URL {
122+
let tempDir = FileManager.default.temporaryDirectory
123+
let tempFile = tempDir.appendingPathComponent("DocTest-\(UUID()).swift")
124+
125+
// Wrap the code with necessary imports and structure
126+
let wrappedCode = """
127+
import Foundation
128+
import SyntaxKit
129+
130+
// Documentation example code:
131+
\(code)
132+
"""
133+
134+
try wrappedCode.write(to: tempFile, atomically: true, encoding: .utf8)
135+
return tempFile
136+
}
137+
138+
/// Validates Swift syntax by parsing the file content
139+
private func validateSwiftSyntax(_ fileURL: URL) throws -> SyntaxValidationResult {
140+
// For documentation tests, we validate Swift syntax using the Swift parser
141+
// This only checks for syntax errors, not full compilation or type checking
142+
143+
let code = try String(contentsOf: fileURL)
144+
145+
// Skip Package.swift examples and incomplete snippets
146+
if code.contains("Package(") || code.contains("dependencies:") || code.contains(".package(") {
147+
return SyntaxValidationResult(success: true, error: nil)
148+
}
149+
150+
// Skip examples that obviously require runtime execution or have other imports
151+
if code.contains("@main") || (code.contains("import") && !code.contains("import SyntaxKit")) {
152+
return SyntaxValidationResult(success: true, error: nil)
153+
}
154+
155+
// Skip shell commands or configuration examples
156+
if code.contains("swift build") || code.contains("swift test") || code.contains("swift package")
157+
{
158+
return SyntaxValidationResult(success: true, error: nil)
159+
}
160+
161+
// For SyntaxKit examples, create a complete, parseable Swift source
162+
let cleanSource =
163+
code
164+
.replacingOccurrences(of: "import SyntaxKit", with: "")
165+
.replacingOccurrences(of: "import Foundation", with: "")
166+
.trimmingCharacters(in: .whitespacesAndNewlines)
167+
168+
// Skip if the remaining code is too fragmentary to parse
169+
if cleanSource.isEmpty || cleanSource.count < 10
170+
|| (!cleanSource.contains("{") && !cleanSource.contains("let")
171+
&& !cleanSource.contains("var"))
172+
{
173+
return SyntaxValidationResult(success: true, error: nil)
174+
}
175+
176+
// Try to parse as complete Swift statements
177+
let wrappedSource = """
178+
func testExample() {
179+
\(cleanSource)
180+
}
181+
"""
182+
183+
let parsed = Parser.parse(source: wrappedSource)
184+
185+
// Check for syntax errors in the parsed result
186+
if parsed.hasError {
187+
return SyntaxValidationResult(
188+
success: false,
189+
error: "Syntax parsing detected errors in the code"
190+
)
191+
}
192+
193+
return SyntaxValidationResult(success: true, error: nil)
194+
}
195+
}

0 commit comments

Comments
 (0)