@@ -33,12 +33,9 @@ import SwiftSyntax
3333import Testing
3434
3535/// Test harness for extracting and validating Swift code examples from documentation
36- package struct DocumentationTestHarness {
37- /// Default file extensions for documentation files
38- internal static let defaultPathExtensions = [ " md " ]
36+ package struct DocumentationTestHarness : DocumentationValidator {
3937 /// Swift code validator instance
4038 private let codeValidator : any SyntaxValidator
41- private let fileSearcher : any FileSearcher
4239 private let codeBlocksFrom : CodeBlockExtractor
4340
4441 /// Creates a new documentation test harness
@@ -48,58 +45,26 @@ package struct DocumentationTestHarness {
4845 /// - codeBlocksFrom: Function to extract code blocks from content
4946 package init (
5047 codeValidator: any SyntaxValidator = CodeSyntaxValidator ( ) ,
51- fileSearcher: any FileSearcher = FileManager . default,
5248 codeBlocksFrom: @escaping CodeBlockExtractor = CodeBlockExtraction . callAsFunction ( _: )
5349 ) {
5450 self . codeValidator = codeValidator
55- self . fileSearcher = fileSearcher
5651 self . codeBlocksFrom = codeBlocksFrom
5752 }
5853
59- /// Validates all Swift code examples found in documentation files
60- /// - Parameters:
61- /// - relativePaths: Array of relative paths to search for documentation
62- /// - projectRoot: Root URL of the project
63- /// - pathExtensions: File extensions to search for (defaults to ["md"])
64- /// - Returns: Array of validation results for all code blocks found
65- /// - Throws: FileSearchError if file operations fail
66- package func validate(
67- relativePaths: [ String ] , atProjectRoot projectRoot: URL ,
68- withPathExtensions pathExtensions: [ String ] = Self . defaultPathExtensions
69- ) throws -> [ ValidationResult ] {
70- let documentationFiles = try relativePaths. flatMap { docPath in
71- let absolutePath = projectRoot. appendingPathComponent ( docPath)
72- return try self . fileSearcher. findDocumentationFiles (
73- in: absolutePath,
74- pathExtensions: pathExtensions
75- )
76- }
77- var allResults : [ ValidationResult ] = [ ]
78-
79- for filePath in documentationFiles {
80- let results = try validateExamplesInFile ( filePath)
81- allResults. append ( contentsOf: results)
82- }
83-
84- return allResults
85- }
86-
8754 /// Validates all Swift code examples in a specific documentation file
8855 /// - Parameter fileURL: URL of the file to validate
8956 /// - Returns: Array of validation results for code blocks in the file
9057 /// - Throws: Error if file cannot be read or parsed
91- package func validateExamplesInFile ( _ fileURL: URL ) throws -> [ ValidationResult ] {
58+ package func validateFile ( at fileURL: URL ) throws -> [ ValidationResult ] {
9259 // let fullPath = try resolveFilePath(filePath)
9360 let content = try String ( contentsOf: fileURL)
9461
9562 let codeBlocks = try codeBlocksFrom ( content)
9663 var results : [ ValidationResult ] = [ ]
9764
9865 for (index, codeBlock) in codeBlocks. enumerated ( ) {
99- try results. append (
100- #require(
101- validateCodeBlock ( fileURL. codeBlock ( codeBlock, at: index) )
102- )
66+ results. append (
67+ validateCodeBlock ( fileURL. codeBlock ( codeBlock, at: index) )
10368 )
10469 }
10570
@@ -109,81 +74,15 @@ package struct DocumentationTestHarness {
10974 /// Validates a single code block
11075 private func validateCodeBlock(
11176 _ parameters: CodeBlockValidationParameters
112- ) -> ValidationResult ? {
113- switch parameters. codeBlock. blockType {
114- case . example:
115- // Test compilation and basic execution
116- return codeValidator. validateSyntax ( from: parameters)
117-
118- case . packageManifest:
119- #if canImport(Foundation) && (os(macOS) || os(Linux))
120- // Package.swift files need special handling
121- var processError : ProcessError ?
122- do {
123- try validatePackageManifest ( parameters. code)
124- processError = nil
125- } catch {
126- processError = error
127- }
128- return ValidationResult (
129- parameters: parameters,
130- testType: . parsing,
131- error: processError. map { ValidationError . processError ( $0) }
132- )
133- #else
134- return ValidationResult (
135- parameters: parameters,
136- testType: . skipped,
137- error: nil
138- )
139- #endif
140- case . shellCommand:
141- // Skip shell commands for now
77+ ) -> ValidationResult {
78+ guard case . example = parameters. codeBlock. blockType else {
14279 return ValidationResult (
14380 parameters: parameters,
14481 testType: . skipped,
14582 error: nil
14683 )
14784 }
85+ // Test compilation and basic execution
86+ return codeValidator. validateSyntax ( from: parameters)
14887 }
149-
150- #if canImport(Foundation) && (os(macOS) || os(Linux))
151- /// Validates a Package.swift manifest
152- private func validatePackageManifest(
153- _ code: String
154- ) throws ( ProcessError) {
155- let process = Process ( )
156- do {
157- // Create temporary Package.swift and validate it parses
158- let tempDir = FileManager . default. temporaryDirectory
159- . appendingPathComponent ( " SyntaxKit-DocTest- \( UUID ( ) ) " )
160-
161- try FileManager . default. createDirectory ( at: tempDir, withIntermediateDirectories: true )
162- defer { try ? FileManager . default. removeItem ( at: tempDir) }
163-
164- let packageFile = tempDir. appendingPathComponent ( " Package.swift " )
165- try code. write ( to: packageFile, atomically: true , encoding: . utf8)
166-
167- // Use swift package tools to validate
168- process. currentDirectoryURL = tempDir
169- process. executableURL = URL ( fileURLWithPath: " /usr/bin/swift " )
170- process. arguments = [ " package " , " describe " , " --type " , " json " ]
171-
172- let pipe = Pipe ( )
173- process. standardOutput = pipe
174- process. standardError = pipe
175-
176- try process. run ( )
177- process. waitUntilExit ( )
178- } catch {
179- throw . setupError( error)
180- }
181-
182- guard process. terminationStatus == 0 else {
183- return
184- }
185-
186- throw . packageValidationFailed
187- }
188- #endif
18988}
0 commit comments