Skip to content

Latest commit

 

History

History
702 lines (528 loc) · 14.1 KB

File metadata and controls

702 lines (528 loc) · 14.1 KB

API Reference

Complete reference for all Regrafter APIs.

Core APIs

move()

Move JSX elements, text nodes, or expressions to a new location with automatic dependency management.

function move(
  files: FileInput[],
  from: Selector,
  to: Selector,
  mode: Move,
  options?: Options
): Result<TransformedCode[], RegraffError>

Parameters:

Parameter Type Description
files FileInput[] Array of files to transform
from Selector Source element location
to Selector Target location
mode Move Positioning relative to target (Inside, Before, After)
options Options? Optional configuration

Returns: Result<TransformedCode[], RegraffError>

Example:

import { move, Move, isOk } from 'regrafter';

const result = move(
  files,
  { file: 'App.tsx', line: 10, column: 5 },
  { file: 'App.tsx', line: 20, column: 5 },
  Move.Inside
);

if (isOk(result)) {
  console.log('Transformed code:', result.value);
} else {
  console.error('Move failed:', result.error);
}

Cross-file moves:

const result = move(
  files,
  { file: 'Dashboard.tsx', line: 15, column: 9 },
  { file: 'Sidebar.tsx', line: 10, column: 5 },
  Move.Inside
);

if (isOk(result)) {
  const codes = result.value;
  const newFiles = codes.filter(c => c.isNew);
  if (newFiles.length > 0) {
    console.log('Created shared modules:', newFiles.map(f => f.file));
  }
}

Move Directions:

The mode parameter controls how the element is positioned relative to the target:

  • Move.Before - Insert element before the target element
  • Move.After - Insert element after the target element
  • Move.Inside - Insert element as a child of the target element (prepends by default)
  • Move.Replace - Replace the target element entirely

Controlling Insertion Position (Move.Inside only):

When using Move.Inside, the element is prepended (inserted as the first child) by default. You can control the insertion position using the insertIndex option:

// Default: prepend (insert at beginning)
move(files, from, to, Move.Inside);

// Insert at a specific position (0-based index)
move(files, from, to, Move.Inside, { insertIndex: 0 }); // First child
move(files, from, to, Move.Inside, { insertIndex: 2 }); // Third child

// Append (insert at end)
move(files, from, to, Move.Inside, { insertIndex: -1 }); // Last child

Example: Controlling child order

// Source:
<div>
  <p>Existing child</p>
</div>
<span>New element</span>

// Default prepend behavior:
move(files, from, to, Move.Inside);
// Result: <div><span>New element</span><p>Existing child</p></div>

// Append behavior:
move(files, from, to, Move.Inside, { insertIndex: -1 });
// Result: <div><p>Existing child</p><span>New element</span></div>

extract()

Extract selected JSX into a new reusable component with automatic dependency lifting.

function extract(
  files: FileInput[],
  selection: Selector,
  componentName: string,
  options?: ExtractOptions
): Result<TransformedCode[], RegraffError>

Parameters:

Parameter Type Description
files FileInput[] Array of files to transform
selection Selector JSX to extract
componentName string Name for the new component
options ExtractOptions? Optional configuration

ExtractOptions:

interface ExtractOptions {
  targetFile?: string;        // Target file (defaults to same file)
  insertPosition?: Selector;  // Where to insert component (defaults to before current component)
  exportComponent?: boolean;  // Export the component (default: false)
  memo?: boolean;            // Wrap in React.memo (default: false)
  forwardRef?: boolean;      // Use forwardRef (default: false)
}

Returns: Result<TransformedCode[], RegraffError>

Example:

import { extract, isOk } from 'regrafter';

// Extract JSX into a new component
const result = extract(
  files,
  { file: 'App.tsx', line: 15, column: 9 },  // Selection
  'UserProfile',                               // Component name
  { exportComponent: true, memo: true }
);

if (isOk(result)) {
  console.log('Created component UserProfile');
  console.log('Transformed code:', result.value);
}

Extract to different file:

const result = extract(
  files,
  { file: 'Dashboard.tsx', line: 20, column: 5 },
  'DashboardCard',
  {
    targetFile: 'components/DashboardCard.tsx',
    exportComponent: true
  }
);

inline()

Inline a component definition, replacing all call sites with the component's implementation.

function inline(
  files: FileInput[],
  component: Component | string,
  options?: InlineOptions
): Result<InlineResult, RegraffError>

Parameters:

Parameter Type Description
files FileInput[] Array of files to transform
component Component | string Component to inline (selector or name)
options InlineOptions? Optional configuration

Component Selector:

type Component = {
  file: string;
  name: string;
} | Selector;

InlineOptions:

interface InlineOptions {
  callSites?: Selector[];     // Specific call sites to inline (defaults to all)
  preserveDefinition?: boolean; // Keep original definition (default: false)
  simplifyJSX?: boolean;       // Simplify resulting JSX (default: true)
}

Returns: Result<InlineResult, RegraffError>

interface InlineResult {
  codes: TransformedCode[];
  inlinedCallSites: number;
  removedDefinition: boolean;
}

Example:

import { inline, isOk } from 'regrafter';

// Inline all usages of a component
const result = inline(
  files,
  { file: 'components/Button.tsx', name: 'Button' }
);

if (isOk(result)) {
  const { codes, inlinedCallSites } = result.value;
  console.log(`Inlined ${inlinedCallSites} call sites`);
}

Inline specific call sites:

const result = inline(
  files,
  'SmallComponent',  // Component name
  {
    callSites: [
      { file: 'App.tsx', line: 10, column: 5 },
      { file: 'App.tsx', line: 25, column: 9 }
    ],
    preserveDefinition: true
  }
);

Validation & Analysis APIs

canMove()

Quick validation check without executing transformation.

function canMove(
  files: FileInput[],
  from: Selector,
  to: Selector,
  mode: Move
): boolean

Example:

import { canMove, Move } from 'regrafter';

if (canMove(files, from, to, Move.Inside)) {
  // Safe to proceed
  const result = move(files, from, to, Move.Inside);
}

analyze()

Detailed dependency analysis without performing transformation.

function analyze(
  files: FileInput[],
  from: Selector,
  to: Selector,
  mode: Move
): Result<MoveAnalysis, RegraffError>

Returns: MoveAnalysis object

interface MoveAnalysis {
  canMove: boolean;
  dependencies: Dependency[];
  hoistedDeps: Dependency[];
  stats: AnalysisStats;
  reason?: string;
  suggestedFixes?: SuggestedFix[];
}

Example:

import { analyze, Move, isOk } from 'regrafter';

const result = analyze(files, from, to, Move.Inside);

if (isOk(result)) {
  const analysis = result.value;

  if (analysis.canMove) {
    console.log('Dependencies:', analysis.dependencies);
    console.log('Would hoist:', analysis.hoistedDeps);
    console.log('Statistics:', analysis.stats);
  } else {
    console.log('Cannot move:', analysis.reason);
    console.log('Suggested fixes:', analysis.suggestedFixes);
  }
}

canExtract()

Validate if JSX can be extracted into a component.

function canExtract(
  files: FileInput[],
  selection: Selector
): boolean

analyzeExtract()

Detailed analysis for component extraction.

function analyzeExtract(
  files: FileInput[],
  selection: Selector,
  componentName: string
): Result<ExtractAnalysis, RegraffError>

Returns: ExtractAnalysis object

interface ExtractAnalysis {
  canExtract: boolean;
  props: PropInfo[];
  dependencies: Dependency[];
  hooks: string[];
  reason?: string;
  suggestedFixes?: SuggestedFix[];
}

Optimization APIs

optimize()

Optimize files by sinking over-hoisted dependencies to minimal necessary scopes.

function optimize(
  files: FileInput[],
  options?: OptimizeOptions
): Result<TransformedCode[], RegraffError>

OptimizeOptions:

interface OptimizeOptions {
  aggressive?: boolean;  // More aggressive optimization (default: false)
}

Example:

import { optimize, isOk } from 'regrafter';

const result = optimize(files, { aggressive: true });

if (isOk(result)) {
  console.log('Optimized code:', result.value);
}

Legacy API

regraft()

High-level API that wraps move() with validation and optimization. Maintained for backward compatibility.

function regraft(
  files: FileInput[],
  from: Selector,
  to: Selector,
  mode: Move,
  options?: Options
): Result<RegraftResult, RegraffError>

Note: New code should prefer using move(), extract(), or inline() directly for better control.


Types

Selector

Element selection using position or AST path.

type Selector = PositionSelector | PathSelector;

interface PositionSelector {
  file: string;
  line: number;
  column: number;
}

interface PathSelector {
  file: string;
  path: string;  // AST path (e.g., 'Program.body[0].declaration')
}

Move

Element positioning mode.

enum Move {
  Inside = 'inside',   // Insert as child
  Before = 'before',   // Insert before target
  After = 'after'      // Insert after target
}

Options

Configuration options for transformations.

interface Options {
  optimize?: boolean;         // Run optimization (default: true)
  dryRun?: boolean;          // Preview only (default: false)
  preserveComments?: boolean; // Preserve comments (default: true)
  formatOutput?: boolean;     // Format with Prettier (default: true)
}

FileInput

Input file specification.

interface FileInput {
  path: string;      // File path
  content: string;   // Source code
}

TransformedCode

Output file with transformation metadata.

interface TransformedCode {
  file: string;      // File path
  content: string;   // Transformed code
  changed: boolean;  // Whether modified
  isNew?: boolean;   // Whether newly created
  original?: string; // Original content if changed
}

Dependency

Dependency information.

interface Dependency {
  type: DependencyType;
  symbol: string;
  scope: string;
  locations: Location[];
}

enum DependencyType {
  Hook = 'Hook',
  Variable = 'Variable',
  Import = 'Import',
  Prop = 'Prop',
  Context = 'Context',
  Ref = 'Ref'
}

MoveAnalysis

Analysis result for move operations.

interface MoveAnalysis {
  canMove: boolean;
  dependencies: Dependency[];
  hoistedDeps: Dependency[];
  stats: AnalysisStats;
  reason?: string;
  suggestedFixes?: SuggestedFix[];
}

interface AnalysisStats {
  totalDeps: number;
  hoistedCount: number;
  crossFileMove: boolean;
  sharedModulesCreated: number;
}

SuggestedFix

Error recovery suggestion.

interface SuggestedFix {
  description: string;
  action?: string;
  automatic: boolean;
}

Result Monad

All Regrafter APIs use a Result monad for error handling instead of throwing exceptions.

type Result<T, E> = Ok<T> | Err<E>;

interface Ok<T> {
  ok: true;
  value: T;
}

interface Err<E> {
  ok: false;
  error: E;
}

Helper functions:

// Type guards
isOk(result): result is Ok<T>
isErr(result): result is Err<E>

// Constructors
ok<T>(value: T): Ok<T>
err<E>(error: E): Err<E>

// Transformations
map<T, U>(result: Result<T, E>, fn: (value: T) => U): Result<U, E>
flatMap<T, U>(result: Result<T, E>, fn: (value: T) => Result<U, E>): Result<U, E>
mapErr<T, E, F>(result: Result<T, E>, fn: (error: E) => F): Result<T, F>

// Extraction
unwrap<T>(result: Result<T, E>): T  // Throws if Err
unwrapOr<T>(result: Result<T, E>, defaultValue: T): T
unwrapOrElse<T>(result: Result<T, E>, fn: (error: E) => T): T

// Combining
all<T>(results: Result<T, E>[]): Result<T[], E>
any<T>(results: Result<T, E>[]): Result<T, E[]>

// Async support
mapAsync<T, U>(result: Result<T, E>, fn: (value: T) => Promise<U>): Promise<Result<U, E>>
flatMapAsync<T, U>(result: Result<T, E>, fn: (value: T) => Promise<Result<U, E>>): Promise<Result<U, E>>

// Error handling
tryCatch<T>(fn: () => T): Result<T, Error>
tryCatchAsync<T>(fn: () => Promise<T>): Promise<Result<T, Error>>

Example usage:

import { move, Move, isOk, isErr, map, unwrapOr } from 'regrafter';

const result = move(files, from, to, Move.Inside);

// Type guard approach
if (isOk(result)) {
  console.log('Success:', result.value);
} else {
  console.error('Error:', result.error);
}

// Transformation approach
const formatted = map(result, codes =>
  codes.map(c => c.content).join('\n')
);

// Default value approach
const codes = unwrapOr(result, []);

Batch Processing

Process multiple operations efficiently.

interface BatchResult<T, E> {
  successful: T[];
  failed: Array<{ index: number; error: E }>;
  successCount: number;
  failureCount: number;
}

function processBatch<T, E>(
  items: T[],
  processor: (item: T) => Result<T, E>
): BatchResult<T, E>

Example:

import { processBatch, move, Move } from 'regrafter';

const moves = [
  { from: selector1, to: target1 },
  { from: selector2, to: target2 },
  { from: selector3, to: target3 }
];

const batchResult = processBatch(moves, ({ from, to }) =>
  move(files, from, to, Move.Inside)
);

console.log(`Success: ${batchResult.successCount}`);
console.log(`Failed: ${batchResult.failureCount}`);

for (const failure of batchResult.failed) {
  console.error(`Move ${failure.index} failed:`, failure.error);
}