Kotlin
Classes
KotlinScanner
class KotlinScanner implements Scanner
Use KotlinScanner to extract API elements from Kotlin source files so skrypt can generate documentation for your Kotlin codebase.
When skrypt encounters .kt or .kts files during a skrypt generate run, KotlinScanner is the scanner that handles them. You'd interact with this class directly only if you're building a custom scanning pipeline or extending skrypt's behavior — in normal usage, skrypt selects and invokes it automatically.
KotlinScanner reads each file and pulls out classes, data classes, sealed classes, objects, interfaces, enum classes, and top-level functions, returning structured APIElement records that the doc generator turns into MDX pages. It automatically skips test files (any path containing Test.kt or /test/) so your generated docs stay focused on public API surface.
Parameters — canHandle(filePath)
| Name | Type | Required | Description |
|---|---|---|---|
filePath | string | Yes | Absolute or relative path to the file. Returns true only for .kt/.kts files outside test directories — use this to check before calling scanFile. |
Parameters — scanFile(filePath)
| Name | Type | Required | Description |
|---|---|---|---|
filePath | string | Yes | Path to the .kt or .kts file to parse. Must be readable by the process — relative paths resolve from the current working directory. |
Returns
scanFile returns a Promise<ScanResult> containing an array of APIElement objects, each with the element's name, kind (e.g. "class", "function"), parameters, return type, and source context. Pass the result to skrypt's doc generator or merge it with results from other scanners when building multi-language documentation.
Heads up
- Test files are silently skipped —
canHandlereturnsfalsefor any path matchingTest.ktor/test/, so you won't get an error if you pass one in; you'll just get no output. languagesis set to['kotlin']and is used by skrypt's scanner registry to route files automatically. If you're registering this scanner manually, make sure nothing else claims thekotlinlanguage key first.
Example:
// Self-contained example: simulating KotlinScanner behavior
// without importing from autodocs
import { readFileSync, writeFileSync, mkdirSync } from 'fs'
import { tmpdir } from 'os'
import { join } from 'path'
// --- Inline types (mirrors autodocs internals) ---
interface Parameter {
name: string
type: string
required: boolean
}
interface APIElement {
name: string
kind: string
parameters: Parameter[]
returnType: string | null
sourceContext: string
lineNumber: number
}
interface ScanResult {
elements: APIElement[]
filePath: string
}
interface Scanner {
languages: string[]
canHandle(filePath: string): boolean
scanFile(filePath: string): Promise<ScanResult>
}
// --- Minimal KotlinScanner implementation (mirrors autodocs behavior) ---
class KotlinScanner implements Scanner {
languages = ['kotlin']
canHandle(filePath: string): boolean {
return /\.kts?$/.test(filePath) &&
!filePath.includes('Test.kt') &&
!filePath.includes('/test/')
}
async scanFile(filePath: string): Promise<ScanResult> {
const source = readFileSync(filePath, 'utf-8')
const elements: APIElement[] = []
const lines = source.split('\n')
const patterns: Array<{ regex: RegExp; kind: string }> = [
{ regex: /^(?:data\s+)?class\s+(\w+)/, kind: 'class' },
{ regex: /^sealed\s+class\s+(\w+)/, kind: 'sealed class' },
{ regex: /^interface\s+(\w+)/, kind: 'interface' },
{ regex: /^object\s+(\w+)/, kind: 'object' },
{ regex: /^enum\s+class\s+(\w+)/, kind: 'enum class' },
{ regex: /^fun\s+(\w+)\(/, kind: 'function' },
]
lines.forEach((line, i) => {
const trimmed = line.trim()
for (const { regex, kind } of patterns) {
const match = trimmed.match(regex)
if (match) {
elements.push({
name: match[1],
kind,
parameters: [],
returnType: null,
sourceContext: lines.slice(Math.max(0, i - 1), i + 3).join('\n'),
lineNumber: i + 1,
})
break
}
}
})
return { elements, filePath }
}
}
// --- Demo ---
async function main() {
// Write a sample Kotlin file to a temp location
const tmpDir = join(tmpdir(), 'skrypt-demo')
mkdirSync(tmpDir, { recursive: true })
const sampleKotlin = `
data class User(val id: String, val email: String)
sealed class AuthResult
interface UserRepository
object DatabaseConfig
enum class Role { ADMIN, VIEWER }
fun fetchUser(id: String): User? = null
`.trim()
const ktFile = join(tmpDir, 'UserService.kt')
const testFile = join(tmpDir, 'UserServiceTest.kt')
writeFileSync(ktFile, sampleKotlin)
writeFileSync(testFile, sampleKotlin)
const scanner = new KotlinScanner()
console.log('Supported languages:', scanner.languages)
// => ['kotlin']
console.log('Handles UserService.kt:', scanner.canHandle(ktFile))
// => true
console.log('Handles UserServiceTest.kt:', scanner.canHandle(testFile))
// => false (test files are skipped)
try {
const result = await scanner.scanFile(ktFile)
console.log(`\nFound ${result.elements.length} API elements in ${ktFile}:`)
for (const el of result.elements) {
console.log(` [${el.kind}] ${el.name} (line ${el.lineNumber})`)
}
// => [data class] User (line 1)
// => [sealed class] AuthResult (line 2)
// => [interface] UserRepository (line 3)
// => [object] DatabaseConfig (line 4)
// => [enum class] Role (line 5)
// => [function] fetchUser (line 6)
} catch (err) {
console.error('Scan failed:', err)
}
}
main()
Methods
canHandle
canHandle(filePath: string): boolean
Use canHandle to determine whether a file should be processed by the KotlinScanner before scanning begins.
Call this before passing a file path to the scanner — it's the gating check that skrypt uses internally to route each discovered file to the right language scanner. If you're building a custom scanning pipeline or filtering a list of files, use this to identify which ones are valid Kotlin source files.
Returns true only for .kt and .kts files that aren't test files. Specifically, it rejects any path containing Test.kt or a /test/ directory segment, so your generated docs stay focused on production API surface rather than test helpers.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
filePath | string | Yes | Absolute or relative path to the file being evaluated. Paths containing Test.kt or /test/ are automatically excluded. |
Returns
true if the file is a non-test Kotlin source file that KotlinScanner can parse; false otherwise. Use this as a guard before invoking the scanner — passing an unsupported file will produce no results or unexpected behavior.
Heads up
- The check is path-based only — the file doesn't need to exist on disk for
canHandleto return a result. A path likesrc/UserService.ktreturnstrueeven if the file hasn't been written yet. - Test exclusion is pattern-matched on the path string, not file content. A file named
UserTestHelper.kt(noTest.ktsuffix) would pass the check even if it contains test utilities.
Example:
// Inline mock of KotlinScanner.canHandle for a self-contained example
const kotlinFilePattern = /\.kts?$/
function canHandle(filePath: string): boolean {
return kotlinFilePattern.test(filePath) &&
!filePath.includes('Test.kt') &&
!filePath.includes('/test/')
}
const files = [
'src/main/kotlin/com/acme/UserService.kt',
'src/main/kotlin/com/acme/PaymentProcessor.kts',
'src/test/kotlin/com/acme/UserServiceTest.kt',
'src/main/kotlin/com/acme/OrderServiceTest.kt',
'src/main/java/com/acme/LegacyService.java',
'scripts/migrate.kts',
]
try {
const scannable = files.filter(canHandle)
const skipped = files.filter(f => !canHandle(f))
console.log('Will scan:', scannable)
// Will scan: [
// 'src/main/kotlin/com/acme/UserService.kt',
// 'src/main/kotlin/com/acme/PaymentProcessor.kts',
// 'scripts/migrate.kts'
// ]
console.log('Skipped:', skipped)
// Skipped: [
// 'src/test/kotlin/com/acme/UserServiceTest.kt',
// 'src/main/kotlin/com/acme/OrderServiceTest.kt',
// 'src/main/java/com/acme/LegacyService.java'
// ]
} catch (err) {
console.error('Filtering failed:', err)
}
scanFile
async scanFile(filePath: string): Promise<ScanResult>
Use scanFile to extract all public API elements from a Kotlin source file and get them back as a structured result ready for documentation generation.
Reach for this when you're building a documentation pipeline and need to process individual .kt or .kts files one at a time — for example, when walking a directory tree and feeding each file through KotlinScanner before passing results to a doc generator.
scanFile reads the file from disk, parses its lines for classes, functions, properties, and their signatures, and returns both the extracted elements and any parse errors encountered. Files matching *Test.kt or living under a /test/ path are outside the intended scope of this method — use canScan() first to confirm a file is eligible.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
filePath | string | Yes | Absolute or relative path to the .kt or .kts file to parse. Relative paths resolve from the current working directory at runtime. |
Returns
Returns a Promise<ScanResult> containing an elements array of discovered API items (each with name, signature, parameters, and source context) and an errors array of non-fatal parse warnings. Pass elements directly to your doc generator or topic organizer; check errors to surface files that parsed partially so you can triage them without failing the whole pipeline.
Heads up
scanFilewill throw if the file doesn't exist or isn't readable — it does not return a failedScanResult. Wrap calls intry/catchand handle filesystem errors separately from parse errors inresult.errors.- Test files are silently out of scope. If you pass a
*Test.ktpath, the scanner will still attempt to parse it — callcanScan(filePath)beforehand to filter them out.
Example:
import { readFileSync } from 'fs'
import { join } from 'path'
// Inline types (do not import from autodocs)
interface Parameter {
name: string
type: string
required: boolean
description: string
}
interface APIElement {
name: string
kind: 'class' | 'function' | 'property'
signature: string
parameters: Parameter[]
sourceContext: string
lineNumber: number
}
interface ScanResult {
elements: APIElement[]
errors: string[]
}
// Minimal KotlinScanner mock — replace with real instantiation in your project
class KotlinScanner {
canScan(filePath: string): boolean {
return /\.kts?$/.test(filePath) &&
!filePath.includes('Test.kt') &&
!filePath.includes('/test/')
}
async scanFile(filePath: string): Promise<ScanResult> {
const source = readFileSync(filePath, 'utf-8')
const elements: APIElement[] = []
const errors: string[] = []
// Simplified extraction: find top-level fun declarations
source.split('\n').forEach((line, i) => {
const match = line.match(/^(public\s+)?fun\s+(\w+)\(([^)]*)\)/)
if (match) {
elements.push({
name: match[2],
kind: 'function',
signature: line.trim(),
parameters: [],
sourceContext: line.trim(),
lineNumber: i + 1,
})
}
})
return { elements, errors }
}
}
// --- Usage ---
const scanner = new KotlinScanner()
const filePath = join(process.cwd(), 'src/payments/PaymentProcessor.kt')
async function main() {
if (!scanner.canScan(filePath)) {
console.log('Skipping: file is not a scannable Kotlin source.')
process.exit(0)
}
try {
const result = await scanner.scanFile(filePath)
if (result.errors.length > 0) {
console.warn('Parse warnings:', result.errors)
}
console.log(`Found ${result.elements.length} API elements:`)
for (const el of result.elements) {
console.log(` [${el.kind}] ${el.name} — line ${el.lineNumber}`)
}
// Expected output:
// Found 3 API elements:
// [function] processPayment — line 12
// [function] refund — line 34
// [function] getStatus — line 58
} catch (err) {
console.error('Failed to scan file (check path and permissions):', err)
}
}
main()