123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243 |
- /**
- * espower-source - Power Assert instrumentor from source to source.
- *
- * https://github.com/power-assert-js/espower-source
- *
- * Copyright (c) 2014-2018 Takuto Wada
- * Licensed under the MIT license.
- * https://github.com/power-assert-js/espower-source/blob/master/MIT-LICENSE.txt
- */
- 'use strict';
- var espower = require('espower');
- var acorn = require('acorn');
- require('acorn-es7-plugin')(acorn);
- var estraverse = require('estraverse');
- var mergeVisitors = require('merge-estraverse-visitors');
- var empowerAssert = require('empower-assert');
- var escodegen = require('escodegen');
- var extend = require('xtend');
- var convert = require('convert-source-map');
- var transfer = require('multi-stage-sourcemap').transfer;
- var _path = require('path');
- var isAbsolute = require('path-is-absolute');
- function mergeSourceMap (incomingSourceMap, outgoingSourceMap) {
- if (typeof outgoingSourceMap === 'string' || outgoingSourceMap instanceof String) {
- outgoingSourceMap = JSON.parse(outgoingSourceMap);
- }
- if (!incomingSourceMap) {
- return outgoingSourceMap;
- }
- return JSON.parse(transfer({fromSourceMap: outgoingSourceMap, toSourceMap: incomingSourceMap}));
- }
- function copyPropertyIfExists (name, from, to) {
- if (from[name]) {
- to.setProperty(name, from[name]);
- }
- }
- function reconnectSourceMap (inMap, outMap) {
- var mergedRawMap = mergeSourceMap(inMap, outMap.toObject());
- var reMap = convert.fromObject(mergedRawMap);
- copyPropertyIfExists('sources', inMap, reMap);
- copyPropertyIfExists('sourceRoot', inMap, reMap);
- copyPropertyIfExists('sourcesContent', inMap, reMap);
- return reMap;
- }
- function handleIncomingSourceMap (originalCode, options) {
- var inMap;
- if (options.sourceMap) {
- if (typeof options.sourceMap === 'string' || options.sourceMap instanceof String) {
- options.sourceMap = JSON.parse(options.sourceMap);
- }
- inMap = options.sourceMap;
- } else {
- var sourceMappingURL = retrieveSourceMapURL(originalCode);
- var commented;
- // relative file sourceMap
- // //# sourceMappingURL=foo.js.map or /*# sourceMappingURL=foo.js.map */
- if (sourceMappingURL && !/^data:application\/json[^,]+base64,/.test(sourceMappingURL)) {
- commented = convert.fromMapFileSource(originalCode, _path.dirname(options.path));
- } else {
- // inline sourceMap or none sourceMap
- commented = convert.fromSource(originalCode);
- }
- if (commented) {
- inMap = commented.toObject();
- options.sourceMap = inMap;
- }
- }
- return inMap;
- }
- // copy from https://github.com/evanw/node-source-map-support/blob/master/source-map-support.js#L99
- function retrieveSourceMapURL(source) {
- // //# sourceMappingURL=foo.js.map /*# sourceMappingURL=foo.js.map */
- var re = /(?:\/\/[@#][ \t]+sourceMappingURL=([^\s'"]+?)[ \t]*$)|(?:\/\*[@#][ \t]+sourceMappingURL=([^\*]+?)[ \t]*(?:\*\/)[ \t]*$)/mg;
- // Keep executing the search to find the *last* sourceMappingURL to avoid
- // picking up sourceMappingURLs from comments, strings, etc.
- var lastMatch, match;
- while (match = re.exec(source)) {
- lastMatch = match;
- }
- if (!lastMatch) {
- return null;
- }
- return lastMatch[1];
- };
- function adjustFilepath (filepath, sourceRoot) {
- if (!sourceRoot || !isAbsolute(filepath)) {
- return filepath;
- }
- return _path.relative(sourceRoot, filepath);
- }
- function syntaxErrorLine(lineNumber, maxLineNumberLength, line) {
- var content = [' '];
- var lineNumberString = String(lineNumber);
- for (var i = lineNumberString.length; i < maxLineNumberLength; i++) {
- content.push(' ');
- }
- content.push(lineNumberString, ': ', line);
- return content.join('');
- }
- function showSyntaxErrorDetail(code, error, filepath) {
- var i;
- var begin = code.lastIndexOf('\n', error.pos);
- var end = code.indexOf('\n', error.pos);
- if (end === -1) {
- end = undefined;
- }
- var line = code.slice(begin + 1, end);
- var beforeLines = [];
- for (i = 0; i < 5; i++) {
- if (begin === -1) {
- break;
- }
- var lastBegin = begin;
- begin = code.lastIndexOf('\n', begin - 1);
- beforeLines.unshift(code.slice(begin + 1, lastBegin));
- if (begin === 0) {
- break;
- }
- }
- var afterLines = [];
- for (i = 0; i < 5; i++) {
- if (end === undefined) {
- break;
- }
- var lastEnd = end;
- end = code.indexOf('\n', end + 1);
- if (end === -1) {
- end = undefined;
- }
- afterLines.push(code.slice(lastEnd + 1, end));
- }
- var lines = [''];
- var numberLength = String(error.loc.line + afterLines.length).length;
- for (i = 0; i < beforeLines.length; i++) {
- lines.push(
- syntaxErrorLine(error.loc.line - beforeLines.length + i, numberLength, beforeLines[i])
- );
- }
- lines.push(syntaxErrorLine(error.loc.line, numberLength, line));
- var lineContent = [];
- for (i = 0; i < 6 + numberLength + error.loc.column - 1; i++) {
- lineContent.push(' ');
- }
- lineContent.push('^');
- lines.push(lineContent.join(''));
- for (i = 0; i < afterLines.length; i++) {
- lines.push(
- syntaxErrorLine(error.loc.line + i + 1, numberLength, afterLines[i])
- );
- }
- lines.push('', 'Parse Error: ' + error.message + (filepath ? ' in ' + filepath : ''));
- var detail = lines.join('\n');
- var err = new SyntaxError(detail);
- err.loc = error.loc;
- err.pos = error.pos;
- err.raisedAt = error.pos;
- throw err;
- }
- function instrument (originalCode, filepath, options) {
- var jsAst;
- try {
- jsAst = acorn.parse(originalCode, {locations: true, ecmaVersion: options.ecmaVersion, sourceType: options.sourceType, plugins: {asyncawait: true}});
- } catch (e) {
- if (e instanceof SyntaxError && e.pos && e.loc) {
- showSyntaxErrorDetail(originalCode, e, filepath);
- }
- throw e;
- }
- var modifiedAst = estraverse.replace(jsAst, mergeVisitors([
- {
- enter: empowerAssert.enter
- },
- espower.createVisitor(jsAst, options)
- ]));
- var escodegenOptions = extend({
- sourceMap: adjustFilepath(filepath || options.path, options.sourceRoot),
- sourceContent: originalCode,
- sourceMapWithCode: true
- });
- if (options.sourceRoot) {
- escodegenOptions.sourceMapRoot = options.sourceRoot;
- }
- return escodegen.generate(modifiedAst, escodegenOptions);
- }
- function instrumentWithoutSourceMapOutput (originalCode, options) {
- var jsAst;
- try {
- jsAst = acorn.parse(originalCode, {locations: true, ecmaVersion: options.ecmaVersion, sourceType: options.sourceType, plugins: {asyncawait: true}});
- } catch (e) {
- if (e instanceof SyntaxError && e.pos && e.loc) {
- showSyntaxErrorDetail(originalCode, e);
- }
- throw e;
- }
- var modifiedAst = estraverse.replace(jsAst, mergeVisitors([
- {
- enter: empowerAssert.enter
- },
- espower.createVisitor(jsAst, options)
- ]));
- return escodegen.generate(modifiedAst);
- }
- function mergeEspowerOptions (options, filepath) {
- return extend(espower.defaultOptions(), {
- ecmaVersion: 2018,
- sourceType: 'module',
- path: filepath
- }, options);
- }
- module.exports = function espowerSource (originalCode, filepath, options) {
- if (typeof originalCode === 'undefined' || originalCode === null) {
- throw new espower.EspowerError('`originalCode` is not specified', espowerSource);
- }
- var espowerOptions = mergeEspowerOptions(options, filepath);
- var inMap = handleIncomingSourceMap(originalCode, espowerOptions);
- if (!(filepath || espowerOptions.path)) {
- return instrumentWithoutSourceMapOutput(originalCode, espowerOptions);
- }
- var instrumented = instrument(originalCode, filepath, espowerOptions);
- var outMap = convert.fromJSON(instrumented.map.toString());
- if (inMap) {
- var reMap = reconnectSourceMap(inMap, outMap);
- return instrumented.code + '\n' + reMap.toComment() + '\n';
- } else {
- return instrumented.code + '\n' + outMap.toComment() + '\n';
- }
- };
|