From 8dfb58ffc49ac585980eaca63f373b3b9cdf2751 Mon Sep 17 00:00:00 2001 From: Momtchil Momtchev Date: Tue, 30 Nov 2021 19:55:53 +0100 Subject: [PATCH 01/10] implement a plugin framework --- __tests__/__snapshots__/bin.js.snap | 156 +++++++++++++++++++++++++ __tests__/__snapshots__/test.js.snap | 164 +++++++++++++++++++++++++++ __tests__/bin.js | 8 +- __tests__/fixture/plugin.txt | 6 + __tests__/test.js | 25 ++++ docs/POLYGLOT.md | 2 - src/commands/shared_options.js | 4 + src/config.js | 8 +- src/index.js | 37 +++++- src/merge_config.js | 30 +++++ src/mock_plugin.js | 50 ++++++++ src/plugin_api.js | 8 ++ 12 files changed, 490 insertions(+), 8 deletions(-) create mode 100644 __tests__/fixture/plugin.txt delete mode 100644 docs/POLYGLOT.md create mode 100644 src/mock_plugin.js create mode 100644 src/plugin_api.js diff --git a/__tests__/__snapshots__/bin.js.snap b/__tests__/__snapshots__/bin.js.snap index e89925da4..b31635f72 100644 --- a/__tests__/__snapshots__/bin.js.snap +++ b/__tests__/__snapshots__/bin.js.snap @@ -2024,6 +2024,162 @@ f5 comment exports[`lint command generates lint output 1`] = `""`; +exports[`load a plugin 1`] = ` +Array [ + Object { + "augments": Array [], + "context": Object { + "file": "[path]", + "loc": Object { + "end": Object { + "column": 2, + "line": 8, + }, + "start": Object { + "column": 0, + "line": 5, + }, + }, + }, + "description": Object { + "children": Array [ + Object { + "children": Array [ + Object { + "type": "text", + "value": "This function returns the number one.", + }, + ], + "type": "paragraph", + }, + ], + "type": "root", + }, + "examples": Array [], + "implements": Array [], + "kind": "function", + "loc": Object { + "end": Object { + "column": 3, + "line": 4, + }, + "start": Object { + "column": 0, + "line": 1, + }, + }, + "members": Object { + "events": Array [], + "global": Array [], + "inner": Array [], + "instance": Array [], + "static": Array [], + }, + "name": "simple.input", + "namespace": "simple.input", + "params": Array [], + "path": Array [ + Object { + "kind": "function", + "name": "simple.input", + }, + ], + "properties": Array [], + "returns": Array [ + Object { + "description": Object { + "children": Array [ + Object { + "children": Array [ + Object { + "type": "text", + "value": "numberone", + }, + ], + "type": "paragraph", + }, + ], + "type": "root", + }, + "title": "returns", + "type": Object { + "name": "number", + "type": "NameExpression", + }, + }, + ], + "sees": Array [], + "tags": Array [ + Object { + "description": "numberone", + "lineNumber": 2, + "title": "returns", + "type": Object { + "name": "number", + "type": "NameExpression", + }, + }, + ], + "throws": Array [], + "todos": Array [], + "yields": Array [], + }, + Object { + "after": "", + "api": false, + "augments": Array [], + "context": Object { + "file": "[path]", + "loc": Object { + "end": Object { + "column": 4, + "line": 5, + }, + "start": Object { + "column": 1, + "line": 5, + }, + }, + }, + "end": 19, + "examples": Array [], + "implements": Array [], + "loc": Object { + "end": Object { + "column": 1, + "line": 2, + }, + "start": Object { + "column": 1, + "line": 0, + }, + }, + "members": Object { + "events": Array [], + "global": Array [], + "inner": Array [], + "instance": Array [], + "static": Array [], + }, + "namespace": "", + "params": Array [], + "path": Array [], + "properties": Array [], + "returns": Array [], + "sees": Array [], + "start": 0, + "tags": Array [], + "throws": Array [], + "todos": Array [], + "type": "CommentBlock", + "value": "* + * @method dummy + ", + "yields": Array [], + }, +] +`; + exports[`should use browser resolve 1`] = ` Array [ Object { diff --git a/__tests__/__snapshots__/test.js.snap b/__tests__/__snapshots__/test.js.snap index e8a0a2294..3d392be7d 100644 --- a/__tests__/__snapshots__/test.js.snap +++ b/__tests__/__snapshots__/test.js.snap @@ -287,6 +287,170 @@ Array [ ] `; +exports[`Check that plugins are loaded 1`] = ` +Array [ + Object { + "augments": Array [], + "context": Object { + "loc": SourceLocation { + "end": Position { + "column": 2, + "line": 8, + }, + "filename": undefined, + "identifierName": undefined, + "start": Position { + "column": 0, + "line": 5, + }, + }, + }, + "description": Object { + "children": Array [ + Object { + "children": Array [ + Object { + "type": "text", + "value": "This function returns the number one.", + }, + ], + "type": "paragraph", + }, + ], + "type": "root", + }, + "errors": Array [], + "examples": Array [], + "implements": Array [], + "kind": "function", + "loc": SourceLocation { + "end": Position { + "column": 3, + "line": 4, + }, + "filename": undefined, + "identifierName": undefined, + "start": Position { + "column": 0, + "line": 1, + }, + }, + "members": Object { + "events": Array [], + "global": Array [], + "inner": Array [], + "instance": Array [], + "static": Array [], + }, + "name": "simple.input", + "namespace": "simple.input", + "params": Array [], + "path": Array [ + Object { + "kind": "function", + "name": "simple.input", + }, + ], + "properties": Array [], + "returns": Array [ + Object { + "description": Object { + "children": Array [ + Object { + "children": Array [ + Object { + "type": "text", + "value": "numberone", + }, + ], + "type": "paragraph", + }, + ], + "type": "root", + }, + "title": "returns", + "type": Object { + "name": "number", + "type": "NameExpression", + }, + }, + ], + "sees": Array [], + "tags": Array [ + Object { + "description": "numberone", + "lineNumber": 2, + "title": "returns", + "type": Object { + "name": "number", + "type": "NameExpression", + }, + }, + ], + "throws": Array [], + "todos": Array [], + "yields": Array [], + }, + Object { + "after": "", + "api": false, + "augments": Array [], + "context": Object { + "loc": Object { + "end": Object { + "column": 4, + "line": 5, + }, + "start": Object { + "column": 1, + "line": 5, + }, + }, + }, + "end": 19, + "errors": Array [ + Object { + "message": "could not determine @name for hierarchy", + }, + ], + "examples": Array [], + "implements": Array [], + "loc": Object { + "end": Object { + "column": 1, + "line": 2, + }, + "start": Object { + "column": 1, + "line": 0, + }, + }, + "members": Object { + "events": Array [], + "global": Array [], + "inner": Array [], + "instance": Array [], + "static": Array [], + }, + "namespace": "", + "params": Array [], + "path": Array [], + "properties": Array [], + "returns": Array [], + "sees": Array [], + "start": 0, + "tags": Array [], + "throws": Array [], + "todos": Array [], + "type": "CommentBlock", + "value": "* + * @method dummy + ", + "yields": Array [], + }, +] +`; + exports[`Use Source attribute only 1`] = ` Array [ Object { diff --git a/__tests__/bin.js b/__tests__/bin.js index 7f46bca1e..e668ad26f 100644 --- a/__tests__/bin.js +++ b/__tests__/bin.js @@ -3,7 +3,6 @@ import path from 'path'; import os from 'os'; import { exec } from 'child_process'; -import tmp from 'tmp'; import fs from 'fs-extra'; import { fileURLToPath } from 'url'; @@ -60,6 +59,13 @@ test.skip('defaults to parsing package.json main', async function () { expect(data.length).toBeTruthy(); }); +test('load a plugin', async function () { + const data = await documentation([ + 'build fixture/simple.input.js fixture/plugin.txt --plugin=../src/mock_plugin.js' + ]); + expect(normalize(data)).toMatchSnapshot(); +}); + test('accepts config file', async function () { const data = await documentation([ 'build fixture/sorting/input.js -c fixture/config.json' diff --git a/__tests__/fixture/plugin.txt b/__tests__/fixture/plugin.txt new file mode 100644 index 000000000..2244c29af --- /dev/null +++ b/__tests__/fixture/plugin.txt @@ -0,0 +1,6 @@ +/** + * @method test + */ + +test + diff --git a/__tests__/test.js b/__tests__/test.js index aa9037c0c..d7263057e 100644 --- a/__tests__/test.js +++ b/__tests__/test.js @@ -13,6 +13,7 @@ import _ from 'lodash'; import chdir from 'chdir'; import config from '../src/config'; import { fileURLToPath } from 'url'; +import { jest } from '@jest/globals'; const UPDATE = !!process.env.UPDATE; const __filename = fileURLToPath(import.meta.url); @@ -71,6 +72,30 @@ test('Check that external modules could parse as input', async function () { expect(result).toMatchSnapshot(); }); +test('Check that plugins are loaded', async function () { + const initCb = jest.fn(); + const parseCb = jest.fn(); + const mockPlugin = await import('../src/mock_plugin.js'); + mockPlugin.mockInit(initCb, parseCb); + + const dir = path.join(__dirname, 'fixture'); + const result = await documentation.build( + [path.join(dir, 'simple.input.js'), path.join(dir, 'plugin.txt')], + { plugin: ['./mock_plugin.js'] } + ); + normalize(result); + expect(result).toMatchSnapshot(); + + expect(initCb.mock.calls.length).toBe(1); + expect(parseCb.mock.calls.length).toBe(2); + expect( + parseCb.mock.calls[0][0].file.includes('fixture/plugin.txt') + ).toBeTruthy(); + expect( + parseCb.mock.calls[1][0].file.includes('fixture/simple.input.js') + ).toBeTruthy(); +}); + test('bad input', function () { glob .sync(path.join(__dirname, 'fixture/bad', '*.input.js')) diff --git a/docs/POLYGLOT.md b/docs/POLYGLOT.md deleted file mode 100644 index f0005ae0b..000000000 --- a/docs/POLYGLOT.md +++ /dev/null @@ -1,2 +0,0 @@ -🚨 Polyglot mode is now deprecated. It will be replaced by a pluggable -input system in future versions. 🚨 diff --git a/src/commands/shared_options.js b/src/commands/shared_options.js index 0379debb8..fae99fa00 100644 --- a/src/commands/shared_options.js +++ b/src/commands/shared_options.js @@ -45,6 +45,10 @@ export const sharedInputOptions = { type: 'array', alias: 'pe' }, + plugin: { + type: 'array', + describe: 'load a plugin' + }, access: { describe: 'Include only comments with a given access level, out of private, ' + diff --git a/src/config.js b/src/config.js index 17d49d3d4..260c67e03 100644 --- a/src/config.js +++ b/src/config.js @@ -1,11 +1,11 @@ const defaultConfig = { - // package.json ignored and don't get project infromation + // package.json ignored and don't get project information 'no-package': false, - // Extenstions which by dafault are parse + // Extensions which by default are parsed parseExtension: ['.mjs', '.js', '.jsx', '.es5', '.es6', '.vue', '.ts', '.tsx'] }; -function normalaze(config, global) { +function normalize(config, global) { if (config.parseExtension) { config.parseExtension = Array.from( new Set([...config.parseExtension, ...global.parseExtension]) @@ -24,6 +24,6 @@ export default { this.globalConfig.parseExtension = [...defaultConfig.parseExtension]; }, add(parameters) { - Object.assign(this.globalConfig, normalaze(parameters, this.globalConfig)); + Object.assign(this.globalConfig, normalize(parameters, this.globalConfig)); } }; diff --git a/src/index.js b/src/index.js index abe624c7c..72d89085a 100644 --- a/src/index.js +++ b/src/index.js @@ -26,6 +26,7 @@ import md from './output/markdown.js'; import json from './output/json.js'; import createFormatters from './output/util/formatters.js'; import LinkerStack from './output/util/linker_stack.js'; +import pluginAPI from './plugin_api.js'; /** * Build a pipeline of comment handlers. @@ -76,7 +77,24 @@ export function expandInputs(indexes, config) { return shallow(indexes, config); } - return dependency(indexes, config); + let idxShallow = []; + if (config.plugin) { + for (const plugin of config.plugin) { + if (config._module[plugin].shallow) { + idxShallow = idxShallow.concat( + indexes.filter(idx => + config._module[plugin].shallow(idx, config, pluginAPI) + ) + ); + } + } + } + const depsShallow = shallow(idxShallow, config); + + const idxFull = indexes.filter(idx => !idxShallow.includes(idx)); + const depsFull = dependency(idxFull, config); + + return Promise.all([depsShallow, depsFull]).then(([a, b]) => a.concat(b)); } function buildInternal(inputsAndConfig) { @@ -104,6 +122,14 @@ function buildInternal(inputsAndConfig) { ]); const extractedComments = _.flatMap(inputs, function (sourceFile) { + if (config.plugin) { + for (const plugin of config.plugin) { + if (config._module[plugin].parse) { + const r = config._module[plugin].parse(sourceFile, config, pluginAPI); + if (r) return r.map(buildPipeline); + } + } + } return parseJavaScript(sourceFile, config).map(buildPipeline); }).filter(Boolean); @@ -132,6 +158,14 @@ function lintInternal(inputsAndConfig) { ]); const extractedComments = _.flatMap(inputs, sourceFile => { + if (config.plugin) { + for (const plugin of config.plugin) { + if (config._module[plugin].parse) { + const r = config._module[plugin].parse(sourceFile, config, pluginAPI); + if (r) return r.map(lintPipeline); + } + } + } return parseJavaScript(sourceFile, config).map(lintPipeline); }).filter(Boolean); @@ -180,6 +214,7 @@ export const lint = (indexes, args) => * @param {Array} args.external a string regex / glob match pattern * that defines what external modules will be whitelisted and included in the * generated documentation. + * @param {Array} [args.plugin=[]] load plugins * @param {boolean} [args.shallow=false] whether to avoid dependency parsing * even in JavaScript code. * @param {Array} [args.order=[]] optional array that diff --git a/src/merge_config.js b/src/merge_config.js index cea2591c6..eef314f6f 100644 --- a/src/merge_config.js +++ b/src/merge_config.js @@ -79,6 +79,36 @@ export default async function mergeConfig(config = {}) { conf.add(config); conf.add(await readConfigFile(conf.globalConfig.config)); conf.add(await readPackage(conf.globalConfig['no-package'])); + if (conf.globalConfig.plugin) { + await loadPlugins(conf.globalConfig); + } return conf.globalConfig; } + +/** + * Load the external plugins + * + * @param {Object} configuration plugins section of the configuration + * @returns {void} + */ +async function loadPlugins(config) { + if (!config._module) + Object.defineProperty(config, '_module', { + enumerable: false, + writable: false, + configurable: false, + value: {} + }); + for (const plugin of config.plugin) { + try { + config._module[plugin] = await import(plugin); + if (config._module[plugin].init) { + await config._module[plugin].init(); + } + } catch (e) { + console.error(`Failed loading ${plugin}`); + throw e; + } + } +} diff --git a/src/mock_plugin.js b/src/mock_plugin.js new file mode 100644 index 000000000..0471099c5 --- /dev/null +++ b/src/mock_plugin.js @@ -0,0 +1,50 @@ +let initCb, parseCb, depCb, dummy; + +export function mockInit(init, parse, dep) { + initCb = init; + parseCb = parse; + depCb = dep; +} + +export async function init() { + if (initCb) initCb(...arguments); + dummy = [ + { + after: '', + api: false, + start: 0, + end: 19, + type: 'CommentBlock', + value: '*\n * @method dummy\n ', + context: { + file: 'plugin.txt', + loc: { start: { line: 5, column: 1 }, end: { line: 5, column: 4 } } + }, + loc: { start: { line: 0, column: 1 }, end: { line: 2, column: 1 } }, + augments: [], + errors: [], + examples: [], + implements: [], + params: [], + properties: [], + returns: [], + sees: [], + tags: [], + throws: [], + todos: [], + yields: [] + } + ]; +} + +export function parse(file) { + if (parseCb) parseCb(...arguments); + if (file.file.includes('plugin.txt')) return dummy; + return false; +} + +export function shallow(file) { + if (depCb) depCb(...arguments); + if (file.includes('plugin.txt')) return true; + return false; +} diff --git a/src/plugin_api.js b/src/plugin_api.js new file mode 100644 index 000000000..6cfdda6d7 --- /dev/null +++ b/src/plugin_api.js @@ -0,0 +1,8 @@ +import parseJSDoc from './parse.js'; +import isJSDocComment from './is_jsdoc_comment.js'; +const pluginAPI = { + parseJSDoc, + isJSDocComment +}; + +export default pluginAPI; From 17b8f8ad2420e3d475a2f4475890fb6804d6cefa Mon Sep 17 00:00:00 2001 From: Momtchil Momtchev Date: Wed, 1 Dec 2021 12:40:23 +0100 Subject: [PATCH 02/10] pass the configuration object to the plugin init --- __tests__/test.js | 4 +++- src/merge_config.js | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/__tests__/test.js b/__tests__/test.js index d7263057e..379b96006 100644 --- a/__tests__/test.js +++ b/__tests__/test.js @@ -81,12 +81,14 @@ test('Check that plugins are loaded', async function () { const dir = path.join(__dirname, 'fixture'); const result = await documentation.build( [path.join(dir, 'simple.input.js'), path.join(dir, 'plugin.txt')], - { plugin: ['./mock_plugin.js'] } + { plugin: ['./mock_plugin.js'], order: 'test' } ); normalize(result); expect(result).toMatchSnapshot(); expect(initCb.mock.calls.length).toBe(1); + expect(initCb.mock.calls[0][0].order).toBe('test'); + expect(parseCb.mock.calls.length).toBe(2); expect( parseCb.mock.calls[0][0].file.includes('fixture/plugin.txt') diff --git a/src/merge_config.js b/src/merge_config.js index eef314f6f..6ad688d8c 100644 --- a/src/merge_config.js +++ b/src/merge_config.js @@ -104,7 +104,7 @@ async function loadPlugins(config) { try { config._module[plugin] = await import(plugin); if (config._module[plugin].init) { - await config._module[plugin].init(); + await config._module[plugin].init(config); } } catch (e) { console.error(`Failed loading ${plugin}`); From 39c4fb1de088d9fe475f1f7de6b5a83da89a7f8a Mon Sep 17 00:00:00 2001 From: Momtchil Momtchev Date: Wed, 1 Dec 2021 18:19:50 +0100 Subject: [PATCH 03/10] absorb context info from the parsing phase --- src/parse.js | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/parse.js b/src/parse.js index a10c7b2b8..cd63bf99e 100644 --- a/src/parse.js +++ b/src/parse.js @@ -666,10 +666,24 @@ export default function parseJSDoc(comment, loc, context) { } }); + for (const tag of [ + 'kind', + 'name', + 'returns', + 'params', + 'properties', + 'errors', + 'augments', + 'throws', + 'yields', + 'implements' + ]) + if (context[tag]) result[tag] = context[tag]; + // Using the @name tag, or any other tag that sets the name of a comment, // disconnects the comment from its surrounding code. if (context && result.name) { - delete context.ast; + if (context.ast) delete context.ast; } return result; From 8f917615e013821c4662e5bd10708b895798a180 Mon Sep 17 00:00:00 2001 From: Momtchil Momtchev Date: Wed, 1 Dec 2021 19:05:04 +0100 Subject: [PATCH 04/10] allow explicit jsdoc tags to override the parsing --- src/parse.js | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/parse.js b/src/parse.js index cd63bf99e..b55e7f3f8 100644 --- a/src/parse.js +++ b/src/parse.js @@ -636,6 +636,20 @@ export default function parseJSDoc(comment, loc, context) { result.todos = []; result.yields = []; + for (const tag of [ + 'kind', + 'name', + 'returns', + 'params', + 'properties', + 'errors', + 'augments', + 'throws', + 'yields', + 'implements' + ]) + if (context[tag]) result[tag] = context[tag]; + if (result.description) { result.description = parseMarkdown(result.description); } @@ -666,20 +680,6 @@ export default function parseJSDoc(comment, loc, context) { } }); - for (const tag of [ - 'kind', - 'name', - 'returns', - 'params', - 'properties', - 'errors', - 'augments', - 'throws', - 'yields', - 'implements' - ]) - if (context[tag]) result[tag] = context[tag]; - // Using the @name tag, or any other tag that sets the name of a comment, // disconnects the comment from its surrounding code. if (context && result.name) { From 1137f380218e5a391c4a00024a3aea208f2cdbb7 Mon Sep 17 00:00:00 2001 From: Momtchil Momtchev Date: Thu, 2 Dec 2021 12:28:25 +0100 Subject: [PATCH 05/10] check using/overriding context data from the plugin --- __tests__/__snapshots__/bin.js.snap | 151 +++++++++++++++--- __tests__/__snapshots__/test.js.snap | 227 ++++++++++++++++++++++----- __tests__/test.js | 15 +- src/mock_plugin.js | 49 +++--- src/parse.js | 28 ++-- 5 files changed, 379 insertions(+), 91 deletions(-) diff --git a/__tests__/__snapshots__/bin.js.snap b/__tests__/__snapshots__/bin.js.snap index b31635f72..70fda8853 100644 --- a/__tests__/__snapshots__/bin.js.snap +++ b/__tests__/__snapshots__/bin.js.snap @@ -2125,8 +2125,6 @@ Array [ "yields": Array [], }, Object { - "after": "", - "api": false, "augments": Array [], "context": Object { "file": "[path]", @@ -2141,19 +2139,128 @@ Array [ }, }, }, - "end": 19, + "description": "", "examples": Array [], "implements": Array [], - "loc": Object { - "end": Object { - "column": 1, - "line": 2, + "kind": "function", + "members": Object { + "events": Array [], + "global": Array [], + "inner": Array [], + "instance": Array [], + "static": Array [], + }, + "name": "dummy", + "namespace": "dummy", + "params": Array [], + "path": Array [ + Object { + "kind": "function", + "name": "dummy", }, - "start": Object { - "column": 1, - "line": 0, + ], + "properties": Array [], + "returns": Array [], + "sees": Array [], + "tags": Array [ + Object { + "description": null, + "lineNumber": 1, + "name": "dummy", + "title": "method", + }, + ], + "throws": Array [], + "todos": Array [], + "yields": Array [], + }, + Object { + "augments": Array [], + "context": Object { + "file": "[path]", + "kind": "method", + "loc": Object { + "end": Object { + "column": 4, + "line": 5, + }, + "start": Object { + "column": 1, + "line": 5, + }, + }, + "name": "dummy_method", + }, + "description": "", + "examples": Array [], + "implements": Array [], + "kind": "method", + "members": Object { + "events": Array [], + "global": Array [], + "inner": Array [], + "instance": Array [], + "static": Array [], + }, + "name": "dummy_method", + "namespace": "dummy_method", + "params": Array [ + Object { + "lineNumber": 1, + "name": "dummy_param", + "title": "param", + "type": Object { + "name": "number", + "type": "NameExpression", + }, + }, + ], + "path": Array [ + Object { + "kind": "method", + "name": "dummy_method", }, + ], + "properties": Array [], + "returns": Array [], + "sees": Array [], + "tags": Array [ + Object { + "description": null, + "lineNumber": 1, + "name": "dummy_param", + "title": "param", + "type": Object { + "name": "number", + "type": "NameExpression", + }, + }, + ], + "throws": Array [], + "todos": Array [], + "yields": Array [], + }, + Object { + "augments": Array [], + "context": Object { + "file": "[path]", + "kind": "SHOULD_NOT_APPEAR_IN_THE_RESULT", + "loc": Object { + "end": Object { + "column": 4, + "line": 5, + }, + "start": Object { + "column": 1, + "line": 5, + }, + }, + "name": "SHOULD_NOT_APPEAR_IN_THE_RESULT", }, + "description": "", + "examples": Array [], + "implements": Array [], + "kind": "function", "members": Object { "events": Array [], "global": Array [], @@ -2161,20 +2268,28 @@ Array [ "instance": Array [], "static": Array [], }, - "namespace": "", + "name": "not_so_dummy", + "namespace": "not_so_dummy", "params": Array [], - "path": Array [], + "path": Array [ + Object { + "kind": "function", + "name": "not_so_dummy", + }, + ], "properties": Array [], "returns": Array [], "sees": Array [], - "start": 0, - "tags": Array [], + "tags": Array [ + Object { + "description": null, + "lineNumber": 1, + "name": "not_so_dummy", + "title": "method", + }, + ], "throws": Array [], "todos": Array [], - "type": "CommentBlock", - "value": "* - * @method dummy - ", "yields": Array [], }, ] diff --git a/__tests__/__snapshots__/test.js.snap b/__tests__/__snapshots__/test.js.snap index 3d392be7d..ff595d1ed 100644 --- a/__tests__/__snapshots__/test.js.snap +++ b/__tests__/__snapshots__/test.js.snap @@ -287,21 +287,21 @@ Array [ ] `; -exports[`Check that plugins are loaded 1`] = ` +exports[`Check that plugins are loaded and used 1`] = ` Array [ Object { "augments": Array [], "context": Object { "loc": SourceLocation { "end": Position { - "column": 2, - "line": 8, + "column": 1, + "line": 13, }, "filename": undefined, "identifierName": undefined, "start": Position { "column": 0, - "line": 5, + "line": 10, }, }, }, @@ -311,7 +311,7 @@ Array [ "children": Array [ Object { "type": "text", - "value": "This function returns the number one.", + "value": "This function returns the number plus two.", }, ], "type": "paragraph", @@ -320,13 +320,18 @@ Array [ "type": "root", }, "errors": Array [], - "examples": Array [], + "examples": Array [ + Object { + "description": "var result = returnTwo(4); +// result is 6", + }, + ], "implements": Array [], "kind": "function", "loc": SourceLocation { "end": Position { "column": 3, - "line": 4, + "line": 9, }, "filename": undefined, "identifierName": undefined, @@ -342,13 +347,37 @@ Array [ "instance": Array [], "static": Array [], }, - "name": "simple.input", - "namespace": "simple.input", - "params": Array [], + "name": "returnTwo", + "namespace": "returnTwo", + "params": Array [ + Object { + "description": Object { + "children": Array [ + Object { + "children": Array [ + Object { + "type": "text", + "value": "the number", + }, + ], + "type": "paragraph", + }, + ], + "type": "root", + }, + "lineNumber": 3, + "name": "a", + "title": "param", + "type": Object { + "name": "Number", + "type": "NameExpression", + }, + }, + ], "path": Array [ Object { "kind": "function", - "name": "simple.input", + "name": "returnTwo", }, ], "properties": Array [], @@ -360,7 +389,7 @@ Array [ "children": Array [ Object { "type": "text", - "value": "numberone", + "value": "numbertwo", }, ], "type": "paragraph", @@ -370,7 +399,7 @@ Array [ }, "title": "returns", "type": Object { - "name": "number", + "name": "Number", "type": "NameExpression", }, }, @@ -378,22 +407,36 @@ Array [ "sees": Array [], "tags": Array [ Object { - "description": "numberone", - "lineNumber": 2, + "description": "the number", + "lineNumber": 3, + "name": "a", + "title": "param", + "type": Object { + "name": "Number", + "type": "NameExpression", + }, + }, + Object { + "description": "numbertwo", + "lineNumber": 4, "title": "returns", "type": Object { - "name": "number", + "name": "Number", "type": "NameExpression", }, }, + Object { + "description": "var result = returnTwo(4); +// result is 6", + "lineNumber": 5, + "title": "example", + }, ], "throws": Array [], "todos": Array [], "yields": Array [], }, Object { - "after": "", - "api": false, "augments": Array [], "context": Object { "loc": Object { @@ -407,24 +450,128 @@ Array [ }, }, }, - "end": 19, - "errors": Array [ + "description": "", + "errors": Array [], + "examples": Array [], + "implements": Array [], + "kind": "function", + "loc": undefined, + "members": Object { + "events": Array [], + "global": Array [], + "inner": Array [], + "instance": Array [], + "static": Array [], + }, + "name": "dummy", + "namespace": "dummy", + "params": Array [], + "path": Array [ Object { - "message": "could not determine @name for hierarchy", + "kind": "function", + "name": "dummy", + }, + ], + "properties": Array [], + "returns": Array [], + "sees": Array [], + "tags": Array [ + Object { + "description": null, + "lineNumber": 1, + "name": "dummy", + "title": "method", }, ], + "throws": Array [], + "todos": Array [], + "yields": Array [], + }, + Object { + "augments": Array [], + "context": Object { + "loc": Object { + "end": Object { + "column": 4, + "line": 5, + }, + "start": Object { + "column": 1, + "line": 5, + }, + }, + }, + "description": "", + "errors": Array [], "examples": Array [], "implements": Array [], - "loc": Object { - "end": Object { - "column": 1, - "line": 2, + "kind": "method", + "loc": undefined, + "members": Object { + "events": Array [], + "global": Array [], + "inner": Array [], + "instance": Array [], + "static": Array [], + }, + "name": "dummy_method", + "namespace": "dummy_method", + "params": Array [ + Object { + "lineNumber": 1, + "name": "dummy_param", + "title": "param", + "type": Object { + "name": "number", + "type": "NameExpression", + }, }, - "start": Object { - "column": 1, - "line": 0, + ], + "path": Array [ + Object { + "kind": "method", + "name": "dummy_method", + }, + ], + "properties": Array [], + "returns": Array [], + "sees": Array [], + "tags": Array [ + Object { + "description": null, + "lineNumber": 1, + "name": "dummy_param", + "title": "param", + "type": Object { + "name": "number", + "type": "NameExpression", + }, + }, + ], + "throws": Array [], + "todos": Array [], + "yields": Array [], + }, + Object { + "augments": Array [], + "context": Object { + "loc": Object { + "end": Object { + "column": 4, + "line": 5, + }, + "start": Object { + "column": 1, + "line": 5, + }, }, }, + "description": "", + "errors": Array [], + "examples": Array [], + "implements": Array [], + "kind": "function", + "loc": undefined, "members": Object { "events": Array [], "global": Array [], @@ -432,20 +579,28 @@ Array [ "instance": Array [], "static": Array [], }, - "namespace": "", + "name": "not_so_dummy", + "namespace": "not_so_dummy", "params": Array [], - "path": Array [], + "path": Array [ + Object { + "kind": "function", + "name": "not_so_dummy", + }, + ], "properties": Array [], "returns": Array [], "sees": Array [], - "start": 0, - "tags": Array [], + "tags": Array [ + Object { + "description": null, + "lineNumber": 1, + "name": "not_so_dummy", + "title": "method", + }, + ], "throws": Array [], "todos": Array [], - "type": "CommentBlock", - "value": "* - * @method dummy - ", "yields": Array [], }, ] diff --git a/__tests__/test.js b/__tests__/test.js index 379b96006..4c4502379 100644 --- a/__tests__/test.js +++ b/__tests__/test.js @@ -72,7 +72,7 @@ test('Check that external modules could parse as input', async function () { expect(result).toMatchSnapshot(); }); -test('Check that plugins are loaded', async function () { +test('Check that plugins are loaded and used', async function () { const initCb = jest.fn(); const parseCb = jest.fn(); const mockPlugin = await import('../src/mock_plugin.js'); @@ -80,12 +80,21 @@ test('Check that plugins are loaded', async function () { const dir = path.join(__dirname, 'fixture'); const result = await documentation.build( - [path.join(dir, 'simple.input.js'), path.join(dir, 'plugin.txt')], + [path.join(dir, 'simple-two.input.js'), path.join(dir, 'plugin.txt')], { plugin: ['./mock_plugin.js'], order: 'test' } ); normalize(result); expect(result).toMatchSnapshot(); + // name from JSDoc tag + expect(result[1].name).toBe('dummy'); + + // name parsed by the plugin + expect(result[2].name).toBe('dummy_method'); + + // name from plugin parsing overridden by JSDoc tag + expect(result[3].name).toBe('not_so_dummy'); + expect(initCb.mock.calls.length).toBe(1); expect(initCb.mock.calls[0][0].order).toBe('test'); @@ -94,7 +103,7 @@ test('Check that plugins are loaded', async function () { parseCb.mock.calls[0][0].file.includes('fixture/plugin.txt') ).toBeTruthy(); expect( - parseCb.mock.calls[1][0].file.includes('fixture/simple.input.js') + parseCb.mock.calls[1][0].file.includes('fixture/simple-two.input.js') ).toBeTruthy(); }); diff --git a/src/mock_plugin.js b/src/mock_plugin.js index 0471099c5..f67e8a40a 100644 --- a/src/mock_plugin.js +++ b/src/mock_plugin.js @@ -10,36 +10,43 @@ export async function init() { if (initCb) initCb(...arguments); dummy = [ { - after: '', - api: false, - start: 0, - end: 19, - type: 'CommentBlock', value: '*\n * @method dummy\n ', context: { file: 'plugin.txt', - loc: { start: { line: 5, column: 1 }, end: { line: 5, column: 4 } } + loc: { start: { line: 5, column: 1 }, end: { line: 5, column: 4 } }, + sortKey: 'a' }, - loc: { start: { line: 0, column: 1 }, end: { line: 2, column: 1 } }, - augments: [], - errors: [], - examples: [], - implements: [], - params: [], - properties: [], - returns: [], - sees: [], - tags: [], - throws: [], - todos: [], - yields: [] + loc: { start: { line: 0, column: 1 }, end: { line: 2, column: 1 } } + }, + { + value: '*\n * @param {number} dummy_param\n ', + context: { + file: 'plugin.txt', + loc: { start: { line: 5, column: 1 }, end: { line: 5, column: 4 } }, + sortKey: 'b', + kind: 'method', + name: 'dummy_method' + }, + loc: { start: { line: 0, column: 1 }, end: { line: 2, column: 1 } } + }, + { + value: '*\n * @method not_so_dummy\n ', + context: { + file: 'plugin.txt', + loc: { start: { line: 5, column: 1 }, end: { line: 5, column: 4 } }, + sortKey: 'c', + kind: 'SHOULD_NOT_APPEAR_IN_THE_RESULT', + name: 'SHOULD_NOT_APPEAR_IN_THE_RESULT' + }, + loc: { start: { line: 0, column: 1 }, end: { line: 2, column: 1 } } } ]; } -export function parse(file) { +export function parse(file, _config, api) { if (parseCb) parseCb(...arguments); - if (file.file.includes('plugin.txt')) return dummy; + if (file.file.includes('plugin.txt')) + return dummy.map(c => api.parseJSDoc(c.value, c.log, c.context)); return false; } diff --git a/src/parse.js b/src/parse.js index b55e7f3f8..d756440a5 100644 --- a/src/parse.js +++ b/src/parse.js @@ -636,19 +636,21 @@ export default function parseJSDoc(comment, loc, context) { result.todos = []; result.yields = []; - for (const tag of [ - 'kind', - 'name', - 'returns', - 'params', - 'properties', - 'errors', - 'augments', - 'throws', - 'yields', - 'implements' - ]) - if (context[tag]) result[tag] = context[tag]; + if (context) { + for (const tag of [ + 'kind', + 'name', + 'returns', + 'params', + 'properties', + 'errors', + 'augments', + 'throws', + 'yields', + 'implements' + ]) + if (context[tag]) result[tag] = context[tag]; + } if (result.description) { result.description = parseMarkdown(result.description); From 3c4d853c0be826577a16e12a513abb408b9bdc36 Mon Sep 17 00:00:00 2001 From: Momtchil Momtchev Date: Thu, 2 Dec 2021 20:52:19 +0100 Subject: [PATCH 06/10] remove the selective shallow dependencies not compatible with globbing without extensive modifications to the code --- __tests__/test.js | 4 ++-- src/index.js | 19 +------------------ src/mock_plugin.js | 11 ++--------- 3 files changed, 5 insertions(+), 29 deletions(-) diff --git a/__tests__/test.js b/__tests__/test.js index 4c4502379..76538e1f2 100644 --- a/__tests__/test.js +++ b/__tests__/test.js @@ -100,10 +100,10 @@ test('Check that plugins are loaded and used', async function () { expect(parseCb.mock.calls.length).toBe(2); expect( - parseCb.mock.calls[0][0].file.includes('fixture/plugin.txt') + parseCb.mock.calls[0][0].file.includes('fixture/simple-two.input.js') ).toBeTruthy(); expect( - parseCb.mock.calls[1][0].file.includes('fixture/simple-two.input.js') + parseCb.mock.calls[1][0].file.includes('fixture/plugin.txt') ).toBeTruthy(); }); diff --git a/src/index.js b/src/index.js index 72d89085a..dd0b21394 100644 --- a/src/index.js +++ b/src/index.js @@ -77,24 +77,7 @@ export function expandInputs(indexes, config) { return shallow(indexes, config); } - let idxShallow = []; - if (config.plugin) { - for (const plugin of config.plugin) { - if (config._module[plugin].shallow) { - idxShallow = idxShallow.concat( - indexes.filter(idx => - config._module[plugin].shallow(idx, config, pluginAPI) - ) - ); - } - } - } - const depsShallow = shallow(idxShallow, config); - - const idxFull = indexes.filter(idx => !idxShallow.includes(idx)); - const depsFull = dependency(idxFull, config); - - return Promise.all([depsShallow, depsFull]).then(([a, b]) => a.concat(b)); + return dependency(indexes, config); } function buildInternal(inputsAndConfig) { diff --git a/src/mock_plugin.js b/src/mock_plugin.js index f67e8a40a..2e4d25018 100644 --- a/src/mock_plugin.js +++ b/src/mock_plugin.js @@ -1,9 +1,8 @@ -let initCb, parseCb, depCb, dummy; +let initCb, parseCb, dummy; -export function mockInit(init, parse, dep) { +export function mockInit(init, parse) { initCb = init; parseCb = parse; - depCb = dep; } export async function init() { @@ -49,9 +48,3 @@ export function parse(file, _config, api) { return dummy.map(c => api.parseJSDoc(c.value, c.log, c.context)); return false; } - -export function shallow(file) { - if (depCb) depCb(...arguments); - if (file.includes('plugin.txt')) return true; - return false; -} From 46cd67b02d17c79d96d7d3bad9475118d6bf3ae3 Mon Sep 17 00:00:00 2001 From: Momtchil Momtchev Date: Fri, 3 Dec 2021 17:02:49 +0100 Subject: [PATCH 07/10] correctly sort undefined values --- src/sort.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/sort.js b/src/sort.js index a5bde334f..17736017b 100644 --- a/src/sort.js +++ b/src/sort.js @@ -110,6 +110,8 @@ function compareCommentsByField(field, a, b) { if (akey && bkey) { return akey.localeCompare(bkey, undefined, { caseFirst: 'upper' }); } + if (akey) return 1; + if (bkey) return -1; return 0; } From 38bcfcbb9b7a48ebd90d969b38db957b394aa2aa Mon Sep 17 00:00:00 2001 From: Momtchil Momtchev Date: Fri, 3 Dec 2021 17:11:35 +0100 Subject: [PATCH 08/10] the new sort order is the correct one --- __tests__/lib/__snapshots__/sort.js.snap | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/__tests__/lib/__snapshots__/sort.js.snap b/__tests__/lib/__snapshots__/sort.js.snap index dee9fc0b0..13fa12e9b 100644 --- a/__tests__/lib/__snapshots__/sort.js.snap +++ b/__tests__/lib/__snapshots__/sort.js.snap @@ -156,26 +156,26 @@ exports[`sort toc with files absolute path 3`] = ` Array [ Object { "context": Object { - "sortKey": "a", + "sortKey": "b", }, - "kind": "function", "memberof": "classB", - "name": "apples", + "name": "carrot", }, Object { "context": Object { - "sortKey": "c", + "sortKey": "a", }, "kind": "function", "memberof": "classB", - "name": "bananas", + "name": "apples", }, Object { "context": Object { - "sortKey": "b", + "sortKey": "c", }, + "kind": "function", "memberof": "classB", - "name": "carrot", + "name": "bananas", }, ] `; From 257c54ef9771c8b9221daa5e77f32e9f18ffd29c Mon Sep 17 00:00:00 2001 From: Momtchil Momtchev Date: Fri, 3 Dec 2021 20:38:34 +0100 Subject: [PATCH 09/10] support sorting by memberof --- __tests__/lib/__snapshots__/sort.js.snap | 32 ++++++++++++++++++++++-- __tests__/lib/sort.js | 8 +++++- docs/USAGE.md | 2 +- src/commands/shared_options.js | 2 +- src/sort.js | 3 ++- 5 files changed, 41 insertions(+), 6 deletions(-) diff --git a/__tests__/lib/__snapshots__/sort.js.snap b/__tests__/lib/__snapshots__/sort.js.snap index 13fa12e9b..f35e6bf19 100644 --- a/__tests__/lib/__snapshots__/sort.js.snap +++ b/__tests__/lib/__snapshots__/sort.js.snap @@ -139,7 +139,7 @@ Array [ "sortKey": "c", }, "kind": "function", - "memberof": "classB", + "memberof": "classA", "name": "bananas", }, Object { @@ -174,8 +174,36 @@ Array [ "sortKey": "c", }, "kind": "function", - "memberof": "classB", + "memberof": "classA", + "name": "bananas", + }, +] +`; + +exports[`sort toc with files absolute path 4`] = ` +Array [ + Object { + "context": Object { + "sortKey": "c", + }, + "kind": "function", + "memberof": "classA", "name": "bananas", }, + Object { + "context": Object { + "sortKey": "b", + }, + "memberof": "classB", + "name": "carrot", + }, + Object { + "context": Object { + "sortKey": "a", + }, + "kind": "function", + "memberof": "classB", + "name": "apples", + }, ] `; diff --git a/__tests__/lib/sort.js b/__tests__/lib/sort.js index 76ce57f3c..941b163b1 100644 --- a/__tests__/lib/sort.js +++ b/__tests__/lib/sort.js @@ -141,7 +141,7 @@ test('sort toc with files absolute path', function () { context: { sortKey: 'c' }, name: 'bananas', kind: 'function', - memberof: 'classB' + memberof: 'classA' }; const snowflake = { @@ -159,4 +159,10 @@ test('sort toc with files absolute path', function () { sortOrder: ['kind', 'alpha'] }) ).toMatchSnapshot(); + + expect( + sort([carrot, apples, bananas], { + sortOrder: ['memberof', 'kind', 'alpha'] + }) + ).toMatchSnapshot(); }); diff --git a/docs/USAGE.md b/docs/USAGE.md index 5fdc6bf03..f801e2568 100644 --- a/docs/USAGE.md +++ b/docs/USAGE.md @@ -58,7 +58,7 @@ Options: [boolean] [default: false] --sort-order The order to sort the documentation, may be specified multiple times - [choices: "source", "alpha", "kind"] + [choices: "source", "alpha", "kind", "memberof"] [default: "source"] --output, -o output location. omit for stdout, otherwise is a filename for single-file outputs and a directory diff --git a/src/commands/shared_options.js b/src/commands/shared_options.js index 0379debb8..0e8464dfd 100644 --- a/src/commands/shared_options.js +++ b/src/commands/shared_options.js @@ -75,7 +75,7 @@ export const sharedInputOptions = { 'sort-order': { describe: 'The order to sort the documentation', array: true, - choices: ['source', 'alpha', 'kind', 'access'], + choices: ['source', 'alpha', 'kind', 'access', 'memberof'], default: ['source'] }, resolve: { diff --git a/src/sort.js b/src/sort.js index 17736017b..db27b02d5 100644 --- a/src/sort.js +++ b/src/sort.js @@ -123,7 +123,8 @@ const sortFns = { alpha: compareCommentsByField.bind(null, 'name'), source: compareCommentsBySourceLocation, kind: compareCommentsByField.bind(null, 'kind'), - access: compareCommentsByField.bind(null, 'access') + access: compareCommentsByField.bind(null, 'access'), + memberof: compareCommentsByField.bind(null, 'memberof') }; function sortComments(comments, sortOrder) { From 2dc9c2f69e87e95e9ce853c26aa212f2ffb0a6f5 Mon Sep 17 00:00:00 2001 From: Momtchil Momtchev Date: Sat, 4 Dec 2021 13:13:40 +0100 Subject: [PATCH 10/10] support a custom sort order --- __tests__/lib/__snapshots__/sort.js.snap | 28 ++++++++++++++++++++++++ __tests__/lib/sort.js | 26 ++++++++++++++++++++++ docs/CONFIG.md | 28 ++++++++++++++++++++++++ src/sort.js | 14 ++++++++++-- 4 files changed, 94 insertions(+), 2 deletions(-) diff --git a/__tests__/lib/__snapshots__/sort.js.snap b/__tests__/lib/__snapshots__/sort.js.snap index f35e6bf19..6a9936c66 100644 --- a/__tests__/lib/__snapshots__/sort.js.snap +++ b/__tests__/lib/__snapshots__/sort.js.snap @@ -1,5 +1,33 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`sort by custom order 1`] = ` +Array [ + Object { + "context": Object { + "sortKey": "b", + }, + "memberof": "classB", + "name": "carrot", + }, + Object { + "context": Object { + "sortKey": "c", + }, + "kind": "typedef", + "memberof": "classA", + "name": "bananas", + }, + Object { + "context": Object { + "sortKey": "a", + }, + "kind": "method", + "memberof": "classB", + "name": "apples", + }, +] +`; + exports[`sort toc with files 1`] = ` Array [ Object { diff --git a/__tests__/lib/sort.js b/__tests__/lib/sort.js index 941b163b1..4e0063b72 100644 --- a/__tests__/lib/sort.js +++ b/__tests__/lib/sort.js @@ -166,3 +166,29 @@ test('sort toc with files absolute path', function () { }) ).toMatchSnapshot(); }); + +test('sort by custom order', function () { + const apples = { + context: { sortKey: 'a' }, + name: 'apples', + kind: 'method', + memberof: 'classB' + }; + const carrot = { + context: { sortKey: 'b' }, + name: 'carrot', + memberof: 'classB' + }; + const bananas = { + context: { sortKey: 'c' }, + name: 'bananas', + kind: 'typedef', + memberof: 'classA' + }; + + expect( + sort([carrot, apples, bananas], { + sortOrder: [{ kind: ['typedef', 'method'] }, 'alpha'] + }) + ).toMatchSnapshot(); +}); diff --git a/docs/CONFIG.md b/docs/CONFIG.md index 6dab84d58..1c6da18f2 100644 --- a/docs/CONFIG.md +++ b/docs/CONFIG.md @@ -72,3 +72,31 @@ toc: - shortestPath - salesman ``` + +## Sorting + +Sorting options can be specified in the configuration file. Example: + +```yml +sortOrder: + - memberof + - alpha +``` + +Additionally, a custom sort order can be given, which is not possible when using the CLI option. Example: + +```yml +sortOrder: + - kind: + - namespace + - class + - interface + - typedef + - enum + - constant + - function + - property + - member + - memberof + - alpha +``` diff --git a/src/sort.js b/src/sort.js index db27b02d5..76551823e 100644 --- a/src/sort.js +++ b/src/sort.js @@ -103,11 +103,16 @@ export default function (comments, options) { return fixed.concat(unfixed); } -function compareCommentsByField(field, a, b) { +function compareCommentsByField(field, a, b, customOrder) { const akey = a[field]; const bkey = b[field]; if (akey && bkey) { + if (customOrder) { + const aIdx = customOrder.findIndex(o => o == akey); + const bIdx = customOrder.findIndex(o => o == bkey); + return aIdx - bIdx; + } return akey.localeCompare(bkey, undefined, { caseFirst: 'upper' }); } if (akey) return 1; @@ -130,7 +135,12 @@ const sortFns = { function sortComments(comments, sortOrder) { return comments.sort((a, b) => { for (const sortMethod of sortOrder || ['source']) { - const r = sortFns[sortMethod](a, b); + const sortMethodName = + typeof sortMethod === 'object' + ? Object.keys(sortMethod)[0] + : sortMethod; + const customOrder = sortMethod[sortMethodName]; + const r = sortFns[sortMethodName](a, b, customOrder); if (r !== 0) return r; } return 0; pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy