js4php: thanks!
April 15th, 2013JS4PHP book is almost a wrap! It started as "well, I have this blog post, then I have these slides from this conference, how hard could it be to make it into a book form?" Pretty hard, turns out. I'm surprised every time. Takes a while, you get distracted by something shinier and so on... No such thing as "quick book project", for me, at least.
But it's almost ready to go, O'Reilly folks are optimistic it will be done in time for the Fluent conference in May (where I'm not speaking btw). The price on Amazon really makes me happy, I think $7.90 for a tech book is unheard of.
So anyway, just wanted to say thanks to people that helped with the book along the way.
Thanks
First and foremost, big thanks and gratitude to my second-time reviewers who helped me with "JavaScript Patterns" before, the Three Musketeers and d'Artagnan, the ever-so-amazing Andrea Giammarchi, Asen Bozhilov, Dmitry Soshnikov and Juriy "kangax" Zaytsev. As with the previous book they helped me tremendously with their deep knowledge and experience with JavaScript, their attention to detail and technical accuracy above all. You'll see little notes in the book following bold general statements, these notes often come from one of these guys saying: "hmm, this is not entirely correct and not always true, because...". I am forever in debt to these four ridiculously talented developers who also happen to be great and friendly people.
Many thanks to Chris Shiflett and Sean Coates. They made sure the PHP side of things make sense, but what's more, this whole book started as a post on their PHPAdvent (now WebAdvent.org) blog, followed by a talk at the ConFoo conference which Sean helps organize.
Next, thanks to the Facebook engineers that hang out on the JavaScript group. I posted an early draft there asking for comments. Three people even went through the whole thing and gave me invaluable feedback and further nitpicking, which is the best quality in a technical reviewer. Thanks to Alok Menghrajani, James Ide and Alex Himel.
Finally, thanks to Randy Owens who read the "early release" of the book and meticulously filed tens of errata reports.
Posted in books | 33 Comments »
Unit testing in AsciiDoc
February 12th, 2013While finishing off this book, which I choose to write in AsciiDoc format, I really appreciate the time I took to write myself a handy little script for testing the little code examples. Because, something always gets wrong in the final steps of editing and is nice to have some automated assurance that you didn't break something stupid.
AsciiDoc or MarkDown or some other text setup is how technical books should be written. Or any books, really. Having clear plain text lets you focus on the content and not wrestle with Word formatting. And it's also easy to parse and test.
Code in AsciiDoc
The code blocks in AsciiDoc are easy to spot. They look like:
[source,js] ---------------------------------------------------------------------- var a = 0xff; a === 255; //:: true ----------------------------------------------------------------------
Testing and linting
All you have to do in a little unit testing utility is to extract these clearly defined blocks and run them to make sure there are no silly syntax errors, creeping in in the very last edits.
But why stop with a syntax check? Why not also run jslint and also instrument and run the code and see any expected values. Asserts, in testspeak.
In my setup I did just that: lint with jshint, instrument the places where I know the expected values, lint these too (why the hell not?), run them and assert the expected values.
Output
Here's how the output of the utility looks like:
$ node scripts/test.js passed: 362, skipped: 43 linted: 317, nolints: 45
Since this is a book with some educational bad examples, not everything can be linted. Or run for that matter.
JSHint
I choose JSHint over JSLint as it allows more flexibility to relax the rules. Also it's a node module I can import.
var jslint = require('jshint').JSHINT;
These are my lint options:
// JSHint options need to be more relaxed because the book also // points out bad patterns var lintopts = { indent: 2, // 2 spaces for indentation trailing: true, // disallow spaces at the end of a line white: true, plusplus: false, // allows ++ and -- browser: true, // assumes some common browser globals exist // such as `document` node: true, // assumes the code can run in node.js // and globals such as `global` are defined expr: true, // ok to have expressions that seemingly do nothing // such as `a; // true` which the samples use to show // result values loopfunc: true, // allows definition of a function in a loop // for educational purposes (in the part about closures) newcap: false, // allows calling constructors (capitalized functions) // without `new`, again just for educational purposes proto: true, // allows using `__proto__` which is great for understanding // prototypes, although it's not supported in all browsers };
And the lint function that lints a snippet of code:
// lint a snippet function lint(snip) { // lint the snippet with all the options and have it assume // assert objext exists if (!jslint(snip, lintopts, {assert: false})) { log('------'); log(snip); log('------'); log(jslint.errors[0]); process.exit(); } }
Instrumenting and executing a snippet
Execution is simple:
// run a snippet function exec(snip) { // muck some stuff up and zap log() var mock = "function define(){}; function alert(){}; console.log = function(){};"; try { eval(snip + mock); passed++; } catch (e) { log('------'); log(snip); log('------'); log(e.message); process.exit(); } }
The instrumentation is a little more interesting.
I often write snippets like:
// assign var a = 1; // test a; // 1
Now I need a little bit of marker that will tell the instrumentation that a;
is a piece of code to execute and 1
is the expected value. I came up with //::
So the above becomes:
var a = 1; a; //:: 1
I also like to add some more explanation besides the returned value. I use ,,
for that. So:
var a = 1; a; //:: 1,, as you'd expect
Instrumented, this becomes:
var a = 1; assert.deepEqual("a;", 1, "Error line #2");
The line is the line in the book with all code and prose, so it's easy to find.
The special markup like //::
and ,,
gets stripped by another script that I run before commit.
A few other features are: support for NaN
which doesn't deepEqual to anything and expecting errors with assert.throws(..)
So I can write and test code like:
sum(21, 21); //:: 42 plum(21, 21); //:: Error:: plum() is not defined
(:: that follow Error is the same as ,,)
Also:
Number("3,14"); //:: NaN
So here's the code instrumentation:
// Add asserts function prep(l, n) { var parts = l.split(/;\s*\/\/::/); // "//::" separates expression to execute from its result var nonspace = parts[0].match(/\S/); var spaces = nonspace === null ? "" : Array(nonspace.index + 1).join(" "); parts[0] = parts[0].trim(); if (parts[1]) { var r = parts[1].split(/\s*(,,|::)\s*/)[0].trim(); // the result may have ,, or ::, ignore what's on the right // e.g. //:: true,, of course! // e.g. //:: ReferenceError::Invalid whatever if (r.indexOf('Error') !== -1) { // expect //:: Error to throw return spaces + 'assert.throws(function () {' + parts[0] + '; }, ' + r + ', "error line #' + n + '");'; } if (r === 'NaN') { // special NaN case return spaces + 'assert(isNaN(' + parts[0] + '), true, "error line #' + n + '");' } // usual return spaces + 'assert.deepEqual(' + parts[0] + ', ' + r + ', "error line #' + n + '");'; } return l; }
Main
Dependencies, locals, options and the main parser loop is how it all begins/ends:
// dependencies where I can see them var assert = require('assert'); var fs = require('fs'); var jslint = require('jshint').JSHINT; // buncha local vars var snip, rawsnip.....; // short var log = console.log; // JSHint options var lintopts = { indent: 2, // 2 spaces for indentation // .... }; // read the book one line at a time fs.readFileSync('book.asc').toString().split('\n').forEach(function(src, num) { // src is a line in the book // num is the line number });
There are a few additional features at snippet-level:
Ability to continue from a previous snippet using --//-- at the top of the snippet delimiter
Let's declare a variable: [source,js] ---------------------------------------------------------------------- var a = 1; ---------------------------------------------------------------------- And then another one: [source,js] --------------------------------------------------------------------//-- var b = 2; ---------------------------------------------------------------------- And let's sum [source,js] --------------------------------------------------------------------//-- a + b; //:: 3 ----------------------------------------------------------------------
Ability to skip a non-working snippet using ////
[source,js] ----------------------------------------------------------------------////-- var 1v; // invalid ----------------------------------------------------------------------
Ability to run in non-strict mode (because strict is default) using ++
[source,js] ----------------------------------------------------------------------++-- var a = 012; a === 10; //:: true ----------------------------------------------------------------------
nolint option
[source,js] ---------------------------------------------------------------------- /*nolint*/ assoc["one"]; //:: 1 ----------------------------------------------------------------------
Cleanup before commit
Cleaning up all instrumentation markers and hints for the lint and the tests (gist):
var clean = require('fs').readFileSync('book.asc').toString().split('\n').filter(function(line) { if (line.indexOf('/*nolint*/') === 0 || line.indexOf('/*global') === 0) { return false; } return true; }) .join('\n') .replace(/--\+\+--/g, '--') .replace(/--\/\/--/g, '--') .replace(/--\/\/\/\/--/g, '--'); console.log(clean);
Github gist
Here's the test.js script in its entirety.
Posted in books | 10 Comments »
Optional parameters
February 11th, 2013JavaScript has no syntax that allows you to have a default value for a function parameter as you often do in most other languages. This is scheduled for a future version of ECMAScript, but for now you have to take care of this yourself inside the body of your function.
There are several patterns that do the job, but here's a new one. It was suggested to me by Andrea "WebReflection" Giammarchi in his technical review of the upcoming JS4PHP book.
Andrea doesn't remember blogging about this pattern and I don't remember ever seeing it. So here goes.
Say you have a function with all 4 default parameters, mimicking for example PHP's declaration:
function sum($a = 1, $b = 2, $c = 3, $d = 4) ...
function sum(a, b, c, d) { // note no `break` needed switch (arguments.length) { case 0: a = 1; case 1: b = 2; case 2: c = 3; case 3: d = 4; } return a + b + c + d; }
Test:
sum(); // 10 sum(1); // 10 sum(11); // 20 sum(1, 2, 3, 24); // 30 sum(11, 22); // 40
Obviously this doesn't work when you have an optional param, followed by a required one, but that's just bad design.
Thoughts?
Posted in Functions | 15 Comments »
Shim sniffing
June 4th, 2012Extending native objects and prototypes is bad. If not vile, mean and Jesuitic.
// Noooooo! Array.prototype.map = function() { // stuff };
Unless it's desirable, for example for adding ECMAScript5 methods in legacy browsers.
In which case we do something like:
if (!Array.prototype.map) { Array.