Skip to content

Scanner — Java

Java

Classes

JavaScanner

class JavaScanner implements Scanner
TypeScript

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

NameTypeDescription
languagesstring[]Always ['java'] — tells the skrypt scanner registry which file types this scanner owns.

Methods

canHandle(filePath)

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

NameTypeRequiredDescription
filePathstringYesPath 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 public members are extracted. Package-private, protected, and private methods are intentionally ignored — if a method isn't in your public API, it won't appear in your docs.
  • JavaScanner uses 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()
TypeScript

Methods

canHandle

canHandle(filePath: string): boolean
TypeScript

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.

NameTypeRequiredDescription
filePathstringYesAbsolute 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.java inside a /test/ directory will be excluded, but a test file outside that directory (e.g., src/UserTest.java) will also be excluded due to the Test.java suffix 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'
// ]
TypeScript

scanFile

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

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

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

  • scanFile reads 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 with Promise.all.
  • Calling this on a test file (path includes /test/ or ends in Test.java) won't throw — it returns a result with an empty elements array. Check result.elements.length if 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
TypeScript
Was this helpful?