Skip to content

Scanner — Kotlin

Kotlin

Classes

KotlinScanner

class KotlinScanner implements Scanner
TypeScript

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)

NameTypeRequiredDescription
filePathstringYesAbsolute 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)

NameTypeRequiredDescription
filePathstringYesPath 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 — canHandle returns false for any path matching Test.kt or /test/, so you won't get an error if you pass one in; you'll just get no output.
  • languages is 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 the kotlin language 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()
TypeScript

Methods

canHandle

canHandle(filePath: string): boolean
TypeScript

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

NameTypeRequiredDescription
filePathstringYesAbsolute 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 canHandle to return a result. A path like src/UserService.kt returns true even 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 (no Test.kt suffix) 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)
}
TypeScript

scanFile

async scanFile(filePath: string): Promise<ScanResult>
TypeScript

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

NameTypeRequiredDescription
filePathstringYesAbsolute 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

  • scanFile will throw if the file doesn't exist or isn't readable — it does not return a failed ScanResult. Wrap calls in try/catch and handle filesystem errors separately from parse errors in result.errors.
  • Test files are silently out of scope. If you pass a *Test.kt path, the scanner will still attempt to parse it — call canScan(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()
TypeScript
Was this helpful?