Swift
Classes
SwiftScanner
class SwiftScanner implements Scanner
Use SwiftScanner to extract public API elements from Swift source files so skrypt can generate documentation for your Swift codebase.
Reach for this when you're integrating Swift support into a skrypt documentation pipeline, or when you need to programmatically inspect which public symbols a Swift file exposes. It fits into the scanner stage of the workflow — before AI doc generation runs, SwiftScanner parses your .swift files and hands back structured APIElement objects that skrypt uses to produce MDX output.
SwiftScanner targets only production Swift files. It automatically skips test files (anything ending in Tests.swift, Test.swift, or living under a /Tests/ directory), so your generated docs stay clean without any manual filtering.
Parameters — canHandle(filePath)
| Name | Type | Required | Description |
|---|---|---|---|
filePath | string | Yes | Absolute or relative path to the file you want to check. Returns false for test files and any non-.swift extension, preventing accidental scanning of unsupported files. |
Returns — canHandle
Returns true if the file is a scannable Swift source file. Use this as a guard before calling scan() to avoid errors on unsupported files.
Returns — scan
Returns a ScanResult containing an array of APIElement objects — one per discovered public symbol (class, struct, enum, protocol, actor, function, or method). Each element includes the symbol name, kind, parameters, return type, and source context. Pass the result directly to skrypt's doc generation step or serialize it for caching.
Heads up
- Only
publicandopendeclarations are extracted. Internal,fileprivate, andprivatesymbols are intentionally ignored — if a symbol isn't showing up, check its access modifier first.SwiftScannerreads files synchronously. For large Swift codebases, scan files in batches rather than all at once to avoid blocking the event loop.
Example:
// Self-contained example — types and scanner behavior inlined
interface Parameter {
name: string
type: string
required: boolean
description: string
}
interface APIElement {
name: string
kind: string
signature: string
parameters: Parameter[]
returnType?: string
sourceContext?: string
lineNumber?: number
}
interface ScanResult {
elements: APIElement[]
language: string
filePath: string
}
interface Scanner {
languages: string[]
canHandle(filePath: string): boolean
scan(filePath: string): ScanResult
}
// Inline mock of SwiftScanner — mirrors real behavior
class SwiftScanner implements Scanner {
languages = ['swift']
canHandle(filePath: string): boolean {
return (
/\.swift$/.test(filePath) &&
!filePath.endsWith('Tests.swift') &&
!filePath.endsWith('Test.swift') &&
!filePath.includes('/Tests/')
)
}
scan(filePath: string): ScanResult {
// In production, reads the file and extracts public symbols via regex.
// Here we return realistic mock output for a Swift networking file.
return {
elements: [
{
name: 'APIClient',
kind: 'class',
signature: 'public class APIClient',
parameters: [],
sourceContext: 'public class APIClient { ... }',
lineNumber: 12,
},
{
name: 'fetch',
kind: 'method',
signature: 'public func fetch<T: Decodable>(endpoint: String, responseType: T.Type) async throws -> T',
parameters: [
{ name: 'endpoint', type: 'String', required: true, description: 'The API endpoint path' },
{ name: 'responseType', type: 'T.Type', required: true, description: 'The Decodable type to decode the response into' },
],
returnType: 'T',
sourceContext: 'public func fetch<T: Decodable>(...) async throws -> T { ... }',
lineNumber: 28,
},
],
language: 'swift',
filePath,
}
}
}
// --- Usage ---
const scanner = new SwiftScanner()
const filesToCheck = [
'/Users/dev/MyApp/Sources/Networking/APIClient.swift',
'/Users/dev/MyApp/Sources/Networking/APIClientTests.swift', // should be skipped
'/Users/dev/MyApp/Sources/Models/User.swift',
]
try {
for (const filePath of filesToCheck) {
if (!scanner.canHandle(filePath)) {
console.log(`Skipping: ${filePath}`)
continue
}
const result = scanner.scan(filePath)
console.log(`Scanned: ${filePath}`)
console.log(` Found ${result.elements.length} public symbol(s):`)
for (const el of result.elements) {
console.log(` - [${el.kind}] ${el.name} (line ${el.lineNumber})`)
}
}
} catch (err) {
console.error('Scan failed:', err)
}
// Expected output:
// Scanned: /Users/dev/MyApp/Sources/Networking/APIClient.swift
// Found 2 public symbol(s):
// - [class] APIClient (line 12)
// - [method] fetch (line 28)
// Skipping: /Users/dev/MyApp/Sources/Networking/APIClientTests.swift
// Scanned: /Users/dev/MyApp/Sources/Models/User.swift
// Found 2 public symbol(s):
// - [class] APIClient (line 12)
// - [method] fetch (line 28)
Methods
canHandle
canHandle(filePath: string): boolean
Use canHandle to determine whether a SwiftScanner instance should process a given file before scanning it.
Call this before invoking the scanner on a file path — it's the standard gate-check in skrypt's multi-scanner pipeline to route each file to the right language handler.
canHandle returns true only for .swift files that aren't test files. It filters out paths ending in Test.swift or Tests.swift, and any file living under a /Tests/ directory. This means your documentation will never include test scaffolding, only your public API surface.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
filePath | string | Yes | Absolute or relative path to the file being evaluated — paths containing /Tests/ or ending in Test.swift/Tests.swift are automatically excluded |
Returns
Returns true if the file is a non-test Swift source file that this scanner can process, false otherwise. Use the result to decide whether to proceed with a full scan — passing a false result to the scanner will produce no useful output.
Heads up
- The check is path-based only — the file doesn't need to exist on disk for
canHandleto return a result. A path like./Sources/MyLib/AuthClient.swiftreturnstrueeven if the file hasn't been written yet.- Files named
MyFeatureTest.swift(singular) are excluded, not justTests.swift— both naming conventions are caught.
Example:
// Inline types to keep this self-contained
interface Scanner {
languages: string[];
canHandle(filePath: string): boolean;
}
// Minimal SwiftScanner re-implementation matching the real behavior
class SwiftScanner implements Scanner {
languages = ['swift'];
canHandle(filePath: string): boolean {
return (
/\.swift$/.test(filePath) &&
!filePath.endsWith('Tests.swift') &&
!filePath.endsWith('Test.swift') &&
!filePath.includes('/Tests/')
);
}
}
const scanner = new SwiftScanner();
const filePaths = [
'./Sources/PaymentKit/ChargeClient.swift', // ✓ public source
'./Sources/PaymentKit/ChargeClientTests.swift', // ✗ test file (plural)
'./Sources/PaymentKit/ChargeClientTest.swift', // ✗ test file (singular)
'./Tests/PaymentKit/ChargeClientSpec.swift', // ✗ inside /Tests/ dir
'./Sources/PaymentKit/Models/Invoice.ts', // ✗ wrong extension
];
for (const filePath of filePaths) {
const result = scanner.canHandle(filePath);
console.log(`${result ? '✓' : '✗'} ${filePath}`);
}
// Expected output:
// ✓ ./Sources/PaymentKit/ChargeClient.swift
// ✗ ./Sources/PaymentKit/ChargeClientTests.swift
// ✗ ./Sources/PaymentKit/ChargeClientTest.swift
// ✗ ./Tests/PaymentKit/ChargeClientSpec.swift
// ✗ ./Sources/PaymentKit/Models/Invoice.ts
scanFile
async scanFile(filePath: string): Promise<ScanResult>
Use scanFile to extract all documented API elements from a single Swift source file, giving you the structured data needed to generate documentation for that file.
Reach for this when you're building a custom documentation pipeline and need to process files individually — for example, to parallelize scanning across a large codebase or to re-scan only files that changed. In a typical SwiftScanner workflow, you'd call scanFile for each .swift file discovered by your directory traversal, then aggregate the results.
scanFile reads the file from disk, parses its Swift source, and extracts API elements (classes, methods, properties, etc.) along with their signatures, parameters, and inline documentation comments. Parsing errors are captured per-file rather than thrown, so a single malformed file won't halt a batch scan.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
filePath | string | Yes | Absolute or relative path to the .swift file to scan. Relative paths resolve from the current working directory at runtime. |
Returns
Returns a Promise<ScanResult> containing two fields: elements — an array of APIElement objects representing every public API surface found in the file — and errors — an array of strings describing any parse failures encountered. Pass elements to your documentation renderer or aggregator; check errors before assuming the scan was complete.
Heads up
- Test files are automatically excluded by
SwiftScannerbeforescanFileis called (files ending inTest.swiftor under a/Tests/path). CallingscanFiledirectly on a test file will still return a result, but the output may include test-only symbols you don't want in your docs. errorsbeing non-empty doesn't meanelementsis empty — the scanner recovers and continues after parse failures, so you may get partial results alongside errors.
Example:
import { readFileSync, writeFileSync } from "fs";
import { join } from "path";
// --- Inline types (do not import from autodocs) ---
interface Parameter {
name: string;
type: string;
description: string;
}
interface APIElement {
name: string;
kind: "class" | "method" | "property" | "enum" | "struct";
signature: string;
description: string;
parameters: Parameter[];
returnType?: string;
lineNumber: number;
}
interface ScanResult {
elements: APIElement[];
errors: string[];
}
// --- Minimal SwiftScanner mock ---
class SwiftScanner {
async scanFile(filePath: string): Promise<ScanResult> {
const source = readFileSync(filePath, "utf-8");
const elements: APIElement[] = [];
const errors: string[] = [];
const methodRegex =
/\/\/\/\s*(.+)\n\s*(?:public\s+)?func\s+(\w+)\(([^)]*)\)(?:\s*->\s*(\S+))?/g;
let match: RegExpExecArray | null;
while ((match = methodRegex.exec(source)) !== null) {
const [, description, name, rawParams, returnType] = match;
const parameters: Parameter[] = rawParams
.split(",")
.filter(Boolean)
.map((p) => {
const [paramName, paramType] = p.trim().split(":").map((s) => s.trim());
return { name: paramName, type: paramType ?? "Any", description: "" };
});
elements.push({
name,
kind: "method",
signature: match[0].split("\n").pop()?.trim() ?? "",
description: description.trim(),
parameters,
returnType,
lineNumber: source.slice(0, match.index).split("\n").length,
});
}
return { elements, errors };
}
}
// --- Write a temporary Swift file to scan ---
const swiftSource = `
/// Fetches a user by their unique identifier.
public func getUser(id: String) -> User
/// Creates a new session token for the given user.
public func createSession(userId: String, expiresIn: Int) -> String
`;
const tmpPath = join(process.cwd(), "UserService.swift");
writeFileSync(tmpPath, swiftSource, "utf-8");
// --- Run the scan ---
async function main() {
const scanner = new SwiftScanner();
try {
const result = await scanner.scanFile(tmpPath);
if (result.errors.length > 0) {
console.warn("Parse errors encountered:", result.errors);
}
console.log(`Found ${result.elements.length} API elements:\n`);
for (const el of result.elements) {
console.log(`[${el.kind}] ${el.name} (line ${el.lineNumber})`);
console.log(` Description : ${el.description}`);
console.log(` Returns : ${el.returnType ?? "void"}`);
console.log(` Parameters : ${el.parameters.map((p) => `${p.name}: ${p.type}`).join(", ") || "none"}`);
console.log();
}
} catch (err) {
console.error("Failed to scan file:", err);
}
}
main();
// Expected output:
// Found 2 API elements:
//
// [method] getUser (line 3)
// Description : Fetches a user by their unique identifier.
// Returns : User
// Parameters : id: String
//
// [method] createSession (line 6)
// Description : Creates a new session token for the given user.
// Returns : String
// Parameters : userId: String, expiresIn: Int