From 629ea6f3a8118c8cb916a269c3c79989f4d075d3 Mon Sep 17 00:00:00 2001 From: Erik Arvidsson Date: Tue, 31 May 2016 15:36:17 -0700 Subject: [PATCH] Add support for inferring private based on the name This adds a command line flag called `--infer-private` which is a string (defaults to `^_`) which is used as a regexp for inferring if a name is private or not. For example: ```js /** C */ class C { /** I'm public */ m() {} /** I'm private */ _p() {} } ``` Fixes #436 --- docs/USAGE.md | 51 +-- index.js | 3 + lib/commands/shared_options.js | 5 + lib/infer/access.js | 24 ++ test/bin.js | 17 + test/fixture/infer-private.input.js | 12 + test/fixture/infer-private.output.json | 386 ++++++++++++++++++++++ test/fixture/infer-private.output.md | 15 + test/fixture/infer-private.output.md.json | 185 +++++++++++ test/lib/infer/access.js | 40 +++ 10 files changed, 719 insertions(+), 19 deletions(-) create mode 100644 lib/infer/access.js create mode 100644 test/fixture/infer-private.input.js create mode 100644 test/fixture/infer-private.output.json create mode 100644 test/fixture/infer-private.output.md create mode 100644 test/fixture/infer-private.output.md.json create mode 100644 test/lib/infer/access.js diff --git a/docs/USAGE.md b/docs/USAGE.md index a1e56b288..ac8e5a6f9 100644 --- a/docs/USAGE.md +++ b/docs/USAGE.md @@ -19,27 +19,40 @@ Commands: readme inject documentation into your README.md Options: - --help Show help [boolean] - --version Show version number [boolean] - --shallow shallow mode turns off dependency resolution, only processing - the specified files (or the main script specified in - package.json) [boolean] [default: false] - --config, -c configuration file. an array defining explicit sort order - --external a string / glob match pattern that defines which external - modules will be whitelisted and included in the generated - documentation. [default: null] - --extension, -e only input source files matching this extension will be - parsed, this option can be used multiple times. - --polyglot polyglot mode turns off dependency resolution and enables - multi-language support. use this to document c++ [boolean] - --private, -p generate documentation tagged as private + --help Show help [boolean] + --version Show version number [boolean] + --shallow shallow mode turns off dependency resolution, only + processing the specified files (or the main script + specified in package.json) [boolean] [default: false] + --config, -c configuration file. an array defining explicit sort order + --external a string / glob match pattern that defines which external + modules will be whitelisted and included in the generated + documentation. [default: null] + --extension, -e only input source files matching this extension will be + parsed, this option can be used multiple times. + --polyglot polyglot mode turns off dependency resolution and enables + multi-language support. use this to document c++ [boolean] + --private, -p generate documentation tagged as private [boolean] [default: false] - --access, -a Include only comments with a given access level, out of - private, protected, public, undefined. By default, public, - protected, and undefined access levels are included + --access, -a Include only comments with a given access level, out of + private, protected, public, undefined. By default, public, + protected, and undefined access levels are included [choices: "public", "private", "protected", "undefined"] - --github, -g infer links to github in documentation [boolean] + --github, -g infer links to github in documentation [boolean] + --infer-private Infer private access based on the name. This is a regular + expression that is used to match the name [string] + --theme, -t specify a theme: this must be a valid theme module + --name project name. by default, inferred from package.json + --watch, -w watch input files and rebuild documentation when they + change [boolean] + --project-version project version. by default, inferred from package.json + --output, -o output location. omit for stdout, otherwise is a filename + for single-file outputs and a directory name for multi-file + outputs like html [default: "stdout"] + --format, -f [choices: "json", "md", "html"] [default: "json"] Examples: - documentation build foo.js parse documentation in a given file + documentation build foo.js -f md > parse documentation in a file and + API.md generate API documentation as + Markdown ``` diff --git a/index.js b/index.js index 93eca1933..a908eb033 100644 --- a/index.js +++ b/index.js @@ -18,6 +18,7 @@ var fs = require('fs'), inferProperties = require('./lib/infer/properties'), inferMembership = require('./lib/infer/membership'), inferReturn = require('./lib/infer/return'), + inferAccess = require('./lib/infer/access'), formatLint = require('./lib/lint').formatLint, lintComments = require('./lib/lint').lintComments; @@ -150,6 +151,7 @@ function buildSync(indexes, options) { }, []) .map(pipeline( inferName(), + inferAccess(options.inferPrivate), inferAugments(), inferKind(), inferParams(), @@ -205,6 +207,7 @@ module.exports.lint = function lint(indexes, options, callback) { .map(pipeline( lintComments, inferName(), + inferAccess(options.inferPrivate), inferAugments(), inferKind(), inferParams(), diff --git a/lib/commands/shared_options.js b/lib/commands/shared_options.js index d6e0dc5d5..870ff5b51 100644 --- a/lib/commands/shared_options.js +++ b/lib/commands/shared_options.js @@ -48,6 +48,11 @@ function sharedInputOptions(parser) { type: 'boolean', describe: 'infer links to github in documentation', alias: 'g' + }) + .option('infer-private', { + type: 'string', + describe: 'Infer private access based on the name. This is a regular expression that ' + + 'is used to match the name' }); } diff --git a/lib/infer/access.js b/lib/infer/access.js new file mode 100644 index 000000000..bdc507aa5 --- /dev/null +++ b/lib/infer/access.js @@ -0,0 +1,24 @@ +'use strict'; + +var shouldSkipInference = require('./should_skip_inference'); + +module.exports = function (pattern) { + var re = pattern && new RegExp(pattern); + + /** + * Infers access (only private atm) from the name. + * + * @name inferAccess + * @param {Object} comment parsed comment + * @returns {Object} comment with access inferred + */ + return shouldSkipInference(function inferAccess(comment) { + // This needs to run after inferName beacuse we infer the access based on + // the name. + if (re && comment.name && comment.access === undefined && re.test(comment.name)) { + comment.access = 'private'; + } + + return comment; + }); +}; diff --git a/test/bin.js b/test/bin.js index 82bb1b34e..3ba9e650c 100644 --- a/test/bin.js +++ b/test/bin.js @@ -228,6 +228,23 @@ test('--private flag', function (t) { }, false); }); +test('--infer-private flag', function (t) { + documentation(['build fixture/infer-private.input.js --infer-private ^_'], {}, function (err, data) { + t.error(err); + + // This uses JSON.parse with a reviver used as a visitor. + JSON.parse(data, function (n, v) { + // Make sure we do not see any names that match `^_`. + if (n === 'name') { + t.equal(typeof v, 'string'); + t.ok(!/_$/.test(v)); + } + return v; + }); + t.end(); + }, false); +}); + test('write to file', function (t) { var dst = path.join(os.tmpdir(), (Date.now() + Math.random()).toString()); diff --git a/test/fixture/infer-private.input.js b/test/fixture/infer-private.input.js new file mode 100644 index 000000000..ddfbdb45e --- /dev/null +++ b/test/fixture/infer-private.input.js @@ -0,0 +1,12 @@ +/** + * _p description + */ +function _p() {} + +/** C description */ +class C { + /** m description */ + m() {} + /** _p description */ + _p() {} +} diff --git a/test/fixture/infer-private.output.json b/test/fixture/infer-private.output.json new file mode 100644 index 000000000..7feda0c7a --- /dev/null +++ b/test/fixture/infer-private.output.json @@ -0,0 +1,386 @@ +[ + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "_p description", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 15, + "offset": 14 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 15, + "offset": 14 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 15, + "offset": 14 + } + } + }, + "tags": [], + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 3, + "column": 3 + } + }, + "context": { + "loc": { + "start": { + "line": 4, + "column": 0 + }, + "end": { + "line": 4, + "column": 16 + } + }, + "code": "/**\n * _p description\n */\nfunction _p() {}\n\n/** C description */\nclass C {\n /** m description */\n m() {}\n /** _p description */\n _p() {}\n}\n" + }, + "errors": [], + "name": "_p", + "kind": "function", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "_p", + "kind": "function" + } + ], + "namespace": "_p" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "C description", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 14, + "offset": 13 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 14, + "offset": 13 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 14, + "offset": 13 + } + } + }, + "tags": [], + "loc": { + "start": { + "line": 6, + "column": 0 + }, + "end": { + "line": 6, + "column": 20 + } + }, + "context": { + "loc": { + "start": { + "line": 7, + "column": 0 + }, + "end": { + "line": 12, + "column": 1 + } + }, + "code": "/**\n * _p description\n */\nfunction _p() {}\n\n/** C description */\nclass C {\n /** m description */\n m() {}\n /** _p description */\n _p() {}\n}\n" + }, + "errors": [], + "name": "C", + "kind": "class", + "members": { + "instance": [ + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "m description", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 14, + "offset": 13 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 14, + "offset": 13 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 14, + "offset": 13 + } + } + }, + "tags": [], + "loc": { + "start": { + "line": 8, + "column": 2 + }, + "end": { + "line": 8, + "column": 22 + } + }, + "context": { + "loc": { + "start": { + "line": 9, + "column": 2 + }, + "end": { + "line": 9, + "column": 8 + } + }, + "code": "/**\n * _p description\n */\nfunction _p() {}\n\n/** C description */\nclass C {\n /** m description */\n m() {}\n /** _p description */\n _p() {}\n}\n" + }, + "errors": [], + "name": "m", + "kind": "function", + "memberof": "C", + "scope": "instance", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "C", + "kind": "class" + }, + { + "name": "m", + "kind": "function", + "scope": "instance" + } + ], + "namespace": "C#m" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "_p description", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 15, + "offset": 14 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 15, + "offset": 14 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 15, + "offset": 14 + } + } + }, + "tags": [], + "loc": { + "start": { + "line": 10, + "column": 2 + }, + "end": { + "line": 10, + "column": 23 + } + }, + "context": { + "loc": { + "start": { + "line": 11, + "column": 2 + }, + "end": { + "line": 11, + "column": 9 + } + }, + "code": "/**\n * _p description\n */\nfunction _p() {}\n\n/** C description */\nclass C {\n /** m description */\n m() {}\n /** _p description */\n _p() {}\n}\n" + }, + "errors": [], + "name": "_p", + "kind": "function", + "memberof": "C", + "scope": "instance", + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "C", + "kind": "class" + }, + { + "name": "_p", + "kind": "function", + "scope": "instance" + } + ], + "namespace": "C#_p" + } + ], + "static": [], + "events": [] + }, + "path": [ + { + "name": "C", + "kind": "class" + } + ], + "namespace": "C" + } +] \ No newline at end of file diff --git a/test/fixture/infer-private.output.md b/test/fixture/infer-private.output.md new file mode 100644 index 000000000..d5fbb19dd --- /dev/null +++ b/test/fixture/infer-private.output.md @@ -0,0 +1,15 @@ +# \_p + +\_p description + +# C + +C description + +## m + +m description + +## \_p + +\_p description diff --git a/test/fixture/infer-private.output.md.json b/test/fixture/infer-private.output.md.json new file mode 100644 index 000000000..d87ee6ff3 --- /dev/null +++ b/test/fixture/infer-private.output.md.json @@ -0,0 +1,185 @@ +{ + "type": "root", + "children": [ + { + "depth": 1, + "type": "heading", + "children": [ + { + "type": "text", + "value": "_p" + } + ] + }, + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "_p description", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 15, + "offset": 14 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 15, + "offset": 14 + }, + "indent": [] + } + }, + { + "depth": 1, + "type": "heading", + "children": [ + { + "type": "text", + "value": "C" + } + ] + }, + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "C description", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 14, + "offset": 13 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 14, + "offset": 13 + }, + "indent": [] + } + }, + { + "depth": 2, + "type": "heading", + "children": [ + { + "type": "text", + "value": "m" + } + ] + }, + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "m description", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 14, + "offset": 13 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 14, + "offset": 13 + }, + "indent": [] + } + }, + { + "depth": 2, + "type": "heading", + "children": [ + { + "type": "text", + "value": "_p" + } + ] + }, + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "_p description", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 15, + "offset": 14 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 15, + "offset": 14 + }, + "indent": [] + } + } + ] +} \ No newline at end of file diff --git a/test/lib/infer/access.js b/test/lib/infer/access.js new file mode 100644 index 000000000..76454a1c3 --- /dev/null +++ b/test/lib/infer/access.js @@ -0,0 +1,40 @@ +'use strict'; + +var test = require('tap').test, + parse = require('../../../lib/parsers/javascript'), + inferName = require('../../../lib/infer/name')(), + inferAccess = require('../../../lib/infer/access'); + +function toComment(fn) { + return parse({ + source: '(' + fn.toString() + ')' + })[0]; +} + +function evaluate(fn, re) { + return inferAccess(re)(inferName(toComment(fn))); +} + +test('inferAccess', function (t) { + t.equal(evaluate(function () { + /** Test */ + function _name() {} + }, '^_').access, 'private'); + + t.equal(evaluate(function () { + /** @private */ + function name() {} + }, '^_').access, 'private'); + + t.equal(evaluate(function () { + /** @public */ + function _name() {} + }, '^_').access, 'public'); + + t.equal(evaluate(function () { + /** Test */ + function name_() {} + }, '_$').access, 'private'); + + t.end(); +});