refactor(jsdoc): change template/plugin loading

JSDoc now discovers templates, plugins, and other resources by using the same mechanism as `require()`. It does not search additional paths for these resources.

BREAKING CHANGE: Config files must specify different paths to resources. `jsdoc/path.getResourcePath` is removed.
This commit is contained in:
Jeff Williams 2019-12-01 18:52:09 -08:00
parent 3eaa696a4d
commit ca7a33f68b
7 changed files with 26 additions and 231 deletions

View File

@ -291,28 +291,6 @@ module.exports = (() => {
return cli; return cli;
}; };
function resolvePluginPaths(paths) {
const path = require('jsdoc/path');
const pluginPaths = [];
paths.forEach(plugin => {
const basename = path.basename(plugin);
const dirname = path.dirname(plugin);
const pluginPath = path.getResourcePath(dirname, basename);
if (!pluginPath) {
logger.error('Unable to find the plugin "%s"', plugin);
return;
}
pluginPaths.push( pluginPath );
});
return pluginPaths;
}
cli.createParser = () => { cli.createParser = () => {
const handlers = require('jsdoc/src/handlers'); const handlers = require('jsdoc/src/handlers');
const parser = require('jsdoc/src/parser'); const parser = require('jsdoc/src/parser');
@ -321,7 +299,6 @@ module.exports = (() => {
props.parser = parser.createParser(env.conf.parser); props.parser = parser.createParser(env.conf.parser);
if (env.conf.plugins) { if (env.conf.plugins) {
env.conf.plugins = resolvePluginPaths(env.conf.plugins);
plugins.installPlugins(env.conf.plugins, props.parser); plugins.installPlugins(env.conf.plugins, props.parser);
} }
@ -388,22 +365,17 @@ module.exports = (() => {
cli.generateDocs = () => { cli.generateDocs = () => {
let message; let message;
const path = require('jsdoc/path'); const path = require('path');
const resolver = require('jsdoc/tutorial/resolver'); const resolver = require('jsdoc/tutorial/resolver');
const taffy = require('taffydb').taffy; const taffy = require('taffydb').taffy;
let template; let template;
env.opts.template = (() => { env.opts.template = env.opts.template || path.join(__dirname, 'templates', 'default');
const publish = env.opts.template || 'templates/default';
const templatePath = path.getResourcePath(publish);
// if we didn't find the template, keep the user-specified value so the error message is
// useful
return templatePath || env.opts.template;
})();
try { try {
// TODO: Just look for a `publish` function in the specified module, not a `publish.js`
// file _and_ a `publish` function.
template = require(`${env.opts.template}/publish`); template = require(`${env.opts.template}/publish`);
} }
catch (e) { catch (e) {

View File

@ -3,7 +3,6 @@
* @module jsdoc/path * @module jsdoc/path
*/ */
const env = require('jsdoc/env'); const env = require('jsdoc/env');
const fs = require('fs');
const path = require('path'); const path = require('path');
function prefixReducer(previousPath, current) { function prefixReducer(previousPath, current) {
@ -77,93 +76,6 @@ exports.commonPrefix = (paths = []) => {
return prefix; return prefix;
}; };
/**
* Retrieve the fully qualified path to the requested resource.
*
* If the resource path is specified as a relative path, JSDoc searches for the resource in the
* following locations, in this order:
*
* 1. The current working directory
* 2. The directory where the JSDoc configuration file is located
* 3. The JSDoc directory
* 4. Anyplace where `require()` can find the resource (for example, in your project's
* `node_modules` directory)
*
* If the resource path is specified as a fully qualified path, JSDoc searches for the resource in
* the following locations, in this order:
*
* 1. The resource path
* 2. Anyplace where `require()` can find the resource (for example, in your project's
* `node_modules` directory)
*
* @param {string} filepath - The path to the requested resource. May be an absolute path; a path
* relative to the JSDoc directory; or a path relative to the current working directory.
* @param {string} [filename] - The filename of the requested resource.
* @return {string} The fully qualified path to the requested resource. Includes the filename if one
* was provided.
*/
exports.getResourcePath = (filepath, filename) => {
let result = null;
const searchDirs = [env.pwd, path.dirname(env.opts.configure || ''), env.dirname];
function exists(p) {
try {
fs.statSync(p);
return true;
}
catch (e) {
return false;
}
}
function resolve(p) {
try {
return require.resolve(p);
}
catch (e) {
return null;
}
}
function find(p) {
// does the requested path exist?
if ( exists(p) ) {
result = p;
}
else {
// can `require()` find the requested path?
result = resolve(p);
}
return Boolean(result);
}
filepath = path.join(filepath, filename || '');
// is the filepath absolute? if so, just use it
if ( path.isAbsolute(filepath) ) {
find(filepath);
}
else {
searchDirs.some(searchDir => {
if (searchDir) {
return find( path.resolve(path.join(searchDir, filepath)) );
}
else {
return false;
}
});
}
// if we still haven't found the resource, maybe it's an installed module
if (!result) {
result = resolve(filepath);
}
return result;
};
Object.keys(path).forEach(member => { Object.keys(path).forEach(member => {
exports[member] = path[member]; exports[member] = path[member];
}); });

View File

@ -7,7 +7,6 @@ const logger = require('jsdoc/util/logger');
const MarkdownIt = require('markdown-it'); const MarkdownIt = require('markdown-it');
const marked = require('marked'); const marked = require('marked');
const mda = require('markdown-it-anchor'); const mda = require('markdown-it-anchor');
const path = require('jsdoc/path');
/** /**
* Enumeration of Markdown parsers that are available. * Enumeration of Markdown parsers that are available.
@ -138,25 +137,18 @@ function unencodeQuotes(source) {
*/ */
function getHighlighter(conf) { function getHighlighter(conf) {
let highlighter; let highlighter;
let highlighterPath;
switch (typeof conf.highlight) { switch (typeof conf.highlight) {
case 'string': case 'string':
highlighterPath = path.getResourcePath(conf.highlight); try {
highlighter = require(conf.highlight).highlight;
if (highlighterPath) { } catch (e) {
highlighter = require(highlighterPath).highlight; logger.error(e);
}
if (typeof highlighter !== 'function') { if (typeof highlighter !== 'function') {
logger.error('The syntax highlighting module "%s" does not assign a method ' + logger.error(`The syntax highlighting module ${conf.highlight} does not assign a ` +
'to exports.highlight. Using the default syntax highlighter.', 'method to `exports.highlight`. Using the default syntax highlighter.');
conf.highlight);
highlighter = highlight;
}
}
else {
logger.error('Unable to find the syntax highlighting module "%s". Using the ' +
'default syntax highlighter.', conf.highlight);
highlighter = highlight; highlighter = highlight;
} }
@ -257,8 +249,8 @@ function getParseFunction(parserName, conf) {
return parserFunction; return parserFunction;
default: default:
logger.error('Unrecognized Markdown parser "%s". Markdown support is disabled.', logger.error(`Unrecognized Markdown parser "${parserName}". Markdown support is ` +
parserName); 'disabled.');
return undefined; return undefined;
} }

View File

@ -453,8 +453,7 @@ exports.publish = (taffyData, opts, tutorials) => {
// set up templating // set up templating
view.layout = conf.default.layoutFile ? view.layout = conf.default.layoutFile ?
path.getResourcePath(path.dirname(conf.default.layoutFile), path.resolve(conf.default.layoutFile) :
path.basename(conf.default.layoutFile) ) :
'layout.tmpl'; 'layout.tmpl';
// set up tutorials for helper // set up tutorials for helper

View File

@ -1,5 +1,5 @@
'use strict'; 'use strict';
exports.highlight = function(code, language) { exports.highlight = function(code, language) {
return '<pre><code>' + code + ' in this language: ' + language + '</code></pre>'; return `<pre><code>${code} in this language: ${language}</code></pre>`;
}; };

View File

@ -22,10 +22,6 @@ describe('jsdoc/path', () => {
expect(path.commonPrefix).toBeFunction(); expect(path.commonPrefix).toBeFunction();
}); });
it('should export a "getResourcePath" function', () => {
expect(path.getResourcePath).toBeFunction();
});
describe('commonPrefix', () => { describe('commonPrefix', () => {
let oldPwd; let oldPwd;
let cwd; let cwd;
@ -124,87 +120,4 @@ describe('jsdoc/path', () => {
}); });
} }
}); });
describe('getResourcePath', () => {
let oldConf;
let oldPwd;
beforeEach(() => {
oldConf = env.opts.configure;
oldPwd = env.pwd;
env.opts.configure = path.join(env.dirname, 'lib', 'conf.json');
env.pwd = __dirname;
});
afterEach(() => {
env.opts.configure = oldConf;
env.pwd = oldPwd;
});
it('resolves pwd-relative path that exists', () => {
const resolved = path.getResourcePath('doclet');
expect(resolved).toBe( path.join(__dirname, 'doclet.js') );
});
it('resolves relative to ./ path that exists', () => {
// `path.join` discards the `.`, so we join with `path.sep` instead
const p = ['.', 'util'].join(path.sep);
const resolved = path.getResourcePath(p);
expect(resolved).toBe( path.join(__dirname, 'util') );
});
it('resolves relative to ../ path that exists', () => {
const p = path.join('..', 'jsdoc', 'util');
const resolved = path.getResourcePath(p);
expect(resolved).toBe( path.join(__dirname, 'util') );
});
it('resolves path using node_modules/', () => {
const resolved = path.getResourcePath('node_modules', 'catharsis');
expect(resolved).toBe( path.join(env.dirname, 'node_modules', 'catharsis') );
});
it('resolves paths relative to the configuration file\'s path', () => {
const resolved = path.getResourcePath('jsdoc');
expect(resolved).toBe( path.join(env.dirname, 'lib', 'jsdoc') );
});
it('resolves paths relative to the JSDoc path', () => {
const resolved = path.getResourcePath( path.join('lib', 'jsdoc') );
expect(resolved).toBe( path.join(env.dirname, 'lib', 'jsdoc') );
});
it('resolves installed module', () => {
const resolved = path.getResourcePath('catharsis');
expect(resolved).toBe( path.join(env.dirname, 'node_modules', 'catharsis',
'catharsis.js') );
});
it('fails to find a relative path that does not exist', () => {
const resolved = path.getResourcePath('foo');
expect(resolved).toBeNull();
});
it('finds an absolute path that does exist', () => {
const p = path.join(env.dirname, 'lib');
const resolved = path.getResourcePath(p);
expect(resolved).toBe(p);
});
it('fails to find an absolute path that does not exist', () => {
const resolved = path.getResourcePath( path.join(env.dirname, 'foo') );
expect(resolved).toBeNull();
});
});
}); });

View File

@ -2,6 +2,7 @@ describe('jsdoc/util/markdown', () => {
const env = require('jsdoc/env'); const env = require('jsdoc/env');
const logger = require('jsdoc/util/logger'); const logger = require('jsdoc/util/logger');
const markdown = require('jsdoc/util/markdown'); const markdown = require('jsdoc/util/markdown');
const path = require('path');
it('should exist', () => { it('should exist', () => {
expect(markdown).toBeObject(); expect(markdown).toBeObject();
@ -152,7 +153,9 @@ describe('jsdoc/util/markdown', () => {
it('should support `highlight` as the path to a highlighter module', () => { it('should support `highlight` as the path to a highlighter module', () => {
let parser; let parser;
setMarkdownConf({ highlight: 'test/fixtures/markdown/highlighter' }); setMarkdownConf({
highlight: path.join(env.dirname, 'test/fixtures/markdown/highlighter')
});
parser = markdown.getParser(); parser = markdown.getParser();
expect(parser('```js\nhello\n```')).toBe( expect(parser('```js\nhello\n```')).toBe(
@ -163,7 +166,9 @@ describe('jsdoc/util/markdown', () => {
it('should log an error if the `highlight` module cannot be found', () => { it('should log an error if the `highlight` module cannot be found', () => {
spyOn(logger, 'error'); spyOn(logger, 'error');
setMarkdownConf({ highlight: 'foo/bar/baz' }); setMarkdownConf({
highlight: 'foo/bar/baz'
});
markdown.getParser(); markdown.getParser();
expect(logger.error).toHaveBeenCalled(); expect(logger.error).toHaveBeenCalled();
@ -173,7 +178,9 @@ describe('jsdoc/util/markdown', () => {
'`exports.highlight`', () => { '`exports.highlight`', () => {
spyOn(logger, 'error'); spyOn(logger, 'error');
setMarkdownConf({ highlight: 'test/fixtures/markdown/badhighlighter' }); setMarkdownConf({
highlight: path.join(env.dirname, 'test/fixtures/markdown/badhighlighter')
});
markdown.getParser(); markdown.getParser();
expect(logger.error).toHaveBeenCalled(); expect(logger.error).toHaveBeenCalled();