CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-pretty-format

Stringify any JavaScript value with customizable formatting, plugins, and terminal colors.

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

plugin-system.mddocs/

Plugin System

Extensible plugin architecture for handling custom data types with both modern and legacy interfaces. Plugins allow pretty-format to serialize application-specific data structures with custom formatting logic.

Capabilities

Plugin Interface

Two plugin interfaces are supported: the modern NewPlugin interface (recommended) and the legacy OldPlugin interface for backwards compatibility.

type Plugin = NewPlugin | OldPlugin;

interface NewPlugin {
  /** Test function to determine if this plugin should handle the value */
  test: (val: any) => boolean;
  /** Serialize function using the improved interface (version 21+) */
  serialize: (
    val: any,
    config: Config,
    indentation: string,
    depth: number,
    refs: Refs,
    printer: Printer
  ) => string;
}

interface OldPlugin {
  /** Test function to determine if this plugin should handle the value */
  test: (val: any) => boolean;
  /** Print function using the original interface */
  print: (
    val: unknown,
    print: (val: unknown) => string,
    indent: (str: string) => string,
    options: PluginOptions,
    colors: Colors
  ) => string;
}

interface PluginOptions {
  edgeSpacing: string;
  min: boolean;
  spacing: string;
}

Test Function

The test function determines whether a plugin should handle a specific value.

Guidelines for writing test functions:

// Example test functions
const reactElementTest = (val: any) => 
  val && val.$$typeof === Symbol.for('react.element');

const domElementTest = (val: any) => 
  val && typeof val.nodeType === 'number' && val.nodeType === 1;

// Safe property access patterns
const safeTest = (val: any) => 
  val != null && typeof val.customProp === 'string';

// Efficient type checking
const arrayLikeTest = (val: any) => 
  Array.isArray(val) && val.constructor.name === 'CustomArray';

Usage Examples:

const customPlugin = {
  test(val) {
    // Safe null checking to prevent TypeErrors
    return val != null && 
           typeof val === 'object' && 
           val.constructor.name === 'MyCustomClass';
  },
  serialize(val, config, indentation, depth, refs, printer) {
    return `MyCustomClass { ${val.toString()} }`;
  }
};

Modern Plugin Interface (NewPlugin)

The recommended interface available in version 21 and later with full access to formatting context.

interface NewPlugin {
  test: (val: any) => boolean;
  serialize: (
    val: any,
    config: Config,
    indentation: string,
    depth: number,
    refs: Refs,
    printer: Printer
  ) => string;
}

Serialize Function Parameters:

  • val: The value that "passed the test"
  • config: Unchanging config object derived from options
  • indentation: Current indentation string (concatenate to config.indent)
  • depth: Current depth number (compare to config.maxDepth)
  • refs: Current refs array for finding circular references
  • printer: Callback function to serialize children

Usage Examples:

const advancedPlugin = {
  test(val) {
    return val && val.type === 'CustomCollection';
  },
  serialize(val, config, indentation, depth, refs, printer) {
    // Check for circular references
    if (refs.includes(val)) {
      return '[Circular]';
    }
    
    // Check depth limit
    if (++depth > config.maxDepth) {
      return '[CustomCollection]';
    }
    
    // Add to refs for circular detection
    const newRefs = [...refs, val];
    
    // Format children using printer callback
    const items = val.items
      .slice(0, config.maxWidth)
      .map(item => 
        indentation + config.indent + 
        printer(item, config, indentation + config.indent, depth, newRefs)
      )
      .join(config.spacingInner);
    
    return `CustomCollection {${config.spacingOuter}${items}${config.spacingOuter}${indentation}}`;
  }
};

Legacy Plugin Interface (OldPlugin)

The original interface for backwards compatibility with limited access to formatting context.

interface OldPlugin {
  test: (val: any) => boolean;
  print: (
    val: unknown,
    print: (val: unknown) => string,
    indent: (str: string) => string,
    options: PluginOptions,
    colors: Colors
  ) => string;
}

Print Function Parameters:

  • val: The value that "passed the test"
  • print: Current printer callback function to serialize children
  • indent: Current indenter callback function to indent lines at next level
  • options: Config object with min, spacing, and edgeSpacing properties
  • colors: Colors object derived from options

Usage Examples:

const legacyPlugin = {
  test(val) {
    return typeof val === 'function';
  },
  print(val, printer, indenter, options, colors) {
    const name = val.name || 'anonymous';
    const paramCount = val.length;
    
    return `[Function ${name} ${paramCount}]`;
  }
};

Plugin Registration

Plugins are registered through the plugins option in the format function.

interface OptionsReceived {
  plugins?: Plugin[];
}

Usage Examples:

import { format } from "pretty-format";

// Single plugin
const result = format(value, {
  plugins: [myCustomPlugin]
});

// Multiple plugins (order matters - first matching plugin wins)
const formatted = format(value, {
  plugins: [
    specificPlugin,    // More specific plugins first
    generalPlugin,     // General plugins last
    fallbackPlugin
  ]
});

// With built-in plugins
import { plugins } from "pretty-format";
const { ReactElement, DOMElement } = plugins;

const formatted = format(reactComponent, {
  plugins: [ReactElement, myCustomPlugin]
});

Plugin Execution Flow

  1. Test Phase: Each plugin's test function is called in order
  2. First Match: The first plugin returning true is selected
  3. Serialization: The selected plugin's serialize or print method is called
  4. Fallback: If no plugin matches, built-in formatting is used

Error Handling

Plugins can throw errors which are wrapped in PrettyFormatPluginError.

class PrettyFormatPluginError extends Error {
  constructor(message: string, stack: string);
}

Error Scenarios:

  • Plugin test function throws an exception
  • Plugin serialize/print function throws an exception
  • Plugin returns non-string value from serialize/print

Usage Examples:

const robustPlugin = {
  test(val) {
    try {
      return val && val.customType === 'special';
    } catch (error) {
      // Test failures are caught and re-thrown as PrettyFormatPluginError
      return false;
    }
  },
  serialize(val, config, indentation, depth, refs, printer) {
    try {
      return `Special: ${val.value}`;
    } catch (error) {
      // Serialize failures become PrettyFormatPluginError  
      throw new Error(`Failed to serialize special value: ${error.message}`);
    }
  }
};

Performance Considerations

  • Test Efficiency: Keep test functions fast since they're called frequently
  • Early Returns: Return false quickly for non-matching values
  • Minimal Computation: Avoid heavy computation in test functions
  • Property Access: Use safe property access patterns to prevent errors

Usage Examples:

// Efficient test function
const efficientPlugin = {
  test(val) {
    // Quick type check first
    if (typeof val !== 'object' || val === null) {
      return false;
    }
    
    // Then check specific properties
    return val.constructor.name === 'MyClass';
  },
  serialize(val, config, indentation, depth, refs, printer) {
    // Heavy computation only happens for matching values
    return formatMyClass(val, config);
  }
};

Install with Tessl CLI

npx tessl i tessl/npm-pretty-format

docs

built-in-plugins.md

core-formatting.md

index.md

plugin-system.md

tile.json