вторник, 27 сентября 2016 г.

Better Node.js Stack Traces

Error: BAD
    ────────────────────────────
    at Object.<anonymous> (D:\Trace\lib.js:215:7)  
    ───────────────────────────
    213 » }
    214 » */
    215 » throw new Error('BAD');
    ------------^
    216 » 
    217 » 

    at Module.Module._compile [as _compile] (module.js:556:32)
    at Object.Module._extensions..js [as .js] (module.js:565:10)
    at Module.Module.load [as load] (module.js:473:32)
    at tryModuleLoad (module.js:432:12)
    at Function.Module._load [as _load] (module.js:424:3)
    at Module.Module.require [as require] (module.js:483:17)
    at require (internal/module.js:20:19)
    ───────────────────────────
    at Object.<anonymous> (D:\Trace\test.js:9:9)
    ───────────────────────────
     7 » 
     8 » 
     9 » var lib = require('./lib.js');
    -------------^
    10 » 
    11 » 

    at Module.Module._compile [as _compile] (module.js:556:32)

Код файла с ошибкой app.js

require('./stacktrace.js')();

require('fs').readFile('./test.js', function () {
    throw new Error('BAD');
});

Код файла stacktrace.js

/*
─────────────────────
/path/to/your/code.js
─────────────────────
189 »
190 » function bar (x) {
191 »   throw new Error('x: ' + x);
---------------^
192 » }
193 »
194 » foo(bar);
*/

var LINES_BEFORE = 2
    , LINES_AFTER = 3
    , MAX_COLUMNS = 80
    , DEFAULT_INDENT = 4
    , GUTTER_CONTENT = ' » '
    , ENDASH_CHAR = '-'
    , UP_ARROW_CHAR = '^'
    , ELLIPSIS_CHAR = '…'
    , EMDASH_CHAR = '─'
    , ERR_FILE_NOT_EXIST = 'ENOENT'
    , DEFAULT_LIBRARY_REGEX = /node_modules/
    , OUTPUT_PREFIX = repeatCharacter(' ', DEFAULT_INDENT); // ==> repeatCharacter()

module.exports = function () {
    Error.prepareStackTrace = function (error, frames) {
        var lines = [];
        try {
            lines.push(error.toString());
        } catch (error) {
            try {
                lines.push('<error: ' + error + '>');
            } catch (e) {
                lines.push('<error>');
            }
        }
        frames.forEach(function (frame) {
            var line;
            try {
                line = formatFrame(frame); // ==> formatFrame()
            } catch (error) {
                try {
                    line = '<error: ' + error + '>';
                } catch (e) {
                    line = '<error>';
                }
            }
            lines.push(line);
        });
        return lines.join('\n');
    };
};

function formatFrame (frame) {
    var line = ''
        , underline = ''
        , betterLine = ''
        , addPrefix = true
        , fileLocationAndContext = getFileLocationAndContextFrom(frame)  // ==> getFileLocationAndContextFrom()
        , fileLocation = fileLocationAndContext.fileLocation
        , context = fileLocationAndContext.context
        , functionName = frame.getFunctionName()
        , methodName = frame.getMethodName()
        , isConstructor = frame.isConstructor()
        , isMethodCall = !(frame.isToplevel() || isConstructor);
    if (isMethodCall) {
        line += frame.getTypeName() + '.';
        if (functionName) {
            line += functionName;
            if (methodName && (methodName != functionName)) {
                line += ' [as ' + methodName + ']';
            }
        } else {
            line += methodName || '<anonymous>';
        }
    } else if (isConstructor) {
        line += 'new ' + (functionName || '<anonymous>');
    } else if (functionName) {
        line += functionName;
    } else {
        line += fileLocation;
        addPrefix = false;
    }
    if (addPrefix) {
        line += ' (' + fileLocation + ')';
    }
    line = 'at ' + line;
    if (context) {
        underline = OUTPUT_PREFIX + line.replace(/./g, EMDASH_CHAR);
        betterLine = line.replace(/./g, EMDASH_CHAR) + '\n' + OUTPUT_PREFIX + [line, underline, context].join('\n');
        return OUTPUT_PREFIX + betterLine + '\n';
    } else {
        return OUTPUT_PREFIX + line;
    }
}

function getFileLocationAndContextFrom (frame) {
    var fileLocation = ''
        , context = null
        , fileName
        , lineNumber
        , columnNumber;
    if (frame.isNative()) {
        fileLocation = 'native';
    } else if (frame.isEval()) {
        fileLocation = 'eval at ' + frame.getEvalOrigin();
    } else {
        fileName = frame.getFileName();
        if (fileName) {
            fileLocation += fileName;
            lineNumber = frame.getLineNumber();
            if (lineNumber != null) {
                fileLocation += ':' + lineNumber;
                columnNumber = frame.getColumnNumber();
                if (columnNumber) {
                    fileLocation += ':' + columnNumber;
                }
                try {
                    if (shouldCollapseDirectory(fileName)) { // ==> shouldCollapseDirectory()
                        context = null;
                    } else {
                        context = formatContext(fileName, lineNumber, columnNumber); // ==> formatContext()
                    }
                } catch(error) {
                    if (error.code === ERR_FILE_NOT_EXIST) {
                        context = null;
                    } else {
                        context = error;
                    }
                }
            }
        }
    }
    if (!fileLocation) {
        fileLocation = 'unknown source';
    }
    return {
          fileLocation: fileLocation
        , context: context
    };
}

function shouldCollapseDirectory (fileName) {
    return DEFAULT_LIBRARY_REGEX.test(fileName);
}

function formatContext (fileName, lineNumber, columnNumber) {
    var code;
    try {
        code = require('fs').readFileSync(fileName).toString();
    } catch(error) {
        if (error.code === ERR_FILE_NOT_EXIST) {
            throw error;
        }
        return OUTPUT_PREFIX + error.toString();
    }

    // Figure out the lines of context before and after
    var lines = code.split('\n')
        , preLines = lines.slice(lineNumber - LINES_BEFORE - 1, lineNumber)
        , postLines = lines.slice(lineNumber, lineNumber + LINES_AFTER);

    // Collect formatted versions of all the lines
    var formattedLines = []
        , maxLineNumber = lineNumber + LINES_AFTER
        , currentLineNumber = lineNumber - LINES_BEFORE;

    function renderLines (lines) {
        while (lines.length) {
            formattedLines.push(
                formatCodeLine(currentLineNumber, lines.shift(), maxLineNumber) // ==> formatCodeLine()
            );
            currentLineNumber++;
        }
    }

    renderLines(preLines);

    formattedLines.push(formatCodeArrow(currentLineNumber - 1, columnNumber, maxLineNumber)); // ==> formatCodeArrow()

    renderLines(postLines);

    return OUTPUT_PREFIX + formattedLines.join('\n' + OUTPUT_PREFIX);
}

function formatCodeLine (lineNumber, line, maxLineNumber) {
    var pad = maxLineNumber.toString().length - lineNumber.toString().length
        , padding = '';
    while (pad-- > 0) {
        padding += ' ';
    }
    if (line.length > MAX_COLUMNS) {
        line = line.slice(0, MAX_COLUMNS - 1) + ELLIPSIS_CHAR;
    }
    return padding + lineNumber + GUTTER_CONTENT + line;
}

function formatCodeArrow (lineNumber, columnNumber, maxLineNumber) {
    var length = (GUTTER_CONTENT + maxLineNumber).length + columnNumber;
    return repeatCharacter(ENDASH_CHAR, length).slice(0, length - 1) + UP_ARROW_CHAR; // ==> repeatCharacter()
}

function repeatCharacter (character, length) {
    return new Array(length).join(character) + character;
}

Комментариев нет:

Отправить комментарий