diff --git a/lib/handlebars/helpers/log.js b/lib/handlebars/helpers/log.js index 799ec6b2f..7709a371f 100644 --- a/lib/handlebars/helpers/log.js +++ b/lib/handlebars/helpers/log.js @@ -14,6 +14,21 @@ export default function (instance) { } args[0] = level; + // Only add depth to args if it's explicitly provided via hash + // (not from options.data.depth, which is reserved for template nesting depth) + let depth; + if (options.hash.depth != null) { + depth = options.hash.depth; + } + if (depth != null) { + // Normalize depth to a number to avoid type/ordering ambiguity in logger + let numericDepth = typeof depth === 'number' ? depth : Number(depth); + if (!Number.isNaN(numericDepth)) { + // Use a sentinel object to pass depth to avoid ambiguity with numeric messages + args.splice(1, 0, { __handlebarsLoggerDepth: numericDepth }); + } + } + instance.log(...args); }); } diff --git a/lib/handlebars/logger.js b/lib/handlebars/logger.js index a7022ef04..f5e4278ae 100644 --- a/lib/handlebars/logger.js +++ b/lib/handlebars/logger.js @@ -3,6 +3,7 @@ import { indexOf } from './utils'; let logger = { methodMap: ['debug', 'info', 'warn', 'error'], level: 'info', + depth: 2, // Maps a given level value to the `methodMap` indexes above. lookupLevel: function (level) { @@ -18,10 +19,34 @@ let logger = { return level; }, + convertDepth: function (depth) { + depth = parseInt(depth, 10); + if (isNaN(depth) || depth < 0) { + depth = logger.depth; + } + + return depth; + }, + // Can be overridden in the host environment log: function (level, ...message) { level = logger.lookupLevel(level); + // Determine depth from an explicit sentinel/options object, if provided. + let depth = logger.depth; + if ( + message.length > 0 && + message[0] != null && + typeof message[0] === 'object' && + Object.prototype.hasOwnProperty.call( + message[0], + '__handlebarsLoggerDepth' + ) + ) { + depth = logger.convertDepth(message[0].__handlebarsLoggerDepth); + message = message.slice(1); + } + if ( typeof console !== 'undefined' && logger.lookupLevel(logger.level) <= level @@ -31,6 +56,36 @@ let logger = { if (!console[method]) { method = 'log'; } + + const isNodeEnvironment = + typeof process !== 'undefined' && + process.versions && // eslint-disable-line no-undef + process.versions.node; // eslint-disable-line no-undef + if (isNodeEnvironment && depth !== 2) { + let formatWithOptions; + if (typeof require === 'function') { + try { + formatWithOptions = require('util').formatWithOptions; + } catch (e) { + // util module not available, fallback to default behavior + } + } + if (typeof formatWithOptions === 'function') { + const supportsColor = + typeof process !== 'undefined' && + process.stdout && // eslint-disable-line no-undef + process.stdout.isTTY; // eslint-disable-line no-undef + const formatted = formatWithOptions( + { + depth: depth, + colors: supportsColor, + }, + ...message + ); + message = [formatted]; + } + } + console[method](...message); // eslint-disable-line no-console } }, diff --git a/spec/builtins.js b/spec/builtins.js index e88327c4c..ccea4b16b 100644 --- a/spec/builtins.js +++ b/spec/builtins.js @@ -793,6 +793,97 @@ describe('builtin helpers', function () { expectTemplate('{{log}}').withInput({ blah: 'whee' }).toCompileTo(''); expect(called).to.be.true(); }); + + it('should handle hash depth parameter', function () { + var levelArg, depthArg, logArg; + handlebarsEnv.log = function (level, ...args) { + levelArg = level; + // Check if the first arg is a sentinel object with depth + if ( + args[0] != null && + typeof args[0] === 'object' && + args[0].__handlebarsLoggerDepth !== undefined + ) { + depthArg = args[0].__handlebarsLoggerDepth; + logArg = args[1]; + } else { + logArg = args[0]; + } + }; + + expectTemplate('{{log blah depth=5}}') + .withInput({ blah: 'whee' }) + .toCompileTo(''); + equals(1, levelArg); + equals(5, depthArg); + equals('whee', logArg); + }); + + it('should handle both level and depth parameters', function () { + var levelArg, depthArg, logArg; + handlebarsEnv.log = function (level, ...args) { + levelArg = level; + // Check if the first arg is a sentinel object with depth + if ( + args[0] != null && + typeof args[0] === 'object' && + args[0].__handlebarsLoggerDepth !== undefined + ) { + depthArg = args[0].__handlebarsLoggerDepth; + logArg = args[1]; + } else { + logArg = args[0]; + } + }; + + expectTemplate('{{log blah level="error" depth=10}}') + .withInput({ blah: 'whee' }) + .toCompileTo(''); + equals('error', levelArg); + equals(10, depthArg); + equals('whee', logArg); + }); + + it('should log numeric messages correctly', function () { + var called; + + console.info = console.log = function (log) { + equals(1, log); + called = true; + console.log = $log; + }; + + expectTemplate('{{log 1}}').withInput({}).toCompileTo(''); + equals(true, called); + }); + + it('should log zero as a message', function () { + var called; + + console.info = console.log = function (log) { + equals(0, log); + called = true; + console.log = $log; + }; + + expectTemplate('{{log 0}}').withInput({}).toCompileTo(''); + equals(true, called); + }); + + it('should log multiple numeric messages', function () { + var called; + + console.info = console.log = function (log1, log2, log3) { + equals(1, log1); + equals(2, log2); + equals(3, log3); + called = true; + console.log = $log; + }; + + expectTemplate('{{log 1 2 3}}').withInput({}).toCompileTo(''); + equals(true, called); + }); /* eslint-enable no-console */ }); diff --git a/types/index.d.ts b/types/index.d.ts index 00b1381ab..7c7556f43 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -262,10 +262,11 @@ export interface Logger { WARN: number; ERROR: number; level: number; + depth: number; methodMap: { [level: number]: string }; - log(level: number, obj: string): void; + log(level: number, ...message: any[]): void; } export type CompilerInfo = [number/* revision */, string /* versions */];