babel-plugin-trace
Advanced tools
Comparing version 1.1.0 to 2.0.0-rc.1
1105
lib/index.js
@@ -9,442 +9,22 @@ 'use strict'; | ||
exports.default = function (_ref11) { | ||
var t = _ref11.types, | ||
template = _ref11.template; | ||
exports.getLogFunction = getLogFunction; | ||
exports.handleLabeledStatement = handleLabeledStatement; | ||
function _ref(_id) { | ||
if (!Plugin(_id)) { | ||
throw new TypeError('Function return value violates contract.\n\nExpected:\nPlugin\n\nGot:\n' + _inspect(_id)); | ||
exports.default = function (babel) { | ||
function _ref12(_id14) { | ||
if (!Plugin(_id14)) { | ||
throw new TypeError('Function return value violates contract.\n\nExpected:\nPlugin\n\nGot:\n' + _inspect(_id14)); | ||
} | ||
return _id; | ||
return _id14; | ||
} | ||
if (!PluginParams(arguments[0])) { | ||
throw new TypeError('Value of argument 0 violates contract.\n\nExpected:\nPluginParams\n\nGot:\n' + _inspect(arguments[0])); | ||
if (!PluginParams(babel)) { | ||
throw new TypeError('Value of argument "babel" violates contract.\n\nExpected:\nPluginParams\n\nGot:\n' + _inspect(babel)); | ||
} | ||
var PRESERVE_CONTEXTS = normalizeEnv(process.env.TRACE_CONTEXT); | ||
var PRESERVE_FILES = normalizeEnv(process.env.TRACE_FILE); | ||
var PRESERVE_LEVELS = normalizeEnv(process.env.TRACE_LEVEL); | ||
/** | ||
* Normalize an environment variable, used to override plugin options. | ||
*/ | ||
function normalizeEnv(input) { | ||
function _ref2(_id2) { | ||
if (!(Array.isArray(_id2) && _id2.every(function (item) { | ||
return typeof item === 'string'; | ||
}))) { | ||
throw new TypeError('Function "normalizeEnv" return value violates contract.\n\nExpected:\nstring[]\n\nGot:\n' + _inspect(_id2)); | ||
} | ||
return _id2; | ||
} | ||
if (!(input == null || typeof input === 'string')) { | ||
throw new TypeError('Value of argument "input" violates contract.\n\nExpected:\n?string\n\nGot:\n' + _inspect(input)); | ||
} | ||
if (!input) { | ||
return _ref2([]); | ||
} | ||
return _ref2(input.split(/\s*,\s*/).map(function (context) { | ||
return context.toLowerCase().trim(); | ||
}).filter(function (id) { | ||
return id; | ||
})); | ||
} | ||
/** | ||
* Like `template()` but returns an expression, not an expression statement. | ||
*/ | ||
function expression(input) { | ||
function _ref3(_id3) { | ||
if (!Template(_id3)) { | ||
throw new TypeError('Function "expression" return value violates contract.\n\nExpected:\nTemplate\n\nGot:\n' + _inspect(_id3)); | ||
} | ||
return _id3; | ||
} | ||
if (!(typeof input === 'string')) { | ||
throw new TypeError('Value of argument "input" violates contract.\n\nExpected:\nstring\n\nGot:\n' + _inspect(input)); | ||
} | ||
var fn = template(input); | ||
if (!Template(fn)) { | ||
throw new TypeError('Value of variable "fn" violates contract.\n\nExpected:\nTemplate\n\nGot:\n' + _inspect(fn)); | ||
} | ||
return _ref3(function (ids) { | ||
function _ref4(_id4) { | ||
if (!Node(_id4)) { | ||
throw new TypeError('Function return value violates contract.\n\nExpected:\nNode\n\nGot:\n' + _inspect(_id4)); | ||
} | ||
return _id4; | ||
} | ||
if (!TemplateIds(ids)) { | ||
throw new TypeError('Value of argument "ids" violates contract.\n\nExpected:\nTemplateIds\n\nGot:\n' + _inspect(ids)); | ||
} | ||
var node = fn(ids); | ||
if (!Node(node)) { | ||
throw new TypeError('Value of variable "node" violates contract.\n\nExpected:\nNode\n\nGot:\n' + _inspect(node)); | ||
} | ||
return _ref4(node.expression ? node.expression : node); | ||
}); | ||
} | ||
/** | ||
* Normalize the plugin options. | ||
*/ | ||
function normalizeOpts(opts) { | ||
function _ref5(_id5) { | ||
if (!PluginOptions(_id5)) { | ||
throw new TypeError('Function "normalizeOpts" return value violates contract.\n\nExpected:\nPluginOptions\n\nGot:\n' + _inspect(_id5)); | ||
} | ||
return _id5; | ||
} | ||
if (!PluginOptions(opts)) { | ||
throw new TypeError('Value of argument "opts" violates contract.\n\nExpected:\nPluginOptions\n\nGot:\n' + _inspect(opts)); | ||
} | ||
if (opts[$normalized]) { | ||
return _ref5(opts); | ||
} | ||
if (!opts.aliases) { | ||
opts.aliases = { | ||
log: defaultLog, | ||
trace: defaultLog, | ||
warn: defaultLog | ||
}; | ||
} else { | ||
Object.keys(opts.aliases).forEach(function (key) { | ||
if (typeof opts.aliases[key] === 'string' && opts.aliases[key]) { | ||
var expr = expression(opts.aliases[key]); | ||
if (!(typeof expr === 'function')) { | ||
throw new TypeError('Value of variable "expr" violates contract.\n\nExpected:\n(Message) => Node\n\nGot:\n' + _inspect(expr)); | ||
} | ||
opts.aliases[key] = function (message) { | ||
function _ref6(_id6) { | ||
if (!Node(_id6)) { | ||
throw new TypeError('Function return value violates contract.\n\nExpected:\nNode\n\nGot:\n' + _inspect(_id6)); | ||
} | ||
return _id6; | ||
} | ||
if (!Message(message)) { | ||
throw new TypeError('Value of argument "message" violates contract.\n\nExpected:\nMessage\n\nGot:\n' + _inspect(message)); | ||
} | ||
return _ref6(expr(message)); | ||
}; | ||
} | ||
}); | ||
} | ||
opts[$normalized] = true; | ||
return _ref5(opts); | ||
} | ||
/** | ||
* The default log() function. | ||
*/ | ||
function defaultLog(message, metadata) { | ||
function _ref7(_id7) { | ||
if (!Node(_id7)) { | ||
throw new TypeError('Function "defaultLog" return value violates contract.\n\nExpected:\nNode\n\nGot:\n' + _inspect(_id7)); | ||
} | ||
return _id7; | ||
} | ||
if (!Message(message)) { | ||
throw new TypeError('Value of argument "message" violates contract.\n\nExpected:\nMessage\n\nGot:\n' + _inspect(message)); | ||
} | ||
if (!Metadata(metadata)) { | ||
throw new TypeError('Value of argument "metadata" violates contract.\n\nExpected:\nMetadata\n\nGot:\n' + _inspect(metadata)); | ||
} | ||
var prefix = metadata.context + ':'; | ||
if (metadata.indent) { | ||
prefix += new Array(metadata.indent + 1).join(' '); | ||
if (!(typeof prefix === 'string')) { | ||
throw new TypeError('Value of variable "prefix" violates contract.\n\nExpected:\nstring\n\nGot:\n' + _inspect(prefix)); | ||
} | ||
} | ||
if (t.isSequenceExpression(message.content)) { | ||
return _ref7(t.callExpression(t.memberExpression(t.identifier('console'), t.identifier('log')), [t.stringLiteral(prefix)].concat(message.content.expressions))); | ||
} else { | ||
return _ref7(expression('console.log(PREFIX, CONTENT)')({ | ||
PREFIX: t.stringLiteral(prefix), | ||
CONTENT: message.content | ||
})); | ||
} | ||
} | ||
function generatePrefix(dirname, basename) { | ||
function _ref8(_id8) { | ||
if (!(typeof _id8 === 'string')) { | ||
throw new TypeError('Function "generatePrefix" return value violates contract.\n\nExpected:\nstring\n\nGot:\n' + _inspect(_id8)); | ||
} | ||
return _id8; | ||
} | ||
if (!(typeof dirname === 'string')) { | ||
throw new TypeError('Value of argument "dirname" violates contract.\n\nExpected:\nstring\n\nGot:\n' + _inspect(dirname)); | ||
} | ||
if (!(typeof basename === 'string')) { | ||
throw new TypeError('Value of argument "basename" violates contract.\n\nExpected:\nstring\n\nGot:\n' + _inspect(basename)); | ||
} | ||
if (basename !== 'index') { | ||
return basename; | ||
} | ||
basename = _path2.default.basename(dirname); | ||
if (basename !== 'src' && basename !== 'lib') { | ||
return basename; | ||
} | ||
return _ref8(_path2.default.basename(_path2.default.dirname(dirname))); | ||
} | ||
/** | ||
* Collect the metadata for a given node path, which will be | ||
* made available to logging functions. | ||
*/ | ||
function collectMetadata(path, opts) { | ||
function _ref9(_id9) { | ||
if (!Metadata(_id9)) { | ||
throw new TypeError('Function "collectMetadata" return value violates contract.\n\nExpected:\nMetadata\n\nGot:\n' + _inspect(_id9)); | ||
} | ||
return _id9; | ||
} | ||
if (!NodePath(path)) { | ||
throw new TypeError('Value of argument "path" violates contract.\n\nExpected:\nNodePath\n\nGot:\n' + _inspect(path)); | ||
} | ||
if (!PluginOptions(opts)) { | ||
throw new TypeError('Value of argument "opts" violates contract.\n\nExpected:\nPluginOptions\n\nGot:\n' + _inspect(opts)); | ||
} | ||
var filename = _path2.default.resolve(process.cwd(), path.hub.file.opts.filename); | ||
if (!(typeof filename === 'string')) { | ||
throw new TypeError('Value of variable "filename" violates contract.\n\nExpected:\nstring\n\nGot:\n' + _inspect(filename)); | ||
} | ||
var dirname = _path2.default.dirname(filename); | ||
if (!(typeof dirname === 'string')) { | ||
throw new TypeError('Value of variable "dirname" violates contract.\n\nExpected:\nstring\n\nGot:\n' + _inspect(dirname)); | ||
} | ||
var extname = _path2.default.extname(filename); | ||
if (!(typeof extname === 'string')) { | ||
throw new TypeError('Value of variable "extname" violates contract.\n\nExpected:\nstring\n\nGot:\n' + _inspect(extname)); | ||
} | ||
var basename = _path2.default.basename(filename, extname); | ||
if (!(typeof basename === 'string')) { | ||
throw new TypeError('Value of variable "basename" violates contract.\n\nExpected:\nstring\n\nGot:\n' + _inspect(basename)); | ||
} | ||
var prefix = generatePrefix(dirname, basename); | ||
var names = []; | ||
if (!(Array.isArray(names) && names.every(function (item) { | ||
return typeof item === 'string'; | ||
}))) { | ||
throw new TypeError('Value of variable "names" violates contract.\n\nExpected:\nstring[]\n\nGot:\n' + _inspect(names)); | ||
} | ||
var indent = 0; | ||
var parent = void 0; | ||
if (!(parent == null || NodePath(parent))) { | ||
throw new TypeError('Value of variable "parent" violates contract.\n\nExpected:\n?NodePath\n\nGot:\n' + _inspect(parent)); | ||
} | ||
var parentName = path.getAncestry().slice(1).reduce(function (parts, item) { | ||
if (!(Array.isArray(parts) && parts.every(function (item) { | ||
return typeof item === 'string'; | ||
}))) { | ||
throw new TypeError('Value of argument "parts" violates contract.\n\nExpected:\nstring[]\n\nGot:\n' + _inspect(parts)); | ||
} | ||
if (!NodePath(item)) { | ||
throw new TypeError('Value of argument "item" violates contract.\n\nExpected:\nNodePath\n\nGot:\n' + _inspect(item)); | ||
} | ||
if (item.isClassMethod()) { | ||
if (!parent) { | ||
parent = item; | ||
} | ||
parts.unshift(item.node.key.type === 'Identifier' ? item.node.key.name : '[computed method]'); | ||
} else if (item.isClassDeclaration()) { | ||
if (!parent) { | ||
parent = item; | ||
} | ||
parts.unshift(item.node.id ? item.node.id.name : '[anonymous class@' + item.node.loc.start.line + ']'); | ||
} else if (item.isFunction()) { | ||
if (!parent) { | ||
parent = item; | ||
} | ||
parts.unshift(item.node.id && item.node.id.name || '[anonymous@' + item.node.loc.start.line + ']'); | ||
} else if (item.isProgram()) { | ||
if (!parent) { | ||
parent = item; | ||
} | ||
} else if (!parent && !item.isClassBody() && !item.isBlockStatement()) { | ||
indent++; | ||
} | ||
return parts; | ||
}, []).join(':'); | ||
if (!(typeof parentName === 'string')) { | ||
throw new TypeError('Value of variable "parentName" violates contract.\n\nExpected:\nstring\n\nGot:\n' + _inspect(parentName)); | ||
} | ||
var hasStartMessage = false; | ||
var isStartMessage = false; | ||
if (parent && !parent.isProgram()) { | ||
_parent$get$get = parent.get('body').get('body'); | ||
if (!(_parent$get$get && (typeof _parent$get$get[Symbol.iterator] === 'function' || Array.isArray(_parent$get$get)))) { | ||
throw new TypeError('Expected _parent$get$get to be iterable, got ' + _inspect(_parent$get$get)); | ||
} | ||
var _iteratorNormalCompletion = true; | ||
var _didIteratorError = false; | ||
var _iteratorError = undefined; | ||
try { | ||
for (var _iterator = _parent$get$get[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { | ||
var _parent$get$get; | ||
var _child = _step.value; | ||
if (!NodePath(_child)) { | ||
throw new TypeError('Value of variable "child" violates contract.\n\nExpected:\nNodePath\n\nGot:\n' + _inspect(_child)); | ||
} | ||
if (_child.node[$handled]) { | ||
hasStartMessage = true; | ||
break; | ||
} | ||
if (!_child.isLabeledStatement()) { | ||
break; | ||
} | ||
var label = _child.get('label'); | ||
if (!NodePath(label)) { | ||
throw new TypeError('Value of variable "label" violates contract.\n\nExpected:\nNodePath\n\nGot:\n' + _inspect(label)); | ||
} | ||
if (opts.aliases[label.node.name]) { | ||
hasStartMessage = true; | ||
if (_child.node === path.node) { | ||
isStartMessage = true; | ||
} | ||
break; | ||
} | ||
} | ||
} catch (err) { | ||
_didIteratorError = true; | ||
_iteratorError = err; | ||
} finally { | ||
try { | ||
if (!_iteratorNormalCompletion && _iterator.return) { | ||
_iterator.return(); | ||
} | ||
} finally { | ||
if (_didIteratorError) { | ||
throw _iteratorError; | ||
} | ||
} | ||
} | ||
} | ||
var context = prefix + ':' + parentName; | ||
return _ref9({ indent: indent, prefix: prefix, parentName: parentName, context: context, hasStartMessage: hasStartMessage, isStartMessage: isStartMessage, filename: filename, dirname: dirname, basename: basename, extname: extname }); | ||
} | ||
/** | ||
* Determine whether the given logging statement should be stripped. | ||
*/ | ||
function shouldStrip(name, metadata, opts) { | ||
if (!(typeof name === 'string')) { | ||
throw new TypeError('Value of argument "name" violates contract.\n\nExpected:\nstring\n\nGot:\n' + _inspect(name)); | ||
} | ||
if (!Metadata(metadata)) { | ||
throw new TypeError('Value of argument "metadata" violates contract.\n\nExpected:\nMetadata\n\nGot:\n' + _inspect(metadata)); | ||
} | ||
if (!PluginOptions(opts)) { | ||
throw new TypeError('Value of argument "opts" violates contract.\n\nExpected:\nPluginOptions\n\nGot:\n' + _inspect(opts)); | ||
} | ||
if (!opts.strip) { | ||
return false; | ||
} else if (opts.strip === true) { | ||
return !hasStripOverride(name, metadata); | ||
} else if (typeof opts.strip === 'string') { | ||
if (opts.strip === process.env.NODE_ENV) { | ||
return !hasStripOverride(name, metadata); | ||
} | ||
} else if (opts.strip[process.env.NODE_ENV]) { | ||
return !hasStripOverride(name, metadata); | ||
} | ||
return true; | ||
} | ||
function hasStripOverride(name, metadata) { | ||
if (!(typeof name === 'string')) { | ||
throw new TypeError('Value of argument "name" violates contract.\n\nExpected:\nstring\n\nGot:\n' + _inspect(name)); | ||
} | ||
if (!Metadata(metadata)) { | ||
throw new TypeError('Value of argument "metadata" violates contract.\n\nExpected:\nMetadata\n\nGot:\n' + _inspect(metadata)); | ||
} | ||
if (PRESERVE_CONTEXTS.length && PRESERVE_CONTEXTS.some(function (context) { | ||
return metadata.context.toLowerCase().indexOf(context) !== -1; | ||
})) { | ||
return true; | ||
} else if (PRESERVE_FILES.length && PRESERVE_FILES.some(function (filename) { | ||
return metadata.filename.toLowerCase().indexOf(filename) !== -1; | ||
})) { | ||
return true; | ||
} else if (PRESERVE_LEVELS.length && PRESERVE_LEVELS.some(function (level) { | ||
return level === name.toLowerCase(); | ||
})) { | ||
return true; | ||
} else { | ||
return false; | ||
} | ||
} | ||
return _ref({ | ||
return _ref12({ | ||
visitor: { | ||
Program: function Program(program, _ref12) { | ||
var opts = _ref12.opts; | ||
Program: function Program(program, _ref15) { | ||
var opts = _ref15.opts; | ||
@@ -461,68 +41,3 @@ if (!NodePath(program)) { | ||
var label = path.get('label'); | ||
if (!NodePath(label)) { | ||
throw new TypeError('Value of variable "label" violates contract.\n\nExpected:\nNodePath\n\nGot:\n' + _inspect(label)); | ||
} | ||
opts = normalizeOpts(opts); | ||
if (!opts.aliases[label.node.name]) { | ||
return; | ||
} | ||
var metadata = collectMetadata(path, opts); | ||
if (!Metadata(metadata)) { | ||
throw new TypeError('Value of variable "metadata" violates contract.\n\nExpected:\nMetadata\n\nGot:\n' + _inspect(metadata)); | ||
} | ||
if (shouldStrip(label.node.name, metadata, opts)) { | ||
path.remove(); | ||
return; | ||
} | ||
path.traverse({ | ||
"VariableDeclaration|Function|AssignmentExpression|UpdateExpression|YieldExpression|ReturnStatement": function VariableDeclarationFunctionAssignmentExpressionUpdateExpressionYieldExpressionReturnStatement(item) { | ||
if (!NodePath(item)) { | ||
throw new TypeError('Value of argument "item" violates contract.\n\nExpected:\nNodePath\n\nGot:\n' + _inspect(item)); | ||
} | ||
throw path.buildCodeFrameError('Logging statements cannot have side effects.'); | ||
}, | ||
ExpressionStatement: function ExpressionStatement(statement) { | ||
if (!NodePath(statement)) { | ||
throw new TypeError('Value of argument "statement" violates contract.\n\nExpected:\nNodePath\n\nGot:\n' + _inspect(statement)); | ||
} | ||
if (statement.node[$handled]) { | ||
return; | ||
} | ||
var message = { | ||
prefix: t.stringLiteral(metadata.prefix), | ||
content: statement.get('expression').node, | ||
hasStartMessage: t.booleanLiteral(metadata.hasStartMessage), | ||
isStartMessage: t.booleanLiteral(metadata.isStartMessage), | ||
indent: t.numericLiteral(metadata.indent), | ||
parentName: t.stringLiteral(metadata.parentName), | ||
filename: t.stringLiteral(metadata.filename), | ||
dirname: t.stringLiteral(metadata.dirname), | ||
basename: t.stringLiteral(metadata.basename), | ||
extname: t.stringLiteral(metadata.extname) | ||
}; | ||
if (!Message(message)) { | ||
throw new TypeError('Value of variable "message" violates contract.\n\nExpected:\nMessage\n\nGot:\n' + _inspect(message)); | ||
} | ||
var replacement = t.expressionStatement(opts.aliases[label.node.name](message, metadata)); | ||
replacement[$handled] = true; | ||
statement.replaceWith(replacement); | ||
} | ||
}); | ||
if (path.get('body').isBlockStatement()) { | ||
path.replaceWithMultiple(path.get('body').node.body); | ||
} else { | ||
path.replaceWith(path.get('body').node); | ||
} | ||
handleLabeledStatement(babel, path, opts); | ||
} | ||
@@ -555,5 +70,19 @@ }); | ||
var PluginTemplate = function () { | ||
function PluginTemplate(input) { | ||
return typeof input === 'function'; | ||
} | ||
; | ||
Object.defineProperty(PluginTemplate, Symbol.hasInstance, { | ||
value: function value(input) { | ||
return PluginTemplate(input); | ||
} | ||
}); | ||
return PluginTemplate; | ||
}(); | ||
var PluginParams = function () { | ||
function PluginParams(input) { | ||
return input != null && input.types instanceof Object && typeof input.template === 'function'; | ||
return input != null && input.types instanceof Object && PluginTemplate(input.template); | ||
} | ||
@@ -575,5 +104,8 @@ | ||
return typeof _key === 'string' || Template(_key); | ||
})) && (input.strip === undefined || typeof input.strip === 'boolean' || typeof input.strip === 'string' || input.strip != null && _typeof(input.strip) === 'object' && Object.keys(input.strip).every(function (key) { | ||
})) && (input.strip === undefined || typeof input.strip === 'boolean' || input.strip != null && _typeof(input.strip) === 'object' && Object.keys(input.strip).every(function (key) { | ||
var _key2 = input.strip[key]; | ||
return typeof _key2 === 'boolean'; | ||
return typeof _key2 === 'boolean' || _key2 != null && (typeof _key2 === 'undefined' ? 'undefined' : _typeof(_key2)) === 'object' && Object.keys(_key2).every(function (key) { | ||
var _key3 = _key2[key]; | ||
return typeof _key3 === 'boolean'; | ||
}); | ||
})); | ||
@@ -591,7 +123,35 @@ } | ||
var LogFunction = function () { | ||
function LogFunction(input) { | ||
return typeof input === 'function'; | ||
} | ||
; | ||
Object.defineProperty(LogFunction, Symbol.hasInstance, { | ||
value: function value(input) { | ||
return LogFunction(input); | ||
} | ||
}); | ||
return LogFunction; | ||
}(); | ||
var LogLevel = function () { | ||
function LogLevel(input) { | ||
return input === 'log' || input === 'warn' || input === 'error'; | ||
} | ||
; | ||
Object.defineProperty(LogLevel, Symbol.hasInstance, { | ||
value: function value(input) { | ||
return LogLevel(input); | ||
} | ||
}); | ||
return LogLevel; | ||
}(); | ||
var Visitors = function () { | ||
function Visitors(input) { | ||
return input != null && (typeof input === 'undefined' ? 'undefined' : _typeof(input)) === 'object' && Object.keys(input).every(function (key) { | ||
var _key3 = input[key]; | ||
return Visitor(_key3); | ||
var _key4 = input[key]; | ||
return Visitor(_key4); | ||
}); | ||
@@ -626,4 +186,4 @@ } | ||
return input != null && (typeof input === 'undefined' ? 'undefined' : _typeof(input)) === 'object' && Object.keys(input).every(function (key) { | ||
var _key4 = input[key]; | ||
return Node(_key4); | ||
var _key5 = input[key]; | ||
return Node(_key5); | ||
}); | ||
@@ -756,3 +316,538 @@ } | ||
var PRESERVE_CONTEXTS = normalizeEnv(process.env.TRACE_CONTEXT); | ||
var PRESERVE_FILES = normalizeEnv(process.env.TRACE_FILE); | ||
var PRESERVE_LEVELS = normalizeEnv(process.env.TRACE_LEVEL); | ||
/** | ||
* Normalize an environment variable, used to override plugin options. | ||
*/ | ||
function normalizeEnv(input) { | ||
function _ref(_id) { | ||
if (!(Array.isArray(_id) && _id.every(function (item) { | ||
return typeof item === 'string'; | ||
}))) { | ||
throw new TypeError('Function "normalizeEnv" return value violates contract.\n\nExpected:\nstring[]\n\nGot:\n' + _inspect(_id)); | ||
} | ||
return _id; | ||
} | ||
if (!(input == null || typeof input === 'string')) { | ||
throw new TypeError('Value of argument "input" violates contract.\n\nExpected:\n?string\n\nGot:\n' + _inspect(input)); | ||
} | ||
if (!input) { | ||
return _ref([]); | ||
} | ||
return _ref(input.split(',').map(function (context) { | ||
return context.toLowerCase().trim(); | ||
}).filter(function (id) { | ||
return id; | ||
})); | ||
} | ||
/** | ||
* Like `template()` but returns an expression, not an expression statement. | ||
*/ | ||
function expression(input, template) { | ||
function _ref2(_id2) { | ||
if (!Template(_id2)) { | ||
throw new TypeError('Function "expression" return value violates contract.\n\nExpected:\nTemplate\n\nGot:\n' + _inspect(_id2)); | ||
} | ||
return _id2; | ||
} | ||
if (!(typeof input === 'string')) { | ||
throw new TypeError('Value of argument "input" violates contract.\n\nExpected:\nstring\n\nGot:\n' + _inspect(input)); | ||
} | ||
if (!PluginTemplate(template)) { | ||
throw new TypeError('Value of argument "template" violates contract.\n\nExpected:\nPluginTemplate\n\nGot:\n' + _inspect(template)); | ||
} | ||
var fn = template(input); | ||
if (!Template(fn)) { | ||
throw new TypeError('Value of variable "fn" violates contract.\n\nExpected:\nTemplate\n\nGot:\n' + _inspect(fn)); | ||
} | ||
return _ref2(function (ids) { | ||
function _ref3(_id3) { | ||
if (!Node(_id3)) { | ||
throw new TypeError('Function return value violates contract.\n\nExpected:\nNode\n\nGot:\n' + _inspect(_id3)); | ||
} | ||
return _id3; | ||
} | ||
if (!TemplateIds(ids)) { | ||
throw new TypeError('Value of argument "ids" violates contract.\n\nExpected:\nTemplateIds\n\nGot:\n' + _inspect(ids)); | ||
} | ||
var node = fn(ids); | ||
if (!Node(node)) { | ||
throw new TypeError('Value of variable "node" violates contract.\n\nExpected:\nNode\n\nGot:\n' + _inspect(node)); | ||
} | ||
return _ref3(node.expression ? node.expression : node); | ||
}); | ||
} | ||
/** | ||
* The default log() function. | ||
*/ | ||
function getLogFunction(_ref13, logLevel) { | ||
var t = _ref13.types, | ||
template = _ref13.template; | ||
function _ref4(_id4) { | ||
if (!LogFunction(_id4)) { | ||
throw new TypeError('Function "getLogFunction" return value violates contract.\n\nExpected:\nLogFunction\n\nGot:\n' + _inspect(_id4)); | ||
} | ||
return _id4; | ||
} | ||
if (!PluginParams(arguments[0])) { | ||
throw new TypeError('Value of argument 0 violates contract.\n\nExpected:\nPluginParams\n\nGot:\n' + _inspect(arguments[0])); | ||
} | ||
if (!LogLevel(logLevel)) { | ||
throw new TypeError('Value of argument "logLevel" violates contract.\n\nExpected:\nLogLevel\n\nGot:\n' + _inspect(logLevel)); | ||
} | ||
return _ref4(function log(message, metadata) { | ||
function _ref5(_id5) { | ||
if (!Node(_id5)) { | ||
throw new TypeError('Function "log" return value violates contract.\n\nExpected:\nNode\n\nGot:\n' + _inspect(_id5)); | ||
} | ||
return _id5; | ||
} | ||
if (!Message(message)) { | ||
throw new TypeError('Value of argument "message" violates contract.\n\nExpected:\nMessage\n\nGot:\n' + _inspect(message)); | ||
} | ||
if (!Metadata(metadata)) { | ||
throw new TypeError('Value of argument "metadata" violates contract.\n\nExpected:\nMetadata\n\nGot:\n' + _inspect(metadata)); | ||
} | ||
var prefix = metadata.context + ':'; | ||
if (metadata.indent) { | ||
prefix += new Array(metadata.indent + 1).join(' '); | ||
if (!(typeof prefix === 'string')) { | ||
throw new TypeError('Value of variable "prefix" violates contract.\n\nExpected:\nstring\n\nGot:\n' + _inspect(prefix)); | ||
} | ||
} | ||
if (t.isSequenceExpression(message.content)) { | ||
return _ref5(t.callExpression(t.memberExpression(t.identifier('console'), t.identifier(logLevel)), [t.stringLiteral(prefix)].concat(message.content.expressions))); | ||
} else { | ||
return _ref5(expression('console.LOGLEVEL(PREFIX, CONTENT)', template)({ | ||
LOGLEVEL: t.identifier(logLevel), | ||
PREFIX: t.stringLiteral(prefix), | ||
CONTENT: message.content | ||
})); | ||
} | ||
}); | ||
} | ||
/** | ||
* Normalize the plugin options. | ||
*/ | ||
function normalizeOpts(babel, opts) { | ||
function _ref6(_id6) { | ||
if (!PluginOptions(_id6)) { | ||
throw new TypeError('Function "normalizeOpts" return value violates contract.\n\nExpected:\nPluginOptions\n\nGot:\n' + _inspect(_id6)); | ||
} | ||
return _id6; | ||
} | ||
if (!PluginParams(babel)) { | ||
throw new TypeError('Value of argument "babel" violates contract.\n\nExpected:\nPluginParams\n\nGot:\n' + _inspect(babel)); | ||
} | ||
if (!PluginOptions(opts)) { | ||
throw new TypeError('Value of argument "opts" violates contract.\n\nExpected:\nPluginOptions\n\nGot:\n' + _inspect(opts)); | ||
} | ||
if (opts[$normalized]) { | ||
return _ref6(opts); | ||
} | ||
if (!opts.aliases) { | ||
var log = getLogFunction(babel, 'log'); | ||
opts.aliases = { | ||
log: log, | ||
trace: log, | ||
warn: getLogFunction(babel, 'warn') | ||
}; | ||
} else { | ||
Object.keys(opts.aliases).forEach(function (key) { | ||
if (typeof opts.aliases[key] === 'string' && opts.aliases[key]) { | ||
var expr = expression(opts.aliases[key], babel.template); | ||
if (!(typeof expr === 'function')) { | ||
throw new TypeError('Value of variable "expr" violates contract.\n\nExpected:\n(Message) => Node\n\nGot:\n' + _inspect(expr)); | ||
} | ||
opts.aliases[key] = function (message) { | ||
function _ref7(_id7) { | ||
if (!Node(_id7)) { | ||
throw new TypeError('Function return value violates contract.\n\nExpected:\nNode\n\nGot:\n' + _inspect(_id7)); | ||
} | ||
return _id7; | ||
} | ||
if (!Message(message)) { | ||
throw new TypeError('Value of argument "message" violates contract.\n\nExpected:\nMessage\n\nGot:\n' + _inspect(message)); | ||
} | ||
return _ref7(expr(message)); | ||
}; | ||
} | ||
}); | ||
} | ||
if (opts.strip === undefined) { | ||
opts.strip = { | ||
log: { production: true }, | ||
trace: true, | ||
warn: { production: true } | ||
}; | ||
} | ||
opts[$normalized] = true; | ||
return _ref6(opts); | ||
} | ||
function generatePrefix(dirname, basename) { | ||
function _ref8(_id8) { | ||
if (!(typeof _id8 === 'string')) { | ||
throw new TypeError('Function "generatePrefix" return value violates contract.\n\nExpected:\nstring\n\nGot:\n' + _inspect(_id8)); | ||
} | ||
return _id8; | ||
} | ||
if (!(typeof dirname === 'string')) { | ||
throw new TypeError('Value of argument "dirname" violates contract.\n\nExpected:\nstring\n\nGot:\n' + _inspect(dirname)); | ||
} | ||
if (!(typeof basename === 'string')) { | ||
throw new TypeError('Value of argument "basename" violates contract.\n\nExpected:\nstring\n\nGot:\n' + _inspect(basename)); | ||
} | ||
if (basename !== 'index') { | ||
return basename; | ||
} | ||
basename = _path2.default.basename(dirname); | ||
if (basename !== 'src' && basename !== 'lib') { | ||
return basename; | ||
} | ||
return _ref8(_path2.default.basename(_path2.default.dirname(dirname))); | ||
} | ||
/** | ||
* Collect the metadata for a given node path, which will be | ||
* made available to logging functions. | ||
*/ | ||
function collectMetadata(path, opts) { | ||
function _ref9(_id9) { | ||
if (!Metadata(_id9)) { | ||
throw new TypeError('Function "collectMetadata" return value violates contract.\n\nExpected:\nMetadata\n\nGot:\n' + _inspect(_id9)); | ||
} | ||
return _id9; | ||
} | ||
if (!NodePath(path)) { | ||
throw new TypeError('Value of argument "path" violates contract.\n\nExpected:\nNodePath\n\nGot:\n' + _inspect(path)); | ||
} | ||
if (!PluginOptions(opts)) { | ||
throw new TypeError('Value of argument "opts" violates contract.\n\nExpected:\nPluginOptions\n\nGot:\n' + _inspect(opts)); | ||
} | ||
var filename = _path2.default.resolve(process.cwd(), path.hub.file.opts.filename); | ||
if (!(typeof filename === 'string')) { | ||
throw new TypeError('Value of variable "filename" violates contract.\n\nExpected:\nstring\n\nGot:\n' + _inspect(filename)); | ||
} | ||
var dirname = _path2.default.dirname(filename); | ||
if (!(typeof dirname === 'string')) { | ||
throw new TypeError('Value of variable "dirname" violates contract.\n\nExpected:\nstring\n\nGot:\n' + _inspect(dirname)); | ||
} | ||
var extname = _path2.default.extname(filename); | ||
if (!(typeof extname === 'string')) { | ||
throw new TypeError('Value of variable "extname" violates contract.\n\nExpected:\nstring\n\nGot:\n' + _inspect(extname)); | ||
} | ||
var basename = _path2.default.basename(filename, extname); | ||
if (!(typeof basename === 'string')) { | ||
throw new TypeError('Value of variable "basename" violates contract.\n\nExpected:\nstring\n\nGot:\n' + _inspect(basename)); | ||
} | ||
var prefix = generatePrefix(dirname, basename); | ||
var names = []; | ||
if (!(Array.isArray(names) && names.every(function (item) { | ||
return typeof item === 'string'; | ||
}))) { | ||
throw new TypeError('Value of variable "names" violates contract.\n\nExpected:\nstring[]\n\nGot:\n' + _inspect(names)); | ||
} | ||
var indent = 0; | ||
var parent = void 0; | ||
if (!(parent == null || NodePath(parent))) { | ||
throw new TypeError('Value of variable "parent" violates contract.\n\nExpected:\n?NodePath\n\nGot:\n' + _inspect(parent)); | ||
} | ||
var parentName = path.getAncestry().slice(1).reduce(function (parts, item) { | ||
if (!(Array.isArray(parts) && parts.every(function (item) { | ||
return typeof item === 'string'; | ||
}))) { | ||
throw new TypeError('Value of argument "parts" violates contract.\n\nExpected:\nstring[]\n\nGot:\n' + _inspect(parts)); | ||
} | ||
if (!NodePath(item)) { | ||
throw new TypeError('Value of argument "item" violates contract.\n\nExpected:\nNodePath\n\nGot:\n' + _inspect(item)); | ||
} | ||
if (item.isClassMethod()) { | ||
if (!parent) { | ||
parent = item; | ||
} | ||
parts.unshift(item.node.key.type === 'Identifier' ? item.node.key.name : '[computed method]'); | ||
} else if (item.isClassDeclaration()) { | ||
if (!parent) { | ||
parent = item; | ||
} | ||
parts.unshift(item.node.id ? item.node.id.name : '[anonymous class@' + item.node.loc.start.line + ']'); | ||
} else if (item.isFunction()) { | ||
if (!parent) { | ||
parent = item; | ||
} | ||
parts.unshift(item.node.id && item.node.id.name || '[anonymous@' + item.node.loc.start.line + ']'); | ||
} else if (item.isProgram()) { | ||
if (!parent) { | ||
parent = item; | ||
} | ||
} else if (!parent && !item.isClassBody() && !item.isBlockStatement()) { | ||
indent++; | ||
} | ||
return parts; | ||
}, []).join(':'); | ||
if (!(typeof parentName === 'string')) { | ||
throw new TypeError('Value of variable "parentName" violates contract.\n\nExpected:\nstring\n\nGot:\n' + _inspect(parentName)); | ||
} | ||
var hasStartMessage = false; | ||
var isStartMessage = false; | ||
if (parent && !parent.isProgram()) { | ||
_parent$get$get = parent.get('body').get('body'); | ||
if (!(_parent$get$get && (typeof _parent$get$get[Symbol.iterator] === 'function' || Array.isArray(_parent$get$get)))) { | ||
throw new TypeError('Expected _parent$get$get to be iterable, got ' + _inspect(_parent$get$get)); | ||
} | ||
var _iteratorNormalCompletion = true; | ||
var _didIteratorError = false; | ||
var _iteratorError = undefined; | ||
try { | ||
for (var _iterator = _parent$get$get[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { | ||
var _parent$get$get; | ||
var _child = _step.value; | ||
if (!NodePath(_child)) { | ||
throw new TypeError('Value of variable "child" violates contract.\n\nExpected:\nNodePath\n\nGot:\n' + _inspect(_child)); | ||
} | ||
if (_child.node[$handled]) { | ||
hasStartMessage = true; | ||
break; | ||
} | ||
if (!_child.isLabeledStatement()) { | ||
break; | ||
} | ||
var label = _child.get('label'); | ||
if (!NodePath(label)) { | ||
throw new TypeError('Value of variable "label" violates contract.\n\nExpected:\nNodePath\n\nGot:\n' + _inspect(label)); | ||
} | ||
if (opts.aliases[label.node.name]) { | ||
hasStartMessage = true; | ||
if (_child.node === path.node) { | ||
isStartMessage = true; | ||
} | ||
break; | ||
} | ||
} | ||
} catch (err) { | ||
_didIteratorError = true; | ||
_iteratorError = err; | ||
} finally { | ||
try { | ||
if (!_iteratorNormalCompletion && _iterator.return) { | ||
_iterator.return(); | ||
} | ||
} finally { | ||
if (_didIteratorError) { | ||
throw _iteratorError; | ||
} | ||
} | ||
} | ||
} | ||
var context = prefix + ':' + parentName; | ||
return _ref9({ indent: indent, prefix: prefix, parentName: parentName, context: context, hasStartMessage: hasStartMessage, isStartMessage: isStartMessage, filename: filename, dirname: dirname, basename: basename, extname: extname }); | ||
} | ||
/** | ||
* Determine whether the given logging statement should be stripped. | ||
*/ | ||
function shouldStrip(name, metadata, _ref14) { | ||
var strip = _ref14.strip; | ||
if (!(typeof name === 'string')) { | ||
throw new TypeError('Value of argument "name" violates contract.\n\nExpected:\nstring\n\nGot:\n' + _inspect(name)); | ||
} | ||
if (!Metadata(metadata)) { | ||
throw new TypeError('Value of argument "metadata" violates contract.\n\nExpected:\nMetadata\n\nGot:\n' + _inspect(metadata)); | ||
} | ||
if (!PluginOptions(arguments[2])) { | ||
throw new TypeError('Value of argument 2 violates contract.\n\nExpected:\nPluginOptions\n\nGot:\n' + _inspect(arguments[2])); | ||
} | ||
switch (typeof strip === 'undefined' ? 'undefined' : _typeof(strip)) { | ||
case 'boolean': | ||
if (!strip) return false; | ||
// strip === true | ||
break; | ||
case 'object': | ||
var se = strip[name]; | ||
if (!se || (typeof se === 'undefined' ? 'undefined' : _typeof(se)) === 'object' && !se[process.env.NODE_ENV]) return false; | ||
// strip[name] === true || strip[name][env] === true | ||
break; | ||
default: | ||
return false; | ||
} | ||
if (PRESERVE_CONTEXTS.length) { | ||
var context = metadata.context.toLowerCase(); | ||
if (PRESERVE_CONTEXTS.some(function (pc) { | ||
return context.includes(pc); | ||
})) return false; | ||
} | ||
if (PRESERVE_FILES.length) { | ||
var _filename = metadata.filename.toLowerCase(); | ||
if (PRESERVE_FILES.some(function (pf) { | ||
return _filename.includes(pf); | ||
})) return false; | ||
} | ||
if (PRESERVE_LEVELS.length) { | ||
var level = name.toLowerCase(); | ||
if (PRESERVE_LEVELS.some(function (pl) { | ||
return level === pl; | ||
})) return false; | ||
} | ||
return true; | ||
} | ||
function handleLabeledStatement(babel, path, opts) { | ||
if (!PluginParams(babel)) { | ||
throw new TypeError('Value of argument "babel" violates contract.\n\nExpected:\nPluginParams\n\nGot:\n' + _inspect(babel)); | ||
} | ||
if (!NodePath(path)) { | ||
throw new TypeError('Value of argument "path" violates contract.\n\nExpected:\nNodePath\n\nGot:\n' + _inspect(path)); | ||
} | ||
if (!PluginOptions(opts)) { | ||
throw new TypeError('Value of argument "opts" violates contract.\n\nExpected:\nPluginOptions\n\nGot:\n' + _inspect(opts)); | ||
} | ||
var t = babel.types; | ||
var label = path.get('label'); | ||
if (!NodePath(label)) { | ||
throw new TypeError('Value of variable "label" violates contract.\n\nExpected:\nNodePath\n\nGot:\n' + _inspect(label)); | ||
} | ||
opts = normalizeOpts(babel, opts); | ||
if (!opts.aliases[label.node.name]) { | ||
return; | ||
} | ||
var metadata = collectMetadata(path, opts); | ||
if (!Metadata(metadata)) { | ||
throw new TypeError('Value of variable "metadata" violates contract.\n\nExpected:\nMetadata\n\nGot:\n' + _inspect(metadata)); | ||
} | ||
if (shouldStrip(label.node.name, metadata, opts)) { | ||
path.remove(); | ||
return; | ||
} | ||
path.traverse({ | ||
"VariableDeclaration|Function|AssignmentExpression|UpdateExpression|YieldExpression|ReturnStatement": function VariableDeclarationFunctionAssignmentExpressionUpdateExpressionYieldExpressionReturnStatement(item) { | ||
if (!NodePath(item)) { | ||
throw new TypeError('Value of argument "item" violates contract.\n\nExpected:\nNodePath\n\nGot:\n' + _inspect(item)); | ||
} | ||
throw path.buildCodeFrameError('Logging statements cannot have side effects.'); | ||
}, | ||
ExpressionStatement: function ExpressionStatement(statement) { | ||
if (!NodePath(statement)) { | ||
throw new TypeError('Value of argument "statement" violates contract.\n\nExpected:\nNodePath\n\nGot:\n' + _inspect(statement)); | ||
} | ||
if (statement.node[$handled]) { | ||
return; | ||
} | ||
var message = { | ||
prefix: t.stringLiteral(metadata.prefix), | ||
content: statement.get('expression').node, | ||
hasStartMessage: t.booleanLiteral(metadata.hasStartMessage), | ||
isStartMessage: t.booleanLiteral(metadata.isStartMessage), | ||
indent: t.numericLiteral(metadata.indent), | ||
parentName: t.stringLiteral(metadata.parentName), | ||
filename: t.stringLiteral(metadata.filename), | ||
dirname: t.stringLiteral(metadata.dirname), | ||
basename: t.stringLiteral(metadata.basename), | ||
extname: t.stringLiteral(metadata.extname) | ||
}; | ||
if (!Message(message)) { | ||
throw new TypeError('Value of variable "message" violates contract.\n\nExpected:\nMessage\n\nGot:\n' + _inspect(message)); | ||
} | ||
var replacement = t.expressionStatement(opts.aliases[label.node.name](message, metadata)); | ||
replacement[$handled] = true; | ||
statement.replaceWith(replacement); | ||
} | ||
}); | ||
if (path.get('body').isBlockStatement()) { | ||
path.replaceWithMultiple(path.get('body').node.body); | ||
} else { | ||
path.replaceWith(path.get('body').node); | ||
} | ||
} | ||
/** | ||
* # Trace | ||
@@ -759,0 +854,0 @@ */ |
{ | ||
"name": "babel-plugin-trace", | ||
"version": "1.1.0", | ||
"version": "2.0.0-rc.1", | ||
"description": "Super convenient syntax for execution tracing, logging and debugging JavaScript applications via a babel plugin.", | ||
@@ -22,2 +22,3 @@ "main": "lib/index.js", | ||
"babel-plugin", | ||
"babel-plugin-macros", | ||
"logging", | ||
@@ -51,3 +52,6 @@ "trace", | ||
"should": "^6.0.1" | ||
}, | ||
"dependencies": { | ||
"babel-plugin-macros": "^2.2.1" | ||
} | ||
} |
# Babel Plugin: Trace | ||
This is a [Babel](https://babeljs.io/) plugin which adds a straightforward, declarative syntax for adding debug logging to JavaScript applications. | ||
This is a [Babel](https://babeljs.io/) plugin & macro which adds a straightforward, declarative syntax for adding debug logging to JavaScript applications. | ||
[](https://travis-ci.org/codemix/babel-plugin-trace) | ||
# What? | ||
## What? | ||
@@ -13,3 +13,3 @@ It's common to insert `console.log()` statements to help keep track of the internal state of functions when writing tricky pieces of code. During development this is very useful, but it creates a lot of noise in the console, and when development of that particular piece of code is complete, the developer is likely to delete the `console.log()` calls. If we're lucky, they might leave comments in their place. | ||
This plugin repurposes JavaScript LabeledStatements like `log:` and `trace:` to provide a logging / tracing syntax which can be selectively enabled or disabled at the folder, file, or function level at build time. Normally, these labels are only used as targets for labeled `break` and `continue` statements. | ||
This plugin repurposes JavaScript [LabeledStatements](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/label) like `log:` and `trace:` to provide a logging / tracing syntax which can be selectively enabled or disabled at the folder, file, or function level at build time. Normally, these labels are only used as targets for labeled `break` and `continue` statements. | ||
@@ -22,17 +22,17 @@ When disabled in production the logging statements are completely dropped out, incurring no overhead. The syntax looks like this: | ||
async function authenticate (username, password) { | ||
trace: 'authenticating user', username; | ||
log: 'authenticating user', username; | ||
const user = await db.select().from('users').where({username: username}); | ||
if (!user) { | ||
trace: 'no such user'; | ||
log: 'no such user'; | ||
return false; | ||
} | ||
else if (!user.checkPassword(password)) { | ||
trace: 'invalid password'; | ||
log: 'invalid password'; | ||
return false; | ||
} | ||
else if (!user.isActive) { | ||
trace: 'user is not active'; | ||
log: 'user is not active'; | ||
return false; | ||
} | ||
trace: 'logging user', username, 'into the site'; | ||
log: 'logging user', username, 'into the site'; | ||
return true; | ||
@@ -53,12 +53,14 @@ } | ||
As well as `trace:`, you can also use `log:` and `warn:`, or specify your own using the `aliases` plugin option. | ||
As well as `log:`, you can also use `trace:` and `warn:`, or specify your own using the `aliases` plugin option. By default all `trace:` logs will be stripped (unless specifically enabled) and `warn:` will use `console.warn` rather than `console.log`. | ||
## Installation | ||
# Installation & Configuration | ||
Install via [npm](https://npmjs.org/package/babel-plugin-trace). | ||
```sh | ||
``` | ||
npm install --save-dev babel-plugin-trace | ||
``` | ||
Then, in your babel configuration (usually in your `.babelrc` file), add `"trace"` to your list of plugins: | ||
## Plugin Configuration | ||
In your Babel configuration, add `"trace"` to your list of plugins. The default configuration will strip all logging from production builds, as well as trace logging from development and test environment, corresponding to this configuration: | ||
```json | ||
@@ -68,4 +70,6 @@ { | ||
["trace", { | ||
"env": { | ||
"production": { "strip": true } | ||
"strip": { | ||
"log": { "production": true }, | ||
"trace": true, | ||
"warn": { "production": true } | ||
} | ||
@@ -77,5 +81,3 @@ }] | ||
The above example configuration will remove all tracing when `NODE_ENV=production`. | ||
Alternatively, you may wish to disable all tracing by default, enabling it only for certain files or functions using environment variables: | ||
`"strip"` may also take a `true` value to disable all logging by default. In this case you can still enable it for certain files or functions using environment variables: | ||
```json | ||
@@ -89,5 +91,29 @@ { | ||
## Macro Configuration | ||
# Environment Variables | ||
As an alternative to use as a plugin, `babel-plugin-trace/macro` is provided for use together with [babel-plugin-macros](https://github.com/kentcdodds/babel-plugin-macros). This is relevant in particular if you're working with a [create-react-app](https://github.com/facebook/create-react-app) project, as that does not otherwise allow for Babel plugins to be used. With macro use, you'll need to import the macro in every file where you'd like to enable logging: | ||
```js | ||
import initTrace from 'babel-plugin-trace/macro' | ||
initTrace() | ||
log: 'This is', { a: 'message' } | ||
``` | ||
To customise the logging labels, import them as named imports of the macro (the default import is still required, as the labels don't really match the imported variable bindings): | ||
```js | ||
import initTrace, { log, announce } from 'babel-plugin-trace/macro' | ||
initTrace() | ||
announce: 'This is', { an: 'announcement' } | ||
``` | ||
Unfortunately the `initTrace` call is required until [kentcdodds/babel-plugin-macros#65](https://github.com/kentcdodds/babel-plugin-macros/pull/65) is merged, after which the API simplifies to: | ||
```js | ||
import 'babel-plugin-trace/macro' | ||
log: 'This is', { another: 'message' } | ||
``` | ||
## Environment Variables | ||
### `TRACE_LEVEL` - Enable only specific logging levels | ||
@@ -126,6 +152,5 @@ Log only `warn` statements. | ||
## License | ||
# License | ||
Published by [codemix](http://codemix.com/) under a permissive MIT License, see [LICENSE.md](./LICENSE.md). | ||
425
src/index.js
@@ -7,5 +7,7 @@ import fspath from "path"; | ||
type PluginTemplate = (source: string) => Template; | ||
type PluginParams = { | ||
types: Object; | ||
template: (source: string) => Template; | ||
template: PluginTemplate; | ||
}; | ||
@@ -17,5 +19,10 @@ | ||
}; | ||
strip?: boolean|string|{[key: string]: boolean}; | ||
strip?: boolean | { | ||
[key: string]: boolean | { [key: string]: boolean } | ||
}; | ||
}; | ||
type LogFunction = (message: Message, metadata: Metadata) => Node; | ||
type LogLevel = 'log' | 'warn' | 'error'; | ||
type Visitors = { | ||
@@ -78,64 +85,34 @@ [key: string]: Visitor | ||
const PRESERVE_CONTEXTS = normalizeEnv(process.env.TRACE_CONTEXT); | ||
const PRESERVE_FILES = normalizeEnv(process.env.TRACE_FILE); | ||
const PRESERVE_LEVELS = normalizeEnv(process.env.TRACE_LEVEL); | ||
/** | ||
* # Trace | ||
* Normalize an environment variable, used to override plugin options. | ||
*/ | ||
export default function ({types: t, template}: PluginParams): Plugin { | ||
const PRESERVE_CONTEXTS = normalizeEnv(process.env.TRACE_CONTEXT); | ||
const PRESERVE_FILES = normalizeEnv(process.env.TRACE_FILE); | ||
const PRESERVE_LEVELS = normalizeEnv(process.env.TRACE_LEVEL); | ||
/** | ||
* Normalize an environment variable, used to override plugin options. | ||
*/ | ||
function normalizeEnv (input: ?string): string[] { | ||
if (!input) { | ||
return []; | ||
} | ||
return input.split(/\s*,\s*/) | ||
.map(context => context.toLowerCase().trim()) | ||
.filter(id => id); | ||
function normalizeEnv (input: ?string): string[] { | ||
if (!input) { | ||
return []; | ||
} | ||
return input.split(',') | ||
.map(context => context.toLowerCase().trim()) | ||
.filter(id => id); | ||
} | ||
/** | ||
* Like `template()` but returns an expression, not an expression statement. | ||
*/ | ||
function expression (input: string): Template { | ||
const fn: Template = template(input); | ||
return function (ids: TemplateIds): Node { | ||
const node: Node = fn(ids); | ||
return node.expression ? node.expression : node; | ||
}; | ||
} | ||
/** | ||
* Like `template()` but returns an expression, not an expression statement. | ||
*/ | ||
function expression (input: string, template: PluginTemplate): Template { | ||
const fn: Template = template(input); | ||
return function (ids: TemplateIds): Node { | ||
const node: Node = fn(ids); | ||
return node.expression ? node.expression : node; | ||
}; | ||
} | ||
/** | ||
* Normalize the plugin options. | ||
*/ | ||
function normalizeOpts (opts: PluginOptions): PluginOptions { | ||
if (opts[$normalized]) { | ||
return opts; | ||
} | ||
if (!opts.aliases) { | ||
opts.aliases = { | ||
log: defaultLog, | ||
trace: defaultLog, | ||
warn: defaultLog | ||
}; | ||
} | ||
else { | ||
Object.keys(opts.aliases).forEach(key => { | ||
if (typeof opts.aliases[key] === 'string' && opts.aliases[key]) { | ||
const expr: ((message: Message) => Node) = expression(opts.aliases[key]); | ||
opts.aliases[key] = (message: Message): Node => expr(message); | ||
} | ||
}); | ||
} | ||
opts[$normalized] = true; | ||
return opts; | ||
} | ||
/** | ||
* The default log() function. | ||
*/ | ||
function defaultLog (message: Message, metadata: Metadata): Node { | ||
/** | ||
* The default log() function. | ||
*/ | ||
export function getLogFunction ({ types: t, template }: PluginParams, logLevel: LogLevel): LogFunction { | ||
return function log (message: Message, metadata: Metadata): Node { | ||
let prefix: string = `${metadata.context}:`; | ||
@@ -149,3 +126,3 @@ if (metadata.indent) { | ||
t.identifier('console'), | ||
t.identifier('log') | ||
t.identifier(logLevel) | ||
), | ||
@@ -156,3 +133,4 @@ [t.stringLiteral(prefix)].concat(message.content.expressions) | ||
else { | ||
return expression(`console.log(PREFIX, CONTENT)`)({ | ||
return expression(`console.LOGLEVEL(PREFIX, CONTENT)`, template)({ | ||
LOGLEVEL: t.identifier(logLevel), | ||
PREFIX: t.stringLiteral(prefix), | ||
@@ -163,179 +141,214 @@ CONTENT: message.content | ||
} | ||
} | ||
function generatePrefix (dirname: string, basename: string): string { | ||
if (basename !== 'index') { | ||
return basename; | ||
} | ||
basename = fspath.basename(dirname); | ||
if (basename !== 'src' && basename !== 'lib') { | ||
return basename; | ||
} | ||
/** | ||
* Normalize the plugin options. | ||
*/ | ||
function normalizeOpts (babel: PluginParams, opts: PluginOptions): PluginOptions { | ||
if (opts[$normalized]) { | ||
return opts; | ||
} | ||
if (!opts.aliases) { | ||
const log = getLogFunction(babel, 'log'); | ||
opts.aliases = { | ||
log: log, | ||
trace: log, | ||
warn: getLogFunction(babel, 'warn') | ||
}; | ||
} | ||
else { | ||
Object.keys(opts.aliases).forEach(key => { | ||
if (typeof opts.aliases[key] === 'string' && opts.aliases[key]) { | ||
const expr: ((message: Message) => Node) = expression(opts.aliases[key], babel.template); | ||
opts.aliases[key] = (message: Message): Node => expr(message); | ||
} | ||
}); | ||
} | ||
if (opts.strip === undefined) { | ||
opts.strip = { | ||
log: { production: true }, | ||
trace: true, | ||
warn: { production: true } | ||
}; | ||
} | ||
opts[$normalized] = true; | ||
return opts; | ||
} | ||
return fspath.basename(fspath.dirname(dirname)); | ||
function generatePrefix (dirname: string, basename: string): string { | ||
if (basename !== 'index') { | ||
return basename; | ||
} | ||
basename = fspath.basename(dirname); | ||
if (basename !== 'src' && basename !== 'lib') { | ||
return basename; | ||
} | ||
return fspath.basename(fspath.dirname(dirname)); | ||
} | ||
/** | ||
* Collect the metadata for a given node path, which will be | ||
* made available to logging functions. | ||
*/ | ||
function collectMetadata (path: NodePath, opts: PluginOptions): Metadata { | ||
const filename: string = fspath.resolve(process.cwd(), path.hub.file.opts.filename); | ||
const dirname: string = fspath.dirname(filename); | ||
const extname: string = fspath.extname(filename); | ||
const basename: string = fspath.basename(filename, extname); | ||
const prefix: string = generatePrefix(dirname, basename); | ||
const names: string[] = []; | ||
let indent: number = 0; | ||
let parent: ?NodePath; | ||
/** | ||
* Collect the metadata for a given node path, which will be | ||
* made available to logging functions. | ||
*/ | ||
function collectMetadata (path: NodePath, opts: PluginOptions): Metadata { | ||
const filename: string = fspath.resolve(process.cwd(), path.hub.file.opts.filename); | ||
const dirname: string = fspath.dirname(filename); | ||
const extname: string = fspath.extname(filename); | ||
const basename: string = fspath.basename(filename, extname); | ||
const prefix: string = generatePrefix(dirname, basename); | ||
const names: string[] = []; | ||
let indent: number = 0; | ||
let parent: ?NodePath; | ||
const parentName: string = path.getAncestry().slice(1).reduce((parts: string[], item: NodePath) => { | ||
if (item.isClassMethod()) { | ||
if (!parent) { | ||
parent = item; | ||
} | ||
parts.unshift(item.node.key.type === 'Identifier' ? item.node.key.name : '[computed method]'); | ||
const parentName: string = path.getAncestry().slice(1).reduce((parts: string[], item: NodePath) => { | ||
if (item.isClassMethod()) { | ||
if (!parent) { | ||
parent = item; | ||
} | ||
else if (item.isClassDeclaration()) { | ||
if (!parent) { | ||
parent = item; | ||
} | ||
parts.unshift(item.node.id ? item.node.id.name : `[anonymous class@${item.node.loc.start.line}]`); | ||
parts.unshift(item.node.key.type === 'Identifier' ? item.node.key.name : '[computed method]'); | ||
} | ||
else if (item.isClassDeclaration()) { | ||
if (!parent) { | ||
parent = item; | ||
} | ||
else if (item.isFunction()) { | ||
if (!parent) { | ||
parent = item; | ||
} | ||
parts.unshift((item.node.id && item.node.id.name) || `[anonymous@${item.node.loc.start.line}]`); | ||
parts.unshift(item.node.id ? item.node.id.name : `[anonymous class@${item.node.loc.start.line}]`); | ||
} | ||
else if (item.isFunction()) { | ||
if (!parent) { | ||
parent = item; | ||
} | ||
else if (item.isProgram()) { | ||
if (!parent) { | ||
parent = item; | ||
} | ||
parts.unshift((item.node.id && item.node.id.name) || `[anonymous@${item.node.loc.start.line}]`); | ||
} | ||
else if (item.isProgram()) { | ||
if (!parent) { | ||
parent = item; | ||
} | ||
else if (!parent && !item.isClassBody() && !item.isBlockStatement()) { | ||
indent++; | ||
} | ||
else if (!parent && !item.isClassBody() && !item.isBlockStatement()) { | ||
indent++; | ||
} | ||
return parts; | ||
}, []).join(':'); | ||
let hasStartMessage: boolean = false; | ||
let isStartMessage: boolean = false; | ||
if (parent && !parent.isProgram()) { | ||
for (let child: NodePath of parent.get('body').get('body')) { | ||
if (child.node[$handled]) { | ||
hasStartMessage = true; | ||
break; | ||
} | ||
return parts; | ||
}, []).join(':'); | ||
let hasStartMessage: boolean = false; | ||
let isStartMessage: boolean = false; | ||
if (parent && !parent.isProgram()) { | ||
for (let child: NodePath of parent.get('body').get('body')) { | ||
if (child.node[$handled]) { | ||
hasStartMessage = true; | ||
break; | ||
if (!child.isLabeledStatement()) { | ||
break; | ||
} | ||
const label: NodePath = child.get('label'); | ||
if (opts.aliases[label.node.name]) { | ||
hasStartMessage = true; | ||
if (child.node === path.node) { | ||
isStartMessage = true; | ||
} | ||
if (!child.isLabeledStatement()) { | ||
break; | ||
} | ||
const label: NodePath = child.get('label'); | ||
if (opts.aliases[label.node.name]) { | ||
hasStartMessage = true; | ||
if (child.node === path.node) { | ||
isStartMessage = true; | ||
} | ||
break; | ||
} | ||
break; | ||
} | ||
} | ||
const context: string = `${prefix}:${parentName}`; | ||
return {indent, prefix, parentName, context, hasStartMessage, isStartMessage, filename, dirname, basename, extname}; | ||
} | ||
const context: string = `${prefix}:${parentName}`; | ||
return {indent, prefix, parentName, context, hasStartMessage, isStartMessage, filename, dirname, basename, extname}; | ||
} | ||
/** | ||
* Determine whether the given logging statement should be stripped. | ||
*/ | ||
function shouldStrip (name: string, metadata: Metadata, opts: PluginOptions): boolean { | ||
if (!opts.strip) { | ||
/** | ||
* Determine whether the given logging statement should be stripped. | ||
*/ | ||
function shouldStrip (name: string, metadata: Metadata, { strip }: PluginOptions): boolean { | ||
switch (typeof strip) { | ||
case 'boolean': | ||
if (!strip) return false; | ||
// strip === true | ||
break; | ||
case 'object': | ||
const se = strip[name]; | ||
if (!se || (typeof se === 'object' && !se[process.env.NODE_ENV])) return false; | ||
// strip[name] === true || strip[name][env] === true | ||
break; | ||
default: | ||
return false; | ||
} | ||
else if (opts.strip === true) { | ||
return !hasStripOverride(name, metadata); | ||
} | ||
else if (typeof opts.strip === 'string') { | ||
if (opts.strip === process.env.NODE_ENV) { | ||
return !hasStripOverride(name, metadata); | ||
} | ||
} | ||
else if (opts.strip[process.env.NODE_ENV]) { | ||
return !hasStripOverride(name, metadata); | ||
} | ||
return true; | ||
} | ||
if (PRESERVE_CONTEXTS.length) { | ||
const context = metadata.context.toLowerCase(); | ||
if (PRESERVE_CONTEXTS.some(pc => context.includes(pc))) return false; | ||
} | ||
if (PRESERVE_FILES.length) { | ||
const filename = metadata.filename.toLowerCase(); | ||
if (PRESERVE_FILES.some(pf => filename.includes(pf))) return false; | ||
} | ||
if (PRESERVE_LEVELS.length) { | ||
const level = name.toLowerCase(); | ||
if (PRESERVE_LEVELS.some(pl => level === pl)) return false; | ||
} | ||
return true; | ||
} | ||
function hasStripOverride (name: string, metadata: Metadata) { | ||
if (PRESERVE_CONTEXTS.length && PRESERVE_CONTEXTS.some(context => metadata.context.toLowerCase().indexOf(context) !== -1)) { | ||
return true; | ||
} | ||
else if (PRESERVE_FILES.length && PRESERVE_FILES.some(filename => metadata.filename.toLowerCase().indexOf(filename) !== -1)) { | ||
return true; | ||
} | ||
else if (PRESERVE_LEVELS.length && PRESERVE_LEVELS.some(level => level === name.toLowerCase())) { | ||
return true; | ||
} | ||
else { | ||
return false; | ||
} | ||
export function handleLabeledStatement (babel: PluginParams, path: NodePath, opts: PluginOptions): void { | ||
const t = babel.types; | ||
const label: NodePath = path.get('label'); | ||
opts = normalizeOpts(babel, opts); | ||
if (!opts.aliases[label.node.name]) { | ||
return; | ||
} | ||
const metadata: Metadata = collectMetadata(path, opts); | ||
if (shouldStrip(label.node.name, metadata, opts)) { | ||
path.remove(); | ||
return; | ||
} | ||
path.traverse({ | ||
"VariableDeclaration|Function|AssignmentExpression|UpdateExpression|YieldExpression|ReturnStatement" (item: NodePath): void { | ||
throw path.buildCodeFrameError(`Logging statements cannot have side effects.`); | ||
}, | ||
ExpressionStatement (statement: NodePath): void { | ||
if (statement.node[$handled]) { | ||
return; | ||
} | ||
const message: Message = { | ||
prefix: t.stringLiteral(metadata.prefix), | ||
content: statement.get('expression').node, | ||
hasStartMessage: t.booleanLiteral(metadata.hasStartMessage), | ||
isStartMessage: t.booleanLiteral(metadata.isStartMessage), | ||
indent: t.numericLiteral(metadata.indent), | ||
parentName: t.stringLiteral(metadata.parentName), | ||
filename: t.stringLiteral(metadata.filename), | ||
dirname: t.stringLiteral(metadata.dirname), | ||
basename: t.stringLiteral(metadata.basename), | ||
extname: t.stringLiteral(metadata.extname) | ||
}; | ||
const replacement = t.expressionStatement(opts.aliases[label.node.name](message, metadata)); | ||
replacement[$handled] = true; | ||
statement.replaceWith(replacement); | ||
} | ||
}); | ||
if (path.get('body').isBlockStatement()) { | ||
path.replaceWithMultiple(path.get('body').node.body); | ||
} | ||
else { | ||
path.replaceWith(path.get('body').node); | ||
} | ||
} | ||
/** | ||
* # Trace | ||
*/ | ||
export default function (babel: PluginParams): Plugin { | ||
return { | ||
visitor: { | ||
Program (program: NodePath, {opts}) { | ||
Program (program: NodePath, { opts }) { | ||
program.traverse({ | ||
LabeledStatement (path: NodePath): void { | ||
const label: NodePath = path.get('label'); | ||
opts = normalizeOpts(opts); | ||
if (!opts.aliases[label.node.name]) { | ||
return; | ||
} | ||
const metadata: Metadata = collectMetadata(path, opts); | ||
if (shouldStrip(label.node.name, metadata, opts)) { | ||
path.remove(); | ||
return; | ||
} | ||
path.traverse({ | ||
"VariableDeclaration|Function|AssignmentExpression|UpdateExpression|YieldExpression|ReturnStatement" (item: NodePath): void { | ||
throw path.buildCodeFrameError(`Logging statements cannot have side effects.`); | ||
}, | ||
ExpressionStatement (statement: NodePath): void { | ||
if (statement.node[$handled]) { | ||
return; | ||
} | ||
const message: Message = { | ||
prefix: t.stringLiteral(metadata.prefix), | ||
content: statement.get('expression').node, | ||
hasStartMessage: t.booleanLiteral(metadata.hasStartMessage), | ||
isStartMessage: t.booleanLiteral(metadata.isStartMessage), | ||
indent: t.numericLiteral(metadata.indent), | ||
parentName: t.stringLiteral(metadata.parentName), | ||
filename: t.stringLiteral(metadata.filename), | ||
dirname: t.stringLiteral(metadata.dirname), | ||
basename: t.stringLiteral(metadata.basename), | ||
extname: t.stringLiteral(metadata.extname) | ||
}; | ||
const replacement = t.expressionStatement(opts.aliases[label.node.name](message, metadata)); | ||
replacement[$handled] = true; | ||
statement.replaceWith(replacement); | ||
} | ||
}); | ||
if (path.get('body').isBlockStatement()) { | ||
path.replaceWithMultiple(path.get('body').node.body); | ||
} | ||
else { | ||
path.replaceWith(path.get('body').node); | ||
} | ||
handleLabeledStatement(babel, path, opts); | ||
} | ||
}); | ||
} | ||
} | ||
}; | ||
} |
class Thing { | ||
foo () { | ||
trace: "foo"; | ||
log: "foo"; | ||
if (true) { | ||
trace: "bar"; | ||
log: "bar"; | ||
if (true) { | ||
trace: "qux"; | ||
log: "qux"; | ||
(() => { | ||
trace: "nested"; | ||
warn: "nested"; | ||
})(); | ||
@@ -19,2 +19,2 @@ } | ||
thing.foo(); | ||
} | ||
} |
export default function demo () { | ||
trace: "hello world"; | ||
log: "hello world"; | ||
if (Math.random() > 0.5) { | ||
trace: "Got result."; | ||
log: "Got result."; | ||
} | ||
else { | ||
trace: "Got result." | ||
log: "Got result." | ||
} | ||
} | ||
} |
export default function demo () { | ||
trace: { | ||
log: { | ||
"hello world"; | ||
"foo bar"; | ||
} | ||
} | ||
} |
export default function demo () { | ||
trace: "hello world", "foo", "bar"; | ||
} | ||
log: "hello world", "foo", "bar"; | ||
} |
export default function demo () { | ||
trace: "hello world"; | ||
} | ||
log: "hello world"; | ||
} |
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
51523
16
1278
150
13
1
1
1
+ Addedbabel-plugin-macros@^2.2.1
+ Added@babel/code-frame@7.26.2(transitive)
+ Added@babel/helper-validator-identifier@7.25.9(transitive)
+ Added@babel/runtime@7.26.9(transitive)
+ Added@types/parse-json@4.0.2(transitive)
+ Addedbabel-plugin-macros@2.8.0(transitive)
+ Addedcallsites@3.1.0(transitive)
+ Addedcosmiconfig@6.0.0(transitive)
+ Addederror-ex@1.3.2(transitive)
+ Addedfunction-bind@1.1.2(transitive)
+ Addedhasown@2.0.2(transitive)
+ Addedimport-fresh@3.3.1(transitive)
+ Addedis-arrayish@0.2.1(transitive)
+ Addedis-core-module@2.16.1(transitive)
+ Addedjs-tokens@4.0.0(transitive)
+ Addedjson-parse-even-better-errors@2.3.1(transitive)
+ Addedlines-and-columns@1.2.4(transitive)
+ Addedparent-module@1.0.1(transitive)
+ Addedparse-json@5.2.0(transitive)
+ Addedpath-parse@1.0.7(transitive)
+ Addedpath-type@4.0.0(transitive)
+ Addedpicocolors@1.1.1(transitive)
+ Addedregenerator-runtime@0.14.1(transitive)
+ Addedresolve@1.22.10(transitive)
+ Addedresolve-from@4.0.0(transitive)
+ Addedsupports-preserve-symlinks-flag@1.0.0(transitive)
+ Addedyaml@1.10.2(transitive)