Codemirror- linting - Is there a event to trigger linting explictly? - javascript

I am designing an application using CodeMirror that comes with toolbar. Because of performance reason, I am not executing the lint to either through async or asynch mode.
I have provided an icon in the toolbar, on clicking this I am doing the parsing and constructing the errors. But I am stuck with how I can update lint error in editor?
Any pointer would be really helpful.

The lint addon adds an "extension" method to all Editor instances called performLint, combine with editor options of lint: { lintOnChange: false }, and then you can invoke the linter by calling mirror.performLint().
If you define your own lint methods ala
CodeMirror.registerHelper('lint', 'mode', (text) => { /* your cool stuff here */ })
that'll get invoked on performLint().

Have you tried setting the lint value dynamically using the below code?
//enable
editor.setOption("lint",true);
//disable
editor.setOption("lint",false);
You can see a demo here JSFiddle link

To trigger lint in one line:
// trigger registered lint handler
editor.setOption("lint", {});
// trigger custom lint handler defined on the fly
editor.setOption("lint", {
getAnnotations: function() { /* some smart code */ return found; }
});
If you wonder why it should ever work, have look into addon/lint/lint.js, specially the following lines:
CodeMirror.defineOption("lint", false, function(cm, val, old) {
...
if (val) {
...
startLinting(cm);
}
});

It sounds like a good feature that doesn't appear to exist.
It is straighforward to create a button to launch a validation from an external button.
For example [no credit] https://jsfiddle.net/q43drhyk/
function checkFormat(editor) {
var success = JSHINT(editor.getValue());
var output = '';
if (!success) {
output = "Check format error:\n\n";
for (var i in JSHINT.errors) {
var err = JSHINT.errors[i];
if (null != err) {
output += err.line + '[' + err.character + ']: ' + err.reason + '\n';
} else {
output += "Check format unknown error:\n";
}
}
alert(output);
}
return success;
}
However this does not render the validation messages in the CodeMirror editor to display in the linenumber gutter, which is what the OP was looking for.
To do that, you could customise the lint add-on code.
For example from the json-lint provided in the standard addons
[https://github.com/codemirror/CodeMirror/blob/master/addon/lint/json-lint.js]
1.Extract the body of registerHelper() into a new function that reproduces the existing function:
CodeMirror.registerHelper("lint", "json", function(text) {
return mylinthandler(text)
}
mylinthandler(text) {
var found = [];
jsonlint.parseError = function(str, hash) {
var loc = hash.loc;
found.push({from: CodeMirror.Pos(loc.first_line - 1, loc.first_column),
to: CodeMirror.Pos(loc.last_line - 1, loc.last_column),
message: str});
};
try { jsonlint.parse(text); }
catch(e) {}
return found;
}
Turn off the auto-lint CodeMirror options with.
lintOnChange: false
Then when you wish to lint, call
mylinthandler(editor.getValue())

Related

ESLint no-undef - js issue with BigCommerce

I've got this code in a .js file that I'm running through ESLint. But it's throwing an error about this line: iFrameResize({.
Saying: error 'iFrameResize' is not defined no-undef.
If I define it like this: const iFrameResize()
My code no longer works, how do I make ESLint happy and keep the code working?
export default class Page extends PageManager {
before(next) {
next();
}
loaded(next) {
next();
}
after(next) {
const url = Url.parse(location.href, true);
const IC_PAGE = '/international-checkout';
const currentPageUrl = url.pathname;
if (currentPageUrl.indexOf(IC_PAGE) !== -1 && $('#icForm').length === 1) {
$(document).ready(() => {
if ($('#icForm').length === 1) {
if ($('.GiftStatus') && $('.GiftStatus').val() === '1') {
alert('Gift Certificate is not available for international orders. Please remove Gift Certificate from shopping cart before proceeding with International Checkout.');
window.parent.location.href = '/cart.php';
return false;
}
$('.icformfields').each((i, e) => {
const currentId = $(e).attr('id');
const res = currentId.split('-');
const finalId = Number(res[1]) + 1;
const finalName = $(e).attr('name') + finalId;
$(e.currentTarget).attr('name', finalName);
});
document.getElementById('icIframe').src = 'https://www.internationalcheckout.com/cart.php';
document.getElementById('icForm').submit();
$('#icIframe').load(() => {
$('#icForm').remove();
$('#loading').css('display', 'none');
$('html, body').animate({
scrollTop: $('#icIframe').offset().top,
}, 1000);
$('#icIframe').fadeIn();
});
}
});
iFrameResize({
checkOrigin: false,
enablePublicMethods: true,
});
}
next();
}
}
I want to know how to satisfy ESLint without disabling error reporting for the particular line.
It's also worth noting that eslint provides multiple ways around this.
Please see the eslint docs.
I would recommend adding the following to the top of your file. Use this method to define global dependencies that are only used in a couple of places:
/* global iFrameResize */
You can also provide an array:
/* global iFrameResize, iFrameManage, etc */
If you're using iFrameResize a lot or if you're dependent on something like jQuery, consider defining it as a global in your .eslintrc file.
"globals": {
"iFrameManage": true,
}
if you are sure that the code is working on iFrameResize() and maybe it's the because of the architecture you've setup with js files you might just want to ignore that error. simplest is
// eslint-disable-line
which disables esilnt for that line.
Since this function definition comes from the library that is probably attaching it to the global scope which is window so calling it from that scope does the trick
window.iFrameResizer()
Now eslint understands that you're calling function that resides at window object, so it doesn't complain

Set Bluetooth Listener on Tizen Wearable

I'm using Tizen's Wearable SDK to create a watch face, and while I am able to retrieve the Bluetooth's power state by using blueAdapter = tizen.bluetooth.getDefaultAdapter(); and bluetoothPowered = blueAdapter.powered;, I'd rather use a Bluetooth listener instead of constantly calling blueAdapter.powered.
The problem I'm having is that although I did a straight forward copy and paste of the sample code (although I did change the adapter's name, see below) from Tizen's Bluetooth API webpage, I'm getting the following error: 'undefined' is not a function (evaluating 'blueAdapter.setChangeListener(changeListener)').
This is the code I am using:
var blueAdapter = tizen.bluetooth.getDefaultAdapter();
var changeListener = {
onstatechanged: function(powered) {
console.log ("Power state is changed into: " + powered);
},
onnamechanged: function( name) {
console.log("Name is changed to: " + name);
},
onvisibilitychanged: function(visible) {
console.log("Visibility is changed into: " + visible);
}
};
blueAdapter.setChangeListener(changeListener);
Not sure if it helps, but I am using the following the privileges:
http://tizen.org/privilege/bluetooth.admin
http://tizen.org/privilege/bluetooth.gap
Any help would be appreciated.
Although it's been a while now, have you tried changing the syntax like below?
function changeListener(handler) {
handler.onstatechanged = function(powered) {
console.log ("Power state is changed into: " + powered);
}
handler.onnamechanged = function(name) {
console.log("Name is changed to: " + name);
}
handler.onvisibilitychanged = function(visible) {
console.log("Visibility is changed into: " + visible);
}
};
this replaces your listener object by a function, which should then declare the needed functions in the listener. It is just an idea, i did not test it yet.

AngularJS - Stack trace ignoring source map

I've written an AngularJS app but it's proving a bit of a nightmare to debug. I'm using Grunt + uglify to concatenate and minify my application code. It also creates a source map alongside the minified JS file.
The source map seems to work properly when there is a JS error in the file, but outside of the AngularJS application. e.g. If I write console.log('a.b'); at the top of one of the files, the error logged in the Chrome debugger displays line + file info for the original file, not the minified one.
The problem occurs when there is a problem with code that Angular runs itself (e.g. in Controller code). I get a nice stack trace from Angular, but it only details the minified file not the original.
Is there anything I can do to get Angular to acknowledge the source map?
Example error below:
TypeError: Cannot call method 'getElement' of undefined
at Object.addMapControls (http://my-site/wp-content/plugins/my-maps/assets/js/app.min.js:1:2848)
at Object.g [as init] (http://my-site/wp-content/plugins/my-maps/assets/js/app.min.js:1:344)
at new a (http://my-site/wp-content/plugins/my-maps/assets/js/app.min.js:1:591)
at d (http://ajax.googleapis.com/ajax/libs/angularjs/1.2.0-rc.2/angular.min.js:29:495)
at Object.instantiate (http://ajax.googleapis.com/ajax/libs/angularjs/1.2.0-rc.2/angular.min.js:30:123)
Larrifax's answer is good but there is an improved version of the function documented in the same issue report:
.config(function($provide) {
// Fix sourcemaps
// #url https://github.com/angular/angular.js/issues/5217#issuecomment-50993513
$provide.decorator('$exceptionHandler', function($delegate) {
return function(exception, cause) {
$delegate(exception, cause);
setTimeout(function() {
throw exception;
});
};
});
})
This will generate two stack traces, as Andrew Magee noted: one formatted by Angular, then a second one formatted by the browser. The second trace will apply sourcemaps. It's probably not a great idea to disable the duplicates, because you may have other Angular modules that also do work with exceptions that could be called after this via the delegation.
The only solution I could find is to bite the bullet and parse the source maps yourself. Here is some code that will do this. First you need to add source-map to your page. Then add this code:
angular.module('Shared').factory('$exceptionHandler',
function($log, $window, $injector) {
var getSourceMappedStackTrace = function(exception) {
var $q = $injector.get('$q'),
$http = $injector.get('$http'),
SMConsumer = window.sourceMap.SourceMapConsumer,
cache = {};
// Retrieve a SourceMap object for a minified script URL
var getMapForScript = function(url) {
if (cache[url]) {
return cache[url];
} else {
var promise = $http.get(url).then(function(response) {
var m = response.data.match(/\/\/# sourceMappingURL=(.+\.map)/);
if (m) {
var path = url.match(/^(.+)\/[^/]+$/);
path = path && path[1];
return $http.get(path + '/' + m[1]).then(function(response) {
return new SMConsumer(response.data);
});
} else {
return $q.reject();
}
});
cache[url] = promise;
return promise;
}
};
if (exception.stack) { // not all browsers support stack traces
return $q.all(_.map(exception.stack.split(/\n/), function(stackLine) {
var match = stackLine.match(/^(.+)(http.+):(\d+):(\d+)/);
if (match) {
var prefix = match[1], url = match[2], line = match[3], col = match[4];
return getMapForScript(url).then(function(map) {
var pos = map.originalPositionFor({
line: parseInt(line, 10),
column: parseInt(col, 10)
});
var mangledName = prefix.match(/\s*(at)?\s*(.*?)\s*(\(|#)/);
mangledName = (mangledName && mangledName[2]) || '';
return ' at ' + (pos.name ? pos.name : mangledName) + ' ' +
$window.location.origin + pos.source + ':' + pos.line + ':' +
pos.column;
}, function() {
return stackLine;
});
} else {
return $q.when(stackLine);
}
})).then(function (lines) {
return lines.join('\n');
});
} else {
return $q.when('');
}
};
return function(exception) {
getSourceMappedStackTrace(exception).then($log.error);
};
});
This code will download the source, then download the sourcemaps, parse them, and finally attempt to replace the locations in the stack trace the mapped locations. This works perfectly in Chrome, and quite acceptably in Firefox. The disadvantage is that you are adding a fairly large dependency to your code base and that you move from very fast synchronous error reporting to fairly slow async error reporting.
I just had the same issue and have been hunting around for a solution - apparently it's a Chrome issue with stack traces in general and happens to apply to Angular because it uses stack traces in error reporting. See:
Will the source mapping in Google Chrome push to Error.stack
I would take a look at the following project: https://github.com/novocaine/sourcemapped-stacktrace
It does essentially the same thing as the answer from #jakub-hampl but might be useful.
According to this issue it seems that Angular's $logProvider breaks sourcemapping. A workaround like this is suggested in the issue:
var module = angular.module('source-map-exception-handler', [])
module.config(function($provide) {
$provide.decorator('$exceptionHandler', function($delegate) {
return function(exception, cause) {
$delegate(exception, cause);
throw exception;
};
});
});
As the bug has been fixed in Chrome (but the issue persists in Angular), a workaround that doesn’t print out the stack trace twice would be this:
app.factory('$exceptionHandler', function() {
return function(exception, cause) {
console.error(exception.stack);
};
});

Hot Code Push NodeJS

I've been trying to figure out this "Hot Code Push" on Node.js. Basically, my main file (that is run when you type node app.js) consists of some settings, configurations, and initializations. In that file I have a file watcher, using chokidar. When I file has been added, I simply require the file. If a file has been changed or updated I would delete the cache delete require.cache[path] and then re-require it. All these modules don't export anything, it just works with the single global Storm object.
Storm.watch = function() {
var chokidar, directories, self = this;
chokidar = require('chokidar');
directories = ['server/', 'app/server', 'app/server/config', 'public'];
clientPath = new RegExp(_.regexpEscape(path.join('app', 'client')));
watcher = chokidar.watch(directories, {
ignored: function(_path) {
if (_path.match(/\./)) {
!_path.match(/\.(js|coffee|iced|styl)$/);
} else {
!_path.match(/(app|config|public)/);
}
},
persistent: true
});
watcher.on('add', function(_path){
self.fileCreated(path.resolve(Storm.root, _path));
//Storm.logger.log(Storm.cliColor.green("File Added: ", _path));
//_console.info("File Updated");
console.log(Storm.css.compile(' {name}: {file}', "" +
"name" +
"{" +
"color: white;" +
"font-weight:bold;" +
"}" +
"hr {" +
"background: grey" +
"}")({name: "File Added", file: _path.replace(Storm.root, ""), hr: "=================================================="}));
});
watcher.on('change', function(_path){
_path = path.resolve(Storm.root, _path);
if (fs.existsSync(_path)) {
if (_path.match(/\.styl$/)) {
self.clientFileUpdated(_path);
} else {
self.fileUpdated(_path);
}
} else {
self.fileDeleted(_path);
}
//Storm.logger.log(Storm.cliColor.green("File Changed: ", _path));
console.log(Storm.css.compile(' {name}: {file}', "" +
"name" +
"{" +
"color: yellow;" +
"font-weight:bold;" +
"}" +
"hr {" +
"background: grey" +
"}")({name: "File Changed", file: _path.replace(Storm.root, ""), hr: "=================================================="}));
});
watcher.on('unlink', function(_path){
self.fileDeleted(path.resolve(Storm.root, _path));
//Storm.logger.log(Storm.cliColor.green("File Deleted: ", _path));
console.log(Storm.css.compile(' {name}: {file}', "" +
"name" +
"{" +
"color: red;" +
"font-weight:bold;" +
"}" +
"hr {" +
"background: grey" +
"}")({name: "File Deleted", file: _path.replace(Storm.root, ""), hr: "=================================================="}));
});
watcher.on('error', function(error){
console.log(error);
});
};
Storm.watch.prototype.fileCreated = function(_path) {
if (_path.match('views')) {
return;
}
try {
require.resolve(_path);
} catch (error) {
require(_path);
}
};
Storm.watch.prototype.fileDeleted = function(_path) {
delete require.cache[require.resolve(_path)];
};
Storm.watch.prototype.fileUpdated = function(_path) {
var self = this;
pattern = function(string) {
return new RegExp(_.regexpEscape(string));
};
if (_path.match(pattern(path.join('app', 'templates')))) {
Storm.View.cache = {};
} else if (_path.match(pattern(path.join('app', 'helpers')))) {
self.reloadPath(path, function(){
self.reloadPaths(path.join(Storm.root, 'app', 'controllers'));
});
} else if (_path.match(pattern(path.join('config', 'assets.coffee')))) {
self.reloadPath(_path, function(error, config) {
//Storm.config.assets = config || {};
});
} else if (_path.match(/app\/server\/(models|controllers)\/.+\.(?:coffee|js|iced)/)) {
var isController, directory, klassName, klass;
self.reloadPath(_path, function(error, config) {
if (error) {
throw new Error(error);
}
});
Storm.serverRefresh();
isController = RegExp.$1 == 'controllers';
directory = 'app/' + RegExp.$1;
klassName = _path.split('/');
klassName = klassName[klassName.length - 1];
klassName = klassName.split('.');
klassName.pop();
klassName = klassName.join('.');
klassName = _.camelize(klassName);
if (!klass) {
require(_path);
} else {
console.log(_path);
self.reloadPath(_path)
}
} else if (_path.match(/config\/routes\.(?:coffee|js|iced)/)) {
self.reloadPath(_path);
} else {
this.reloadPath(_path);
}
};
Storm.watch.prototype.reloadPath = function(_path, cb) {
_path = require.resolve(path.resolve(Storm.root, path.relative(Storm.root, _path)));
delete require.cache[_path];
delete require.cache[path.resolve(path.join(Storm.root, "server", "application", "server.js"))];
//console.log(require.cache[path.resolve(path.join(Storm.root, "server", "application", "server.js"))]);
require("./server.js");
Storm.App.use(Storm.router);
process.nextTick(function(){
Storm.serverRefresh();
var result = require(_path);
if (cb) {
cb(null, result);
}
});
};
Storm.watch.prototype.reloadPaths = function(directory, cb) {
};
Some of the code is incomplete / not used as I'm trying a lot of different methods.
What's Working:
For code like the following:
function run() {
console.log(123);
}
Works perfectly. But any asynchronous code fails to update.
Problem = Asynchronous Code
app.get('/', function(req, res){
// code here..
});
If I then update the file when the nodejs process is running, nothing happens, though it goes through the file watcher and the cache is deleted, then re-established. Another instance where it doesn't work is:
// middleware.js
function hello(req, res, next) {
// code here...
}
// another file:
app.use(hello);
As app.use would still be using the old version of that method.
Question:
How could I fix the problem? Is there something I'm missing?
Please don't throw suggestions to use 3rd party modules like forever. I'm trying to incorporate the functionality within the single instance.
EDIT:
After studying meteors codebase (there's surprisingly little resources on "Hot Code Push" in node.js or browser.) and tinkering around with my own implementation I've successfully made a working solution. https://github.com/TheHydroImpulse/Refresh.js . This is still at an early stage of development, but it seems solid right now. I'll be implementing a browser solution too, just for sake of completion.
Deleting require's cache doesn't actually "unload" your old code, nor does it undo what that code did.
Take for example the following function:
var callbacks=[];
registerCallback = function(cb) {
callbacks.push(cb);
};
Now let's say you have a module that calls that global function.
registerCallback(function() { console.log('foo'); });
After your app starts up, callbacks will have one item. Now we'll modify the module.
registerCallback(function() { console.log('bar'); });
Your 'hot patching' code runs, deletes the require.cached version and re-loads the module.
What you must realize is that now callbacks has two items. First, it has a reference to the function that logs foo (which was added on app startup) and a reference to the function that logs bar (which was just added).
Even though you deleted the cached reference to the module's exports, you can't actually delete the module. As far as the JavaScript runtime is concerned, you simply removed one reference out of many. Any other part of your application can still be hanging on to a reference to something in the old module.
This is exactly what is happening with your HTTP app. When the app first starts up, your modules attach anonymous callbacks to routes. When you modify those modules, they attach a new callback to the same routes; the old callbacks are not deleted. I'm guessing that you're using Express, and it calls route handlers in the order they were added. Thus, the new callback never gets a chance to run.
To be honest, I wouldn't use this approach to reloading you app on modification. Most people write app initialization code under the assumption of a clean environment; you're violating that assumption by running initialization code in a dirty environment – that is, one which is already up and running.
Trying to clean up the environment to allow your initialization code to run is almost certainly more trouble than it's worth. I'd simply restart the entire app when your underlying files have changed.
Meteor solves this problem by allowing modules to "register" themselves as part of the hot code push process.
They implement this in their reload package:
https://github.com/meteor/meteor/blob/master/packages/reload/reload.js#L105-L109
I've seen that Meteor.reload API used in some plugins on GitHub, but they also use it in the session package:
https://github.com/meteor/meteor/blob/master/packages/session/session.js#L103-L115
if (Meteor._reload) {
Meteor._reload.onMigrate('session', function () {
return [true, {keys: Session.keys}];
});
(function () {
var migrationData = Meteor._reload.migrationData('session');
if (migrationData && migrationData.keys) {
Session.keys = migrationData.keys;
}
})();
}
So basically, when the page/window loads, meteor runs a "migration", and it's up to the package to define the data/methods/etc. that get recomputed when a hot code push is made.
It's also being used by their livedata package (search reload).
Between refreshes they're saving the "state" using window.sessionStorage.

Extending console.log without affecting log line

I would like to extend the 'console.log' function to add additional information to its output - but I dont want to affect the script name/line number information generated by the browser in the console window. See how if I create my own implementation, I get useless trace information, should I want to locate that region of code... (they all link to the log implementation, not the actual script that caused the log message)
Basically, my application is a very pluggable infrastructure, were any log output may occur within any number of frames.
As such, I want every log message to include a special unique identifier at the beginning of the log message.
I have tried replacing the console.log method with my own, but chrome complains with
Uncaught TypeError: Illegal invocation
this is how I override it
var orig = console.log;
console.log = function( message )
{
orig( (window == top ? '[root]' : '[' + window.name + ']') + ': ' + message );
}
Any ideas?
[EDIT]
Note: After fixing the 'illegal invocation' problem, it seems the filename/linenumber is still 'polluted' by the override...
[EDIT]
It looks like the general answer is - NO - despite some confusing goose chases, the desired functionality is NOT achievable in the current versions of browsers.
Yes, it is possible to add information without messing up the originating line numbers of the log invocation. Some of the other answers here came close, but the trick is to have your custom logging method return the modified logger. Below is a simple example that was only moderately tested that uses the context variant.
log = function() {
var context = "My Descriptive Logger Prefix:";
return Function.prototype.bind.call(console.log, console, context);
}();
This can be used with:
log("A log message...");
Here is a jsfiddle: http://jsfiddle.net/qprro98v/
One could get easily get creative and pass the context variable in, and remove the auto-executing parens from the function definition. i.e. log("DEBUG:")("A debug message"), log("INFO:")("Here is some info"), etc.
The only really import part about the function (in regards to line numbers) is that it returns the logger.
If your use case can deal with a few restrictions, there is a way that this can be made to work. The restrictions are:
The extra log content has to be calculated at bind time; it cannot be time sensitive or depend on the incoming log message in any way.
The extra log content can only be place at the beginning of the log message.
With these restrictions, the following may work for you:
var context = "ALIASED LOG:"
var logalias;
if (console.log.bind === 'undefined') { // IE < 10
logalias = Function.prototype.bind.call(console.log, console, context);
}
else {
logalias = console.log.bind(console, context);
}
logalias('Hello, world!');
http://jsfiddle.net/Wk2mf/
An acceptable solution can be to make your own log-function that returns a console.log function bound with the log arguments.
log = function() {
// Put your extension code here
var args = Array.prototype.slice.call(arguments);
args.unshift(console);
return Function.prototype.bind.apply(console.log, args);
}
// Note the extra () to call the original console.log
log("Foo", {bar: 1})();
This way the console.log call will be made from the correct line, and will be displayed nicely in the console, allowing you to click on it and everything.
It is actually possible in chrome at least. Here is the most relevant. This may vary depending on setup, and how i got the splits was to just log the whole stack, and find the information I needed.
var stack = new Error().stack;
var file = stack.split("\n")[2].split("/")[4].split("?")[0]
var line = stack.split("\n")[2].split(":")[5];
Here is the whole thing, preserving the native object logging.
var orig = console.log
console.log = function(input) {
var isChrome = navigator.userAgent.indexOf("Chrome") !== -1;
if(isChrome){
var stack = new Error().stack;
var file = stack.split("\n")[2].split("/")[4].split("?")[0]
var line = stack.split("\n")[2].split(":")[5];
var append = file + ":" + line;
}
orig.apply(console, [input, append])
}
You need to call the console.log with the correct context (console):
orig.call(console, message);
To complete your function allowing multiple arguments:
var orig = console.log;
console.log = function() {
var msgs = [],
prefix = (window== top ? '[root]' : '[' + window.name + ']');
while(arguments.length) {
msgs.push(prefix + ': ' + [].shift.call(arguments));
}
orig.apply(console, msgs);
};
Demo: http://jsfiddle.net/je2wR/
Remember that you loose the built-in object/array browser in the console when combining objects with strings using the + sign.
I just answered this on a post that helped me answer the original 'alias' question:
(http://stackoverflow.com/a/12942764/401735)
my_log_alias = console.log.bind(console)
Apparently the capacity to do this has been designed in. Tested. Works.
thereafter my_log_alias is the same as console.log and can be called in the same way; Calling this from inside the function will report the line number for that function call, including the line inside of an alias or advice function where applicable.
Specifically, the line number Chrome provides will tell you the file the line is in, so what you are doing may be unneccesary; Consider reporting this as a bug/feature request in chrome that it provide this info in console.log.
Christopher Currie provided an excellent solution. I've expanded it a bit for my needs. Here's the AMD module:
define([], function () {
var enableDebug = true;
var separator = ">";
function bind(f, thisArg, ctx) {
if (f.bind !== 'undefined') { // IE < 10
return Function.prototype.bind.call(f, thisArg, ctx);
}
else {
return f.bind(thisArg, ctx);
}
}
function newConsole(context, parentConsole) {
var log;
var debug;
var warn;
var error;
if (!parentConsole) {
parentConsole = console;
}
context = context + separator;
if (enableDebug) {
debug = bind(console.log, console, context + "DEBUG" + separator);
} else {
debug = function () {
// suppress all debug messages
};
}
log = bind(console.log, console, context);
warn = bind(console.warn, console, context);
error = bind(console.error, console, context);
return {
debug: debug,
info: log,
log: log,
warn: warn,
error: error,
/* access console context information */
context: context,
/* create a new console with nested context */
nest: function (subContext) {
return newConsole(context + subContext, this);
},
parent: parentConsole
};
}
return newConsole("");
});
By default this will output > {message}. You can also add nested context to you logging, e.g. console.nest("my").log("test") will output >my> test.
I've also added a debug function that will indent messages with >DEBUG>
Hope somebody will find it useful.
Not long ago Chrome introduced a feature that can solve your problem without code hacks. It is called "blackbox" which basically allows you to mark files which should be ignored with their tools.
https://gist.github.com/paulirish/c307a5a585ddbcc17242
Yes, this solution is browser specific, but if you are using Chrome you do want this solution.
The solutions with a huge hack around throwing an Error for each log can show the right line, but it will not be a clickable link in your console.
The solutions based on binding/aliasing only enables you to modify the printed text. You will not be able to forward the arguments to a third function for further processing.
I have looked into this several times and always found it was not possible.
My workaround if you are interested is to assign console to another variable and then wrap all my log messages in a function which lets me modify/style/whatever on the message.
It looks nice with CoffeeScript, not sure its practical with plain JS.
I just get into the habit of prefixing everything with x.
logger.debug x 'Foo'
log x 'Bar'
log x('FooBar %o'), obj
Unfrotuantly it's currenlty not possible, In the future we might be able to do it with the Proxy object in ECMAScript 6.
My use case was to auto-prefix console messages with helpful information like the arguments passed and executing method. at the moment the closest I got is using Function.prototype.apply.
A simple approach is to just write your debug statements as such:
console.info('=== LazyLoad.css(', arguments, '): css files are skipped, gives us a clean slate to style within theme\'s CSS.');
A complicated approach is to use helper function as per below, I personally now prefer the simple approach.
/* Debug prefixing function
* ===========================
*
* A helper used to provide useful prefixing information
* when calling `console.log`, `console.debug`, `console.error`.
* But the catch is that to utilize one must leverage the
* `.apply` function as shown in the below examples.
*
* ```
* console.debug.apply(console, _fDebugPrefix(arguments)
* .concat('your message'));
*
* // or if you need to pass non strings
* console.debug.apply(console, _fDebugPrefix(arguments)
* .concat('json response was:', oJson));
*
*
* // if you need to use strict mode ("use strict") one can't
* // extract the function name but following approach works very
* // well; updating the name is just a matter of search and replace
* var aDebugPrefix = ['fYourFunctionName('
* ,Array.prototype.slice.call(arguments, 0),
* ,')'];
* console.debug.apply(console,
* aDebugPrefix.concat(['json response was:', oJson]));
* ```
*/
function _fDebugPrefix(oArguments) {
try {
return [oArguments.callee.name + '('
,Array.prototype.slice.call(oArguments, 0)
, ')'];
}
catch(err) { // are we in "use strict" mode ?
return ['<callee.name unsupported in "use strict">('
,Array.prototype.slice.call(oArguments, 0)
, ')'];
}
}
Reusable class in TS/JS
// File: LogLevel.ts
enum LogLevel {
error = 0,
warn,
info,
debug,
verbose,
}
export default LogLevel;
// File: Logger.js
import LogLevel from "./LogLevel";
export default class Logger {
static id = "App";
static level = LogLevel.info;
constructor(id) {
this.id = id;
const commonPrefix = `[${Logger.id}/${this.id}]`;
const verboseContext = `[V]${commonPrefix}`;
if (console.log.bind === "undefined") {
// IE < 10
this.verbose = Function.prototype.bind.call(console.log, console, verboseContext);
} else {
this.verbose = console.log.bind(console, verboseContext);
}
if (LogLevel.verbose > Logger.level) {
this.verbose = function() {
return // Suppress
};
}
const debugContext = `[D]${commonPrefix}`;
if (console.debug.bind === "undefined") {
// IE < 10
this.debug = Function.prototype.bind.call(console.debug, console, debugContext);
} else {
this.debug = console.debug.bind(console, debugContext);
}
if (LogLevel.debug > Logger.level) {
this.debug = function() {
return // Suppress
};
}
const infoContext = `[I]${commonPrefix}`;
if (console.info.bind === "undefined") {
// IE < 10
this.info = Function.prototype.bind.call(console.info, console, infoContext);
} else {
this.info = console.info.bind(console, infoContext);
}
if (LogLevel.info > Logger.level) {
this.info = function() {
return // Suppress
};
}
const warnContext = `[W]${commonPrefix}`;
if (console.warn.bind === "undefined") {
// IE < 10
this.warn = Function.prototype.bind.call(console.warn, console, warnContext);
} else {
this.warn = console.warn.bind(console, warnContext);
}
if (LogLevel.warn > Logger.level) {
this.warn = function() {
return // Suppress
};
}
const errorContext = `[E]${commonPrefix}`;
if (console.error.bind === "undefined") {
// IE < 10
this.error = Function.prototype.bind.call(console.error, console, errorContext);
} else {
this.error = console.error.bind(console, errorContext);
}
if (LogLevel.error > Logger.level) {
this.error = function() {
return // Suppress
};
}
}
}
Usage (React):
// File: src/index.tsx
// ...
Logger.id = "MCA"
const env = new Env()
if (env.env == Environment.dev) {
Logger.level = LogLevel.verbose
const log = new Logger("Main")
log.info("Environment is 'Development'")
}
///...
// File: src/App/CookieConsent/index.tsx
import React, { useEffect } from "react";
import { useCookies } from "react-cookie";
import "./index.scss";
import Logger from "#lib/Logger" // #lib is just alias configured in webpack.
const cookieName = "mca-cookie-consent";
// const log = new Logger(CookieConsent.name) // IMPORTANT! Don't put log instance here. It is too early! Put inside function.
export default function CookieConsent(): JSX.Element {
const log = new Logger(CookieConsent.name) // IMPORTANT! Have to be inside function, not in global scope (after imports)
useEffect(() => {
log.verbose(`Consent is accepted: ${isAccepted()}`);
}, []);
const [cookie, setCookie] = useCookies([cookieName]);
function isAccepted(): boolean {
return cookie[cookieName] != undefined;
}
function containerStyle(): React.CSSProperties {
return isAccepted() ? { display: "none" } : {};
}
function handleClick() {
const expires = new Date();
expires.setFullYear(expires.getFullYear() + 1);
log.verbose(`Accepted cookie consent. Expiration: ${expires}`)
setCookie(cookieName, true, { path: "/", expires: expires, sameSite: "lax" });
}
return (
<div className="cookieContainer" style={containerStyle()}>
<div className="cookieContent">
<div>
<p className="cookieText">This website uses cookies to enhance the user experience.</p>
</div>
<div>
<button onClick={handleClick} className="cookieButton">
I understand
</button>
</div>
</div>
</div>
);
}
Output in browser console:
20:47:48.190 [I][MCA/Main] Environment is 'Development' index.tsx:19
20:47:48.286 [V][MCA/CookieConsent] Consent is accepted: false index.tsx:13
20:47:52.250 [V][MCA/CookieConsent] Accepted cookie consent. Expiration: Sun Jan 30 2022 20:47:52 GMT+0100 (Central European Standard Time) index.tsx:29
Hope this helps for some of your cases...
const log = console.log;
export default function middleWare(optionalStringExtension = '') {
console.log = (...args) => {
log(...args, optionalStringExtension);
}
}
Either run as middleware, top of file, or first line of function.
I ran into this issue as well about extending console.log() so that the application can extend, control and do fancy stuff with it in addition to logging stuff to the console. Losing the line number information was tantamount to failure, however. After wrestling with the issue, I came up with a long-winded workaround, but at least it's still a "1-liner" to use.
First, define a global class to use or add some methods to your main existing "app" class:
/**
* Log message to our in-app and possibly on-screen console, return args.
* #param {!string} aMsgLevel - one of "log", "debug", "info", "warn", or "error"
* #param {any} aArgs - the arguments to log (not used directly, just documentation helper)
* #returns args so it can be nested within a console.log.apply(console,app.log()) statement.
*/
MyGlobalClassWithLogMethods.prototype.debugLog = function(aMsgLevel, aArgs) {
var s = '';
var args = [];
for (var i=1; i<arguments.length; i++) {
args.push(arguments[i]);
if (arguments[i])
s += arguments[i].toString()+' ';
}
if (typeof this.mLog === 'undefined')
this.mLog = [];
this.mLog.push({level: aMsgLevel, msg: s});
return args;
};
MyGlobalClassWithLogMethods.prototype.log = function() {
var args = ['log'].concat(Array.prototype.slice.call(arguments));
return this.debugLog.apply(this,args);
};
MyGlobalClassWithLogMethods.prototype.debug = function() {
var args = ['debug'].concat(Array.prototype.slice.call(arguments));
return this.debugLog.apply(this,args);
};
MyGlobalClassWithLogMethods.prototype.info = function() {
var args = ['info'].concat(Array.prototype.slice.call(arguments));
return this.debugLog.apply(this,args);
};
MyGlobalClassWithLogMethods.prototype.warn = function() {
var args = ['warn'].concat(Array.prototype.slice.call(arguments));
return this.debugLog.apply(this,args);
};
MyGlobalClassWithLogMethods.prototype.error = function() {
var args = ['error'].concat(Array.prototype.slice.call(arguments));
return this.debugLog.apply(this,args);
};
//not necessary, but it is used in my example code, so defining it
MyGlobalClassWithLogMethods.prototype.toString = function() {
return "app: " + JSON.stringify(this);
};
Next, we put those methods to use like so:
//JS line done as early as possible so rest of app can use logging mechanism
window.app = new MyGlobalClassWithLogMethods();
//only way to get "line info" reliably as well as log the msg for actual page display;
// ugly, but works. Any number of params accepted, and any kind of var will get
// converted to str using .toString() method.
console.log.apply(console,app.log('the log msg'));
console.debug.apply(console,app.debug('the log msg','(debug)', app));
console.info.apply(console,app.info('the log msg','(info)'));
console.warn.apply(console,app.warn('the log msg','(warn)'));
console.error.apply(console,app.error('the log msg','(error)'));
Now the console gets log messages with their appropriate line information as well as our app contains an array of log messages that can be put to use. For example, to display your in-app log using HTML, JQuery and some CSS the following simplistic example can be used.
First, the HTML:
<div id="debug_area">
<h4 class="text-center">Debug Log</h4>
<ul id="log_list">
<!-- console log/debug/info/warn/error ('msg') lines will go here -->
</ul>
</div>
some CSS:
.log_level_log {
color: black;
background-color: white;
font-size: x-small;
}
.log_level_debug {
color: #060;
background-color: #80FF80;
font-size: x-small;
}
.log_level_info {
color: #00F;
background-color: #BEF;
font-size: x-small;
}
.log_level_warn {
color: #E65C00;
background-color: #FB8;
font-size: x-small;
}
.log_level_error {
color: #F00;
background-color: #FBB;
font-size: x-small;
}
and some JQuery:
var theLog = app.mLog || [];
if (theLog.length>0) {
var theLogList = $('#log_list');
theLogList.empty();
for (var i=0; i<theLog.length; i++) {
theLogList.prepend($('<li class="log_level_'+theLog[i].level+'"></li>').text(theLog[i].msg));
}
}
This is a simplistic use, but once you have the mechanism in place, you can do whatever your imagination can come up with, including leaving the log lines in the code, but setting a threshold so that only warnings and errors get through. Hopefully this helps others with their projects.
Today you have to use args with rest operator, because as the Mozilla docs says Function.arguments has been deprecated and is not accessible in arrow functions. So simply you can extend it like below:
//#1
const myLog= (...args) =>
console.log.bind(console, ...args);
//myLog("this is my new log")();
//#2
const myNewLog= (...args) =>{
const prefix = "Prefixed: ";
return console.log.bind(console, ...[prefix,...args]);
}
//myNewLog("test")()
And you can make a beautifulLog like this:
//#3
const colorizedLog = (text, color= "#40a7e3", ...args) =>
console.log.bind(
console,
`%c ${text}`,
`font-weight:bold; color:${color}`,
...args
);
//colorizedLog("Title:", "#40a7e3", "This is a working example")();
This snippet apply a prefix to logs for all levels (console.log console.debug console.info ...) :
export const makeConsole = (context: string, cons = console): Console =>
Object.getOwnPropertyNames(cons).reduce((c, lev) => {
if (typeof cons[lev] === "function") {
c[lev] = Function.prototype.bind.call(cons[lev], cons, context);
}
return c;
}, {});
console.debug("Hello world!")
// >> Hello world!
console = makeConsole("[logging is fun]")
// >> [logging is fun] Hello world!
Bonus, for React peeps:
export function useConsole(context: string): Console {
return React.useMemo(() => makeConsole(context), [context]);
}
Try setTimeout(console.log.bind(console,'foo'));

Categories

Resources