Java
Classes
JavaScanner
class JavaScanner implements Scanner
Use JavaScanner to extract public API elements from Java source files so skrypt can generate documentation for your Java codebase.
Reach for this when you're configuring skrypt to scan a Java project. JavaScanner is one of skrypt's built-in language scanners — it plugs into the same pipeline as the TypeScript, Python, Go, and Rust scanners, and gets invoked automatically when skrypt encounters .java files during a skrypt generate run.
JavaScanner reads each .java file and pulls out public classes, public methods, and public static methods — the surface area that forms your documented API. It deliberately skips test files (any file under a /test/ directory or ending in Test.java) so your generated docs stay focused on production code.
Properties
| Name | Type | Description |
|---|---|---|
languages | string[] | Always ['java'] — tells the skrypt scanner registry which file types this scanner owns. |
Methods
canHandle(filePath)
| Name | Type | Required | Description |
|---|---|---|---|
filePath | string | Yes | Absolute or relative path to the file being evaluated. Files under /test/ or ending in Test.java return false and are skipped entirely. |
Returns true if this scanner should process the file, false otherwise. skrypt calls this before scanFile to route each file to the right scanner.
scanFile(filePath)
| Name | Type | Required | Description |
|---|---|---|---|
filePath | string | Yes | Path to a .java source file. Must be readable by the process running skrypt. |
Returns a Promise<ScanResult> containing the extracted APIElement array — each element represents a public class, method, or static method found in the file. skrypt passes this result to the AI documentation generator, which produces the final MDX output.
Heads up
- Only
publicmembers are extracted. Package-private,protected, andprivatemethods are intentionally ignored — if a method isn't in your public API, it won't appear in your docs. JavaScanneruses regex-based parsing, not a full AST. Heavily annotated or generated Java code with unusual formatting may produce incomplete results.
Example:
// Self-contained example: manually invoke a JavaScanner-compatible implementation
// to understand what gets extracted before running `skrypt generate`
import { readFileSync, writeFileSync, mkdirSync } from 'fs'
import { tmpdir } from 'os'
import { join } from 'path'
// Inline the Scanner interface (do not import from autodocs)
interface APIElement {
name: string
kind: string
signature: string
filePath: string
lineNumber: number
sourceContext: string
}
interface ScanResult {
elements: APIElement[]
filePath: string
}
interface Scanner {
languages: string[]
canHandle(filePath: string): boolean
scanFile(filePath: string): Promise<ScanResult>
}
// Minimal JavaScanner implementation mirroring autodocs behavior
class JavaScanner implements Scanner {
languages = ['java']
canHandle(filePath: string): boolean {
return /\.java$/.test(filePath) && !filePath.includes('/test/') && !filePath.includes('Test.java')
}
async scanFile(filePath: string): Promise<ScanResult> {
const source = readFileSync(filePath, 'utf-8')
const elements: APIElement[] = []
const lines = source.split('\n')
const patterns = [
{ regex: /public\s+class\s+(\w+)/, kind: 'class' },
{ regex: /public\s+static\s+\w[\w<>\[\]]*\s+(\w+)\s*\(/, kind: 'static_method' },
{ regex: /public\s+\w[\w<>\[\]]*\s+(\w+)\s*\(/, kind: 'method' },
]
lines.forEach((line, i) => {
for (const { regex, kind } of patterns) {
const match = line.match(regex)
if (match) {
elements.push({
name: match[1],
kind,
signature: line.trim(),
filePath,
lineNumber: i + 1,
sourceContext: lines.slice(Math.max(0, i - 1), i + 3).join('\n'),
})
break
}
}
})
return { elements, filePath }
}
}
// Write a sample Java file to a temp directory
const tmpDir = join(tmpdir(), 'skrypt-java-demo')
mkdirSync(tmpDir, { recursive: true })
const sampleJava = `
package com.example;
public class PaymentService {
private String apiKey;
public PaymentService(String apiKey) {
this.apiKey = apiKey;
}
public static PaymentService create(String apiKey) {
return new PaymentService(apiKey);
}
public Receipt charge(String customerId, int amountCents) {
// implementation
return new Receipt(customerId, amountCents);
}
private void log(String message) {
// private — will not be extracted
}
}
`.trim()
const javaFile = join(tmpDir, 'PaymentService.java')
const testFile = join(tmpDir, 'PaymentServiceTest.java')
writeFileSync(javaFile, sampleJava)
writeFileSync(testFile, '// test file — should be skipped')
const scanner = new JavaScanner()
async function main() {
try {
console.log('Supported languages:', scanner.languages)
// → ['java']
console.log('Handles PaymentService.java:', scanner.canHandle(javaFile))
// → true
console.log('Handles PaymentServiceTest.java:', scanner.canHandle(testFile))
// → false
const result = await scanner.scanFile(javaFile)
console.log(`\nExtracted ${result.elements.length} API elements from ${result.filePath}:`)
result.elements.forEach(el => {
console.log(` [${el.kind}] line ${el.lineNumber}: ${el.name}`)
})
// → [class] line 3: PaymentService
// → [static_method] line 11: create
// → [method] line 15: charge
// private log() is absent — not extracted
} catch (err) {
console.error('Scan failed:', err)
process.exit(1)
}
}
main()
Methods
canHandle
canHandle(filePath: string): boolean
Use canHandle to determine whether JavaScanner should process a given file before scanning it.
Call this before invoking scanFile when you're building a pipeline that routes files to the correct scanner — it lets you filter out non-Java files and skip test files in one step.
Beyond checking the .java extension, canHandle also excludes files under any /test/ directory and files ending in Test.java. This means your generated docs won't be polluted with test class signatures.
| Name | Type | Required | Description |
|---|---|---|---|
filePath | string | Yes | Absolute or relative path to the file being evaluated. Paths containing /test/ or ending in Test.java return false even if the extension matches. |
Returns true if the file is a non-test Java source file that JavaScanner can extract API signatures from. Pass the file to scanFile only when this returns true — calling scanFile on an unsupported file will produce empty or incorrect results.
Heads up:
- The test-file check is path-based, not content-based. A file named
UserHelper.javainside a/test/directory will be excluded, but a test file outside that directory (e.g.,src/UserTest.java) will also be excluded due to theTest.javasuffix check.
Example:
// Inline types to keep example self-contained
interface Scanner {
languages: string[];
canHandle(filePath: string): boolean;
}
class JavaScanner implements Scanner {
languages = ['java'];
canHandle(filePath: string): boolean {
return /\.java$/.test(filePath) && !filePath.includes('/test/') && !filePath.includes('Test.java');
}
}
const scanner = new JavaScanner();
const files = [
'src/com/acme/UserService.java',
'src/com/acme/PaymentController.java',
'src/com/acme/test/UserServiceTest.java',
'src/com/acme/UserServiceTest.java',
'src/com/acme/utils.ts',
'src/com/acme/README.md',
];
const scannable = files.filter(f => scanner.canHandle(f));
console.log('Files queued for scanning:', scannable);
// Files queued for scanning: [
// 'src/com/acme/UserService.java',
// 'src/com/acme/PaymentController.java'
// ]
scanFile
async scanFile(filePath: string): Promise<ScanResult>
Use scanFile to extract all public API elements from a Java source file and prepare them for documentation generation.
Reach for this when you're building a custom documentation pipeline and need to programmatically scan individual .java files — for example, processing only files that changed in a pull request, or scanning files from a non-standard directory structure that the CLI's glob patterns don't cover.
JavaScanner reads the file from disk, parses class declarations, methods, and fields, and returns a structured list of API elements with their signatures, parameters, line numbers, and surrounding source context. It automatically skips test files (paths containing /test/ or filenames ending in Test.java), so you don't need to filter those out yourself.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
filePath | string | Yes | Absolute or relative path to the .java file to scan. Must point to a non-test Java file — test files are silently skipped and return an empty result. |
Returns
Returns a Promise<ScanResult> containing an array of APIElement objects, each describing a discovered class, method, or field — including its name, signature, parameter list, return type, and the source lines around it. Pass these elements directly to skrypt's documentation generator or serialize them to feed into your own AI prompt pipeline.
Heads up
scanFilereads from disk synchronously under the hood, so very large files will block briefly. For bulk scanning, consider batching calls rather than running them all concurrently withPromise.all.- Calling this on a test file (path includes
/test/or ends inTest.java) won't throw — it returns a result with an emptyelementsarray. Checkresult.elements.lengthif you need to detect this case.
Example:
import { readFileSync } from "fs";
import { tmpdir } from "os";
import { join } from "path";
import { writeFileSync } from "fs";
// Inline types (do not import from autodocs)
interface Parameter {
name: string;
type: string;
}
interface APIElement {
name: string;
kind: "class" | "method" | "field";
signature: string;
parameters: Parameter[];
returnType: string;
lineNumber: number;
sourceContext: string;
}
interface ScanResult {
elements: APIElement[];
filePath: string;
}
// Minimal JavaScanner mock that mirrors real behavior
class JavaScanner {
canHandle(filePath: string): boolean {
return /\.java$/.test(filePath) && !filePath.includes("/test/") && !filePath.includes("Test.java");
}
async scanFile(filePath: string): Promise<ScanResult> {
if (!this.canHandle(filePath)) {
return { elements: [], filePath };
}
const source = readFileSync(filePath, "utf-8");
const elements: APIElement[] = [];
const lines = source.split("\n");
const methodRegex = /^\s*public\s+([\w<>\[\]]+)\s+(\w+)\s*\(([^)]*)\)/;
lines.forEach((line, index) => {
const match = methodRegex.exec(line);
if (match) {
const [, returnType, name, rawParams] = match;
const parameters: Parameter[] = rawParams
.split(",")
.map((p) => p.trim())
.filter(Boolean)
.map((p) => {
const parts = p.split(/\s+/);
return { type: parts[0], name: parts[1] ?? "arg" };
});
elements.push({
name,
kind: "method",
signature: line.trim(),
parameters,
returnType,
lineNumber: index + 1,
sourceContext: lines.slice(Math.max(0, index - 1), index + 3).join("\n"),
});
}
});
return { elements, filePath };
}
}
// Write a realistic temporary Java file to scan
const javaSource = `
package com.example.payments;
public class PaymentService {
public PaymentResult chargeCard(String customerId, int amountCents) {
// process payment
return new PaymentResult(customerId, amountCents);
}
public boolean refund(String transactionId) {
return true;
}
}
`.trim();
const tmpFile = join(tmpdir(), "PaymentService.java");
writeFileSync(tmpFile, javaSource, "utf-8");
async function main() {
const scanner = new JavaScanner();
try {
const result = await scanner.scanFile(tmpFile);
console.log(`Scanned: ${result.filePath}`);
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(` Signature : ${el.signature}`);
console.log(` Returns : ${el.returnType}`);
console.log(` Params : ${el.parameters.map((p) => `${p.type} ${p.name}`).join(", ") || "none"}`);
console.log();
}
} catch (err) {
console.error("Scan failed:", err);
}
}
main();
// Expected output:
// Scanned: /tmp/PaymentService.java
// Found 2 API elements:
//
// [method] chargeCard (line 4)
// Signature : public PaymentResult chargeCard(String customerId, int amountCents) {
// Returns : PaymentResult
// Params : String customerId, int amountCents
//
// [method] refund (line 9)
// Signature : public boolean refund(String transactionId) {
// Returns : boolean
// Params : String transactionId