diff --git a/.travis.yml b/.travis.yml index 90cc39db..853e3697 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,5 @@ language: node_js node_js: - "0.6" - "0.8" - - "0.9" - "0.10" - "0.11" diff --git a/Changes.markdown b/Changes.markdown index 3a5d4ba1..af419807 100644 --- a/Changes.markdown +++ b/Changes.markdown @@ -3,6 +3,8 @@ ## vNEXT - ??? - Fix content tight between two hr's disappearing (#106) +- Fix (yet more! gah) global variable leaks #99 +- Fix JSHint warnings #65 - Thanks XhmikosR! ## v0.5.0 - 2013-07-26 diff --git a/bin/md2html.js b/bin/md2html.js index f8ce1dd5..22c8304a 100755 --- a/bin/md2html.js +++ b/bin/md2html.js @@ -2,22 +2,21 @@ (function () { "use strict"; - var fs = require("fs") - , markdown = require("markdown").markdown - , nopt = require("nopt") - , stream - , opts - , buffer = "" - ; + var fs = require("fs"), + markdown = require("markdown").markdown, + nopt = require("nopt"), + stream, + opts, + buffer = ""; opts = nopt( - { "dialect": [ "Gruber", "Maruku"] - , "help": Boolean + { "dialect": [ "Gruber", "Maruku"], + "help": Boolean } ); if (opts.help) { - var name = process.argv[1].split("/").pop() + var name = process.argv[1].split("/").pop(); console.warn( require("util").format( "usage: %s [--dialect=DIALECT] FILE\n\nValid dialects are Gruber (the default) or Maruku", name @@ -49,4 +48,4 @@ console.log(html); }); -}()) +}()); diff --git a/lib/markdown.js b/lib/markdown.js index dad87445..5c983fac 100644 --- a/lib/markdown.js +++ b/lib/markdown.js @@ -3,8 +3,6 @@ // Copyright (c) 2009-2010 Ash Berlin // Copyright (c) 2011 Christoph Dorn (http://www.christophdorn.com) -/*jshint browser:true, devel:true */ - (function( expose ) { /** @@ -140,15 +138,17 @@ function mk_block_inspect() { var mk_block = Markdown.mk_block = function(block, trail, line) { // Be helpful for default case in tests. - if ( arguments.length == 1 ) trail = "\n\n"; + if ( arguments.length === 1 ) trail = "\n\n"; + // We actually need a String object, not a string primitive + /* jshint -W053 */ var s = new String(block); s.trailing = trail; // To make it clear its not just a string s.inspect = mk_block_inspect; s.toSource = mk_block_toSource; - if ( line != undefined ) + if ( line !== undefined ) s.lineNumber = line; return s; @@ -171,14 +171,14 @@ Markdown.prototype.split_blocks = function splitBlocks( input, startLine ) { var line_no = 1; - if ( ( m = /^(\s*\n)/.exec(input) ) != null ) { + if ( ( m = /^(\s*\n)/.exec(input) ) !== null ) { // skip (but count) leading blank lines line_no += count_lines( m[0] ); re.lastIndex = m[0].length; } while ( ( m = re.exec(input) ) !== null ) { - if (m[2] == "\n#") { + if (m[2] === "\n#") { m[2] = "\n"; re.lastIndex--; } @@ -253,12 +253,12 @@ Markdown.prototype.toTree = function toTree( source, custom_root ) { try { this.tree = custom_root || this.tree || [ "markdown" ]; - blocks: + blocks_loop: while ( blocks.length ) { var b = this.processBlock( blocks.shift(), blocks ); // Reference blocks and the like won't return any content - if ( !b.length ) continue blocks; + if ( !b.length ) continue blocks_loop; this.tree.push.apply( this.tree, b ); } @@ -279,14 +279,14 @@ Markdown.prototype.debug = function () { print.apply( print, args ); if ( typeof console !== "undefined" && typeof console.log !== "undefined" ) console.log.apply( null, args ); -} +}; Markdown.prototype.loop_re_over_block = function( re, block, cb ) { // Dont use /g regexps with this var m, b = block.valueOf(); - while ( b.length && (m = re.exec(b) ) != null ) { + while ( b.length && (m = re.exec(b) ) !== null ) { b = b.substr( m[0].length ); cb.call(this, m); } @@ -453,7 +453,7 @@ Markdown.dialects.Gruber = { return; } // Hmmm, should this be any block level element or just paras? - var add_to = li[li.length -1] instanceof Array && li[li.length - 1][0] == "para" + var add_to = li[li.length -1] instanceof Array && li[li.length - 1][0] === "para" ? li[li.length -1] : li; @@ -462,8 +462,8 @@ Markdown.dialects.Gruber = { for ( var i = 0; i < inline.length; i++ ) { var what = inline[i], - is_str = typeof what == "string"; - if ( is_str && add_to.length > 1 && typeof add_to[add_to.length-1] == "string" ) { + is_str = typeof what === "string"; + if ( is_str && add_to.length > 1 && typeof add_to[add_to.length-1] === "string" ) { add_to[ add_to.length-1 ] += what; } else { @@ -500,10 +500,10 @@ Markdown.dialects.Gruber = { var list = s.list; var last_li = list[list.length-1]; - if ( last_li[1] instanceof Array && last_li[1][0] == "para" ) { + if ( last_li[1] instanceof Array && last_li[1][0] === "para" ) { return; } - if ( i + 1 == stack.length ) { + if ( i + 1 === stack.length ) { // Last stack frame // Keep the same array, but replace the contents last_li.push( ["para"].concat( last_li.splice(1, last_li.length - 1) ) ); @@ -544,13 +544,14 @@ Markdown.dialects.Gruber = { // We have to grab all lines for a li and call processInline on them // once as there are some inline things that can span lines. - var li_accumulate = ""; + var li_accumulate = "", nl = ""; // Loop over the lines in this block looking for tight lists. tight_search: for ( var line_no = 0; line_no < lines.length; line_no++ ) { - var nl = "", - l = lines[line_no].replace(/^\n/, function(n) { nl = n; return ""; }); + nl = ""; + var l = lines[line_no].replace(/^\n/, function(n) { nl = n; return ""; }); + // TODO: really should cache this var line_re = regex_for_depth( stack.length ); @@ -585,7 +586,7 @@ Markdown.dialects.Gruber = { // wanted_depth deserves. var found = false; for ( i = 0; i < stack.length; i++ ) { - if ( stack[ i ].indent != m[1] ) continue; + if ( stack[ i ].indent !== m[1] ) continue; list = stack[ i ].list; stack.splice( i+1, stack.length - (i+1) ); found = true; @@ -677,13 +678,13 @@ Markdown.dialects.Gruber = { // a // > b // - if ( block[ 0 ] != ">" ) { + if ( block[ 0 ] !== ">" ) { var lines = block.split( /\n/ ), prev = [], line_no = block.lineNumber; // keep shifting lines until you find a crotchet - while ( lines.length && lines[ 0 ][ 0 ] != ">" ) { + while ( lines.length && lines[ 0 ][ 0 ] !== ">" ) { prev.push( lines.shift() ); line_no++; } @@ -696,7 +697,7 @@ Markdown.dialects.Gruber = { // if the next block is also a blockquote merge it in - while ( next.length && next[ 0 ][ 0 ] == ">" ) { + while ( next.length && next[ 0 ][ 0 ] === ">" ) { var b = next.shift(); block = mk_block( block + block.trailing + b, b.trailing, block.lineNumber ); } @@ -741,7 +742,7 @@ Markdown.dialects.Gruber = { var b = this.loop_re_over_block(re, block, function( m ) { - if ( m[2] && m[2][0] == "<" && m[2][m[2].length-1] == ">" ) + if ( m[2] && m[2][0] === "<" && m[2][m[2].length-1] === ">" ) m[2] = m[2].substring( 1, m[2].length - 1 ); var ref = attrs.references[ m[1].toLowerCase() ] = { @@ -806,7 +807,7 @@ Markdown.dialects.Gruber.inline = { function add(x) { //D:self.debug(" adding output", uneval(x)); - if ( typeof x == "string" && typeof out[out.length-1] == "string" ) + if ( typeof x === "string" && typeof out[out.length-1] === "string" ) out[ out.length-1 ] += x; else out.push(x); @@ -815,7 +816,7 @@ Markdown.dialects.Gruber.inline = { while ( text.length > 0 ) { res = this.dialect.inline.__oneElement__.call(this, text, patterns, out ); text = text.substr( res.shift() ); - forEach(res, add ) + forEach(res, add ); } return out; @@ -848,7 +849,7 @@ Markdown.dialects.Gruber.inline = { var m = text.match( /^!\[(.*?)\][ \t]*\([ \t]*([^")]*?)(?:[ \t]+(["'])(.*?)\3)?[ \t]*\)/ ); if ( m ) { - if ( m[2] && m[2][0] == "<" && m[2][m[2].length-1] == ">" ) + if ( m[2] && m[2][0] === "<" && m[2][m[2].length-1] === ">" ) m[2] = m[2].substring( 1, m[2].length - 1 ); m[2] = this.dialect.inline.__call__.call( this, m[2], /\\/ )[0]; @@ -902,7 +903,7 @@ Markdown.dialects.Gruber.inline = { var url = m[1]; consumed += m[0].length; - if ( url && url[0] == "<" && url[url.length-1] == ">" ) + if ( url && url[0] === "<" && url[url.length-1] === ">" ) url = url.substring( 1, url.length - 1 ); // If there is a title we don't have to worry about parens in the url @@ -914,7 +915,7 @@ Markdown.dialects.Gruber.inline = { open_parens++; break; case ")": - if ( --open_parens == 0) { + if ( --open_parens === 0) { consumed -= url.length - len; url = url.substring(0, len); } @@ -955,7 +956,7 @@ Markdown.dialects.Gruber.inline = { // [id] // Only if id is plain (no formatting.) - if ( children.length == 1 && typeof children[0] == "string" ) { + if ( children.length === 1 && typeof children[0] === "string" ) { attrs = { ref: children[0].toLowerCase(), original: orig.substr( 0, consumed ) }; link = [ "link_ref", attrs, children[0] ]; @@ -970,12 +971,12 @@ Markdown.dialects.Gruber.inline = { "<": function autoLink( text ) { var m; - if ( ( m = text.match( /^<(?:((https?|ftp|mailto):[^>]+)|(.*?@.*?\.[a-zA-Z]+))>/ ) ) != null ) { + if ( ( m = text.match( /^<(?:((https?|ftp|mailto):[^>]+)|(.*?@.*?\.[a-zA-Z]+))>/ ) ) !== null ) { if ( m[3] ) { return [ m[0].length, [ "link", { href: "mailto:" + m[3] }, m[3] ] ]; } - else if ( m[2] == "mailto" ) { + else if ( m[2] === "mailto" ) { return [ m[0].length, [ "link", { href: m[1] }, m[1].substr("mailto:".length ) ] ]; } else @@ -1008,7 +1009,7 @@ Markdown.dialects.Gruber.inline = { function strong_em( tag, md ) { var state_slot = tag + "_state", - other_slot = tag == "strong" ? "em_state" : "strong_state"; + other_slot = tag === "strong" ? "em_state" : "strong_state"; function CloseTag(len) { this.len_after = len; @@ -1017,7 +1018,7 @@ function strong_em( tag, md ) { return function ( text, orig_match ) { - if ( this[state_slot][0] == md ) { + if ( this[state_slot][0] === md ) { // Most recent em is of this type //D:this.debug("closing", md); this[state_slot].shift(); @@ -1071,7 +1072,7 @@ Markdown.dialects.Gruber.inline["_"] = strong_em("em", "_"); Markdown.buildBlockOrder = function(d) { var ord = []; for ( var i in d ) { - if ( i == "__order__" || i == "__call__" ) continue; + if ( i === "__order__" || i === "__call__" ) continue; ord.push( i ); } d.__order__ = ord; @@ -1086,7 +1087,7 @@ Markdown.buildInlinePatterns = function(d) { if ( i.match( /^__.*__$/) ) continue; var l = i.replace( /([\\.*+?|()\[\]{}])/g, "\\$1" ) .replace( /\n/, "\\n" ); - patterns.push( i.length == 1 ? l : "(?:" + l + ")" ); + patterns.push( i.length === 1 ? l : "(?:" + l + ")" ); } patterns = patterns.join("|"); @@ -1095,7 +1096,7 @@ Markdown.buildInlinePatterns = function(d) { var fn = d.__call__; d.__call__ = function(text, pattern) { - if ( pattern != undefined ) { + if ( pattern !== undefined ) { return fn.call(this, text, pattern); } else @@ -1111,7 +1112,7 @@ Markdown.DialectHelpers.inline_until_char = function( text, want ) { nodes = []; while ( true ) { - if ( text.charAt( consumed ) == want ) { + if ( text.charAt( consumed ) === want ) { // Found the character we were looking for consumed++; return [ consumed, nodes ]; @@ -1127,7 +1128,7 @@ Markdown.DialectHelpers.inline_until_char = function( text, want ) { // Add any returned nodes. nodes.push.apply( nodes, res.slice( 1 ) ); } -} +}; // Helper function to make sub-classing a dialect easier Markdown.subclassDialect = function( d ) { @@ -1171,7 +1172,7 @@ Markdown.dialects.Maruku.processMetaHash = function processMetaHash( meta_string } return attr; -} +}; function split_meta_hash( meta_string ) { var meta = meta_string.split( "" ), @@ -1200,6 +1201,7 @@ function split_meta_hash( meta_string ) { // shift off the next letter to be used straight away. // it was escaped so we'll keep it whatever it is letter = meta.shift(); + /* falls through */ default : parts[ parts.length - 1 ] += letter; break; @@ -1222,7 +1224,7 @@ Markdown.dialects.Maruku.block.document_meta = function document_meta( block, ne } var pairs = block.split( /\n/ ); - for ( p in pairs ) { + for ( var p in pairs ) { var m = pairs[ p ].match( /(\w+):\s*(.*)$/ ), key = m[ 1 ].toLowerCase(), value = m[ 2 ]; @@ -1259,7 +1261,7 @@ Markdown.dialects.Maruku.block.block_meta = function block_meta( block, next ) { } // add the attributes in - for ( a in attr ) { + for ( var a in attr ) { hash[ a ] = attr[ a ]; } @@ -1279,7 +1281,7 @@ Markdown.dialects.Maruku.block.block_meta = function block_meta( block, next ) { } // attach the attributes to the block - for ( a in attr ) { + for ( var a in attr ) { hash[ a ] = attr[ a ]; } @@ -1335,23 +1337,23 @@ Markdown.dialects.Maruku.block.table = function table (block, next) { var res = [ ], r = new RegExp('^((?:\\\\.|[^\\\\' + ch + '])*)' + ch + '(.*)'), m; - while(m = s.match(r)) { - res.push(m[1]); + while( ( m = s.match( r ) ) ) { + res.push( m[1] ); s = m[2]; } res.push(s); return res; - } + }; var leading_pipe = /^ {0,3}\|(.+)\n {0,3}\|\s*([\-:]+[\-| :]*)\n((?:\s*\|.*(?:\n|$))*)(?=\n|$)/, // find at least an unescaped pipe in each line no_leading_pipe = /^ {0,3}(\S(?:\\.|[^\\|])*\|.*)\n {0,3}([\-:]+\s*\|[\-| :]*)\n((?:(?:\\.|[^\\|])*\|.*(?:\n|$))*)(?=\n|$)/, i, m; - if (m = block.match(leading_pipe)) { + if ( ( m = block.match( leading_pipe ) ) ) { // remove leading pipes in contents // (header and horizontal rule already have the leading pipe left out) m[3] = m[3].replace(/^\s*\|/gm, ''); - } else if (! ( m = block.match(no_leading_pipe))) { + } else if ( ! ( m = block.match( no_leading_pipe ) ) ) { return undefined; } @@ -1388,7 +1390,7 @@ Markdown.dialects.Maruku.block.table = function table (block, next) { }, this); return [table]; -} +}; Markdown.dialects.Maruku.inline[ "{:" ] = function inline_meta( text, matches, out ) { if ( !out.length ) { @@ -1433,7 +1435,7 @@ Markdown.buildBlockOrder ( Markdown.dialects.Maruku.block ); Markdown.buildInlinePatterns( Markdown.dialects.Maruku.inline ); var isArray = Array.isArray || function(obj) { - return Object.prototype.toString.call(obj) == "[object Array]"; + return Object.prototype.toString.call(obj) === "[object Array]"; }; var forEach; @@ -1448,7 +1450,7 @@ else { for (var i = 0; i < arr.length; i++) { cb.call(thisp || arr, arr[i], i, arr); } - } + }; } var isEmpty = function( obj ) { @@ -1459,7 +1461,7 @@ var isEmpty = function( obj ) { } return true; -} +}; function extract_attr( jsonml ) { return isArray(jsonml) @@ -1541,7 +1543,7 @@ function render_tree( jsonml ) { } // be careful about adding whitespace here for inline elements - if ( tag == "img" || tag == "br" || tag == "hr" ) { + if ( tag === "img" || tag === "br" || tag === "hr" ) { return "<"+ tag + tag_attrs + "/>"; } else { diff --git a/package.json b/package.json index 94c73afd..f34affe4 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,8 @@ ], "contributors": [ "Dominic Baggott (http://evilstreak.co.uk)", - "Ash Berlin (http://ashberlin.com)" + "Ash Berlin (http://ashberlin.com)", + "XhmikosR " ], "bugs": { "url": "https://github.com/evilstreak/markdown-js/issues" @@ -42,9 +43,37 @@ "nopt": "~2.1.1" }, "devDependencies": { - "tap": "~0.3.3" + "tap": "~0.3.3", + "jshint": "~2" }, "scripts": { - "test": "tap ./test/*.t.js" + "test": "npm run-script tap && npm run-script lint", + "tap": "tap ./test/*.t.js", + "lint": "jshint lib/ bin/ test/" + }, + "jshintConfig": { + "shadow": true, + "laxbreak": true, + "sub": true, + "loopfunc": true, + + "undef": true, + "unused": false, + "node": true, + "globals": "We also support browser, so define a subset of those globals we use", + "globals": { + "print": true, + "uneval": true, + "window": true + }, + "lastsemic": true, + "eqeqeq": true, + "newcap": true, + "noarg": true, + "newcap": true, + "trailing": true + }, + "engines": { + "node": ">=0.6.0" } } diff --git a/test/features.t.js b/test/features.t.js index 81f191a1..4170a2e6 100644 --- a/test/features.t.js +++ b/test/features.t.js @@ -7,14 +7,14 @@ function test_dialect( dialect, features ) { var slurpFile = function slurpFile( path ) { return fs.readFileSync( path, "utf8" ); - } + }; var isFile = function isFile( f ) { try { - return fs.statSync( f ).isFile() + return fs.statSync( f ).isFile(); } catch (e) { - if ( e.code == "ENOENT" ) return false; + if ( e.code === "ENOENT" ) return false; throw e; } }; @@ -38,7 +38,7 @@ function test_dialect( dialect, features ) { for ( var t in tests ) { // load the raw text var testName = dialect + "/" + feature + "/" + tests[ t ].substring( tests[ t ].lastIndexOf( "/" ) + 1 ), - testFileBase = path.join(test_path, tests[ t ]); + testFileBase = path.join(test_path, tests[ t ]), text = slurpFile( testFileBase + ".text" ); // load the target output diff --git a/test/regressions.t.js b/test/regressions.t.js index 2c817aa3..fb8a3f8a 100644 --- a/test/regressions.t.js +++ b/test/regressions.t.js @@ -11,10 +11,10 @@ var markdown = require("../lib/markdown"), function test(name, cb) { tap.test( name, function(t) { - cb(t, new Markdown ); + cb(t, new Markdown() ); t.end(); }); -}; +} test("split_block", function(t, md) { t.equivalent( @@ -45,7 +45,7 @@ test("headers", function(t, md) { "Closing # optional on atxHeader"); t.equivalent( - h2 = md.dialect.block.atxHeader.call( md, "## h2\n\n", [] ), + md.dialect.block.atxHeader.call( md, "## h2\n\n", [] ), [["header", {level: 2}, "h2"]], "Atx h2 has right level"); @@ -242,7 +242,7 @@ test( "bulletlist", function(t, md) { ] ] ] ], - "Indenting Case V") + "Indenting Case V"); /* Case VI: deep nesting |* one @@ -270,7 +270,7 @@ test( "bulletlist", function(t, md) { ] ] ] ], - "deep nested lists VI") + "deep nested lists VI"); /* Case VII: This one is just fruity! | * foo