CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-istanbul

Comprehensive JavaScript code coverage tool that computes statement, line, function and branch coverage with module loader hooks for transparent instrumentation

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

hooks.mddocs/

Runtime Hooks

Runtime hooks intercept Node.js module loading and script execution to instrument code transparently at runtime. This enables coverage tracking without pre-instrumenting files on disk.

Capabilities

Module Loading Hooks

Hook into Node.js require() calls to instrument modules as they are loaded.

const hook = {
    /**
     * Hooks require() to transform modules as they are loaded
     * @param {Function} matcher - Function that returns true for files to instrument
     * @param {Function} transformer - Function that instruments the code
     * @param {Object} options - Hook configuration options
     */
    hookRequire(matcher: (filename: string) => boolean, transformer: (code: string, filename: string) => string, options?: HookOptions): void;
    
    /**
     * Restores original require() behavior and unhooks instrumentation
     */
    unhookRequire(): void;
    
    /**
     * Hooks vm.createScript() for instrumenting dynamically created scripts
     * @param {Function} matcher - Function that returns true for scripts to instrument  
     * @param {Function} transformer - Function that instruments the code
     * @param {Object} opts - Hook configuration options
     */
    hookCreateScript(matcher: (filename: string) => boolean, transformer: (code: string, filename: string) => string, opts?: HookOptions): void;
    
    /**
     * Restores original vm.createScript() behavior
     */
    unhookCreateScript(): void;
    
    /**
     * Hooks vm.runInThisContext() for instrumenting eval-like code execution
     * @param {Function} matcher - Function that returns true for code to instrument
     * @param {Function} transformer - Function that instruments the code  
     * @param {Object} opts - Hook configuration options
     */
    hookRunInThisContext(matcher: (filename: string) => boolean, transformer: (code: string, filename: string) => string, opts?: HookOptions): void;
    
    /**
     * Restores original vm.runInThisContext() behavior
     */
    unhookRunInThisContext(): void;
    
    /**
     * Removes modules from require cache that match the given matcher
     * @param {Function} matcher - Function that returns true for modules to unload
     */
    unloadRequireCache(matcher: (filename: string) => boolean): void;
};

interface HookOptions {
    /** Enable verbose output for hook operations (defaults to false) */
    verbose?: boolean;
    
    /** Array of file extensions to process (defaults to ['.js']) */
    extensions?: string[];
    
    /** Function called after loading and transforming each module */
    postLoadHook?: (file: string) => void;
    
    /** Additional options passed to transformer */
    [key: string]: any;
}

Usage Examples:

const { hook, Instrumenter, matcherFor } = require('istanbul');

// Create instrumenter
const instrumenter = new Instrumenter();

// Create matcher for JavaScript files (excludes node_modules)
matcherFor({
    root: process.cwd(),
    includes: ['**/*.js'],
    excludes: ['**/node_modules/**', '**/test/**']
}, (err, matcher) => {
    if (err) throw err;
    
    // Hook require with instrumentation
    hook.hookRequire(matcher, (code, filename) => {
        return instrumenter.instrumentSync(code, filename);
    });
    
    // Now all matching required modules will be instrumented
    const myModule = require('./my-module'); // This gets instrumented
    
    // Later, unhook to restore normal behavior
    hook.unhookRequire();
});

File Matching

Use Istanbul's built-in matcher creation for flexible file selection:

const { matcherFor } = require('istanbul');

// Basic matcher for all JS files except node_modules
matcherFor({}, (err, matcher) => {
    hook.hookRequire(matcher, transformer);
});

// Custom matcher with specific includes/excludes
matcherFor({
    root: '/path/to/project',
    includes: ['src/**/*.js', 'lib/**/*.js'], 
    excludes: ['**/*.test.js', '**/node_modules/**', 'build/**']
}, (err, matcher) => {
    hook.hookRequire(matcher, transformer);
});

// Custom matcher function
function customMatcher(filename) {
    return filename.includes('/src/') && 
           filename.endsWith('.js') && 
           !filename.includes('.test.js');
}

hook.hookRequire(customMatcher, transformer);

VM Script Hooks

For applications that use vm.createScript() or eval-like constructs:

const vm = require('vm');

// Hook vm.createScript
hook.hookCreateScript(matcher, (code, filename) => {
    console.log('Instrumenting script:', filename);
    return instrumenter.instrumentSync(code, filename);
});

// Now vm.createScript calls will be instrumented
const script = vm.createScript('console.log("Hello World");', 'dynamic-script.js');
script.runInThisContext();

// Hook vm.runInThisContext for direct eval-like execution
hook.hookRunInThisContext(matcher, transformer);

// This will be instrumented if it matches
vm.runInThisContext('function test() { return 42; }', 'eval-code.js');

Complete Hook Setup

Typical setup for comprehensive coverage tracking:

const { hook, Instrumenter, matcherFor } = require('istanbul');

function setupCoverageHooks(callback) {
    // Initialize coverage tracking
    global.__coverage__ = {};
    
    // Create instrumenter
    const instrumenter = new Instrumenter({
        coverageVariable: '__coverage__',
        embedSource: false,
        preserveComments: false
    });
    
    // Create file matcher
    matcherFor({
        root: process.cwd(),
        includes: ['**/*.js'],
        excludes: [
            '**/node_modules/**',
            '**/test/**', 
            '**/tests/**',
            '**/*.test.js',
            '**/*.spec.js',
            '**/coverage/**'
        ]
    }, (err, matcher) => {
        if (err) return callback(err);
        
        // Transformer function
        const transformer = (code, filename) => {
            try {
                return instrumenter.instrumentSync(code, filename);
            } catch (error) {
                console.warn('Failed to instrument:', filename, error.message);
                return code; // Return original code if instrumentation fails
            }
        };
        
        // Hook all the things
        hook.hookRequire(matcher, transformer);
        hook.hookCreateScript(matcher, transformer);
        hook.hookRunInThisContext(matcher, transformer);
        
        callback(null);
    });
}

// Setup hooks before loading application code
setupCoverageHooks((err) => {
    if (err) {
        console.error('Failed to setup coverage hooks:', err);
        process.exit(1);
    }
    
    console.log('Coverage hooks installed');
    
    // Now load and run your application
    require('./app');
    
    // After application runs, unhook and generate reports
    process.on('exit', () => {
        hook.unhookRequire();
        hook.unhookCreateScript(); 
        hook.unhookRunInThisContext();
        
        // Generate coverage reports
        const { Collector, Reporter } = require('istanbul');
        const collector = new Collector();
        collector.add(global.__coverage__);
        
        const reporter = new Reporter();
        reporter.addAll(['text-summary', 'html']);
        reporter.write(collector, true, () => {
            console.log('Coverage reports generated');
        });
    });
});

Cache Management

Manage Node.js require cache for accurate coverage:

// Unload modules to ensure fresh instrumentation
hook.unloadRequireCache(matcher);

// Example: Unload specific modules
hook.unloadRequireCache((filename) => {
    return filename.includes('/src/') && !filename.includes('node_modules');
});

// Clear entire cache (use with caution)
Object.keys(require.cache).forEach(key => {
    delete require.cache[key];
});

Hook Lifecycle Management

Proper hook management for test suites:

describe('My Test Suite', () => {
    let originalRequire;
    
    before((done) => {
        // Setup hooks
        matcherFor({}, (err, matcher) => {
            if (err) return done(err);
            
            hook.hookRequire(matcher, (code, filename) => {
                return instrumenter.instrumentSync(code, filename);
            });
            
            done();
        });
    });
    
    after(() => {
        // Clean up hooks
        hook.unhookRequire();
        hook.unhookCreateScript();
        hook.unhookRunInThisContext();
    });
    
    beforeEach(() => {
        // Reset coverage for each test
        global.__coverage__ = {};
    });
    
    it('should track coverage', () => {
        const myModule = require('./my-module');
        myModule.someFunction();
        
        // Coverage should be populated
        expect(global.__coverage__).to.have.property('./my-module.js');
    });
});

Error Handling and Debugging

Handle instrumentation errors gracefully:

function robustTransformer(code, filename) {
    try {
        return instrumenter.instrumentSync(code, filename);
    } catch (error) {
        // Log instrumentation failures
        console.warn(`Instrumentation failed for ${filename}:`, error.message);
        
        // Return original code to avoid breaking the application
        return code;
    }
}

// Enable debug mode for troubleshooting
const debugInstrumenter = new Instrumenter({
    debug: true,
    walkDebug: true
});

// Custom matcher with debugging
function debugMatcher(filename) {
    const shouldInstrument = filename.endsWith('.js') && 
                           !filename.includes('node_modules');
    
    if (shouldInstrument) {
        console.log('Will instrument:', filename);
    }
    
    return shouldInstrument;
}

Performance Considerations

Optimize hook performance for large applications:

// Cache instrumented results to avoid re-instrumentation
const instrumentationCache = new Map();

function cachedTransformer(code, filename) {
    const cacheKey = filename + ':' + require('crypto')
        .createHash('md5')
        .update(code)
        .digest('hex');
    
    if (instrumentationCache.has(cacheKey)) {
        return instrumentationCache.get(cacheKey);
    }
    
    const instrumented = instrumenter.instrumentSync(code, filename);
    instrumentationCache.set(cacheKey, instrumented);
    return instrumented;
}

// Optimize matcher for performance
function optimizedMatcher(filename) {
    // Quick checks first
    if (!filename.endsWith('.js')) return false;
    if (filename.includes('node_modules')) return false;
    if (filename.includes('.test.')) return false;
    
    // More expensive checks last
    return filename.includes('/src/') || filename.includes('/lib/');
}

Integration with Test Frameworks

Common integration patterns:

// Mocha integration
function setupMochaCoverage() {
    before(function(done) {
        this.timeout(10000); // Increase timeout for instrumentation
        
        matcherFor({}, (err, matcher) => {
            if (err) return done(err);
            hook.hookRequire(matcher, transformer);
            done();
        });
    });
    
    after(() => {
        hook.unhookRequire();
    });
}

// Jest integration (in setup file)
const setupJestCoverage = () => {
    if (process.env.NODE_ENV === 'test') {
        matcherFor({}, (err, matcher) => {
            if (!err) {
                hook.hookRequire(matcher, transformer);
            }
        });
    }
};

// Manual integration for custom test runners
function runTestsWithCoverage(testFunction) {
    return new Promise((resolve, reject) => {
        matcherFor({}, (err, matcher) => {
            if (err) return reject(err);
            
            hook.hookRequire(matcher, transformer);
            
            Promise.resolve(testFunction())
                .then(resolve)
                .catch(reject)
                .finally(() => {
                    hook.unhookRequire();
                });
        });
    });
}

docs

cli.md

collection.md

configuration.md

hooks.md

index.md

instrumentation.md

reporting.md

storage.md

tree-summarizer.md

utilities.md

tile.json