diff --git a/cli.js b/cli.js index c028be3d..bec33e04 100644 --- a/cli.js +++ b/cli.js @@ -1,4 +1,3 @@ -/* global java */ /* eslint no-process-exit:0, strict: [2, "function"] */ /** * Helper methods for running JSDoc on the command line. @@ -16,6 +15,7 @@ module.exports = (function() { var app = require('jsdoc/app'); var env = require('jsdoc/env'); var logger = require('jsdoc/util/logger'); +var stripBom = require('jsdoc/util/stripbom'); var stripJsonComments = require('strip-json-comments'); var Promise = require('bluebird'); @@ -38,7 +38,7 @@ cli.setVersionInfo = function() { var path = require('path'); // allow this to throw--something is really wrong if we can't read our own package file - var info = JSON.parse( fs.readFileSync(path.join(env.dirname, 'package.json'), 'utf8') ); + var info = JSON.parse( stripBom.strip(fs.readFileSync(path.join(env.dirname, 'package.json'), 'utf8')) ); env.version = { number: info.version, diff --git a/lib/jsdoc/config.js b/lib/jsdoc/config.js index 9ec5b9d2..5a7350ff 100644 --- a/lib/jsdoc/config.js +++ b/lib/jsdoc/config.js @@ -9,6 +9,8 @@ */ 'use strict'; +var stripBom = require('jsdoc/util/stripbom'); + function mergeRecurse(target, source) { Object.keys(source).forEach(function(p) { if ( source[p].constructor === Object ) { @@ -48,7 +50,7 @@ var defaults = { @param {string} [json] - The contents of config.json. */ function Config(json) { - json = JSON.parse( (json || '{}') ); + json = JSON.parse( (stripBom.strip(json) || '{}') ); this._config = mergeRecurse(defaults, json); } diff --git a/lib/jsdoc/package.js b/lib/jsdoc/package.js index dea3a5b3..f7882786 100644 --- a/lib/jsdoc/package.js +++ b/lib/jsdoc/package.js @@ -1,6 +1,7 @@ 'use strict'; var logger = require('jsdoc/util/logger'); +var stripBom = require('jsdoc/util/stripbom'); /** * Provides access to information about a JavaScript package. @@ -79,7 +80,7 @@ exports.Package = function(json) { this.kind = 'package'; try { - packageInfo = JSON.parse(json || '{}'); + packageInfo = JSON.parse(stripBom.strip(json) || '{}'); } catch (e) { logger.error('Unable to parse the package file: %s', e.message); diff --git a/lib/jsdoc/tutorial/resolver.js b/lib/jsdoc/tutorial/resolver.js index cffa44e6..e7f3d142 100644 --- a/lib/jsdoc/tutorial/resolver.js +++ b/lib/jsdoc/tutorial/resolver.js @@ -13,6 +13,7 @@ var env = require('jsdoc/env'); var fs = require('jsdoc/fs'); var logger = require('jsdoc/util/logger'); var path = require('path'); +var stripBom = require('jsdoc/util/stripbom'); var tutorial = require('jsdoc/tutorial'); var hasOwnProp = Object.prototype.hasOwnProperty; @@ -138,7 +139,7 @@ exports.load = function(filepath) { // configuration file case 'json': - var meta = JSON.parse(content); + var meta = JSON.parse(stripBom.strip(content)); addTutorialConf(name, meta); // don't add this as a tutorial return; diff --git a/lib/jsdoc/util/stripbom.js b/lib/jsdoc/util/stripbom.js new file mode 100644 index 00000000..3ca1c3a3 --- /dev/null +++ b/lib/jsdoc/util/stripbom.js @@ -0,0 +1,19 @@ +'use strict'; + +/** + * Module to strip the leading BOM, if present, from UTF-8 files. + * @module + * @private + */ + +/** + * Strip the leading BOM, if present, from a string. + * + * @private + * @param {string} text - The string to strip. + * @return {string} The stripped string. + */ +exports.strip = function(text) { + text = text || ''; + return text.replace(/^\uFEFF/, ''); +}; diff --git a/test/specs/jsdoc/config.js b/test/specs/jsdoc/config.js index 12ebde20..0753e9cf 100644 --- a/test/specs/jsdoc/config.js +++ b/test/specs/jsdoc/config.js @@ -36,6 +36,16 @@ describe('jsdoc/config', function() { }); }); + describe('constructor with leading BOM', function() { + it('should be possible to construct a Config with JSON that has a leading BOM', function() { + function getConfig() { + return new Config('\uFEFF{}').get(); + } + + expect(getConfig).not.toThrow(); + }); + }); + describe('constructor with plugins value', function() { it('should be possible to construct a Config with JSON of an object literal that has a plugin value', function() { var config = new Config('{"plugins":[42]}').get(); diff --git a/test/specs/jsdoc/package.js b/test/specs/jsdoc/package.js index bd71b0db..47474d92 100644 --- a/test/specs/jsdoc/package.js +++ b/test/specs/jsdoc/package.js @@ -45,6 +45,14 @@ describe('jsdoc/package', function() { expect(newPackage).not.toThrow(); }); + it('should accept a JSON-format string with a leading BOM', function() { + function newPackage() { + return new Package('\uFEFF{}'); + } + + expect(newPackage).not.toThrow(); + }); + it('should work when called with no arguments', function() { function newPackage() { return new Package(); diff --git a/test/specs/jsdoc/tutorial/resolver.js b/test/specs/jsdoc/tutorial/resolver.js index 4da23287..cf03422c 100644 --- a/test/specs/jsdoc/tutorial/resolver.js +++ b/test/specs/jsdoc/tutorial/resolver.js @@ -142,6 +142,16 @@ describe('jsdoc/tutorial/resolver', function() { expect(test6.type).toBe(tutorial.TYPES.HTML); expect(constr.type).toBe(tutorial.TYPES.MARKDOWN); }); + + it('JSON files with a leading BOM are handled correctly', function() { + resetRootTutorial(); + + function loadBomTutorials() { + resolver.load(env.dirname + '/test/tutorials/bom'); + } + + expect(loadBomTutorials).not.toThrow(); + }); }); // resolve diff --git a/test/specs/jsdoc/util/stripbom.js b/test/specs/jsdoc/util/stripbom.js new file mode 100644 index 00000000..55783cf1 --- /dev/null +++ b/test/specs/jsdoc/util/stripbom.js @@ -0,0 +1,31 @@ +'use strict'; + +describe('jsdoc/util/stripbom', function() { + var stripBom = require('jsdoc/util/stripbom'); + + it('should exist', function() { + expect(typeof stripBom).toBe('object'); + }); + + it('should export a "strip" method', function() { + expect(typeof stripBom.strip).toBe('function'); + }); + + describe('strip', function() { + it('should strip the leading BOM when present', function() { + var result = stripBom.strip('\uFEFFHello there!'); + + expect(result).toBe('Hello there!'); + }); + + it('should not change the text when no leading BOM is present', function() { + var result = stripBom.strip('Hello there!'); + + expect(result).toBe('Hello there!'); + }); + + it('should return an empty string when the text is null or undefined', function() { + expect(stripBom.strip()).toBe(''); + }); + }); +}); diff --git a/test/tutorials/bom/bom.json b/test/tutorials/bom/bom.json new file mode 100644 index 00000000..f1177889 --- /dev/null +++ b/test/tutorials/bom/bom.json @@ -0,0 +1 @@ +{}