From d75937645ed7eb7f162652594cd27913e7bcba95 Mon Sep 17 00:00:00 2001 From: Alexis ROYER Date: Sat, 2 May 2020 17:56:46 +0200 Subject: [PATCH] Have tsd-jsdoc generate default exports (#98) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Very first 'export default' generation. Still buggy: module declaration is generated twice. * Removal of the IExportDefaultDoclet hack. * Code moved from _createTreeNodes() to _buildTree() for 'export default' doclets. => fixed: generation of 'export default' at the end of the module (and not at the beginning) => fixed: duplicate module declaration * 'module.exports =' pattern management. module2 test addition. * module3 test addition. * Removal of K&R style braces. `debug()` controled by a specific `debug` option. * Addition of debug traces. * opts.generationStrategy option addition. 'documented' => by default 'exported' => not fully implemented, just prevents non documented doclets to be removed for the moment * correctly write doclets at es6 class constructors cherry-pick & merge from @HackbrettXXX's commit 312578e54d14986e6da7e2c93b0aa6d26c306a9b * add a test case for constructors (fails currently) * Constructor generation with documentation. From [PR#74](https://github.com/englercj/tsd-jsdoc/pull/74) with fix. * Emitter._buildTree() strengthening with full jsdoc doclets. * First 'exported' generation strategy implementation. Still needs to be tested. * Test additions for testing the 'exported' generation strategy. Lots of them are not enabled yet and need to be fixed. * (Re)named exports. * Warning addition while waiting for [tsd-jsdoc#104](https://github.com/englercj/tsd-jsdoc/issues/104)'s resolution. * 'export ' pattern support. Fix for 'module.exports = {name: }' pattern. * Named exports with reference to a type of the same name. * 'export default ' pattern support (works like a lambda class would actually). * 'export default ' pattern support. * 'export default ' and 'export default ' patterns support. * 'module.exports=' and 'module.exports=' patterns support. * 'module.exports.name=' and 'module.exports.name=' patterns support. * 'exports.name=' and 'exports.name=' patterns support. * 'module.exports={name=} and 'module.exports={name=}' patterns support. Workaround needed for classes as long as issue [jsdoc#1699](https://github.com/jsdoc/jsdoc/issues/1699) is not fixed. * cyclic dependencies with 'exported' generation strategy. Addition of 'export default {name: ...}' tests (not supported yet). * Test class_all.js working with 'exported' generation strategy. * Test constructors.js & enum_all.js working with 'exported' generation strategy. * Tests function_all.js and interface_all.js working with 'exported' generation strategy. Warn&debug improvements. * Test namespace_all.js working with 'exported' generation strategy. * Tests property_all.js and typedef_all.js working with 'exported' generation strategy. * 'documented' & 'exported' test cases. Instrumentation for jsdoc 3.5.5 vs 3.6.3 investigations. * jsdoc@3.6.3 installation. * jsdoc@3.6.3 fixes. * `walk-back.d.ts` typescript declaration. * `walk-back.d.ts`: application of "tsd-default-export" recommendations. See: https://github.com/Alexis-ROYER/tsd-default-export/blob/master/README.md * Corrections to @englercj’s remarks. * Fix merge tag 'v2.5.0' into export-default 'exported' tests were not executed due to a false trailing 'r' after `generationStrategy` in `test/lib/index.ts`. * Corrections to @englercj’s remarks. Co-authored-by: Lukas Hollaender --- package-lock.json | 242 +++-- package.json | 5 +- src/Emitter.ts | 877 ++++++++++++++++-- src/create_helpers.ts | 213 +++-- src/doclet_utils.ts | 179 +++- src/logger.ts | 49 + src/publish.ts | 51 +- src/type_resolve_helpers.ts | 79 +- src/typings/dts-jsdoc.d.ts | 15 +- src/typings/jsdoc.d.ts | 19 +- test/expected/class_all.d.ts | 93 +- test/expected/constructor_all.d.ts | 59 ++ test/expected/enum_all.d.ts | 25 +- .../expected/exports_cyclic-dependencies.d.ts | 5 + .../exports_export default lambda-class.d.ts | 15 + ...xports_export default lambda-function.d.ts | 8 + .../exports_export default named-class.d.ts | 15 + ...exports_export default named-function.d.ts | 8 + test/expected/exports_export default ref.d.ts | 7 + ...ts_export default {name=lambda-class}.d.ts | 15 + ...export default {name=lambda-function}.d.ts | 8 + ...rts_export default {name=named-class}.d.ts | 15 + ..._export default {name=named-function}.d.ts | 8 + ..._export default {name=ref-other-name}.d.ts | 6 + ...s_export default {name=ref-same-name}.d.ts | 3 + test/expected/exports_export named-types.d.ts | 19 + .../exports_exports.name=lambda-class.d.ts | 15 + .../exports_exports.name=lambda-function.d.ts | 8 + .../exports_exports.name=named-class.d.ts | 15 + .../exports_exports.name=named-function.d.ts | 8 + .../exports_exports.name=ref-other-name.d.ts | 14 + .../exports_exports.name=ref-same-name.d.ts | 14 + .../exports_exports=module.exports=.d.ts | 7 + ...ports_indirect(=module.exports).name=.d.ts | 19 + test/expected/exports_mix-default-named.d.ts | 8 + ...orts_module.exports.name=lambda-class.d.ts | 15 + ...s_module.exports.name=lambda-function.d.ts | 8 + ...ports_module.exports.name=named-class.d.ts | 15 + ...ts_module.exports.name=named-function.d.ts | 8 + ...ts_module.exports.name=ref-other-name.d.ts | 14 + ...rts_module.exports.name=ref-same-name.d.ts | 14 + .../exports_module.exports=lambda-class.d.ts | 15 + ...xports_module.exports=lambda-function.d.ts | 8 + .../exports_module.exports=named-class.d.ts | 15 + ...exports_module.exports=named-function.d.ts | 8 + test/expected/exports_module.exports=ref.d.ts | 7 + ...ts_module.exports={name=lambda-class}.d.ts | 15 + ...module.exports={name=lambda-function}.d.ts | 8 + ...rts_module.exports={name=named-class}.d.ts | 16 + ..._module.exports={name=named-function}.d.ts | 8 + ..._module.exports={name=ref-other-name}.d.ts | 3 + ...s_module.exports={name=ref-same-name}.d.ts | 3 + .../expected/exports_wrong exports.name=.d.ts | 11 + test/expected/function_all.d.ts | 33 +- test/expected/interface_all.d.ts | 51 +- test/expected/mixin_all.d.ts | 29 +- test/expected/module_all.d.ts | 4 + test/expected/namespace_all.d.ts | 92 +- test/expected/property_all.d.ts | 108 +-- test/expected/typedef_all.d.ts | 220 +++-- test/expected/typeof_all.d.ts | 45 +- test/fixtures/class_all.js | 22 +- test/fixtures/constructor_all.js | 103 ++ test/fixtures/enum_all.js | 6 + test/fixtures/exports_cyclic-dependencies.js | 12 + .../exports_export default lambda-class.js | 39 + .../exports_export default lambda-function.js | 18 + .../exports_export default named-class.js | 39 + .../exports_export default named-function.js | 18 + test/fixtures/exports_export default ref.js | 15 + ...orts_export default {name=lambda-class}.js | 49 + ...s_export default {name=lambda-function}.js | 24 + ...ports_export default {name=named-class}.js | 49 + ...ts_export default {name=named-function}.js | 24 + ...ts_export default {name=ref-other-name}.js | 21 + ...rts_export default {name=ref-same-name}.js | 21 + test/fixtures/exports_export named-types.js | 50 + .../exports_exports.name=lambda-class.js | 39 + .../exports_exports.name=lambda-function.js | 18 + .../exports_exports.name=named-class.js | 39 + .../exports_exports.name=named-function.js | 18 + .../exports_exports.name=ref-other-name.js | 70 ++ .../exports_exports.name=ref-same-name.js | 65 ++ .../exports_exports=module.exports=.js | 12 + ...exports_indirect(=module.exports).name=.js | 42 + test/fixtures/exports_mix-default-named.js | 26 + ...xports_module.exports.name=lambda-class.js | 39 + ...rts_module.exports.name=lambda-function.js | 18 + ...exports_module.exports.name=named-class.js | 39 + ...orts_module.exports.name=named-function.js | 18 + ...orts_module.exports.name=ref-other-name.js | 70 ++ ...ports_module.exports.name=ref-same-name.js | 65 ++ .../exports_module.exports=lambda-class.js | 39 + .../exports_module.exports=lambda-function.js | 18 + .../exports_module.exports=named-class.js | 39 + .../exports_module.exports=named-function.js | 18 + test/fixtures/exports_module.exports=ref.js | 15 + ...orts_module.exports={name=lambda-class}.js | 49 + ...s_module.exports={name=lambda-function}.js | 24 + ...ports_module.exports={name=named-class}.js | 49 + ...ts_module.exports={name=named-function}.js | 24 + ...ts_module.exports={name=ref-other-name}.js | 21 + ...rts_module.exports={name=ref-same-name}.js | 20 + test/fixtures/exports_wrong exports.name=.js | 29 + test/fixtures/function_all.js | 13 +- test/fixtures/interface_all.js | 22 +- test/fixtures/mixin_all.js | 6 + test/fixtures/module_all.js | 3 + test/fixtures/namespace_all.js | 11 +- test/fixtures/property_all.js | 23 +- test/fixtures/typedef_all.js | 10 + test/fixtures/typeof_all.js | 7 + test/lib/index.ts | 100 +- test/specs/class.ts | 6 +- test/specs/constructor.ts | 5 + test/specs/enum.ts | 6 +- test/specs/exports.ts | 62 ++ test/specs/function.ts | 6 +- test/specs/interface.ts | 6 +- test/specs/mixin.ts | 6 +- test/specs/module.ts | 6 +- test/specs/namespace.ts | 6 +- test/specs/property.ts | 6 +- test/specs/typedef.ts | 6 +- test/specs/typeof.ts | 6 +- test/typings/walk-back.d.ts | 6 + tsconfig.json | 8 +- 127 files changed, 3922 insertions(+), 766 deletions(-) create mode 100644 test/expected/constructor_all.d.ts create mode 100644 test/expected/exports_cyclic-dependencies.d.ts create mode 100644 test/expected/exports_export default lambda-class.d.ts create mode 100644 test/expected/exports_export default lambda-function.d.ts create mode 100644 test/expected/exports_export default named-class.d.ts create mode 100644 test/expected/exports_export default named-function.d.ts create mode 100644 test/expected/exports_export default ref.d.ts create mode 100644 test/expected/exports_export default {name=lambda-class}.d.ts create mode 100644 test/expected/exports_export default {name=lambda-function}.d.ts create mode 100644 test/expected/exports_export default {name=named-class}.d.ts create mode 100644 test/expected/exports_export default {name=named-function}.d.ts create mode 100644 test/expected/exports_export default {name=ref-other-name}.d.ts create mode 100644 test/expected/exports_export default {name=ref-same-name}.d.ts create mode 100644 test/expected/exports_export named-types.d.ts create mode 100644 test/expected/exports_exports.name=lambda-class.d.ts create mode 100644 test/expected/exports_exports.name=lambda-function.d.ts create mode 100644 test/expected/exports_exports.name=named-class.d.ts create mode 100644 test/expected/exports_exports.name=named-function.d.ts create mode 100644 test/expected/exports_exports.name=ref-other-name.d.ts create mode 100644 test/expected/exports_exports.name=ref-same-name.d.ts create mode 100644 test/expected/exports_exports=module.exports=.d.ts create mode 100644 test/expected/exports_indirect(=module.exports).name=.d.ts create mode 100644 test/expected/exports_mix-default-named.d.ts create mode 100644 test/expected/exports_module.exports.name=lambda-class.d.ts create mode 100644 test/expected/exports_module.exports.name=lambda-function.d.ts create mode 100644 test/expected/exports_module.exports.name=named-class.d.ts create mode 100644 test/expected/exports_module.exports.name=named-function.d.ts create mode 100644 test/expected/exports_module.exports.name=ref-other-name.d.ts create mode 100644 test/expected/exports_module.exports.name=ref-same-name.d.ts create mode 100644 test/expected/exports_module.exports=lambda-class.d.ts create mode 100644 test/expected/exports_module.exports=lambda-function.d.ts create mode 100644 test/expected/exports_module.exports=named-class.d.ts create mode 100644 test/expected/exports_module.exports=named-function.d.ts create mode 100644 test/expected/exports_module.exports=ref.d.ts create mode 100644 test/expected/exports_module.exports={name=lambda-class}.d.ts create mode 100644 test/expected/exports_module.exports={name=lambda-function}.d.ts create mode 100644 test/expected/exports_module.exports={name=named-class}.d.ts create mode 100644 test/expected/exports_module.exports={name=named-function}.d.ts create mode 100644 test/expected/exports_module.exports={name=ref-other-name}.d.ts create mode 100644 test/expected/exports_module.exports={name=ref-same-name}.d.ts create mode 100644 test/expected/exports_wrong exports.name=.d.ts create mode 100644 test/fixtures/constructor_all.js create mode 100644 test/fixtures/exports_cyclic-dependencies.js create mode 100644 test/fixtures/exports_export default lambda-class.js create mode 100644 test/fixtures/exports_export default lambda-function.js create mode 100644 test/fixtures/exports_export default named-class.js create mode 100644 test/fixtures/exports_export default named-function.js create mode 100644 test/fixtures/exports_export default ref.js create mode 100644 test/fixtures/exports_export default {name=lambda-class}.js create mode 100644 test/fixtures/exports_export default {name=lambda-function}.js create mode 100644 test/fixtures/exports_export default {name=named-class}.js create mode 100644 test/fixtures/exports_export default {name=named-function}.js create mode 100644 test/fixtures/exports_export default {name=ref-other-name}.js create mode 100644 test/fixtures/exports_export default {name=ref-same-name}.js create mode 100644 test/fixtures/exports_export named-types.js create mode 100644 test/fixtures/exports_exports.name=lambda-class.js create mode 100644 test/fixtures/exports_exports.name=lambda-function.js create mode 100644 test/fixtures/exports_exports.name=named-class.js create mode 100644 test/fixtures/exports_exports.name=named-function.js create mode 100644 test/fixtures/exports_exports.name=ref-other-name.js create mode 100644 test/fixtures/exports_exports.name=ref-same-name.js create mode 100644 test/fixtures/exports_exports=module.exports=.js create mode 100644 test/fixtures/exports_indirect(=module.exports).name=.js create mode 100644 test/fixtures/exports_mix-default-named.js create mode 100644 test/fixtures/exports_module.exports.name=lambda-class.js create mode 100644 test/fixtures/exports_module.exports.name=lambda-function.js create mode 100644 test/fixtures/exports_module.exports.name=named-class.js create mode 100644 test/fixtures/exports_module.exports.name=named-function.js create mode 100644 test/fixtures/exports_module.exports.name=ref-other-name.js create mode 100644 test/fixtures/exports_module.exports.name=ref-same-name.js create mode 100644 test/fixtures/exports_module.exports=lambda-class.js create mode 100644 test/fixtures/exports_module.exports=lambda-function.js create mode 100644 test/fixtures/exports_module.exports=named-class.js create mode 100644 test/fixtures/exports_module.exports=named-function.js create mode 100644 test/fixtures/exports_module.exports=ref.js create mode 100644 test/fixtures/exports_module.exports={name=lambda-class}.js create mode 100644 test/fixtures/exports_module.exports={name=lambda-function}.js create mode 100644 test/fixtures/exports_module.exports={name=named-class}.js create mode 100644 test/fixtures/exports_module.exports={name=named-function}.js create mode 100644 test/fixtures/exports_module.exports={name=ref-other-name}.js create mode 100644 test/fixtures/exports_module.exports={name=ref-same-name}.js create mode 100644 test/fixtures/exports_wrong exports.name=.js create mode 100644 test/specs/constructor.ts create mode 100644 test/specs/exports.ts create mode 100644 test/typings/walk-back.d.ts diff --git a/package-lock.json b/package-lock.json index 473a02a..722cdc0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,9 +5,9 @@ "requires": true, "dependencies": { "@babel/parser": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.5.5.tgz", - "integrity": "sha512-E5BN68cqR7dhKan1SfqgPGhQ178bkVKpXTPEXnFJBrEt8/DKRZlybmy+IgYLTeN7tp1R5Ccmbm2rBk17sHYU3g==", + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.6.2.tgz", + "integrity": "sha512-mdFqWrSPCmikBoaBYMuBulzTIKuXVPtEISFbRRVNwMWpCms/hmE2kRq0bblUHaNRKrjRlmVbx1sDHmjmRgD2Xg==", "dev": true }, "@types/chai": { @@ -38,10 +38,13 @@ } }, "array-back": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/array-back/-/array-back-4.0.0.tgz", - "integrity": "sha512-ylVYjv5BzoWXWO7e6fWrzjqzgxmUPWdQrHxgzo/v1EaYXfw6+6ipRdIr7KryAGnVHG08O1Yfpchuv0+YhjPL+Q==", - "dev": true + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-2.0.0.tgz", + "integrity": "sha512-eJv4pLLufP3g5kcZry0j6WXpIbzYw9GUB4mVJZno9wfwiBxbizTnHCw3VJb07cBihbFX48Y7oSrW9y+gt4glyw==", + "dev": true, + "requires": { + "typical": "^2.6.1" + } }, "arrify": { "version": "1.0.1", @@ -61,12 +64,6 @@ "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", "dev": true }, - "bluebird": { - "version": "3.5.5", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.5.tgz", - "integrity": "sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w==", - "dev": true - }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -98,26 +95,6 @@ "array-back": "^2.0.0", "fs-then-native": "^2.0.0", "mkdirp2": "^1.0.3" - }, - "dependencies": { - "array-back": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/array-back/-/array-back-2.0.0.tgz", - "integrity": "sha512-eJv4pLLufP3g5kcZry0j6WXpIbzYw9GUB4mVJZno9wfwiBxbizTnHCw3VJb07cBihbFX48Y7oSrW9y+gt4glyw==", - "dev": true, - "requires": { - "typical": "^2.6.1" - } - } - } - }, - "catharsis": { - "version": "0.8.11", - "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.8.11.tgz", - "integrity": "sha512-a+xUyMV7hD1BrDQA/3iPV7oc+6W26BgVJO05PGEoatMyIuPScQKsde6i3YorWX1qs+AZjnJ18NqdKoCtKiNh1g==", - "dev": true, - "requires": { - "lodash": "^4.17.14" } }, "chai": { @@ -189,41 +166,6 @@ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", "dev": true }, - "file-set": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/file-set/-/file-set-2.0.1.tgz", - "integrity": "sha512-XgOUUpgR6FbbfYcniLw0qm1Am7PnNYIAkd+eXxRt42LiYhjaso0WiuQ+VmrNdtwotyM+cLCfZ56AZrySP3QnKA==", - "dev": true, - "requires": { - "array-back": "^2.0.0", - "glob": "^7.1.3" - }, - "dependencies": { - "array-back": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/array-back/-/array-back-2.0.0.tgz", - "integrity": "sha512-eJv4pLLufP3g5kcZry0j6WXpIbzYw9GUB4mVJZno9wfwiBxbizTnHCw3VJb07cBihbFX48Y7oSrW9y+gt4glyw==", - "dev": true, - "requires": { - "typical": "^2.6.1" - } - }, - "glob": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", - "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - } - } - }, "fs-then-native": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/fs-then-native/-/fs-then-native-2.0.0.tgz", @@ -257,9 +199,9 @@ } }, "graceful-fs": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.2.tgz", - "integrity": "sha512-IItsdsea19BoLC7ELy13q1iJFNmd7ofZH5+X/pJr90/nRoPEX0DJo1dHDbgtYWOhJhcCgMDTOw84RZ72q6lB+Q==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", "dev": true }, "growl": { @@ -296,15 +238,6 @@ "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", "dev": true }, - "js2xmlparser": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.0.tgz", - "integrity": "sha512-WuNgdZOXVmBk5kUPMcTcVUpbGRzLfNkv7+7APq7WiDihpXVKrgxo6wwRpRl9OQeEBgKCVk9mR7RbzrnNWC8oBw==", - "dev": true, - "requires": { - "xmlcreate": "^2.0.0" - } - }, "jsdoc": { "version": "3.6.3", "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-3.6.3.tgz", @@ -327,11 +260,77 @@ "underscore": "~1.9.1" }, "dependencies": { + "bluebird": { + "version": "3.5.5", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.5.tgz", + "integrity": "sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w==", + "dev": true + }, + "catharsis": { + "version": "0.8.11", + "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.8.11.tgz", + "integrity": "sha512-a+xUyMV7hD1BrDQA/3iPV7oc+6W26BgVJO05PGEoatMyIuPScQKsde6i3YorWX1qs+AZjnJ18NqdKoCtKiNh1g==", + "dev": true, + "requires": { + "lodash": "^4.17.14" + } + }, "escape-string-regexp": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", "dev": true + }, + "js2xmlparser": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.0.tgz", + "integrity": "sha512-WuNgdZOXVmBk5kUPMcTcVUpbGRzLfNkv7+7APq7WiDihpXVKrgxo6wwRpRl9OQeEBgKCVk9mR7RbzrnNWC8oBw==", + "dev": true, + "requires": { + "xmlcreate": "^2.0.0" + } + }, + "klaw": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz", + "integrity": "sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.9" + } + }, + "marked": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-0.7.0.tgz", + "integrity": "sha512-c+yYdCZJQrsRjTPhUx7VKkApw9bwDkNbHUKo1ovgcfDjb2kc8rLuRbIFyXL5WOEUwzSSKo3IXpph2K6DqB/KZg==", + "dev": true + }, + "requizzle": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.3.tgz", + "integrity": "sha512-YanoyJjykPxGHii0fZP0uUPEXpvqfBDxWV7s6GKAiiOsiqhX6vHNyW3Qzdmqp/iq/ExbhaGbVrjB4ruEVSM4GQ==", + "dev": true, + "requires": { + "lodash": "^4.17.14" + } + }, + "strip-json-comments": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.0.1.tgz", + "integrity": "sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw==", + "dev": true + }, + "underscore": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.1.tgz", + "integrity": "sha512-5/4etnCkd9c8gwgowi5/om/mYO5ajCaOgdzj/oW+0eQV9WxKBDZw5+ycmKmeaTXjInS/W0BzpGLo2xR2aBwZdg==", + "dev": true + }, + "xmlcreate": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.1.tgz", + "integrity": "sha512-MjGsXhKG8YjTKrDCXseFo3ClbMGvUD4en29H2Cev1dv4P/chlpw6KdYmlCWDkhosBVKRDjM836+3e3pm1cBNJA==", + "dev": true } } }, @@ -352,6 +351,47 @@ "walk-back": "^3.0.1" }, "dependencies": { + "array-back": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-4.0.0.tgz", + "integrity": "sha512-ylVYjv5BzoWXWO7e6fWrzjqzgxmUPWdQrHxgzo/v1EaYXfw6+6ipRdIr7KryAGnVHG08O1Yfpchuv0+YhjPL+Q==", + "dev": true + }, + "file-set": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/file-set/-/file-set-2.0.1.tgz", + "integrity": "sha512-XgOUUpgR6FbbfYcniLw0qm1Am7PnNYIAkd+eXxRt42LiYhjaso0WiuQ+VmrNdtwotyM+cLCfZ56AZrySP3QnKA==", + "dev": true, + "requires": { + "array-back": "^2.0.0", + "glob": "^7.1.3" + }, + "dependencies": { + "array-back": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-2.0.0.tgz", + "integrity": "sha512-eJv4pLLufP3g5kcZry0j6WXpIbzYw9GUB4mVJZno9wfwiBxbizTnHCw3VJb07cBihbFX48Y7oSrW9y+gt4glyw==", + "dev": true, + "requires": { + "typical": "^2.6.1" + } + } + } + }, + "glob": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", + "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, "object-to-spawn-args": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-to-spawn-args/-/object-to-spawn-args-1.1.1.tgz", @@ -360,15 +400,6 @@ } } }, - "klaw": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz", - "integrity": "sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.9" - } - }, "linkify-it": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-2.2.0.tgz", @@ -409,12 +440,6 @@ "integrity": "sha512-n8zCGjxA3T+Mx1pG8HEgbJbkB8JFUuRkeTZQuIM8iPY6oQ8sWOPRZJDFC9a/pNg2QkHEjjGkhBEl/RSyzaDZ3A==", "dev": true }, - "marked": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/marked/-/marked-0.7.0.tgz", - "integrity": "sha512-c+yYdCZJQrsRjTPhUx7VKkApw9bwDkNbHUKo1ovgcfDjb2kc8rLuRbIFyXL5WOEUwzSSKo3IXpph2K6DqB/KZg==", - "dev": true - }, "mdurl": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", @@ -514,15 +539,6 @@ "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=", "dev": true }, - "requizzle": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.3.tgz", - "integrity": "sha512-YanoyJjykPxGHii0fZP0uUPEXpvqfBDxWV7s6GKAiiOsiqhX6vHNyW3Qzdmqp/iq/ExbhaGbVrjB4ruEVSM4GQ==", - "dev": true, - "requires": { - "lodash": "^4.17.14" - } - }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -571,12 +587,6 @@ "integrity": "sha512-DBp0lSvX5G9KGRDTkR/R+a29H+Wk2xItOF+MpZLLNDWbEV9tGPnqLPxHEYjmiz8xGtJHRIqmI+hCjmNzqoA4nQ==", "dev": true }, - "strip-json-comments": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.0.1.tgz", - "integrity": "sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw==", - "dev": true - }, "supports-color": { "version": "5.4.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", @@ -645,12 +655,6 @@ "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", "dev": true }, - "underscore": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.1.tgz", - "integrity": "sha512-5/4etnCkd9c8gwgowi5/om/mYO5ajCaOgdzj/oW+0eQV9WxKBDZw5+ycmKmeaTXjInS/W0BzpGLo2xR2aBwZdg==", - "dev": true - }, "walk-back": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/walk-back/-/walk-back-3.0.1.tgz", @@ -663,12 +667,6 @@ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true }, - "xmlcreate": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.1.tgz", - "integrity": "sha512-MjGsXhKG8YjTKrDCXseFo3ClbMGvUD4en29H2Cev1dv4P/chlpw6KdYmlCWDkhosBVKRDjM836+3e3pm1cBNJA==", - "dev": true - }, "yn": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/yn/-/yn-2.0.0.tgz", diff --git a/package.json b/package.json index 58af752..f98f1e4 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "build": "tsc -p tsconfig.json", "watch": "tsc -w -p tsconfig.json", "prepare": "npm run build", - "test": "npm run build && mocha --ui tdd -r ts-node/register test/specs/**.ts" + "test": "npm run build && mocha --ui tdd -r ts-node/register --timeout 5000 --colors test/specs/**.ts" }, "files": [ "dist/*", @@ -29,7 +29,8 @@ "jsdoc-api": "^5.0.3", "mocha": "^5.2.0", "object-to-spawn-args": "^2.0.0", - "ts-node": "^7.0.1" + "ts-node": "^7.0.1", + "walk-back": "^3.0.1" }, "peerDependencies": { "jsdoc": "^3.6.3" diff --git a/src/Emitter.ts b/src/Emitter.ts index 67d43db..e1e6341 100644 --- a/src/Emitter.ts +++ b/src/Emitter.ts @@ -1,31 +1,58 @@ import * as ts from 'typescript'; import { Dictionary } from './Dictionary'; -import { warn } from './logger'; +import { warn, debug, docletDebugInfo } from './logger'; import { assertNever } from './assert_never'; -import { isClassDoclet, isNamespaceDoclet, isEnumDoclet } from './doclet_utils'; +import { + isDocumentedDoclet, + hasParamsDoclet, + isClassDoclet, + isClassDeclarationDoclet, + isConstructorDoclet, + isNamespaceDoclet, + isEnumDoclet, + isDefaultExportDoclet, + isNamedExportDoclet, + isExportsAssignmentDoclet +} from './doclet_utils'; import { createClass, - createFunction, + createClassMember, createClassMethod, - createInterfaceMethod, + createConstructor, + createEnum, + createFunction, createInterface, - createClassMember, createInterfaceMember, - createNamespaceMember, + createInterfaceMethod, createModule, + createExportDefault, createNamespace, + createNamespaceMember, createTypedef, - createEnum, } from './create_helpers'; +import { generateTree, StringTreeNode, resolveTypeParameters } from './type_resolve_helpers'; -interface IDocletTreeNode +export interface IDocletTreeNode { doclet: TDoclet; children: IDocletTreeNode[]; isNested?: boolean; + /** + * Helper flag for named export identification. + */ + isNamedExport?: boolean; + /** + * Flag set by the 'exported' generation strategy when Emitter._markExported() is called. + */ + isExported?: boolean; + /** + * Makes the node be exported with another name for export. + * Prevails on `@ignore` tags when set. + */ + exportName?: string; } -function shouldMoveOutOfClass(doclet: TDoclet) +function shouldMoveOutOfClass(doclet: TDoclet): boolean { return isClassDoclet(doclet) || isNamespaceDoclet(doclet) @@ -47,6 +74,8 @@ export class Emitter parse(docs?: TAnyDoclet[]) { + debug(`Emitter.parse()`); + this.results = []; this._treeRoots = []; this._treeNodes = {}; @@ -56,11 +85,16 @@ export class Emitter this._createTreeNodes(docs); this._buildTree(docs); + if (this.options.generationStrategy === 'exported') + this._markExported(); this._parseTree(); } emit() { + debug(`----------------------------------------------------------------`); + debug(`Emitter.emit()`); + const resultFile = ts.createSourceFile( 'types.d.ts', '', @@ -86,148 +120,537 @@ export class Emitter private _createTreeNodes(docs: TAnyDoclet[]) { + debug(`----------------------------------------------------------------`); + debug(`Emitter._createTreeNodes()`); + for (let i = 0; i < docs.length; ++i) { const doclet = docs[i]; - if (doclet.kind === 'package' || this._ignoreDoclet(doclet)) + if (doclet.kind === 'package') + { + debug(`Emitter._createTreeNodes(): skipping ${docletDebugInfo(doclet)} (package)`); continue; + } - if (!this._treeNodes[doclet.longname]) + const node = this._treeNodes[doclet.longname]; + if (!node) { + debug(`Emitter._createTreeNodes(): adding ${docletDebugInfo(doclet)} to this._treeNodes`); this._treeNodes[doclet.longname] = { doclet, children: [] }; } + else + { + debug(`Emitter._createTreeNodes(): skipping ${docletDebugInfo(doclet)} (doclet name already known)`); + } } } private _buildTree(docs: TAnyDoclet[]) { + debug(`----------------------------------------------------------------`); + debug(`Emitter._buildTree()`); + for (let i = 0; i < docs.length; ++i) { const doclet = docs[i]; - if (doclet.kind === 'package' || this._ignoreDoclet(doclet)) - continue; + this._buildTreeNode(docs, doclet); + } + } - const obj = this._treeNodes[doclet.longname]; + private _buildTreeNode(docs: TAnyDoclet[], doclet: TAnyDoclet) + { + if (doclet.kind === 'package') + { + debug(`Emitter._buildTreeNode(): skipping ${docletDebugInfo(doclet)} (package)`); + return; + } - if (!obj) + if (isClassDoclet(doclet) && isConstructorDoclet(doclet)) + { + // If this doclet is a constructor, do not watch the 'memberof' attribute: + // - it usually has the same value as the owner class's declaration, + // - it does not point to the owner class itself. + // Use the 'longname' which equals to the owner class's 'longname'. + const ownerClass = this._getNodeFromLongname(doclet.longname, (node: IDocletTreeNode) => isClassDeclarationDoclet(node.doclet)); + if (!ownerClass) { - warn('Failed to find doclet node when building tree, this is likely a bug.', doclet); - continue; + warn(`Failed to find owner class of constructor '${doclet.longname}'.`, doclet); + return; } + // jsdoc@3.6.3 may generate multiple doclets for constructors. + // Watch in the class children whether a constructor is already registered. + if (this._checkDuplicateChild(doclet, ownerClass, (child: IDocletTreeNode) => isConstructorDoclet(child.doclet))) + return; - let interfaceMerge: IDocletTreeNode | null = null; + debug(`Emitter._buildTreeNode(): adding constructor ${docletDebugInfo(doclet)} to class declaration ${docletDebugInfo(ownerClass.doclet)}`); + ownerClass.children.push({ doclet: doclet, children: [] }); - // Generate an interface of the same name as the class to perform - // a namespace merge. - if (doclet.kind === 'class') + // When this constructor is not documented, the 'params' field might not be set. + // Inherit from the owner class when possible, in order to ensure constructor generation with the appropriate parameter list. + if (!hasParamsDoclet(doclet) && isClassDoclet(ownerClass.doclet) && hasParamsDoclet(ownerClass.doclet)) { - const impls = doclet.implements || []; - const mixes = doclet.mixes || []; - const extras = impls.concat(mixes); + debug(`Emitter._buildTreeNode(): inheriting 'params' from owner class ${docletDebugInfo(ownerClass.doclet)} for undocumented constructor ${docletDebugInfo(doclet)}`); + doclet.params = ownerClass.doclet.params; + } - if (extras.length) - { - const longname = this._getInterfaceKey(doclet.longname); - interfaceMerge = this._treeNodes[longname] = { - doclet: { - kind: 'interface', - name: doclet.name, - scope: doclet.scope, - longname: longname, - augments: extras, - memberof: doclet.memberof, - }, - children: [], - }; - } + // Done with this class/constructor. + return; + } + + let interfaceMerge: IDocletTreeNode | null = null; + + // Generate an interface of the same name as the class to perform + // a namespace merge. + if (doclet.kind === 'class') + { + const impls = doclet.implements || []; + const mixes = doclet.mixes || []; + const extras = impls.concat(mixes); + + if (extras.length) + { + const longname = this._getInterfaceKey(doclet.longname); + interfaceMerge = this._treeNodes[longname] = { + doclet: { + kind: 'interface', + name: doclet.name, + scope: doclet.scope, + longname: longname, + augments: extras, + memberof: doclet.memberof, + }, + children: [], + }; + debug(`Emitter._buildTreeNode(): merge interface ${docletDebugInfo(interfaceMerge.doclet)} created for ${docletDebugInfo(doclet)}`); } + } - let namespaceMerge: IDocletTreeNode | null = null; + let namespaceMerge: IDocletTreeNode | null = null; - // Generate an namespace of the same name as the interface/mixin to perform - // a namespace merge containing any static children (ex members and functions). - if (doclet.kind === 'interface' || doclet.kind === 'mixin') + // Generate an namespace of the same name as the interface/mixin to perform + // a namespace merge containing any static children (ex members and functions). + if (doclet.kind === 'interface' || doclet.kind === 'mixin') + { + const staticChildren = docs.filter(d => (d as IDocletBase).memberof === doclet.longname && (d as IDocletBase).scope === 'static'); + if (staticChildren.length) { - const staticChildren = docs.filter(d => (d as IDocletBase).memberof === doclet.longname && (d as IDocletBase).scope === 'static'); - if (staticChildren.length) - { - const longname = this._getNamespaceKey(doclet.longname); - namespaceMerge = this._treeNodes[longname] = { - doclet: { - kind: 'namespace', - name: doclet.name, - scope: doclet.scope, - longname: longname, - memberof: doclet.memberof, - }, - children: [], - }; + const longname = this._getNamespaceKey(doclet.longname); + namespaceMerge = this._treeNodes[longname] = { + doclet: { + kind: 'namespace', + name: doclet.name, + scope: doclet.scope, + longname: longname, + memberof: doclet.memberof, + }, + children: [], + }; + debug(`Emitter._buildTreeNode(): merge namespace ${docletDebugInfo(namespaceMerge.doclet)} created for ${docletDebugInfo(doclet)}`); + + staticChildren.forEach(c => (c as IDocletBase).memberof = longname); + } + } + + // Call isDefaultExportDoclet() a first time here, in order to fix the `.memberof` attribute if not set. + isDefaultExportDoclet(doclet, this._treeNodes); + + if (doclet.memberof) + { + const parent = this._getNodeFromLongname(doclet.memberof, function(node: IDocletTreeNode) { + // When the scope of the doclet is 'instance', look for something that is a class or so. + if (doclet.scope === 'instance') + return isClassDoclet(node.doclet); + return true; + }); + if (!parent) + { + warn(`Failed to find parent of doclet '${doclet.longname}' using memberof '${doclet.memberof}', this is likely due to invalid JSDoc.`, doclet); + return; + } - staticChildren.forEach(c => (c as IDocletBase).memberof = longname); + if (isDefaultExportDoclet(doclet, this._treeNodes)) + { + if (doclet.meta && doclet.meta.code.value && doclet.meta.code.value.startsWith('{')) + { + // 'module.exports = {name: ... }' named export pattern. + debug(`Emitter._buildTreeNode(): 'module.exports = {name: ... }' named export pattern doclet ${docletDebugInfo(doclet)}: skipping doclet but scan the object members`); + // This default export doclet is followed by doclets wich describe each field of the {name: ...} object, + // but those are not named 'export' and thus cannot be detected as named exports by default. + const value = JSON.parse(doclet.meta.code.value); + for (const name in value) + { + this._resolveDocletType(name, parent, + function(namedExportNode: IDocletTreeNode) { + debug(`Emitter._buildTreeNode(): tagging ${docletDebugInfo(namedExportNode.doclet)} as a named export`); + namedExportNode.isNamedExport = true; + } + ); + } + return; } + else + { + // Export doclets may be twiced, escpecially in case of inline or lambda definitions. + // Scan the parent module's children in order to avoid the addition of two doclets for the same default export purpose. + const thisEmitter = this; + if (this._checkDuplicateChild(doclet, parent, (child: IDocletTreeNode) => isDefaultExportDoclet(child.doclet, thisEmitter._treeNodes))) + return; + // No default export doclet yet in the parent module. + debug(`Emitter._buildTreeNode(): adding default export ${docletDebugInfo(doclet)} to module ${docletDebugInfo(parent.doclet)}`); + // The longname of default export doclets is the same as the one of the parent module itself. + // Thus no tree node has been created yet. Let's create one. + parent.children.push({ doclet: doclet, children: [] }); + return; + } + } + if (isExportsAssignmentDoclet(doclet, this._treeNodes)) + { + debug(`Emitter._buildTreeNode(): adding 'exports =' assignment ${docletDebugInfo(doclet)} to module ${docletDebugInfo(parent.doclet)}`); + // The longname of 'exports =' assignment doclets is the same as the one of the parent module itself. + // Thus no tree node has been created yet. Let's create one. + parent.children.push({ doclet: doclet, children: [] }); + return; + } + + const obj = this._treeNodes[doclet.longname]; + if (!obj) + { + warn('Failed to find doclet node when building tree, this is likely a bug.', doclet); + return; } - if (doclet.memberof) + const isParentClassLike = isClassDoclet(parent.doclet); + + // We need to move this into a module of the same name as the parent + if (isParentClassLike && shouldMoveOutOfClass(doclet)) { - const parent = this._treeNodes[doclet.memberof]; + debug(`Emitter._buildTreeNode(): move out of class!`); - if (!parent) + const mod = this._getOrCreateClassNamespace(parent); + + if (interfaceMerge) { - warn(`Failed to find parent of doclet '${doclet.longname}' using memberof '${doclet.memberof}', this is likely due to invalid JSDoc.`, doclet); - continue; + debug(`Emitter._buildTreeNode(): adding ${docletDebugInfo(interfaceMerge.doclet)} to ${docletDebugInfo(mod.doclet)}`); + mod.children.push(interfaceMerge); + } + if (namespaceMerge) + { + debug(`Emitter._buildTreeNode(): adding ${docletDebugInfo(namespaceMerge.doclet)} to ${docletDebugInfo(mod.doclet)}`); + mod.children.push(namespaceMerge); } - const isParentClassLike = isClassDoclet(parent.doclet); + debug(`Emitter._buildTreeNode(): adding ${docletDebugInfo(obj.doclet)} to ${docletDebugInfo(mod.doclet)}`); + mod.children.push(obj); + } + else + { + if (this._checkDuplicateChild(doclet, parent, + function(child: IDocletTreeNode) { + if (child.doclet.kind !== doclet.kind) + return false; + if (child.doclet.longname === doclet.longname) + return true; + // Check also against the optional form of the doclet. + const shortname = doclet.name || ''; + const optionalLongname = doclet.longname.slice(0, doclet.longname.length - shortname.length) + `[${shortname}]`; + if (child.doclet.longname === optionalLongname) + return true; + return false; + } + )) + return; + + const isObjModuleLike = isNamespaceDoclet(doclet); + const isParentModuleLike = isNamespaceDoclet(parent.doclet); - // We need to move this into a module of the same name as the parent - if (isParentClassLike && shouldMoveOutOfClass(doclet)) + if (isObjModuleLike && isParentModuleLike) { - const mod = this._getOrCreateClassNamespace(parent); + debug(`Emitter._buildTreeNode(): nested modules / namespaces!`); + obj.isNested = true; + } + + const isParentEnum = isEnumDoclet(parent.doclet); + if (!isParentEnum) + { if (interfaceMerge) - mod.children.push(interfaceMerge); + { + debug(`Emitter._buildTreeNode(): adding ${docletDebugInfo(interfaceMerge.doclet)} to ${docletDebugInfo(parent.doclet)}`); + parent.children.push(interfaceMerge); + } + if (namespaceMerge) - mod.children.push(namespaceMerge); + { + debug(`Emitter._buildTreeNode(): adding ${docletDebugInfo(namespaceMerge.doclet)} to ${docletDebugInfo(parent.doclet)}`); + parent.children.push(namespaceMerge); + } + + debug(`Emitter._buildTreeNode(): adding ${docletDebugInfo(obj.doclet)} to ${docletDebugInfo(parent.doclet)}`); + parent.children.push(obj); + } + } + } + else + { + const obj = this._treeNodes[doclet.longname]; + if (!obj) + { + warn('Failed to find doclet node when building tree, this is likely a bug.', doclet); + return; + } + + if (interfaceMerge) + { + debug(`Emitter._buildTreeNode(): ${docletDebugInfo(interfaceMerge.doclet)} detected as a root`); + this._treeRoots.push(interfaceMerge); + } - mod.children.push(obj); + if (namespaceMerge) + { + debug(`Emitter._buildTreeNode(): ${docletDebugInfo(namespaceMerge.doclet)} detected as a root`); + this._treeRoots.push(namespaceMerge); + } + + debug(`Emitter._buildTreeNode(): ${docletDebugInfo(obj.doclet)} detected as a root`); + this._treeRoots.push(obj); + } + } + + /** + * Before adding the doclet as a child of a candidate parent, check whether it is a duplicate a an already known child. + * In such a case, the most documented doclet is preferred, or the last one. + * @param doclet Candidate child doclet. + * @param parent Candidate parent node. + * @param match Handler that tells whether an already known child is a duplicate for the previous candidate child `doclet`. + * @returns `true` when a duplicate has been found, `false` otherwise. + */ + private _checkDuplicateChild(doclet: TDoclet, parent: IDocletTreeNode, match: (child: IDocletTreeNode) => boolean): boolean + { + // Check this doclet does not exist yet in the candidate parent's children. + for (const child of parent.children) + { + if (match(child)) + { + if (!isDocumentedDoclet(doclet)) + { + // Do not add the undocumented doclet to the parent twice. + debug(`Emitter._checkConcurrentChild(): skipping undocumented ${docletDebugInfo(doclet)} because ${docletDebugInfo(child.doclet)} is already known in parent ${docletDebugInfo(parent.doclet)}`); + + // At this point, we could be tempted to merge meta information between detached and actual doclets. + // The code below has no particular use in the rest of the process, thus it is not activated yet, + // but it could be of interest, so it is left as a comment: + /*// Check whether the meta information can be merged between a detached and an actual doclet. + if (doclet.meta && doclet.meta.range // <= is `doclet` an actual doclet? + && (!child.doclet.meta || !child.doclet.meta.range)) // <= is `child.doclet` a detached doclet? + { + debug(`Emitter._buildTreeNode(): replacing ${docletDebugInfo(child.doclet)}'s meta info with ${docletDebugInfo(doclet)}'s one`); + child.doclet.meta = doclet.meta; + debug(`Emitter._buildTreeNode(): => is now ${docletDebugInfo(child.doclet)}`); + }*/ } else { - const isObjModuleLike = isNamespaceDoclet(doclet); - const isParentModuleLike = isNamespaceDoclet(parent.doclet); + // Replace the previously known doclet by this new one in other cases. + debug(`Emitter._buildTreeNode(): replacing ${docletDebugInfo(child.doclet)} with ${docletDebugInfo(doclet)} in ${docletDebugInfo(parent.doclet)}`); + child.doclet = doclet; + } + + return true; + } + } + return false; + } - if (isObjModuleLike && isParentModuleLike) - obj.isNested = true; + private _markExported() + { + debug(`----------------------------------------------------------------`); + debug(`Emitter._markExported()`); + + // Ensure `_markExportedNode()` can be used as a callback function with the appropriate `this` context. + this._markExportedNode = this._markExportedNode.bind(this); + + // Scan the tree root nodes, identify the 'module' ones, + // and launch the recursive _markExportedNode() function on them. + for (let i = 0; i < this._treeRoots.length; i++) + { + const node = this._treeRoots[i]; + if (node.doclet.kind === 'module') + this._markExportedNode(node); + } + } + + private _markExportedNode(node: IDocletTreeNode, markThisNode: boolean = true) + { + debug(`Emitter._markExportedNode(${docletDebugInfo(node.doclet)}, markThisNode=${markThisNode})`); + + // First of all, mark the node with the 'isExported' flag. + const doProcessNode = (node.isExported === undefined); + if (markThisNode) + node.isExported = true; + else if (! node.isExported) + node.isExported = false; - const isParentEnum = isEnumDoclet(parent.doclet); + // Process the node once only in order to avoid infinite loops in cas of cyclic dependencies. + if (! doProcessNode) + return; + + // Then, for each kind of node, iterate over the related nodes. + switch (node.doclet.kind) + { + // IClassDoclet: + case 'class': + case 'interface': + case 'mixin': + this._markExportedParams(node, node.doclet.params); + if (node.doclet.augments) + for (const augment of node.doclet.augments) + this._resolveDocletType(augment, node, this._markExportedNode); + if (node.doclet.implements) + for (const implement of node.doclet.implements) + this._resolveDocletType(implement, node, this._markExportedNode); + if (node.doclet.mixes) + for (const mix of node.doclet.mixes) + this._resolveDocletType(mix, node, this._markExportedNode); + this._markExportedChildren(node); + break; + + // IFileDoclet: + case 'file': + this._markExportedParams(node, node.doclet.params); + break; - if (!isParentEnum) + // IEventDoclet: + case 'event': + this._markExportedParams(node, node.doclet.params); + break; + + // IFunctionDoclet: + case 'callback': + case 'function': + if (node.doclet.this) + this._resolveDocletType(node.doclet.this, node, this._markExportedNode); + this._markExportedParams(node, node.doclet.params); + this._markExportedReturns(node, node.doclet.returns); + break; + + // IMemberDoclet: + case 'member': + case 'constant': + if (isDefaultExportDoclet(node.doclet, this._treeNodes)) + { + if (node.doclet.meta && node.doclet.meta.code.value) { - if (interfaceMerge) - parent.children.push(interfaceMerge); - if (namespaceMerge) - parent.children.push(namespaceMerge); + this._resolveDocletType(node.doclet.meta.code.value, node, this._markExportedNode); + } + } + else if (isNamedExportDoclet(node.doclet, this._treeNodes) + && node.doclet.meta && node.doclet.meta.code.value + && (! isEnumDoclet(node.doclet))) + { + const thisEmitter = this; + this._resolveDocletType(node.doclet.meta.code.value, node, function (refNode: IDocletTreeNode) { + // Directly mark the referenced node for export (through this path at least), only if the exported name is changed. + const markThisNode = node.doclet.meta && (node.doclet.meta.code.value === node.doclet.name); + thisEmitter._markExportedNode(refNode, markThisNode); + }, + // Doclet filter: avoid cyclic loops in case this named export references a type with the same name. + function(target: IDocletTreeNode) { + return (target !== node); + } + ); + } + else + { + this._markExportedTypes(node, node.doclet.type); + } + break; - parent.children.push(obj); + // INamespaceDoclet: + case 'module': + // Search for export doclets in the module. + for (const child of node.children) + { + if (isDefaultExportDoclet(child.doclet, this._treeNodes) + || isNamedExportDoclet(child.doclet, this._treeNodes)) + { + this._markExportedNode(child); } } + break; + case 'namespace': + this._markExportedChildren(node); + break; + + // ITypedefDoclet: + case 'typedef': + this._markExportedTypes(node, node.doclet.type); + // When the typedef is for a function, the doclet may have params and returns. + this._markExportedParams(node, node.doclet.params); + this._markExportedReturns(node, node.doclet.returns); + break; + + default: + return assertNever(node.doclet); + } + } + + private _markExportedTypes(node: IDocletTreeNode, types?: IDocletType) + { + if (types) + { + for (const typeName of types.names) + { + this._resolveDocletType(typeName, node, this._markExportedNode); } - else + } + } + + private _markExportedParams(node: IDocletTreeNode, params?: IDocletProp[]) + { + if (params) + { + for (const param of params) { - if (interfaceMerge) - this._treeRoots.push(interfaceMerge); - if (namespaceMerge) - this._treeRoots.push(namespaceMerge); + if (param.type) + { + for (const paramType of param.type.names) + { + this._resolveDocletType(paramType, node, this._markExportedNode); + } + } + } + } + } - this._treeRoots.push(obj); + private _markExportedReturns(node: IDocletTreeNode, returns?: IDocletReturn[]) + { + if (returns) + { + for (const ret of returns) + { + for (const retType of ret.type.names) + { + this._resolveDocletType(retType, node, this._markExportedNode); + } } } } + private _markExportedChildren(node: IDocletTreeNode) + { + for (const child of node.children) + { + this._markExportedNode(child); + } + } + private _parseTree() { + debug(`----------------------------------------------------------------`); + debug(`Emitter._parseTree()`); + for (let i = 0; i < this._treeRoots.length; ++i) { const node = this._parseTreeNode(this._treeRoots[i]); @@ -239,6 +662,20 @@ export class Emitter private _parseTreeNode(node: IDocletTreeNode, parent?: IDocletTreeNode): ts.Node | null { + if (this.options.generationStrategy === 'exported' && !node.isExported) + { + debug(`Emitter._parseTreeNode(${docletDebugInfo(node.doclet)}): skipping doclet, not exported`); + return null; + } + if (!node.exportName // Check the `.exportName` flag before calling `_ignoreDoclet()` in order to avoid false debug lines. + && this._ignoreDoclet(node.doclet)) + { + debug(`Emitter._parseTreeNode(${docletDebugInfo(node.doclet)}): skipping ignored doclet`); + return null; + } + + debug(`Emitter._parseTreeNode(${docletDebugInfo(node.doclet)}, parent=${parent ? docletDebugInfo(parent.doclet) : parent})`); + const children: ts.Node[] = []; if (children) @@ -255,18 +692,70 @@ export class Emitter switch (node.doclet.kind) { case 'class': - return createClass(node.doclet, children); + if (isConstructorDoclet(node.doclet)) + { + // constructor in es6 classes with own doclet + return createConstructor(node.doclet); + } + else + { + return createClass(node.doclet, children, node.exportName); + } case 'constant': case 'member': + if (isDefaultExportDoclet(node.doclet, this._treeNodes) + && node.doclet.meta + && node.doclet.meta.code.value) + { + return createExportDefault(node.doclet, node.doclet.meta.code.value); + } + if (isNamedExportDoclet(node.doclet, this._treeNodes) + && node.doclet.meta + && node.doclet.meta.code.value + && !isEnumDoclet(node.doclet)) + { + if (node.doclet.meta.code.value !== node.doclet.name) + { + const thisEmitter = this; + let tsRes: ts.Node | null = null; + this._resolveDocletType(node.doclet.meta.code.value, node, function(refNode: IDocletTreeNode) { + // Named export from a type with a different name. + // Create a live IDocletTreeNode object with the `.exportName` attribute set. + const namedRefNode: IDocletTreeNode = { + doclet: refNode.doclet, + children: refNode.children, + isNested: refNode.isNested, + isExported: true, + exportName: node.doclet.name + } + tsRes = thisEmitter._parseTreeNode(namedRefNode, parent); + }); + return tsRes; + } + else + { + // Nothing to do. + debug(`Emitter._parseTreeNode(): skipping named export with reference of the same name`); + return null; + } + } + if (isExportsAssignmentDoclet(node.doclet, this._treeNodes)) + { + // Nothing to do. + debug(`Emitter._parseTreeNode(): skipping 'exports =' assignment`); + return null; + } + if (node.doclet.isEnum) - return createEnum(node.doclet); + return createEnum(node.doclet, node.exportName); else if (parent && parent.doclet.kind === 'class') return createClassMember(node.doclet); else if (parent && parent.doclet.kind === 'interface') return createInterfaceMember(node.doclet); else - return createNamespaceMember(node.doclet); + // For both namespace and module members. `node.exportName` may be set. + return createNamespaceMember(node.doclet, node.exportName); case 'callback': case 'function': @@ -279,22 +768,22 @@ export class Emitter else if (parent && parent.doclet.kind === 'interface') return createInterfaceMethod(node.doclet); } - return createFunction(node.doclet); + return createFunction(node.doclet, node.exportName); case 'interface': - return createInterface(node.doclet, children); + return createInterface(node.doclet, children, node.exportName); case 'mixin': - return createInterface(node.doclet, children); + return createInterface(node.doclet, children, node.exportName); case 'module': return createModule(node.doclet, !!node.isNested, children); case 'namespace': - return createNamespace(node.doclet, !!node.isNested, children); + return createNamespace(node.doclet, !!node.isNested, children, node.exportName); case 'typedef': - return createTypedef(node.doclet, children); + return createTypedef(node.doclet, children, node.exportName); case 'file': return null; @@ -310,18 +799,41 @@ export class Emitter private _ignoreDoclet(doclet: TAnyDoclet): boolean { - if (doclet.kind === 'package' - || doclet.ignore - || (!this.options.private && doclet.access === 'private')) { + // Constructors should be generated with their documentation whatever their access level. + if (doclet.kind !== 'package' && isConstructorDoclet(doclet)) + { + return false; + } + + let reason: string|undefined = undefined; + if (doclet.kind === 'package') + reason = 'package doclet'; + else if (!!doclet.ignore) + reason = 'doclet with an ignore flag'; + else if (!this.options.private && doclet.access === 'private') + reason = 'private access disabled'; + // Disable method overrides. See [tsd-jsdoc#104](https://github.com/englercj/tsd-jsdoc/issues/104). + else if (doclet.kind === 'function' && (doclet.override || doclet.overrides)) + reason = 'overriding doclet'; + if (reason + || doclet.kind === 'package') // <= hack for typescript resolutions + { + debug(`Emitter._ignoreDoclet(doclet=${docletDebugInfo(doclet)}) => true (${reason})`); return true } - if (doclet.access === undefined) { + if (doclet.access === undefined) + { return false } const accessLevels = ["private", "package", "protected", "public"]; - return accessLevels.indexOf(doclet.access.toString()) < accessLevels.indexOf(this.options.access || "package") + const ignored = accessLevels.indexOf(doclet.access.toString()) < accessLevels.indexOf(this.options.access || "package"); + if (ignored) + { + debug(`Emitter._ignoreDoclet(doclet=${docletDebugInfo(doclet)}) => true (low access level)`); + } + return ignored; } private _getInterfaceKey(longname?: string): string @@ -336,14 +848,18 @@ export class Emitter private _getOrCreateClassNamespace(obj: IDocletTreeNode): IDocletTreeNode { - if (obj.doclet.kind === 'namespace') + if (obj.doclet.kind === 'module' || obj.doclet.kind === 'namespace') + { return obj; + } const namespaceKey = this._getNamespaceKey(obj.doclet.longname); let mod = this._treeNodes[namespaceKey]; if (mod) + { return mod; + } mod = this._treeNodes[namespaceKey] = { doclet: { @@ -367,14 +883,173 @@ export class Emitter let parentMod = this._getOrCreateClassNamespace(parent); + debug(`Emitter._getOrCreateClassNamespace(): pushing ${docletDebugInfo(mod.doclet)} as a child of ${docletDebugInfo(parentMod.doclet)}`); mod.doclet.memberof = parentMod.doclet.longname; parentMod.children.push(mod); } else { + debug(`Emitter._getOrCreateClassNamespace(): no memberof, pushing ${docletDebugInfo(mod.doclet)} as a root`); this._treeRoots.push(mod); } return mod; } + + /** + * Search for a IDocletTreeNode from its longname. + * Handles situations where default exported lambda types hold the same longname as the parent module. + * @param longname Longname to search for. + * @param filter Optional filter function that indicates whether a doclet is an appropriate candidate for what we search. + */ + private _getNodeFromLongname(longname: string, filter?: (node: IDocletTreeNode) => boolean): IDocletTreeNode | null + { + function _debug(msg: string) { /*debug(msg);*/ } + + const node = this._treeNodes[longname]; + if (!node) + { + _debug(`Emitter._getNodeFromLongname('${longname}') => null`); + warn(`No such doclet '${longname}'`); + return null; + } + + if (!filter || filter(node)) + { + _debug(`Emitter._getNodeFromLongname('${longname}') => ${docletDebugInfo(node.doclet)}`); + return node; + } + + if (node.doclet.kind === 'module') + { + // The searched item has the same longname as a mdoule. + // This happens when the item is an inline or lambda class exported by default. + // Look for the item in the module's children. + for (const child of node.children) + { + if (child.doclet.longname === longname && filter(child)) + { + _debug(`Emitter._getNodeFromLongname('${longname}') => ${docletDebugInfo(child.doclet)}`); + return child; + } + } + _debug(`Emitter._getNodeFromLongname('${longname}') => null`); + warn(`No such doclet '${longname}' in module`); + return null; + } + else + { + _debug(`Emitter._getNodeFromLongname('${longname}') => null`); + warn(`Unexpected doclet for longname '${longname}`, node.doclet); + return null; + } + } + + /** + * From the context of `currentNode`, search for the doclets which type name corresponds to `typeName`, and apply `callback` on them. + * @param currentNode Starting context for type resolution. + * @param typeName Type name to search for. + * @param callback Callback to apply on each resolved doclet. + * @param filter Optional filter function that indicates whether a doclet is an appropriate candidate for what we search. + */ + private _resolveDocletType( + typeName: string, currentNode: IDocletTreeNode, + callback: (node: IDocletTreeNode) => void, + filter?: (node: IDocletTreeNode) => boolean): void + { + function _debug(msg: string) { /*debug(msg);*/ } + _debug(`Emitter._resolveDocletType(typeName='${typeName}', currentNode=${docletDebugInfo(currentNode.doclet)})`); + + const tokens = generateTree(typeName); + if (!tokens) + { + warn(`Could not resolve type '${typeName}' in current node:`, currentNode.doclet); + return; + } + tokens.dump((msg: string) => _debug(`Emitter._resolveDocletType(): tokens = ${msg}`)); + + const thisEmitter = this; + tokens.walkTypes(function(token: StringTreeNode) { + _debug(`Emitter._resolveDocletType(): token = {name:${token.name}, type:${token.typeToString()}}`); + typeName = token.name; + if (typeName.match(/.*\[ *\].*/)) + typeName = typeName.slice(0, typeName.indexOf('[')); + _debug(`Emitter._resolveDocletType(): typeName = ${typeName}`); + + // Skip basic types. + switch (typeName) + { + case 'void': + case 'null': + case 'string': case 'String': + case 'boolean': case 'Boolean': + case 'number': case 'Number': + case 'function': case 'Function': + case 'any': + case 'object': case 'Object': // <= means 'any' in jsdoc + case '*': // <= means 'any' in jsdoc + case 'Array': + case 'Union': + case 'Promise': + case 'HTMLElement': // <= Basic typescript type. + return; + } + + // Lookup for the target symbol through up the scopes. + let scope: string | undefined = currentNode.doclet.longname; + while (scope) + { + _debug(`Emitter._resolveDocletType(): scope='${scope}'`); + const longnames = [ + `${scope}.${typeName}`, + `${scope}~${typeName}`, + `${scope}~${typeName}$$interface$helper`, + `${scope}~${typeName}$$namespace$helper`, + ]; + let targetFound = false; + for (const longname of longnames) + { + _debug(`Emitter._resolveDocletType(): trying longname '${longname}'...`); + const target = thisEmitter._treeNodes[longname]; + if (target) + { + if (filter && (! filter(target))) + { + _debug(`Emitter._resolveDocletType(): filtered out ${docletDebugInfo(target.doclet)}`); + } + else + { + _debug(`Emitter._resolveDocletType(): found! ${docletDebugInfo(target.doclet)}`); + callback(target); + targetFound = true; + } + } + } + // When one target at least has been found within this scope, stop searching. + if (targetFound) + { + _debug(`Emitter._resolveDocletType(): done`); + return; + } + + // Search for templates. + // Does not work with jsdoc@3.6.3, as long as jsdoc@3.6.x does not support @template tags, nor generates "tags" sections anymore. + const scopeNode: IDocletTreeNode | undefined = thisEmitter._treeNodes[scope]; + if (! scopeNode) break; + for (const tsTypeParameterDeclaration of resolveTypeParameters(scopeNode.doclet)) + { + if (tsTypeParameterDeclaration.name.text === typeName) + { + _debug(`Emitter._resolveDocletType(): template found! in ${docletDebugInfo(scopeNode.doclet)}`); + // No doclet. Stop searching. + return; + } + } + + scope = scopeNode.doclet.memberof; + } + + warn(`Could not resolve type '${typeName}' in current node:`, currentNode.doclet); + }); + } } diff --git a/src/create_helpers.ts b/src/create_helpers.ts index f8ad3c9..3a682f6 100644 --- a/src/create_helpers.ts +++ b/src/create_helpers.ts @@ -1,5 +1,5 @@ import * as ts from 'typescript'; -import { warn } from './logger'; +import { warn, debug, docletDebugInfo } from './logger'; import { isClassDoclet, isEnumDoclet, isFileDoclet, isEventDoclet, isFunctionDoclet, isTypedefDoclet } from './doclet_utils'; import { PropTree } from "./PropTree"; import { @@ -15,6 +15,8 @@ import { const declareModifier = ts.createModifier(ts.SyntaxKind.DeclareKeyword); const constModifier = ts.createModifier(ts.SyntaxKind.ConstKeyword); const readonlyModifier = ts.createModifier(ts.SyntaxKind.ReadonlyKeyword); +const exportModifier = ts.createModifier(ts.SyntaxKind.ExportKeyword); +const defaultModifier = ts.createModifier(ts.SyntaxKind.DefaultKeyword); function validateClassLikeChildren(children: ts.Node[] | undefined, validate: (n: ts.Node) => boolean, msg: string): void { @@ -59,7 +61,8 @@ function validateModuleChildren(children?: ts.Node[]): void && !ts.isEnumDeclaration(child) && !ts.isModuleDeclaration(child) && !ts.isTypeAliasDeclaration(child) - && !ts.isVariableStatement(child)) + && !ts.isVariableStatement(child) + && !ts.isExportAssignment(child)) { warn('Encountered child that is not a supported declaration, this is likely due to invalid JSDoc.', child); children.splice(i, 1); @@ -68,6 +71,26 @@ function validateModuleChildren(children?: ts.Node[]): void } } +function buildName(doclet: IDocletBase, altName?: string): ts.Identifier +{ + if (altName) + return ts.createIdentifier(altName); + if (doclet.name.startsWith('exports.')) + return ts.createIdentifier(doclet.name.replace('exports.', '')); + return ts.createIdentifier(doclet.name); +} + +function buildOptionalName(doclet: IDocletBase, altName?: string): ts.Identifier | undefined +{ + if (altName) + return ts.createIdentifier(altName); + if (doclet.meta && (doclet.meta.code.name === 'module.exports')) + return undefined; + if (doclet.name.startsWith('exports.')) + return ts.createIdentifier(doclet.name.replace('exports.', '')); + return ts.createIdentifier(doclet.name); +} + function formatMultilineComment(comment: string): string { return comment.split('\n').join('\n * '); @@ -170,7 +193,12 @@ function handleComment(doclet: TDoclet, node: T): T } else if (isClassDoclet(doclet) && doclet.classdesc) { - description = `\n * ${formatMultilineComment(doclet.classdesc)}`; + // 'classdesc' field may be set for constructors that have no relevant comment. + // It should not be used here, otherwise it duplicates the class description. + if (! ts.isConstructorDeclaration(node)) + { + description = `\n * ${formatMultilineComment(doclet.classdesc)}`; + } } const examples = handleExamplesComment(doclet); @@ -216,39 +244,60 @@ function handleComment(doclet: TDoclet, node: T): T return node; } -export function createClass(doclet: IClassDoclet, children?: ts.Node[]): ts.ClassDeclaration +export function createClass(doclet: IClassDoclet, children?: ts.Node[], altName?: string): ts.ClassDeclaration { + debug(`createClass(${docletDebugInfo(doclet)}, altName=${altName})`); + validateClassChildren(children); - const mods = doclet.memberof ? undefined : [declareModifier]; + const mods: ts.Modifier[] = []; + if (!doclet.memberof) + mods.push(declareModifier); + if (doclet.meta && doclet.meta.code.name === 'module.exports') + mods.push(exportModifier, defaultModifier); const members = children as ts.ClassElement[] || []; const typeParams = resolveTypeParameters(doclet); const heritageClauses = resolveHeritageClauses(doclet, false); - if (doclet.name.startsWith('exports.')) - doclet.name = doclet.name.replace('exports.', ''); - if (doclet.params) { - const params = createFunctionParams(doclet); - - members.unshift( - ts.createConstructor( - undefined, // decorators - undefined, // modifiers - params, // parameters - undefined // body - ) - ); + // Check whether the constructor has already been declared. + if (members.filter(member => ts.isConstructorDeclaration(member)).length === 0) + { + debug(`createClass(): no constructor set yet, adding one automatically`); + members.unshift( + ts.createConstructor( + undefined, // decorators + undefined, // modifiers + createFunctionParams(doclet), // parameters + undefined // body + ) + ); + } } if (doclet.properties) { const tree = new PropTree(doclet.properties); - for (let i = 0; i < tree.roots.length; ++i) + nextProperty: for (let i = 0; i < tree.roots.length; ++i) { const node = tree.roots[i]; + + // Check whether the property has already been declared. + for (const tsProp of members.filter(member => ts.isPropertyDeclaration(member))) + { + if (tsProp.name) + { + const propName:string = ( tsProp.name).text; + if (propName === node.name) + { + debug(`createClass(): skipping property already declared '${node.name}'`); + continue nextProperty; + } + } + } + const opt = node.prop.optional ? ts.createToken(ts.SyntaxKind.QuestionToken) : undefined; const t = node.children.length ? createTypeLiteral(node.children, node) : resolveType(node.prop.type); @@ -274,15 +323,17 @@ export function createClass(doclet: IClassDoclet, children?: ts.Node[]): ts.Clas return handleComment(doclet, ts.createClassDeclaration( undefined, // decorators mods, // modifiers - doclet.name, // name + buildOptionalName(doclet, altName), // name typeParams, // typeParameters heritageClauses,// heritageClauses members // members )); } -export function createInterface(doclet: IClassDoclet, children?: ts.Node[]): ts.InterfaceDeclaration +export function createInterface(doclet: IClassDoclet, children?: ts.Node[], altName?: string): ts.InterfaceDeclaration { + debug(`createInterface(${docletDebugInfo(doclet)}, altName=${altName})`); + validateInterfaceChildren(children); const mods = doclet.memberof ? undefined : [declareModifier]; @@ -290,34 +341,34 @@ export function createInterface(doclet: IClassDoclet, children?: ts.Node[]): ts. const typeParams = resolveTypeParameters(doclet); const heritageClauses = resolveHeritageClauses(doclet, true); - if (doclet.name.startsWith('exports.')) - doclet.name = doclet.name.replace('exports.', ''); - return handleComment(doclet, ts.createInterfaceDeclaration( undefined, // decorators mods, // modifiers - doclet.name, // name + buildName(doclet, altName), // name, avoid undefined typeParams, // typeParameters heritageClauses,// heritageClauses members // members )); } -export function createFunction(doclet: IFunctionDoclet): ts.FunctionDeclaration +export function createFunction(doclet: IFunctionDoclet, altName?: string): ts.FunctionDeclaration { - const mods = doclet.memberof ? undefined : [declareModifier]; + debug(`createFunction(${docletDebugInfo(doclet)}, altName=${altName})`); + + const mods = []; + if (!doclet.memberof) + mods.push(declareModifier); + if (doclet.meta && (doclet.meta.code.name === 'module.exports')) + mods.push(exportModifier, defaultModifier); const params = createFunctionParams(doclet); const type = createFunctionReturnType(doclet); const typeParams = resolveTypeParameters(doclet); - if (doclet.name.startsWith('exports.')) - doclet.name = doclet.name.replace('exports.', ''); - return handleComment(doclet, ts.createFunctionDeclaration( undefined, // decorators mods, // modifiers undefined, // asteriskToken - doclet.name, // name + buildOptionalName(doclet, altName), // name typeParams, // typeParameters params, // parameters type, // type @@ -327,6 +378,8 @@ export function createFunction(doclet: IFunctionDoclet): ts.FunctionDeclaration export function createClassMethod(doclet: IFunctionDoclet): ts.MethodDeclaration { + debug(`createClassMethod(${docletDebugInfo(doclet)})`); + const mods: ts.Modifier[] = []; const params = createFunctionParams(doclet); const type = createFunctionReturnType(doclet); @@ -345,9 +398,6 @@ export function createClassMethod(doclet: IFunctionDoclet): ts.MethodDeclaration if (doclet.scope === 'static') mods.push(ts.createModifier(ts.SyntaxKind.StaticKeyword)); - if (doclet.name.startsWith('exports.')) - doclet.name = doclet.name.replace('exports.', ''); - const [ name, questionToken ] = resolveOptionalFromName(doclet); return handleComment(doclet, ts.createMethod( undefined, // decorators @@ -364,14 +414,13 @@ export function createClassMethod(doclet: IFunctionDoclet): ts.MethodDeclaration export function createInterfaceMethod(doclet: IFunctionDoclet): ts.MethodSignature { + debug(`createInterfaceMethod(${docletDebugInfo(doclet)})`); + const mods: ts.Modifier[] = []; const params = createFunctionParams(doclet); const type = createFunctionReturnType(doclet); const typeParams = resolveTypeParameters(doclet); - if (doclet.name.startsWith('exports.')) - doclet.name = doclet.name.replace('exports.', ''); - const [ name, questionToken ] = resolveOptionalFromName(doclet); return handleComment(doclet, ts.createMethodSignature( typeParams, // typeParameters @@ -382,8 +431,10 @@ export function createInterfaceMethod(doclet: IFunctionDoclet): ts.MethodSignatu )); } -export function createEnum(doclet: IMemberDoclet): ts.EnumDeclaration +export function createEnum(doclet: IMemberDoclet, altName?: string): ts.EnumDeclaration { + debug(`createEnum(${docletDebugInfo(doclet)}, altName=${altName})`); + const mods: ts.Modifier[] = []; const props: ts.EnumMember[] = []; @@ -407,22 +458,18 @@ export function createEnum(doclet: IMemberDoclet): ts.EnumDeclaration return handleComment(doclet, ts.createEnumDeclaration( undefined, mods, - doclet.name, + buildName(doclet, altName), props, )); } export function createClassMember(doclet: IMemberDoclet): ts.PropertyDeclaration { - const mods: ts.Modifier[] = []; + debug(`createClassMember(${docletDebugInfo(doclet)})`); + const type = resolveType(doclet.type, doclet); - if (doclet.access === 'private') - mods.push(ts.createModifier(ts.SyntaxKind.PrivateKeyword)); - else if (doclet.access === 'protected') - mods.push(ts.createModifier(ts.SyntaxKind.ProtectedKeyword)); - else if (doclet.access === 'public') - mods.push(ts.createModifier(ts.SyntaxKind.PublicKeyword)); + const mods: ts.Modifier[] = getAccessModifiers(doclet); if (doclet.scope === 'static') mods.push(ts.createModifier(ts.SyntaxKind.StaticKeyword)); @@ -441,8 +488,36 @@ export function createClassMember(doclet: IMemberDoclet): ts.PropertyDeclaration )); } +function getAccessModifiers(doclet: IMemberDoclet | IClassDoclet): ts.Modifier[] +{ + const mods: ts.Modifier[] = []; + + if (doclet.access === 'private' || doclet.access === 'package') + mods.push(ts.createModifier(ts.SyntaxKind.PrivateKeyword)); + else if (doclet.access === 'protected') + mods.push(ts.createModifier(ts.SyntaxKind.ProtectedKeyword)); + else if (doclet.access === 'public') + mods.push(ts.createModifier(ts.SyntaxKind.PublicKeyword)); + + return mods +} + +export function createConstructor(doclet: IClassDoclet): ts.ConstructorDeclaration +{ + debug(`createConstructor(${docletDebugInfo(doclet)})`); + + return handleComment(doclet, ts.createConstructor( + undefined, // decorators + getAccessModifiers(doclet), // modifiers + createFunctionParams(doclet), // parameters + undefined // body + )) +} + export function createInterfaceMember(doclet: IMemberDoclet): ts.PropertySignature { + debug(`createInterfaceMember(${docletDebugInfo(doclet)})`); + const mods: ts.Modifier[] = []; const type = resolveType(doclet.type, doclet); @@ -462,8 +537,14 @@ export function createInterfaceMember(doclet: IMemberDoclet): ts.PropertySignatu )); } -export function createNamespaceMember(doclet: IMemberDoclet): ts.VariableStatement +/** + * Used for both namespace and module members. + * `altName` may be set for an exported module member. + */ +export function createNamespaceMember(doclet: IMemberDoclet, altName?: string): ts.VariableStatement { + debug(`createNamespaceMember(${docletDebugInfo(doclet)})`); + const mods = doclet.memberof ? undefined : [declareModifier]; const flags = (doclet.kind === 'constant' || doclet.readonly) ? ts.NodeFlags.Const : undefined; @@ -475,14 +556,11 @@ export function createNamespaceMember(doclet: IMemberDoclet): ts.VariableStateme // ignore regular type if constant literal, because a literal provides more type information const type = initializer ? undefined : resolveType(doclet.type, doclet); - if (doclet.name.startsWith('exports.')) - doclet.name = doclet.name.replace('exports.', ''); - return handleComment(doclet, ts.createVariableStatement( mods, ts.createVariableDeclarationList([ ts.createVariableDeclaration( - doclet.name, // name + buildName(doclet, altName), // name type, // type initializer // initializer ) @@ -492,17 +570,24 @@ export function createNamespaceMember(doclet: IMemberDoclet): ts.VariableStateme )); } +export function createExportDefault(doclet: IMemberDoclet, value: string): ts.ExportAssignment | null +{ + debug(`createExportDefault(${docletDebugInfo(doclet)}, '${value}')`); + + const expression : ts.Expression = ts.createIdentifier(value); + return handleComment(doclet, ts.createExportDefault(expression)); +} + export function createModule(doclet: INamespaceDoclet, nested: boolean, children?: ts.Node[]): ts.ModuleDeclaration { + debug(`createModule(${docletDebugInfo(doclet)})`); + validateModuleChildren(children); const mods = doclet.memberof ? undefined : [declareModifier]; let body: ts.ModuleBlock | undefined = undefined; let flags = ts.NodeFlags.None; - if (doclet.name.startsWith('exports.')) - doclet.name = doclet.name.replace('exports.', ''); - if (nested) flags |= ts.NodeFlags.NestedNamespace; @@ -520,17 +605,16 @@ export function createModule(doclet: INamespaceDoclet, nested: boolean, children )); } -export function createNamespace(doclet: INamespaceDoclet, nested: boolean, children?: ts.Node[]): ts.ModuleDeclaration +export function createNamespace(doclet: INamespaceDoclet, nested: boolean, children?: ts.Node[], altName?: string): ts.ModuleDeclaration { + debug(`createNamespace(${docletDebugInfo(doclet)}, altName=${altName})`); + validateModuleChildren(children); const mods = doclet.memberof ? undefined : [declareModifier]; let body: ts.ModuleBlock | undefined = undefined; let flags = ts.NodeFlags.Namespace; - if (doclet.name.startsWith('exports.')) - doclet.name = doclet.name.replace('exports.', ''); - if (nested) flags |= ts.NodeFlags.NestedNamespace; @@ -539,30 +623,27 @@ export function createNamespace(doclet: INamespaceDoclet, nested: boolean, child body = ts.createModuleBlock(children as ts.Statement[]); } - const name = ts.createIdentifier(doclet.name); - return handleComment(doclet, ts.createModuleDeclaration( undefined, // decorators mods, // modifiers - name, // name + buildName(doclet, altName), // name body, // body flags // flags )); } -export function createTypedef(doclet: ITypedefDoclet, children?: ts.Node[]): ts.TypeAliasDeclaration +export function createTypedef(doclet: ITypedefDoclet, children?: ts.Node[], altName?: string): ts.TypeAliasDeclaration { + debug(`createTypedef(${docletDebugInfo(doclet)}, altName=${altName})`); + const mods = doclet.memberof ? undefined : [declareModifier]; const type = resolveType(doclet.type, doclet); const typeParams = resolveTypeParameters(doclet); - if (doclet.name.startsWith('exports.')) - doclet.name = doclet.name.replace('exports.', ''); - return handleComment(doclet, ts.createTypeAliasDeclaration( undefined, // decorators mods, // modifiers - doclet.name, // name + buildName(doclet, altName), // name typeParams, // typeParameters type // type )); diff --git a/src/doclet_utils.ts b/src/doclet_utils.ts index 5ee7bbb..ae5ab96 100644 --- a/src/doclet_utils.ts +++ b/src/doclet_utils.ts @@ -1,8 +1,68 @@ +import * as fs from 'fs'; +import * as path from 'path'; +import { Dictionary } from './Dictionary'; +import { IDocletTreeNode } from './Emitter'; +import { warn, debug, docletDebugInfo } from './logger'; + + +export function isDocumentedDoclet(doclet: TDoclet): boolean +{ + // Same predicate as in publish(). + if (doclet.undocumented) + if (doclet.comment && doclet.comment.length > 0) + return true; + else + return false; + else + return true; +} + +export function hasParamsDoclet(doclet: TDoclet): boolean +{ + if (doclet.kind === 'class' + || doclet.kind === 'interface' + || doclet.kind === 'mixin' + || doclet.kind === 'file' + || doclet.kind === 'event' + || doclet.kind === 'function' + || doclet.kind === 'callback' + || doclet.kind === 'typedef') + { + if (doclet.params && doclet.params.length > 0) + return true; + } + return false; +} + export function isClassDoclet(doclet: TDoclet): doclet is IClassDoclet { return doclet.kind === 'class' || doclet.kind === 'interface' || doclet.kind === 'mixin'; } +export function isClassDeclarationDoclet(doclet: TDoclet): boolean +{ + return !!( + doclet.kind === 'class' + && doclet.meta + && ( + // When the owner class's comment contains a `@class` tag, the first doclet for the class is a detached one, + // by the way the `meta.code` section is empty. + !doclet.meta.code.type + || doclet.meta.code.type === 'ClassDeclaration' + || doclet.meta.code.type === 'ClassExpression' + ) + ); +} + +export function isConstructorDoclet(doclet: TDoclet): boolean +{ + return !!( + doclet.kind === 'class' + && doclet.meta + && doclet.meta.code.type === 'MethodDefinition' + ); +} + export function isFileDoclet(doclet: TDoclet): doclet is IFileDoclet { return doclet.kind === 'file'; @@ -38,7 +98,122 @@ export function isPackageDoclet(doclet: TAnyDoclet): doclet is IPackageDoclet return doclet.kind === 'package'; } -export function isEnumDoclet(doclet: TDoclet): doclet is IMemberDoclet +export function isEnumDoclet(doclet: TDoclet): boolean +{ + return isMemberDoclet(doclet) && (doclet.isEnum === true); +} + +export function isDefaultExportDoclet(doclet: TDoclet, treeNodes: Dictionary): boolean +{ + if (doclet.kind !== 'module' + && doclet.meta + && doclet.meta.code.name === 'module.exports' + && doclet.longname.startsWith('module:')) + { + // Jsdoc does not set the memberof attribute for default exports (we shall fix that actually). + // By default, the longname of the default export doclet is the longname of the member module. + const moduleName = doclet.memberof ? doclet.memberof : doclet.longname; + // Let's check the longname corresponds to an existing module in the current tree nodes. + const node = treeNodes[moduleName]; + if (node && node.doclet.kind === 'module') + { + // This is a default export doclet. + + // Let's fix a couple of missing things (if not already fixed) at this point. + if (!doclet.memberof) + { + doclet.memberof = node.doclet.longname; + debug(`isDefaultExport(): ${docletDebugInfo(doclet)}.memberof fixed to '${doclet.memberof}'`); + } + + if (!doclet.meta.code.value) + { + // When the default export value is not given in the `meta.code.value` attribute, + // let's read it directly from the source file. + const sourcePath : string = path.join(doclet.meta.path, doclet.meta.filename); + const fd = fs.openSync(sourcePath, 'r'); + if (fd < 0) + { + warn(`Could not read from '${sourcePath}'`); + return true; + } + const begin = doclet.meta.range[0]; + const end = doclet.meta.range[1]; + const length = end - begin; + const buffer = Buffer.alloc(length); + if (fs.readSync(fd, buffer, 0, length, begin) !== length) + { + warn(`Could not read from '${sourcePath}'`); + return true; + } + doclet.meta.code.value = buffer.toString().trim(); + if (doclet.meta.code.value.endsWith(";")) + doclet.meta.code.value = doclet.meta.code.value.slice(0, -1).trimRight(); + if (doclet.meta.code.value.match(/^export +default +/)) + doclet.meta.code.value = doclet.meta.code.value.replace(/^export +default +/, ""); + debug(`isDefaultExport(): ${docletDebugInfo(doclet)}.meta.code.value fixed to '${doclet.meta.code.value}'`); + } + + return true; + } + } + return false; +} + +export function isNamedExportDoclet(doclet: TDoclet, treeNodes: Dictionary): boolean +{ + // First of all, check whether the `.isNamedExport` flag is already set. + const node = treeNodes[doclet.longname]; + if (node && node.isNamedExport) + { + return true; + } + + // Otherwise, analyze the doclet info. + if (doclet.kind !== 'module' + && doclet.meta + && doclet.meta.code.name + && (doclet.meta.code.name.startsWith('module.exports.') || doclet.meta.code.name.startsWith('exports.')) + && doclet.longname.startsWith('module:') + && doclet.memberof) // <= memberof is set by jsdoc for named exports. + { + // Let's check the parent node is a module. + const parent = treeNodes[doclet.memberof]; + if (parent && parent.doclet.kind === 'module') + { + // Set the `.isNamedExport` attribute by the way. + // This ensures the doclet will still be recognized as a named export, even though its name is changed (in create_helpers.ts possibly). + if (node) + { + node.isNamedExport = true; + } + + return true; + } + } + return false; +} + +/** + * Determines whether the given doclet is a 'exports =' assignment. + * In that case, the doclet longname takes the module's one, + * disturbing by the way the processing of the doclets. + */ +export function isExportsAssignmentDoclet(doclet: TDoclet, treeNodes: Dictionary): + //doclet is IMemberDoclet <- Makes typescript believe `doclet` is of kind `IMemberDoclet`, even when the result is `false` + boolean { - return isMemberDoclet(doclet) && doclet.isEnum; + if (doclet.kind === 'member' + && doclet.meta + && doclet.meta.code.name + && doclet.meta.code.name === 'exports' + && doclet.longname.startsWith('module:') + && doclet.memberof) // <= memberof is set by jsdoc for named exports. + { + // Let's check the memberof node is a module. + const node = treeNodes[doclet.memberof]; + if (node && node.doclet.kind === 'module') + return true; + } + return false; } diff --git a/src/logger.ts b/src/logger.ts index a58c56b..3c8b189 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -25,4 +25,53 @@ export function warn(msg: string, data?: any) { console.warn(data); } + + if (isDebug) + { + // `console.warn()` pushes in stderr. + // Let's push the message in stdout with `console.log()` as well, as `debug()` does for a better debugging experience. + console.log(`${header} WARN: ${msg}`); + if (arguments.length > 1) + { + console.log(data); + } + } +} + +let isDebug = false; + +export function setDebug(value: boolean) +{ + isDebug = value; +} + +export function debug(msg: string, data?: any) +{ + if (typeof(console) === 'undefined') + return; + + if (isDebug) + { + // Mix of tsd-jsdoc header with the jsdoc pattern on 'debug' option activated. + console.log(`${header} DEBUG: ${msg}`); + if (arguments.length > 1) + { + console.log(data); + } + } +} + +export function docletDebugInfo(doclet: TAnyDoclet) : string { + let debugInfo = `{longname='${doclet.longname}', kind='${doclet.kind}'`; + if ((doclet.kind !== 'package') && doclet.meta) + { + if (doclet.meta.code.id) + debugInfo += `, id='${doclet.meta.code.id}'`; + if (doclet.meta.range) + debugInfo += `, range=[${doclet.meta.range[0]}-${doclet.meta.range[1]}]`; + else if (doclet.meta.lineno) + debugInfo += `, lineno=${doclet.meta.lineno}`; + } + debugInfo += `}`; + return debugInfo; } diff --git a/src/publish.ts b/src/publish.ts index 397d9ba..9d150d4 100644 --- a/src/publish.ts +++ b/src/publish.ts @@ -2,7 +2,7 @@ import * as path from 'path'; import * as fs from 'fs'; import * as helper from 'jsdoc/util/templateHelper'; import { Emitter } from './Emitter'; -import { setVerbose } from './logger'; +import { setVerbose, setDebug, warn, debug, docletDebugInfo } from './logger'; /** * @param {TAFFY} data - The TaffyDB containing the data that jsdoc parsed. @@ -10,14 +10,55 @@ import { setVerbose } from './logger'; */ export function publish(data: TDocletDb, opts: ITemplateConfig) { - // remove undocumented stuff. - data({ undocumented: true }).remove(); + // Start with taking into account 'verbose' and 'debug' options. + setVerbose(!!opts.verbose); + setDebug(!!opts.debug); + + // In order not to break backward compatibility, the 'documented' generation strategy is used by default. + if (!opts.generationStrategy) + { + opts.generationStrategy = 'documented'; + } + debug(`publish(): Generation strategy: '${opts.generationStrategy}'`); + + if (opts.generationStrategy === 'documented') + { + // Remove undocumented stuff. + data( + // Use of a function as the TaffyDB query in order to track what is removed. + // See [TaffyDB documentation](http://taffydb.com/writing_queries.html) + function(this: TDoclet) // <= 'this' type declaration inspired from [stackoverflow](https://stackoverflow.com/questions/41944650) + { + if (this.undocumented) + { + // Some doclets are marked 'undocumented', but actually have a 'comment' set. + if ((! this.comment) || (this.comment === '')) + { + debug(`publish(): ${docletDebugInfo(this)} removed`); + return true; + } + else + { + debug(`publish(): ${docletDebugInfo(this)} saved from removal`); + } + } + return false; + } + ).remove(); + } + else if (opts.generationStrategy === 'exported') + { + // We don't remove undocumented doclets with the 'exported' generation strategy. + // The Emitter._markExported() function will make the appropriate selection later. + + // Disclaimer for an experimental feature. + warn(`Note: The 'exported' generation strategy is still an experimental feature for the moment, thank you for your comprehension. ` + + `Feel free to contribute in case you find a bug.`); + } // get the doc list and filter out inherited non-overridden members const docs = data().get().filter(d => !d.inherited || d.overrides); - setVerbose(!!opts.verbose); - // create an emitter to parse the docs const emitter = new Emitter(opts); emitter.parse(docs); diff --git a/src/type_resolve_helpers.ts b/src/type_resolve_helpers.ts index 3d2dce2..d0fd717 100644 --- a/src/type_resolve_helpers.ts +++ b/src/type_resolve_helpers.ts @@ -1,8 +1,6 @@ import * as ts from 'typescript'; -import { Dictionary } from './Dictionary'; -import { warn } from './logger'; +import { warn, debug, docletDebugInfo } from './logger'; import { PropTree, IPropDesc } from './PropTree'; -import { type } from 'os'; const rgxObjectTokenize = /(<|>|,|\(|\)|\||\{|\}|:)/; const rgxCommaAll = /,/g; @@ -20,19 +18,31 @@ enum ENodeType { TYPE, // string, X has no children OBJECT, // {a:b, c:d} has name value pairs for children } -class StringTreeNode { +export class StringTreeNode { children: StringTreeNode[] = []; constructor(public name: string, public type: ENodeType, public parent: StringTreeNode | null) { } - dump(indent:number = 0) : void + dump(output: (msg: string) => void, indent: number = 0) : void { - console.log(`${' '.repeat(indent)}name: ${this.name}, type:${this.typeToString()}`); + output(`${' '.repeat(indent)}name: ${this.name}, type:${this.typeToString()}`); this.children.forEach((child) => { - child.dump(indent + 1); + child.dump(output, indent + 1); }); } + walkTypes(callback: (treeNode: StringTreeNode) => void) : void + { + for (let i = 0; i < this.children.length; ++i) + { + // Skip object field names. + if (this.type === ENodeType.OBJECT && (i % 2 === 0)) + continue; + this.children[i].walkTypes(callback); + } + callback(this); + } + typeToString() : string { switch (this.type) @@ -70,7 +80,7 @@ export function resolveComplexTypeName(name: string, doclet?: TTypedDoclet): ts. return resolveTree(root); } -function generateTree(name: string, parent: StringTreeNode | null = null) : StringTreeNode | null +export function generateTree(name: string, parent: StringTreeNode | null = null) : StringTreeNode | null { const anyNode = new StringTreeNode('any', ENodeType.TYPE, parent); const parts = name.split(rgxObjectTokenize).filter(function (e) { @@ -550,6 +560,8 @@ export function resolveTypeParameters(doclet: TDoclet): ts.TypeParameterDeclarat { const typeParams: ts.TypeParameterDeclaration[] = []; + // Works in jsdoc@3.5.x only, not in jsdoc@3.6.x (up to jsdoc@3.6.3 at least). + // jsdoc@3.6.x does not seem to generate `tags` sections for `@template` tags anymore. if (doclet.tags) { for (let i = 0; i < doclet.tags.length; ++i) @@ -558,22 +570,43 @@ export function resolveTypeParameters(doclet: TDoclet): ts.TypeParameterDeclarat if (tag.title === 'template') { - const types = (tag.text || 'T').split(','); + onTemplateTag(tag.text); + } + } + } + // Otherwise, let's check directly the comment text. + else if (doclet.comment && doclet.comment.includes('@template')) + { + debug(`resolveTypeParameters(): jsdoc@3.6.x @template handling directly in the comment text for ${docletDebugInfo(doclet)}`); + for (let line of doclet.comment.split(/\r?\n/)) + { + line = line.trim(); + if (line.startsWith('*')) + line = line.slice(1).trim(); + if (line.startsWith('@template')) + { + line = line.slice('@template'.length).trim(); + onTemplateTag(line); + } + } + } - for (let x = 0; x < types.length; ++x) - { - const name = types[x].trim(); + function onTemplateTag(tagText?: string) + { + const types = (tagText || 'T').split(','); - if (!name) - continue; + for (let x = 0; x < types.length; ++x) + { + const name = types[x].trim(); - typeParams.push(ts.createTypeParameterDeclaration( - name, // name - undefined, // constraint - undefined // defaultType - )); - } - } + if (!name) + continue; + + typeParams.push(ts.createTypeParameterDeclaration( + name, // name + undefined, // constraint + undefined // defaultType + )); } } @@ -582,7 +615,7 @@ export function resolveTypeParameters(doclet: TDoclet): ts.TypeParameterDeclarat export type TTypedDoclet = IMemberDoclet | ITypedefDoclet | IFunctionDoclet; -export function resolveType(t: IDocletType, doclet?: TTypedDoclet): ts.TypeNode +export function resolveType(t?: IDocletType, doclet?: TTypedDoclet): ts.TypeNode { if (!t || !t.names || t.names.length === 0) { @@ -718,7 +751,7 @@ export function createTypeLiteral(children: IPropDesc[], parent?: IPropDesc): ts let node: ts.TypeNode = ts.createTypeLiteralNode(members); - if (parent) + if (parent && parent.prop.type) { const names = parent.prop.type.names; if (names.length === 1 && names[0].toLowerCase() === 'array.') diff --git a/src/typings/dts-jsdoc.d.ts b/src/typings/dts-jsdoc.d.ts index 7a415f5..fa6521a 100644 --- a/src/typings/dts-jsdoc.d.ts +++ b/src/typings/dts-jsdoc.d.ts @@ -2,6 +2,19 @@ declare interface ITemplateConfig { destination: string; outFile: string; private?: boolean; + /** + * Activate warning traces. + */ verbose?: boolean; - access?: ("public" | "private" | "protected" | "package") + /** + * Activate debug traces. + */ + debug?: boolean; + access?: ("public" | "private" | "protected" | "package"); + /** + * See [tsd-jsdoc#100](https://github.com/englercj/tsd-jsdoc/issues/100). + * The 'exported' generation strategy is activated through an option. + * In order not to break backward compatibility, the 'documented' generation strategy remains the default. + */ + generationStrategy?: ("documented" | "exported"); } diff --git a/src/typings/jsdoc.d.ts b/src/typings/jsdoc.d.ts index 8067671..c481098 100644 --- a/src/typings/jsdoc.d.ts +++ b/src/typings/jsdoc.d.ts @@ -29,10 +29,10 @@ declare interface IDocletType { } declare interface IDocletProp { - type: IDocletType; + type?: IDocletType; name: string; description: string; - comment: string; + comment?: string; defaultvalue?: string; meta?: any; optional?: boolean; @@ -45,9 +45,9 @@ declare interface IDocletReturn { } declare interface IDocletCode { - id: string; - name: string; - type: string; + id?: string; + name?: string; + type?: string; value?: string; paramnames?: string[]; } @@ -76,7 +76,7 @@ declare interface IDocletBase { tags?: IDocletTag[]; memberof?: string; see?: string; - access?: ('public' | 'private' | 'protected'); + access?: ('public' | 'private' | 'protected' | 'package'); examples?: string[]; deprecated?: string; defaultvalue?: string; @@ -120,15 +120,16 @@ declare interface IFunctionDoclet extends IDocletBase { this?: string; params?: IDocletProp[]; returns?: IDocletReturn[]; + overrides?: string; override?: boolean; virtual?: string[]; } declare interface IMemberDoclet extends IDocletBase { kind: 'member' | 'constant'; - readonly: boolean; - isEnum: boolean; - type: IDocletType; + readonly?: boolean; + isEnum?: boolean; + type?: IDocletType; } declare interface INamespaceDoclet extends IDocletBase { diff --git a/test/expected/class_all.d.ts b/test/expected/class_all.d.ts index a780185..f10aa8c 100644 --- a/test/expected/class_all.d.ts +++ b/test/expected/class_all.d.ts @@ -1,62 +1,54 @@ -declare function doStuff(this: OtherThing): void; - -declare class OtherThing { - copy(): void; -} - -declare class Stuff { - doStuff(): void; -} - -declare class BaseClass { - baseFunc(): void; - baseFuncOverridden(): void; -} - -declare class DerivedClass extends BaseClass { - derivedFunc(): void; - baseFuncOverridden(): void; -} - -declare class Things { - doThings(): void; - foobar1?(): void; - foobar2?: number; -} - -/** - * Deep class #1 - */ -declare class DeepClass1 { - constructor(); -} - -declare namespace DeepClass1 { +declare module "util" { + function doStuff(this: OtherThing): void; + class OtherThing { + copy(): void; + } + class Stuff { + doStuff(): void; + } + class BaseClass { + baseFunc(): void; + baseFuncOverridden(): void; + } + class DerivedClass extends BaseClass { + derivedFunc(): void; + baseFuncOverridden(): void; + } + class Things { + doThings(): void; + foobar1?(): void; + foobar2?: number; + } /** - * Deep class #2 + * Deep class #1 */ - class DeepClass2 { + class DeepClass1 { constructor(); } - namespace DeepClass2 { + namespace DeepClass1 { /** - * Deep class #3 + * Deep class #2 */ - class DeepClass3 { + class DeepClass2 { constructor(); } - namespace DeepClass3 { + namespace DeepClass2 { /** - * Deep class #4 + * Deep class #3 */ - class DeepClass4 { + class DeepClass3 { constructor(); } + namespace DeepClass3 { + /** + * Deep class #4 + */ + class DeepClass4 { + constructor(); + } + } } } -} - -declare module "util" { class MyClass { constructor(message: string); message: string; @@ -93,6 +85,11 @@ declare module "util" { interface MyThing extends Stuff, Things { } class MyThing extends OtherThing implements Stuff, Things { + /** + * Constructs! + * @param a - The number. + */ + private constructor(...a: number[]); /** * Derp or something. */ @@ -170,11 +167,5 @@ declare module "util" { */ D: string; static me: number; - /** - * @param other - To copy from. - */ - copy(other: OtherThing): void; } } - - diff --git a/test/expected/constructor_all.d.ts b/test/expected/constructor_all.d.ts new file mode 100644 index 0000000..35c2fc7 --- /dev/null +++ b/test/expected/constructor_all.d.ts @@ -0,0 +1,59 @@ +declare module "constructors" { + /** + * Class documentation. + */ + class MyClass0 { + /** + * Default constructor documentation. + */ + constructor(a: number, b: string); + } + /** + * Class documentation. + */ + class MyClass1 { + /** + * '@public' constructor documentation. + */ + public constructor(a: number, b: string); + } + /** + * Class documentation. + */ + class MyClass2 { + /** + * '@protected' constructor documentation. + */ + protected constructor(a: number, b: string); + } + /** + * Class documentation. + */ + class MyClass3 { + /** + * '@package' constructor documentation. + */ + private constructor(a: number, b: string); + } + /** + * Class documentation. + */ + class MyClass4 { + /** + * '@private' constructor documentation. + */ + private constructor(a: number, b: string); + } + /** + * Class documentation only. + */ + class MyClass5 { + constructor(a: number, b: string); + } + class MyClass6 { + /** + * Constructor documentation only. + */ + constructor(a: number, b: string); + } +} diff --git a/test/expected/enum_all.d.ts b/test/expected/enum_all.d.ts index 5e05182..f001ba0 100644 --- a/test/expected/enum_all.d.ts +++ b/test/expected/enum_all.d.ts @@ -1,15 +1,16 @@ -/** - * Enum for tri-state values. - */ -declare enum triState { +declare module "enums" { /** - * The true value + * Enum for tri-state values. */ - TRUE = 1, - /** - * The false value - */ - FALSE = -1, - MAYBE = true + enum triState { + /** + * The true value + */ + TRUE = 1, + /** + * The false value + */ + FALSE = -1, + MAYBE = true + } } - diff --git a/test/expected/exports_cyclic-dependencies.d.ts b/test/expected/exports_cyclic-dependencies.d.ts new file mode 100644 index 0000000..d8705e1 --- /dev/null +++ b/test/expected/exports_cyclic-dependencies.d.ts @@ -0,0 +1,5 @@ +declare module "test-export-20190923134400" { + class Foo { + copy(other: Foo): void; + } +} diff --git a/test/expected/exports_export default lambda-class.d.ts b/test/expected/exports_export default lambda-class.d.ts new file mode 100644 index 0000000..c862048 --- /dev/null +++ b/test/expected/exports_export default lambda-class.d.ts @@ -0,0 +1,15 @@ +declare module "test-export-20190913220743" { + class _Foo { + } + class _Bar extends _Foo { + } + class _Baz { + } + /** + * Default export with 'export default' on a lambda class. + */ + export default class extends _Baz { + constructor(bar: _Bar); + readonly foo: _Foo; + } +} diff --git a/test/expected/exports_export default lambda-function.d.ts b/test/expected/exports_export default lambda-function.d.ts new file mode 100644 index 0000000..26ce770 --- /dev/null +++ b/test/expected/exports_export default lambda-function.d.ts @@ -0,0 +1,8 @@ +declare module "test-export-20190913220822" { + class _Foo { + } + /** + * Default export with 'export default' on a lambda function. + */ + export default function (): _Foo | null; +} diff --git a/test/expected/exports_export default named-class.d.ts b/test/expected/exports_export default named-class.d.ts new file mode 100644 index 0000000..abd4461 --- /dev/null +++ b/test/expected/exports_export default named-class.d.ts @@ -0,0 +1,15 @@ +declare module "test-export-20190913220412" { + class _Foo { + } + class _Bar extends _Foo { + } + class _Baz { + } + /** + * Default export with 'export default' on a named class. + */ + export default class extends _Baz { + constructor(bar: _Bar); + readonly foo: _Foo; + } +} diff --git a/test/expected/exports_export default named-function.d.ts b/test/expected/exports_export default named-function.d.ts new file mode 100644 index 0000000..758de32 --- /dev/null +++ b/test/expected/exports_export default named-function.d.ts @@ -0,0 +1,8 @@ +declare module "test-export-20190913220502" { + class _Foo { + } + /** + * Default export with 'export default' on a named function. + */ + export default function (): _Foo | null; +} diff --git a/test/expected/exports_export default ref.d.ts b/test/expected/exports_export default ref.d.ts new file mode 100644 index 0000000..b0d5640 --- /dev/null +++ b/test/expected/exports_export default ref.d.ts @@ -0,0 +1,7 @@ +declare module "test-export-20190913215951" { + function _foo(): void; + /** + * Default export with 'export default' on a referenced type. + */ + export default _foo; +} diff --git a/test/expected/exports_export default {name=lambda-class}.d.ts b/test/expected/exports_export default {name=lambda-class}.d.ts new file mode 100644 index 0000000..cef11ce --- /dev/null +++ b/test/expected/exports_export default {name=lambda-class}.d.ts @@ -0,0 +1,15 @@ +declare module "test-export-20190923140527" { + class _Foo { + } + class _Bar extends _Foo { + } + class _Baz { + } + /** + * Named export with 'export default {name: ...}' on a lambda class. + */ + class Qux extends _Baz { + constructor(bar: _Bar); + readonly foo: _Foo; + } +} diff --git a/test/expected/exports_export default {name=lambda-function}.d.ts b/test/expected/exports_export default {name=lambda-function}.d.ts new file mode 100644 index 0000000..89b1d4c --- /dev/null +++ b/test/expected/exports_export default {name=lambda-function}.d.ts @@ -0,0 +1,8 @@ +declare module "test-export-20190923140537" { + class _Foo { + } + /** + * Named export with 'export default {name: ...}' on a lambda function. + */ + function foo(): _Foo | null; +} diff --git a/test/expected/exports_export default {name=named-class}.d.ts b/test/expected/exports_export default {name=named-class}.d.ts new file mode 100644 index 0000000..b105bbf --- /dev/null +++ b/test/expected/exports_export default {name=named-class}.d.ts @@ -0,0 +1,15 @@ +declare module "test-export-20190923140546" { + class _Foo { + } + class _Bar extends _Foo { + } + class _Baz { + } + /** + * Named export with 'export default {name: ...}' on a named class. + */ + class Qux extends _Baz { + constructor(bar: _Bar); + readonly foo: _Foo; + } +} diff --git a/test/expected/exports_export default {name=named-function}.d.ts b/test/expected/exports_export default {name=named-function}.d.ts new file mode 100644 index 0000000..f59473a --- /dev/null +++ b/test/expected/exports_export default {name=named-function}.d.ts @@ -0,0 +1,8 @@ +declare module "test-export-20190923140554" { + class _Foo { + } + /** + * Named export with 'export default {name: ...}' on a named function. + */ + function foo(): _Foo | null; +} diff --git a/test/expected/exports_export default {name=ref-other-name}.d.ts b/test/expected/exports_export default {name=ref-other-name}.d.ts new file mode 100644 index 0000000..93980e3 --- /dev/null +++ b/test/expected/exports_export default {name=ref-other-name}.d.ts @@ -0,0 +1,6 @@ +declare module "test-export-20190923140603" { + /** + * Hack: ignored for 'documented' generation strategy with a (re)named export, generated twice otherwise. + */ + function foo(): void; +} diff --git a/test/expected/exports_export default {name=ref-same-name}.d.ts b/test/expected/exports_export default {name=ref-same-name}.d.ts new file mode 100644 index 0000000..869bc02 --- /dev/null +++ b/test/expected/exports_export default {name=ref-same-name}.d.ts @@ -0,0 +1,3 @@ +declare module "test-export-20190923140611" { + function foo(): void; +} diff --git a/test/expected/exports_export named-types.d.ts b/test/expected/exports_export named-types.d.ts new file mode 100644 index 0000000..3c4da91 --- /dev/null +++ b/test/expected/exports_export named-types.d.ts @@ -0,0 +1,19 @@ +declare module "test-export-20190913220851" { + class _Foo { + } + class _Bar extends _Foo { + } + class _Baz { + } + /** + * Named export with 'export' on a named class. + */ + class Qux extends _Baz { + constructor(bar: _Bar); + readonly foo: _Foo; + } + /** + * Named export with 'export' on a named function. + */ + function foo(): _Foo | null; +} diff --git a/test/expected/exports_exports.name=lambda-class.d.ts b/test/expected/exports_exports.name=lambda-class.d.ts new file mode 100644 index 0000000..60de6e1 --- /dev/null +++ b/test/expected/exports_exports.name=lambda-class.d.ts @@ -0,0 +1,15 @@ +declare module "test-export-20190914004059" { + class _Foo { + } + class _Bar extends _Foo { + } + class _Baz { + } + /** + * Named export with 'exports.name =' on a lambda class. + */ + class Qux extends _Baz { + constructor(bar: _Bar); + readonly foo: _Foo; + } +} diff --git a/test/expected/exports_exports.name=lambda-function.d.ts b/test/expected/exports_exports.name=lambda-function.d.ts new file mode 100644 index 0000000..a483db5 --- /dev/null +++ b/test/expected/exports_exports.name=lambda-function.d.ts @@ -0,0 +1,8 @@ +declare module "test-export-20190914004332" { + class _Foo { + } + /** + * Named export with 'exports.name =' on a lambda function. + */ + function foo(): _Foo | null; +} diff --git a/test/expected/exports_exports.name=named-class.d.ts b/test/expected/exports_exports.name=named-class.d.ts new file mode 100644 index 0000000..888e424 --- /dev/null +++ b/test/expected/exports_exports.name=named-class.d.ts @@ -0,0 +1,15 @@ +declare module "test-export-20190913233811" { + class _Foo { + } + class _Bar extends _Foo { + } + class _Baz { + } + /** + * Named export with 'exports.name =' on a named class. + */ + class Qux extends _Baz { + constructor(bar: _Bar); + readonly foo: _Foo; + } +} diff --git a/test/expected/exports_exports.name=named-function.d.ts b/test/expected/exports_exports.name=named-function.d.ts new file mode 100644 index 0000000..066f1e4 --- /dev/null +++ b/test/expected/exports_exports.name=named-function.d.ts @@ -0,0 +1,8 @@ +declare module "test-export-20190913235007" { + class _Foo { + } + /** + * Named export with 'exports.name =' on a named function. + */ + function foo(): _Foo | null; +} diff --git a/test/expected/exports_exports.name=ref-other-name.d.ts b/test/expected/exports_exports.name=ref-other-name.d.ts new file mode 100644 index 0000000..ce05937 --- /dev/null +++ b/test/expected/exports_exports.name=ref-other-name.d.ts @@ -0,0 +1,14 @@ +declare module "test-export-20190914011810" { + class _Foo { + } + class _Bar extends _Foo { + } + class _Baz { + } + class Qux extends _Baz { + constructor(bar: _Bar); + readonly foo: _Foo; + } + function foo(): _Foo | null; + const bar = 0; +} diff --git a/test/expected/exports_exports.name=ref-same-name.d.ts b/test/expected/exports_exports.name=ref-same-name.d.ts new file mode 100644 index 0000000..863c43f --- /dev/null +++ b/test/expected/exports_exports.name=ref-same-name.d.ts @@ -0,0 +1,14 @@ +declare module "test-export-20190919181749" { + class _Foo { + } + class _Bar extends _Foo { + } + class _Baz { + } + class Qux extends _Baz { + constructor(bar: _Bar); + readonly foo: _Foo; + } + function foo(): _Foo | null; + const bar = 0; +} diff --git a/test/expected/exports_exports=module.exports=.d.ts b/test/expected/exports_exports=module.exports=.d.ts new file mode 100644 index 0000000..448b007 --- /dev/null +++ b/test/expected/exports_exports=module.exports=.d.ts @@ -0,0 +1,7 @@ +declare module "test-export-20190913220248" { + function _foo(): void; + /** + * Jsdoc comment inserted for 'documented' generation strategy. + */ + export default _foo; +} diff --git a/test/expected/exports_indirect(=module.exports).name=.d.ts b/test/expected/exports_indirect(=module.exports).name=.d.ts new file mode 100644 index 0000000..f8bb025 --- /dev/null +++ b/test/expected/exports_indirect(=module.exports).name=.d.ts @@ -0,0 +1,19 @@ +declare module "test-export-20190914010059" { + class _Foo { + constructor(); + } + class _Bar extends _Foo { + constructor(); + } + class _Baz { + constructor(); + } + /** + * Default export with 'module.exports ='. + */ + export default _Foo; + /** + * Effective named export with 'exports.name ='. + */ + type GoodBaz = _Baz; +} diff --git a/test/expected/exports_mix-default-named.d.ts b/test/expected/exports_mix-default-named.d.ts new file mode 100644 index 0000000..de77aba --- /dev/null +++ b/test/expected/exports_mix-default-named.d.ts @@ -0,0 +1,8 @@ +declare module "test-export-20190913220333" { + function _foo(): void; + /** + * Default export with 'module.exports ='. + */ + export default _foo; + const bar = 0; +} diff --git a/test/expected/exports_module.exports.name=lambda-class.d.ts b/test/expected/exports_module.exports.name=lambda-class.d.ts new file mode 100644 index 0000000..26bc05f --- /dev/null +++ b/test/expected/exports_module.exports.name=lambda-class.d.ts @@ -0,0 +1,15 @@ +declare module "test-export-20190914001452" { + class _Foo { + } + class _Bar extends _Foo { + } + class _Baz { + } + /** + * Named export with 'module.exports.name =' on a lambda class. + */ + class Qux extends _Baz { + constructor(bar: _Bar); + readonly foo: _Foo; + } +} diff --git a/test/expected/exports_module.exports.name=lambda-function.d.ts b/test/expected/exports_module.exports.name=lambda-function.d.ts new file mode 100644 index 0000000..5748c0f --- /dev/null +++ b/test/expected/exports_module.exports.name=lambda-function.d.ts @@ -0,0 +1,8 @@ +declare module "test-export-20190914003200" { + class _Foo { + } + /** + * Named export with 'module.exports.name =' on a lambda function. + */ + function foo(): _Foo | null; +} diff --git a/test/expected/exports_module.exports.name=named-class.d.ts b/test/expected/exports_module.exports.name=named-class.d.ts new file mode 100644 index 0000000..65ded2f --- /dev/null +++ b/test/expected/exports_module.exports.name=named-class.d.ts @@ -0,0 +1,15 @@ +declare module "test-export-20190913233106" { + class _Foo { + } + class _Bar extends _Foo { + } + class _Baz { + } + /** + * Named export with 'module.exports.name =' on a named class. + */ + class Qux extends _Baz { + constructor(bar: _Bar); + readonly foo: _Foo; + } +} diff --git a/test/expected/exports_module.exports.name=named-function.d.ts b/test/expected/exports_module.exports.name=named-function.d.ts new file mode 100644 index 0000000..9e59af9 --- /dev/null +++ b/test/expected/exports_module.exports.name=named-function.d.ts @@ -0,0 +1,8 @@ +declare module "test-export-20190913221147" { + class _Foo { + } + /** + * Named export with 'module.exports.name =' on a named function. + */ + function foo(): _Foo | null; +} diff --git a/test/expected/exports_module.exports.name=ref-other-name.d.ts b/test/expected/exports_module.exports.name=ref-other-name.d.ts new file mode 100644 index 0000000..5e74a8c --- /dev/null +++ b/test/expected/exports_module.exports.name=ref-other-name.d.ts @@ -0,0 +1,14 @@ +declare module "test-export-20190913220044" { + class _Foo { + } + class _Bar extends _Foo { + } + class _Baz { + } + class Qux extends _Baz { + constructor(bar: _Bar); + readonly foo: _Foo; + } + function foo(): _Foo | null; + const bar = 0; +} diff --git a/test/expected/exports_module.exports.name=ref-same-name.d.ts b/test/expected/exports_module.exports.name=ref-same-name.d.ts new file mode 100644 index 0000000..536832d --- /dev/null +++ b/test/expected/exports_module.exports.name=ref-same-name.d.ts @@ -0,0 +1,14 @@ +declare module "test-export-20190919181620" { + class _Foo { + } + class _Bar extends _Foo { + } + class _Baz { + } + class Qux extends _Baz { + constructor(bar: _Bar); + readonly foo: _Foo; + } + function foo(): _Foo | null; + const bar = 0; +} diff --git a/test/expected/exports_module.exports=lambda-class.d.ts b/test/expected/exports_module.exports=lambda-class.d.ts new file mode 100644 index 0000000..9bf0cc8 --- /dev/null +++ b/test/expected/exports_module.exports=lambda-class.d.ts @@ -0,0 +1,15 @@ +declare module "test-export-20190913221432" { + class _Foo { + } + class _Bar extends _Foo { + } + class _Baz { + } + /** + * Default export with 'module.exports =' on a lambda class. + */ + export default class extends _Baz { + constructor(bar: _Bar); + readonly foo: _Foo; + } +} diff --git a/test/expected/exports_module.exports=lambda-function.d.ts b/test/expected/exports_module.exports=lambda-function.d.ts new file mode 100644 index 0000000..ea48010 --- /dev/null +++ b/test/expected/exports_module.exports=lambda-function.d.ts @@ -0,0 +1,8 @@ +declare module "test-export-20190913222027" { + class _Foo { + } + /** + * Default export with 'module.exports =' on a lambda function. + */ + export default function (): _Foo | null; +} diff --git a/test/expected/exports_module.exports=named-class.d.ts b/test/expected/exports_module.exports=named-class.d.ts new file mode 100644 index 0000000..ec82b3b --- /dev/null +++ b/test/expected/exports_module.exports=named-class.d.ts @@ -0,0 +1,15 @@ +declare module "test-export-20190913222429" { + class _Foo { + } + class _Bar extends _Foo { + } + class _Baz { + } + /** + * Default export with 'module.exports =' on a named class. + */ + export default class extends _Baz { + constructor(bar: _Bar); + readonly foo: _Foo; + } +} diff --git a/test/expected/exports_module.exports=named-function.d.ts b/test/expected/exports_module.exports=named-function.d.ts new file mode 100644 index 0000000..bacb6e5 --- /dev/null +++ b/test/expected/exports_module.exports=named-function.d.ts @@ -0,0 +1,8 @@ +declare module "test-export-20190913223443" { + class _Foo { + } + /** + * Default export with 'module.exports =' on a named function. + */ + export default function (): _Foo | null; +} diff --git a/test/expected/exports_module.exports=ref.d.ts b/test/expected/exports_module.exports=ref.d.ts new file mode 100644 index 0000000..f62ca9d --- /dev/null +++ b/test/expected/exports_module.exports=ref.d.ts @@ -0,0 +1,7 @@ +declare module "test-export-20190913215617" { + function _foo(): void; + /** + * Default export with 'module.exports =' on a referenced type. + */ + export default _foo; +} diff --git a/test/expected/exports_module.exports={name=lambda-class}.d.ts b/test/expected/exports_module.exports={name=lambda-class}.d.ts new file mode 100644 index 0000000..cdfddf1 --- /dev/null +++ b/test/expected/exports_module.exports={name=lambda-class}.d.ts @@ -0,0 +1,15 @@ +declare module "test-export-20190914004600" { + class _Foo { + } + class _Bar extends _Foo { + } + class _Baz { + } + /** + * Named export with 'module.exports = {name: ...}' on a lambda class. + */ + class Qux extends _Baz { + constructor(bar: _Bar); + readonly foo: _Foo; + } +} diff --git a/test/expected/exports_module.exports={name=lambda-function}.d.ts b/test/expected/exports_module.exports={name=lambda-function}.d.ts new file mode 100644 index 0000000..17ec1d5 --- /dev/null +++ b/test/expected/exports_module.exports={name=lambda-function}.d.ts @@ -0,0 +1,8 @@ +declare module "test-export-20190914005207" { + class _Foo { + } + /** + * Named export with 'module.exports = {name: ...}' on a lambda function. + */ + function foo(): _Foo | null; +} diff --git a/test/expected/exports_module.exports={name=named-class}.d.ts b/test/expected/exports_module.exports={name=named-class}.d.ts new file mode 100644 index 0000000..a239081 --- /dev/null +++ b/test/expected/exports_module.exports={name=named-class}.d.ts @@ -0,0 +1,16 @@ +declare module "test-export-20190913235349" { + class _Foo { + } + class _Bar extends _Foo { + } + class _Baz { + } + /** + * Named export with 'module.exports = {name: ...}' on a named class. + */ + class Qux extends _Baz { + constructor(bar: _Bar); + readonly foo: _Foo; + } +} + diff --git a/test/expected/exports_module.exports={name=named-function}.d.ts b/test/expected/exports_module.exports={name=named-function}.d.ts new file mode 100644 index 0000000..746774e --- /dev/null +++ b/test/expected/exports_module.exports={name=named-function}.d.ts @@ -0,0 +1,8 @@ +declare module "test-export-20190914000510" { + class _Foo { + } + /** + * Named export with 'module.exports = {name: ...}' on a named function. + */ + function foo(): _Foo | null; +} diff --git a/test/expected/exports_module.exports={name=ref-other-name}.d.ts b/test/expected/exports_module.exports={name=ref-other-name}.d.ts new file mode 100644 index 0000000..2e5c984 --- /dev/null +++ b/test/expected/exports_module.exports={name=ref-other-name}.d.ts @@ -0,0 +1,3 @@ +declare module "test-export-20190913220202" { + function foo(): void; +} diff --git a/test/expected/exports_module.exports={name=ref-same-name}.d.ts b/test/expected/exports_module.exports={name=ref-same-name}.d.ts new file mode 100644 index 0000000..6f7215d --- /dev/null +++ b/test/expected/exports_module.exports={name=ref-same-name}.d.ts @@ -0,0 +1,3 @@ +declare module "test-export-20190919181839" { + function foo(): void; +} diff --git a/test/expected/exports_wrong exports.name=.d.ts b/test/expected/exports_wrong exports.name=.d.ts new file mode 100644 index 0000000..5e0af80 --- /dev/null +++ b/test/expected/exports_wrong exports.name=.d.ts @@ -0,0 +1,11 @@ +declare module "test-export-20190914020335" { + function _foo(): void; + /** + * Default export with 'module.exports ='. + */ + export default _foo; + /** + * Good 'exports.name =' export. + */ + var baz: string; +} diff --git a/test/expected/function_all.d.ts b/test/expected/function_all.d.ts index b063092..9c18049 100644 --- a/test/expected/function_all.d.ts +++ b/test/expected/function_all.d.ts @@ -1,18 +1,17 @@ -declare function test1(a?: number, input: { - x: number; - y: number; -}): void; - -declare function test2(x: any[], y: any[], z: any[], w: any[][]): void; - -declare function test3(myObjs: { - foo: number; - bar: boolean; - test1: string; - test2?: string[]; -}[]): void; - -declare class Test12345 { - static f(): number[]; - static f(key: string): number; +declare module "functions" { + function test1(a?: number, input: { + x: number; + y: number; + }): void; + function test2(x: any[], y: any[], z: any[], w: any[][]): void; + function test3(myObjs: { + foo: number; + bar: boolean; + test1: string; + test2?: string[]; + }[]): void; + class Test12345 { + static f(): number[]; + static f(key: string): number; + } } diff --git a/test/expected/interface_all.d.ts b/test/expected/interface_all.d.ts index 85e77b9..a9216f4 100644 --- a/test/expected/interface_all.d.ts +++ b/test/expected/interface_all.d.ts @@ -1,28 +1,29 @@ -declare namespace Color { - function staticMethod1(): void; - function staticMethod2(): void; - var staticMember1: number; - var staticMember2: boolean; - var staticMember3: string; - var staticMember4: any; -} - -/** - * Interface for classes that represent a color. - */ -declare interface Color { - instanceMethod(): void; - instanceMember: number; +declare module "interfaces" { + namespace Color { + function staticMethod1(): void; + function staticMethod2(): void; + var staticMember1: number; + var staticMember2: boolean; + var staticMember3: string; + var staticMember4: any; + } /** - * Get the color as an array of red, green, and blue values, represented as - * decimal numbers between 0 and 1. - * @returns An array containing the red, green, and blue values, - * in that order. + * Interface for classes that represent a color. */ - rgb(): number[]; - foobar1?(): void; - foobar2?(): void; - foobar3?: boolean; - foobar4?: string; - foobar5?: any; + interface Color { + instanceMethod(): void; + instanceMember: number; + /** + * Get the color as an array of red, green, and blue values, represented as + * decimal numbers between 0 and 1. + * @returns An array containing the red, green, and blue values, + * in that order. + */ + rgb(): number[]; + foobar1?(): void; + foobar2?(): void; + foobar3?: boolean; + foobar4?: string; + foobar5?: any; + } } diff --git a/test/expected/mixin_all.d.ts b/test/expected/mixin_all.d.ts index 7555234..bb3b248 100644 --- a/test/expected/mixin_all.d.ts +++ b/test/expected/mixin_all.d.ts @@ -1,17 +1,14 @@ -declare interface MyMixin1 { -} - -declare interface MyMixin2 { -} - -declare interface MyInterface { -} - -declare class MyBaseClass { -} - -declare interface MyMixedClass extends MyInterface, MyMixin1, MyMixin2 { -} - -declare class MyMixedClass extends MyBaseClass implements MyInterface, MyMixin1, MyMixin2 { +declare module "mixins" { + interface MyMixin1 { + } + interface MyMixin2 { + } + interface MyInterface { + } + class MyBaseClass { + } + interface MyMixedClass extends MyInterface, MyMixin1, MyMixin2 { + } + class MyMixedClass extends MyBaseClass implements MyInterface, MyMixin1, MyMixin2 { + } } diff --git a/test/expected/module_all.d.ts b/test/expected/module_all.d.ts index 6719f6b..9dcaf08 100644 --- a/test/expected/module_all.d.ts +++ b/test/expected/module_all.d.ts @@ -22,4 +22,8 @@ declare module "array-to-object-keys" { function arrayToObjectKeys(array: string[], valueGenerator?: valueGenerator | any): { [key: string]: any; }; + /** + * Export arrayToObjectKeys as default. + */ + export default arrayToObjectKeys; } diff --git a/test/expected/namespace_all.d.ts b/test/expected/namespace_all.d.ts index a4a0d42..749d9c8 100644 --- a/test/expected/namespace_all.d.ts +++ b/test/expected/namespace_all.d.ts @@ -1,55 +1,53 @@ -declare namespace FoobarNS { - /** - * A Foo. - */ - class Foo { +declare module "namespaces" { + namespace FoobarNS { /** - * A generic method. - * @param f - A function. - * @param [opt_this = 10] - An object. - * @param [opt_2 = 10] - An object. + * A Foo. */ - f(f: FoobarNS.Foo.FCallback, opt_this?: number, opt_2?: number[] | { - [key: number]: string[]; - }): void; - } - namespace Foo { - /** - * @param first - The first param. - * @param second - The second param. - * @param third - The third param. - */ - type FCallback = (this: S, first: T, second: number, third: T[]) => any; - } - /** - * A Bar. - */ - class Bar extends FoobarNS.Foo { + class Foo { + /** + * A generic method. + * @param f - A function. + * @param [opt_this = 10] - An object. + * @param [opt_2 = 10] - An object. + */ + f(f: FoobarNS.Foo.FCallback, opt_this?: number, opt_2?: number[] | { + [key: number]: string[]; + }): void; + } + namespace Foo { + /** + * @param first - The first param. + * @param second - The second param. + * @param third - The third param. + */ + type FCallback = (this: S, first: T, second: number, third: T[]) => any; + } /** - * A method. + * A Bar. */ - f(): void; - } - interface CircleOptions { + class Bar extends FoobarNS.Foo { + } + interface CircleOptions { + /** + * Circle radius. + */ + radius: number; + } /** - * Circle radius. + * Set circle style for vector features. + * @param [opt_options] - Options. */ - radius: number; - } - /** - * Set circle style for vector features. - * @param [opt_options] - Options. - */ - class Circle { - constructor(opt_options?: FoobarNS.CircleOptions); + class Circle { + constructor(opt_options?: FoobarNS.CircleOptions); + } + var helloWorld1: number; + var helloWorld2: boolean; + const helloWorld3: string; + const helloWorld4: number; + const helloWorld5: boolean; + const helloWorld6: any; + const helloWorld7 = "test"; + const helloWorld8 = 1.2345; + const helloWorld9 = true; } - var helloWorld1: number; - var helloWorld2: boolean; - const helloWorld3: string; - const helloWorld4: number; - const helloWorld5: boolean; - const helloWorld6: any; - const helloWorld7 = "test"; - const helloWorld8 = 1.2345; - const helloWorld9 = true; } diff --git a/test/expected/property_all.d.ts b/test/expected/property_all.d.ts index 40c703e..03a4179 100644 --- a/test/expected/property_all.d.ts +++ b/test/expected/property_all.d.ts @@ -1,66 +1,52 @@ -/** - * @property myProp - foobar - */ -declare class PropTest1 { +declare module "properties" { /** - * foobar - */ - myProp: string; -} - -declare class PropTest2 { - otherProp: number; - myProp: number; -} - -declare class PropTest3 { - /** - * duplicate - */ - myProp: boolean; - /** - * duplicate - */ - myProp: boolean; - myProp: boolean; -} - -/** - * @property myProp - foobar - */ -declare class PropTest4 { - constructor(); - /** - * foobar - */ - myProp: string; -} - -declare class PropTest5 { - constructor(); - otherProp: number; - myProp: number; -} - -declare class PropTest6 { - constructor(); - /** - * duplicate + * @property myProp - foobar */ - myProp: boolean; + class PropTest1 { + /** + * foobar + */ + myProp: string; + } + class PropTest2 { + otherProp: number; + myProp: number; + } + class PropTest3 { + /** + * duplicate + */ + myProp: boolean; + } /** - * duplicate + * @property myProp - foobar */ - myProp: boolean; - myProp: boolean; -} - -declare class PropTest7 { - constructor(); - myProps: { - foo: number; - bar: boolean; - test1: string; - test2?: string[]; - }[]; + class PropTest4 { + constructor(); + /** + * foobar + */ + myProp: string; + } + class PropTest5 { + constructor(); + otherProp: number; + myProp: number; + } + class PropTest6 { + constructor(); + /** + * duplicate + */ + myProp: boolean; + } + class PropTest7 { + constructor(); + myProps: { + foo: number; + bar: boolean; + test1: string; + test2?: string[]; + }[]; + } } diff --git a/test/expected/typedef_all.d.ts b/test/expected/typedef_all.d.ts index 5eaee90..08827ce 100644 --- a/test/expected/typedef_all.d.ts +++ b/test/expected/typedef_all.d.ts @@ -1,116 +1,112 @@ -/** - * The complete Triforce, or one or more components of the Triforce. - * @property hasCourage - Indicates whether the Courage component is present. - * @property hasPower - Indicates whether the Power component is present. - * @property hasWisdom - Indicates whether the Wisdom component is present. - */ -declare type Triforce = { - hasCourage: boolean; - hasPower: boolean; - hasWisdom: boolean; -}; - -/** - * The complete Triforce, or one or more components of the Triforce. - * @property hasCourage - Indicates whether the Courage component is present. - * @property hasPower - Indicates whether the Power component is present. - * @property hasWisdom - Indicates whether the Wisdom component is present. - */ -declare type Anon = { - hasCourage: boolean; - hasPower: boolean; - hasWisdom: boolean; - thing: { - a: { - b: { - c: any; +declare module "typedefs" { + /** + * The complete Triforce, or one or more components of the Triforce. + * @property hasCourage - Indicates whether the Courage component is present. + * @property hasPower - Indicates whether the Power component is present. + * @property hasWisdom - Indicates whether the Wisdom component is present. + */ + type Triforce = { + hasCourage: boolean; + hasPower: boolean; + hasWisdom: boolean; + }; + /** + * The complete Triforce, or one or more components of the Triforce. + * @property hasCourage - Indicates whether the Courage component is present. + * @property hasPower - Indicates whether the Power component is present. + * @property hasWisdom - Indicates whether the Wisdom component is present. + */ + type Anon = { + hasCourage: boolean; + hasPower: boolean; + hasWisdom: boolean; + thing: { + a: { + b: { + c: any; + }; }; + b: number; }; - b: number; }; -}; - -/** - * @property [elementId = "gitGraph"] - Id of the canvas container - * @property [template] - Template of the graph - * @property [author = "Sergio Flores "] - Default author for commits - * @property [mode = (null|"compact")] - Display mode - * @property [canvas] - DOM canvas (ex: document.getElementById("id")) - * @property [orientation = ("vertical-reverse"|"horizontal"|"horizontal-reverse")] - Graph orientation - * @property [reverseArrow = false] - Make arrows point to ancestors if true - * @property [initCommitOffsetX = 0] - Add custom offsetX to initial commit. - * @property [initCommitOffsetY = 0] - Add custom offsetY to initial commit. - * @property [tooltipContainer = document.body] - HTML Element containing tooltips in compact mode. - */ -declare type GitGraphOptions = { - elementId?: string; - template?: Template | string | any; - author?: string; - mode?: string; - canvas?: HTMLElement; - orientation?: string; - reverseArrow?: boolean; - initCommitOffsetX?: number; - initCommitOffsetY?: number; - tooltipContainer?: HTMLElement; -}; - -/** - * A number, or a string containing a number. - */ -declare type NumberLike = number | string; - -/** - * @property pattern - Holds a pattern definition. - * @property pattern.image - URL to an image to use as the pattern. - * @property pattern.width - Width of the pattern. For images this is - * automatically set to the width of the element bounding box if not supplied. - * For non-image patterns the default is 32px. Note that automatic resizing of - * image patterns to fill a bounding box dynamically is only supported for - * patterns with an automatically calculated ID. - * @property pattern.height - Analogous to pattern.width. - * @property pattern.aspectRatio - For automatically calculated width and - * height on images, it is possible to set an aspect ratio. The image will be - * zoomed to fill the bounding box, maintaining the aspect ratio defined. - * @property pattern.x - Horizontal offset of the pattern. Defaults to 0. - * @property pattern.y - Vertical offset of the pattern. Defaults to 0. - * @property pattern.path - Either an SVG path as string, or an - * object. As an object, supply the path string in the `path.d` property. Other - * supported properties are standard SVG attributes like `path.stroke` and - * `path.fill`. If a path is supplied for the pattern, the `image` property is - * ignored. - * @property pattern.color - Pattern color, used as default path stroke. - * @property pattern.opacity - Opacity of the pattern as a float value - * from 0 to 1. - * @property pattern.id - ID to assign to the pattern. This is - * automatically computed if not added, and identical patterns are reused. To - * refer to an existing pattern for a Highcharts color, use - * `color: "url(#pattern-id)"`. - * @property animation - Animation options for the image pattern - * loading. - * Note: doesn't matter what I put, a @property only gets "FUNCTION" from jsdoc - * @property rotate - Rotates the pattern by degrees - * @property wiggle - Wiggles the pattern (default function) - * @property wobble - Wobbles the pattern - * (complex function) - */ -declare type PatternOptions = { - pattern: { - image: string; - width: number; - height: number; - aspectRatio: number; - x: number; - y: number; - path: any | string; - color: string; - opacity: number; - id: string; + /** + * @property [elementId = "gitGraph"] - Id of the canvas container + * @property [template] - Template of the graph + * @property [author = "Sergio Flores "] - Default author for commits + * @property [mode = (null|"compact")] - Display mode + * @property [canvas] - DOM canvas (ex: document.getElementById("id")) + * @property [orientation = ("vertical-reverse"|"horizontal"|"horizontal-reverse")] - Graph orientation + * @property [reverseArrow = false] - Make arrows point to ancestors if true + * @property [initCommitOffsetX = 0] - Add custom offsetX to initial commit. + * @property [initCommitOffsetY = 0] - Add custom offsetY to initial commit. + * @property [tooltipContainer = document.body] - HTML Element containing tooltips in compact mode. + */ + type GitGraphOptions = { + elementId?: string; + template?: Template | string | any; + author?: string; + mode?: string; + canvas?: HTMLElement; + orientation?: string; + reverseArrow?: boolean; + initCommitOffsetX?: number; + initCommitOffsetY?: number; + tooltipContainer?: HTMLElement; + }; + /** + * A number, or a string containing a number. + */ + type NumberLike = number | string; + /** + * @property pattern - Holds a pattern definition. + * @property pattern.image - URL to an image to use as the pattern. + * @property pattern.width - Width of the pattern. For images this is + * automatically set to the width of the element bounding box if not supplied. + * For non-image patterns the default is 32px. Note that automatic resizing of + * image patterns to fill a bounding box dynamically is only supported for + * patterns with an automatically calculated ID. + * @property pattern.height - Analogous to pattern.width. + * @property pattern.aspectRatio - For automatically calculated width and + * height on images, it is possible to set an aspect ratio. The image will be + * zoomed to fill the bounding box, maintaining the aspect ratio defined. + * @property pattern.x - Horizontal offset of the pattern. Defaults to 0. + * @property pattern.y - Vertical offset of the pattern. Defaults to 0. + * @property pattern.path - Either an SVG path as string, or an + * object. As an object, supply the path string in the `path.d` property. Other + * supported properties are standard SVG attributes like `path.stroke` and + * `path.fill`. If a path is supplied for the pattern, the `image` property is + * ignored. + * @property pattern.color - Pattern color, used as default path stroke. + * @property pattern.opacity - Opacity of the pattern as a float value + * from 0 to 1. + * @property pattern.id - ID to assign to the pattern. This is + * automatically computed if not added, and identical patterns are reused. To + * refer to an existing pattern for a Highcharts color, use + * `color: "url(#pattern-id)"`. + * @property animation - Animation options for the image pattern + * loading. + * Note: doesn't matter what I put, a @property only gets "FUNCTION" from jsdoc + * @property rotate - Rotates the pattern by degrees + * @property wiggle - Wiggles the pattern (default function) + * @property wobble - Wobbles the pattern + * (complex function) + */ + type PatternOptions = { + pattern: { + image: string; + width: number; + height: number; + aspectRatio: number; + x: number; + y: number; + path: any | string; + color: string; + opacity: number; + id: string; + }; + animation: any | boolean; + rotate: (...params: any[]) => any; + wiggle: (...params: any[]) => any; + wobble: (...params: any[]) => any; }; - animation: any | boolean; - rotate: (...params: any[]) => any; - wiggle: (...params: any[]) => any; - wobble: (...params: any[]) => any; -}; - - +} diff --git a/test/expected/typeof_all.d.ts b/test/expected/typeof_all.d.ts index 171595e..e738dde 100644 --- a/test/expected/typeof_all.d.ts +++ b/test/expected/typeof_all.d.ts @@ -1,23 +1,24 @@ -declare class Foobar1 { - x: typeof MyClass; - y: typeof MyClass; - z: string | typeof MyClass; - u: typeof MyClass | string; - v: (typeof MyClass)[]; - w: typeof Array; - q: typeof Array; - r: typeof Class; - s: typeof Array; -} - -declare class Foobar2 { - x: typeof MyClass; - y: typeof MyClass; - z: string | typeof MyClass; - u: typeof MyClass | string; - v: (typeof MyClass)[]; - w: typeof Array; - q: typeof Array; - r: typeof Class; - s: typeof Array; +declare module "typeofs" { + class Foobar1 { + x: typeof MyClass; + y: typeof MyClass; + z: string | typeof MyClass; + u: typeof MyClass | string; + v: (typeof MyClass)[]; + w: typeof Array; + q: typeof Array; + r: typeof Class; + s: typeof Array; + } + class Foobar2 { + x: typeof MyClass; + y: typeof MyClass; + z: string | typeof MyClass; + u: typeof MyClass | string; + v: (typeof MyClass)[]; + w: typeof Array; + q: typeof Array; + r: typeof Class; + s: typeof Array; + } } diff --git a/test/fixtures/class_all.js b/test/fixtures/class_all.js index b9dfa7d..4e55231 100644 --- a/test/fixtures/class_all.js +++ b/test/fixtures/class_all.js @@ -1,3 +1,5 @@ +/** @module util */ + /** * @this OtherThing */ @@ -66,14 +68,14 @@ class Things { } /** - * @method Things#[foobar1] + * @method module:util~Things#[foobar1] */ foobar1() { }; /** * @type {Number} - * @name Things#[foobar2] + * @name module:util~Things#[foobar2] */ foobar2 = undefined; } @@ -107,8 +109,6 @@ function DeepClass1() { } } -/** @module util */ - /** * @class MyClass * @param {string} message @@ -123,7 +123,7 @@ class MyClass { /** * GitGraph - * @class GitGraph + * @constructor * @param {object} options - GitGraph options * @param {string} [options.elementId = "gitGraph"] - Id of the canvas container * @param {Template|string|object} [options.template] - Template of the graph @@ -147,7 +147,7 @@ class GitGraph { */ /** - * @class MyThing + * @class * @extends OtherThing * @mixes Stuff * @mixes Things @@ -318,3 +318,13 @@ class MyThing extends OtherThing { * @static */ MyThing.me = 10; + +module.exports = { + doStuff: doStuff, + DerivedClass: DerivedClass, + DeepClass1: DeepClass1, + MyClass: MyClass, + GitGraph: GitGraph, + Something: Something, + MyThing: MyThing +} diff --git a/test/fixtures/constructor_all.js b/test/fixtures/constructor_all.js new file mode 100644 index 0000000..9863164 --- /dev/null +++ b/test/fixtures/constructor_all.js @@ -0,0 +1,103 @@ +/** @module constructors */ + +/** + * Class documentation. + */ +class MyClass0 { + + /** + * Default constructor documentation. + * @param {number} a + * @param {string} b + */ + constructor(a, b) {} +} + +/** + * Class documentation. + */ +class MyClass1 { + + /** + * '@public' constructor documentation. + * @public + * @param {number} a + * @param {string} b + */ + constructor(a, b) {} +} + +/** + * Class documentation. + */ +class MyClass2 { + + /** + * '@protected' constructor documentation. + * @protected + * @param {number} a + * @param {string} b + */ + constructor(a, b) {} +} + +/** + * Class documentation. + */ +class MyClass3 { + + /** + * '@package' constructor documentation. + * @package + * @param {number} a + * @param {string} b + */ + constructor(a, b) {} +} + +/** + * Class documentation. + */ +class MyClass4 { + + /** + * '@private' constructor documentation. + * @private + * @param {number} a + * @param {string} b + */ + constructor(a, b) {} +} + +/** + * Class documentation only. + */ +class MyClass5 { + /** + * @param {number} a + * @param {string} b + */ + constructor(a, b) {} +} + +/** + * + */ +class MyClass6 { + /** + * Constructor documentation only. + * @param {number} a + * @param {string} b + */ + constructor(a, b) {} +} + +module.exports = { + MyClass0: MyClass0, + MyClass1: MyClass1, + MyClass2: MyClass2, + MyClass3: MyClass3, + MyClass4: MyClass4, + MyClass5: MyClass5, + MyClass6: MyClass6 +} diff --git a/test/fixtures/enum_all.js b/test/fixtures/enum_all.js index 8c10dae..9d09d30 100644 --- a/test/fixtures/enum_all.js +++ b/test/fixtures/enum_all.js @@ -1,3 +1,5 @@ +/** @module enums */ + /** * Enum for tri-state values. * @readonly @@ -11,3 +13,7 @@ var triState = { /** @type {boolean} */ MAYBE: true, }; + +module.exports = { + triState: triState +} diff --git a/test/fixtures/exports_cyclic-dependencies.js b/test/fixtures/exports_cyclic-dependencies.js new file mode 100644 index 0000000..622243d --- /dev/null +++ b/test/fixtures/exports_cyclic-dependencies.js @@ -0,0 +1,12 @@ +/** @module test-export-20190923134400 */ + +/** + * + */ +export class Foo { + /** + * @param {Foo} other + */ + copy(other) { + } +} diff --git a/test/fixtures/exports_export default lambda-class.js b/test/fixtures/exports_export default lambda-class.js new file mode 100644 index 0000000..37bee3d --- /dev/null +++ b/test/fixtures/exports_export default lambda-class.js @@ -0,0 +1,39 @@ +/** @module test-export-20190913220743 */ + +class _NotExported { +} + +/** + * + */ +class _Foo { +} + +/** + * @extends _Foo + */ +class _Bar extends _Foo { +} + +/** + * + */ +class _Baz { +} + +/** + * Default export with 'export default' on a lambda class. + * @extends _Baz + */ +export default class extends _Baz { + /** + * @param {_Bar} bar + */ + constructor(bar) { + /** + * @type {_Foo} + * @readonly + */ + this.foo = bar; + } +} diff --git a/test/fixtures/exports_export default lambda-function.js b/test/fixtures/exports_export default lambda-function.js new file mode 100644 index 0000000..a39bcc9 --- /dev/null +++ b/test/fixtures/exports_export default lambda-function.js @@ -0,0 +1,18 @@ +/** @module test-export-20190913220822 */ + +class _NotExported { +} + +/** + * + */ +class _Foo { +} + +/** + * Default export with 'export default' on a lambda function. + * @returns {_Foo | null} + */ +export default function() { + return null; +} diff --git a/test/fixtures/exports_export default named-class.js b/test/fixtures/exports_export default named-class.js new file mode 100644 index 0000000..823761f --- /dev/null +++ b/test/fixtures/exports_export default named-class.js @@ -0,0 +1,39 @@ +/** @module test-export-20190913220412 */ + +class _NotExported { +} + +/** + * + */ +class _Foo { +} + +/** + * @extends _Foo + */ +class _Bar extends _Foo { +} + +/** + * + */ +class _Baz { +} + +/** + * Default export with 'export default' on a named class. + * @extends _Baz + */ +export default class _Qux extends _Baz { + /** + * @param {_Bar} bar + */ + constructor(bar) { + /** + * @type {_Foo} + * @readonly + */ + this.foo = bar; + } +} diff --git a/test/fixtures/exports_export default named-function.js b/test/fixtures/exports_export default named-function.js new file mode 100644 index 0000000..b252b20 --- /dev/null +++ b/test/fixtures/exports_export default named-function.js @@ -0,0 +1,18 @@ +/** @module test-export-20190913220502 */ + +class _NotExported { +} + +/** + * + */ +class _Foo { +} + +/** + * Default export with 'export default' on a named function. + * @returns {_Foo | null} + */ +export default function _foo() { + return null; +} diff --git a/test/fixtures/exports_export default ref.js b/test/fixtures/exports_export default ref.js new file mode 100644 index 0000000..51f58fa --- /dev/null +++ b/test/fixtures/exports_export default ref.js @@ -0,0 +1,15 @@ +/** @module test-export-20190913215951 */ + +class _NotExported { +} + +/** + * + */ +function _foo() { +} + +/** + * Default export with 'export default' on a referenced type. + */ +export default _foo; diff --git a/test/fixtures/exports_export default {name=lambda-class}.js b/test/fixtures/exports_export default {name=lambda-class}.js new file mode 100644 index 0000000..c05d2b5 --- /dev/null +++ b/test/fixtures/exports_export default {name=lambda-class}.js @@ -0,0 +1,49 @@ +/** @module test-export-20190923140527 */ + +class _NotExported { +} + +/** + * + */ +class _Foo { +} + +/** + * @extends _Foo + */ +class _Bar extends _Foo { +} + +/** + * + */ +class _Baz { +} + +/** + * Jsdoc comment for 'documented' generation strategy. + */ +export default { + /** + * Named export with 'export default {name: ...}' on a lambda class. + * @param {_Bar} bar + * @extends _Baz + */ + Qux: + class extends _Baz { + /** + * Does not work! No doclet generated for this constructor. + * @see [jsdoc#1699](https://github.com/jsdoc/jsdoc/issues/1699) + * Please set the constructor documentation in the class declaration documentation. + * @param {_Bar} bar + */ + constructor(bar) { + /** + * @type {_Foo} + * @readonly + */ + this.foo = bar; + } + } +}; diff --git a/test/fixtures/exports_export default {name=lambda-function}.js b/test/fixtures/exports_export default {name=lambda-function}.js new file mode 100644 index 0000000..74fc4e0 --- /dev/null +++ b/test/fixtures/exports_export default {name=lambda-function}.js @@ -0,0 +1,24 @@ +/** @module test-export-20190923140537 */ + +class _NotExported { +} + +/** + * + */ +class _Foo { +} + +/** + * Jsdoc comment for 'documented' generation strategy. + */ +export default { + /** + * Named export with 'export default {name: ...}' on a lambda function. + * @returns {_Foo | null} + */ + foo: + function() { + return null; + } +}; diff --git a/test/fixtures/exports_export default {name=named-class}.js b/test/fixtures/exports_export default {name=named-class}.js new file mode 100644 index 0000000..67dd7a5 --- /dev/null +++ b/test/fixtures/exports_export default {name=named-class}.js @@ -0,0 +1,49 @@ +/** @module test-export-20190923140546 */ + +class _NotExported { +} + +/** + * + */ +class _Foo { +} + +/** + * @extends _Foo + */ +class _Bar extends _Foo { +} + +/** + * + */ +class _Baz { +} + +/** + * Jsdoc comment for 'documented' generation strategy. + */ +export default { + /** + * Named export with 'export default {name: ...}' on a named class. + * @param {_Bar} bar + * @extends _Baz + */ + Qux: + class _Qux extends _Baz { + /** + * Does not work! No doclet generated for this constructor. + * @see [jsdoc#1699](https://github.com/jsdoc/jsdoc/issues/1699) + * Please set the constructor documentation in the class declaration documentation. + * @param {_Bar} bar + */ + constructor(bar) { + /** + * @type {_Foo} + * @readonly + */ + this.foo = bar; + } + } +}; diff --git a/test/fixtures/exports_export default {name=named-function}.js b/test/fixtures/exports_export default {name=named-function}.js new file mode 100644 index 0000000..7e4ea6e --- /dev/null +++ b/test/fixtures/exports_export default {name=named-function}.js @@ -0,0 +1,24 @@ +/** @module test-export-20190923140554 */ + +class _NotExported { +} + +/** + * + */ +class _Foo { +} + +/** + * Jsdoc comment for 'documented' generation strategy. + */ +export default { + /** + * Named export with 'export default {name: ...}' on a named function. + * @returns {_Foo | null} + */ + foo: + function _foo() { + return null; + } +}; diff --git a/test/fixtures/exports_export default {name=ref-other-name}.js b/test/fixtures/exports_export default {name=ref-other-name}.js new file mode 100644 index 0000000..5d9b610 --- /dev/null +++ b/test/fixtures/exports_export default {name=ref-other-name}.js @@ -0,0 +1,21 @@ +/** @module test-export-20190923140603 */ + +class _NotExported { +} + +// Hack: ignored for 'documented' generation strategy with a (re)named export, generated twice otherwise. +/** + * @ignore + */ +function _foo() { +} + +/** + * Jsdoc comment for 'documented' generation strategy. + */ +export default { + /** + * Named export with 'export default {name: ...}' on a referenced type. + */ + foo: _foo +}; diff --git a/test/fixtures/exports_export default {name=ref-same-name}.js b/test/fixtures/exports_export default {name=ref-same-name}.js new file mode 100644 index 0000000..038942b --- /dev/null +++ b/test/fixtures/exports_export default {name=ref-same-name}.js @@ -0,0 +1,21 @@ +/** @module test-export-20190923140611 */ + +class _NotExported { +} + +/** + * + */ +function foo() { +} + +/** + * Jsdoc comment for 'documented' generation strategy. + */ +export default { + /** + * Named export with 'export default {name: ...}' on a referenced type. + */ + foo: foo, + baz: "" +}; diff --git a/test/fixtures/exports_export named-types.js b/test/fixtures/exports_export named-types.js new file mode 100644 index 0000000..bc6fb0c --- /dev/null +++ b/test/fixtures/exports_export named-types.js @@ -0,0 +1,50 @@ +/** @module test-export-20190913220851 */ + +class _NotExported { +} + +/** + * + */ +class _Foo { +} + +/** + * @extends _Foo + */ +class _Bar extends _Foo { +} + +/** + * + */ +class _Baz { +} + +/** + * Named export with 'export' on a named class. + * @extends _Baz + */ +export class Qux extends _Baz { + /** + * @param {_Bar} bar + */ + constructor(bar) { + /** + * @type {_Foo} + * @readonly + */ + this.foo = bar; + } +} + +function _notExported() { +} + +/** + * Named export with 'export' on a named function. + * @returns {_Foo | null} + */ +export function foo() { + return null; +} diff --git a/test/fixtures/exports_exports.name=lambda-class.js b/test/fixtures/exports_exports.name=lambda-class.js new file mode 100644 index 0000000..a7d6ac9 --- /dev/null +++ b/test/fixtures/exports_exports.name=lambda-class.js @@ -0,0 +1,39 @@ +/** @module test-export-20190914004059 */ + +class _NotExported { +} + +/** + * + */ +class _Foo { +} + +/** + * @extends _Foo + */ +class _Bar extends _Foo { +} + +/** + * + */ +class _Baz { +} + +/** + * Named export with 'exports.name =' on a lambda class. + * @extends _Baz + */ +exports.Qux = class extends _Baz { + /** + * @param {_Bar} bar + */ + constructor(bar) { + /** + * @type {_Foo} + * @readonly + */ + this.foo = bar; + } +} diff --git a/test/fixtures/exports_exports.name=lambda-function.js b/test/fixtures/exports_exports.name=lambda-function.js new file mode 100644 index 0000000..911021e --- /dev/null +++ b/test/fixtures/exports_exports.name=lambda-function.js @@ -0,0 +1,18 @@ +/** @module test-export-20190914004332 */ + +class _NotExported { +} + +/** + * + */ +class _Foo { +} + +/** + * Named export with 'exports.name =' on a lambda function. + * @returns {_Foo | null} + */ +exports.foo = function() { + return null; +} diff --git a/test/fixtures/exports_exports.name=named-class.js b/test/fixtures/exports_exports.name=named-class.js new file mode 100644 index 0000000..a6a2534 --- /dev/null +++ b/test/fixtures/exports_exports.name=named-class.js @@ -0,0 +1,39 @@ +/** @module test-export-20190913233811 */ + +class _NotExported { +} + +/** + * + */ +class _Foo { +} + +/** + * @extends _Foo + */ +class _Bar extends _Foo { +} + +/** + * + */ +class _Baz { +} + +/** + * Named export with 'exports.name =' on a named class. + * @extends _Baz + */ +exports.Qux = class _Qux extends _Baz { + /** + * @param {_Bar} bar + */ + constructor(bar) { + /** + * @type {_Foo} + * @readonly + */ + this.foo = bar; + } +} diff --git a/test/fixtures/exports_exports.name=named-function.js b/test/fixtures/exports_exports.name=named-function.js new file mode 100644 index 0000000..e5328f5 --- /dev/null +++ b/test/fixtures/exports_exports.name=named-function.js @@ -0,0 +1,18 @@ +/** @module test-export-20190913235007 */ + +class _NotExported { +} + +/** + * + */ +class _Foo { +} + +/** + * Named export with 'exports.name =' on a named function. + * @returns {_Foo | null} + */ +exports.foo = function _foo() { + return null; +} diff --git a/test/fixtures/exports_exports.name=ref-other-name.js b/test/fixtures/exports_exports.name=ref-other-name.js new file mode 100644 index 0000000..7b0a12a --- /dev/null +++ b/test/fixtures/exports_exports.name=ref-other-name.js @@ -0,0 +1,70 @@ +/** @module test-export-20190914011810 */ + +class _NotExported { +} + +/** + * + */ +class _Foo { +} + +/** + * @extends _Foo + */ +class _Bar extends _Foo { +} + +/** + * + */ +class _Baz { +} + +// Hack: ignored for 'documented' generation strategy with a (re)named export, generated twice otherwise. +/** + * @extends _Baz + * @ignore + */ +class _Qux extends _Baz { + /** + * @param {_Bar} bar + */ + constructor(bar) { + /** + * @type {_Foo} + * @readonly + */ + this.foo = bar; + } +} + +// Hack: ignored for 'documented' generation strategy with a (re)named export, generated twice otherwise. +/** + * @return {_Foo | null} + * @ignore + */ +function _foo() { + return null; +} + +// Hack: ignored for 'documented' generation strategy with a (re)named export, generated twice otherwise. +/** + * @ignore + */ +const _bar = 0; + +/** + * Named export with 'exports.name =' on a referenced class. + */ +exports.Qux = _Qux + +/** + * Named export with 'exports.name =' on a referenced function. + */ +exports.foo = _foo; + +/** + * Named export with 'exports.name =' on a referenced var/const. + */ +exports.bar = _bar; diff --git a/test/fixtures/exports_exports.name=ref-same-name.js b/test/fixtures/exports_exports.name=ref-same-name.js new file mode 100644 index 0000000..5382bae --- /dev/null +++ b/test/fixtures/exports_exports.name=ref-same-name.js @@ -0,0 +1,65 @@ +/** @module test-export-20190919181749 */ + +class _NotExported { +} + +/** + * + */ +class _Foo { +} + +/** + * @extends _Foo + */ +class _Bar extends _Foo { +} + +/** + * + */ +class _Baz { +} + +/** + * @extends _Baz + */ +class Qux extends _Baz { + /** + * @param {_Bar} bar + */ + constructor(bar) { + /** + * @type {_Foo} + * @readonly + */ + this.foo = bar; + } +} + +/** + * @return {_Foo | null} + */ +function foo() { + return null; +} + +/** + * + */ +const bar = 0; + +/** + * Named export with 'exports.name =' on a referenced class. + */ +exports.Qux = Qux + +/** + * Named export with 'exports.name =' on a referenced function. + */ +exports.foo = foo; + +/** + * Named export with 'exports.name =' on a referenced var/const. + */ +exports.bar = bar; diff --git a/test/fixtures/exports_exports=module.exports=.js b/test/fixtures/exports_exports=module.exports=.js new file mode 100644 index 0000000..0cd2ee9 --- /dev/null +++ b/test/fixtures/exports_exports=module.exports=.js @@ -0,0 +1,12 @@ +/** @module test-export-20190913220248 */ + +class _NotExported { +} + +/** + * + */ +function _foo() { +} + +exports = /** Jsdoc comment inserted for 'documented' generation strategy. */ module.exports = _foo; diff --git a/test/fixtures/exports_indirect(=module.exports).name=.js b/test/fixtures/exports_indirect(=module.exports).name=.js new file mode 100644 index 0000000..bdf3bf5 --- /dev/null +++ b/test/fixtures/exports_indirect(=module.exports).name=.js @@ -0,0 +1,42 @@ +/** @module test-export-20190914010059 */ + +class _NotExported { +} + +/** + * + */ +class _Foo { +} + +/** + * + */ +class _Bar extends _Foo { +} + +/** + * + */ +class _Baz { +} + +/** + * Default export with 'module.exports ='. + */ +module.exports = _Foo; + +/** + * Wrong named export with 'exports.name ='. + */ +exports.BadBar = _Bar; + +/** + * Ensure exports points to module.exports. + */ +exports = module.exports; + +/** + * Effective named export with 'exports.name ='. + */ +exports.GoodBaz = _Baz; diff --git a/test/fixtures/exports_mix-default-named.js b/test/fixtures/exports_mix-default-named.js new file mode 100644 index 0000000..398bfd4 --- /dev/null +++ b/test/fixtures/exports_mix-default-named.js @@ -0,0 +1,26 @@ +/** @module test-export-20190913220333 */ + +class _NotExported { +} + +/** + * + */ +function _foo() { +} + +// Hack: ignored for 'documented' generation strategy with a (re)named export, generated twice otherwise. +/** + * @ignore + */ +const _bar = 0; + +/** + * Default export with 'module.exports ='. + */ +module.exports = _foo; + +/** + * Named export with 'module.export.name ='. + */ +module.exports.bar = _bar; diff --git a/test/fixtures/exports_module.exports.name=lambda-class.js b/test/fixtures/exports_module.exports.name=lambda-class.js new file mode 100644 index 0000000..f216858 --- /dev/null +++ b/test/fixtures/exports_module.exports.name=lambda-class.js @@ -0,0 +1,39 @@ +/** @module test-export-20190914001452 */ + +class _NotExported { +} + +/** + * + */ +class _Foo { +} + +/** + * @extends _Foo + */ +class _Bar extends _Foo { +} + +/** + * + */ +class _Baz { +} + +/** + * Named export with 'module.exports.name =' on a lambda class. + * @extends _Baz + */ +module.exports.Qux = class extends _Baz { + /** + * @param {_Bar} bar + */ + constructor(bar) { + /** + * @type {_Foo} + * @readonly + */ + this.foo = bar; + } +} diff --git a/test/fixtures/exports_module.exports.name=lambda-function.js b/test/fixtures/exports_module.exports.name=lambda-function.js new file mode 100644 index 0000000..7c6fda0 --- /dev/null +++ b/test/fixtures/exports_module.exports.name=lambda-function.js @@ -0,0 +1,18 @@ +/** @module test-export-20190914003200 */ + +class _NotExported { +} + +/** + * + */ +class _Foo { +} + +/** + * Named export with 'module.exports.name =' on a lambda function. + * @returns {_Foo | null} + */ +module.exports.foo = function() { + return null; +} diff --git a/test/fixtures/exports_module.exports.name=named-class.js b/test/fixtures/exports_module.exports.name=named-class.js new file mode 100644 index 0000000..4c55e92 --- /dev/null +++ b/test/fixtures/exports_module.exports.name=named-class.js @@ -0,0 +1,39 @@ +/** @module test-export-20190913233106 */ + +class _NotExported { +} + +/** + * + */ +class _Foo { +} + +/** + * @extends _Foo + */ +class _Bar extends _Foo { +} + +/** + * + */ +class _Baz { +} + +/** + * Named export with 'module.exports.name =' on a named class. + * @extends _Baz + */ +module.exports.Qux = class _Qux extends _Baz { + /** + * @param {_Bar} bar + */ + constructor(bar) { + /** + * @type {_Foo} + * @readonly + */ + this.foo = bar; + } +} diff --git a/test/fixtures/exports_module.exports.name=named-function.js b/test/fixtures/exports_module.exports.name=named-function.js new file mode 100644 index 0000000..3941099 --- /dev/null +++ b/test/fixtures/exports_module.exports.name=named-function.js @@ -0,0 +1,18 @@ +/** @module test-export-20190913221147 */ + +class _NotExported { +} + +/** + * + */ +class _Foo { +} + +/** + * Named export with 'module.exports.name =' on a named function. + * @returns {_Foo | null} + */ +module.exports.foo = function _foo() { + return null; +} diff --git a/test/fixtures/exports_module.exports.name=ref-other-name.js b/test/fixtures/exports_module.exports.name=ref-other-name.js new file mode 100644 index 0000000..7753b91 --- /dev/null +++ b/test/fixtures/exports_module.exports.name=ref-other-name.js @@ -0,0 +1,70 @@ +/** @module test-export-20190913220044 */ + +class _NotExported { +} + +/** + * + */ +class _Foo { +} + +/** + * @extends _Foo + */ +class _Bar extends _Foo { +} + +/** + * + */ +class _Baz { +} + +// Hack: ignored for 'documented' generation strategy with a (re)named export, generated twice otherwise. +/** + * @extends _Baz + * @ignore + */ +class _Qux extends _Baz { + /** + * @param {_Bar} bar + */ + constructor(bar) { + /** + * @type {_Foo} + * @readonly + */ + this.foo = bar; + } +} + +// Hack: ignored for 'documented' generation strategy with a (re)named export, generated twice otherwise. +/** + * @return {_Foo | null} + * @ignore + */ +function _foo() { + return null; +} + +// Hack: ignored for 'documented' generation strategy with a (re)named export, generated twice otherwise. +/** + * @ignore + */ +const _bar = 0; + +/** + * Named export with 'module.exports.name =' on a referenced class. + */ +module.exports.Qux = _Qux + +/** + * Named export with 'module.exports.name =' on a referenced function. + */ +module.exports.foo = _foo; + +/** + * Named export with 'module.exports.name =' on a referenced var/const. + */ +module.exports.bar = _bar; diff --git a/test/fixtures/exports_module.exports.name=ref-same-name.js b/test/fixtures/exports_module.exports.name=ref-same-name.js new file mode 100644 index 0000000..259b7c9 --- /dev/null +++ b/test/fixtures/exports_module.exports.name=ref-same-name.js @@ -0,0 +1,65 @@ +/** @module test-export-20190919181620 */ + +class _NotExported { +} + +/** + * + */ +class _Foo { +} + +/** + * @extends _Foo + */ +class _Bar extends _Foo { +} + +/** + * + */ +class _Baz { +} + +/** + * @extends _Baz + */ +class Qux extends _Baz { + /** + * @param {_Bar} bar + */ + constructor(bar) { + /** + * @type {_Foo} + * @readonly + */ + this.foo = bar; + } +} + +/** + * @return {_Foo | null} + */ +function foo() { + return null; +} + +/** + * + */ +const bar = 0; + +/** + * Named export with 'module.exports.name =' on a referenced class. + */ +module.exports.Qux = Qux + +/** + * Named export with 'module.exports.name =' on a referenced function. + */ +module.exports.foo = foo; + +/** + * Named export with 'module.exports.name =' on a referenced var/const. + */ +module.exports.bar = bar; diff --git a/test/fixtures/exports_module.exports=lambda-class.js b/test/fixtures/exports_module.exports=lambda-class.js new file mode 100644 index 0000000..3013f02 --- /dev/null +++ b/test/fixtures/exports_module.exports=lambda-class.js @@ -0,0 +1,39 @@ +/** @module test-export-20190913221432 */ + +class _NotExported { +} + +/** + * + */ +class _Foo { +} + +/** + * @extends _Foo + */ +class _Bar extends _Foo { +} + +/** + * + */ +class _Baz { +} + +/** + * Default export with 'module.exports =' on a lambda class. + * @extends _Baz + */ +module.exports = class extends _Baz { + /** + * @param {_Bar} bar + */ + constructor(bar) { + /** + * @type {_Foo} + * @readonly + */ + this.foo = bar; + } +} diff --git a/test/fixtures/exports_module.exports=lambda-function.js b/test/fixtures/exports_module.exports=lambda-function.js new file mode 100644 index 0000000..91a3cb7 --- /dev/null +++ b/test/fixtures/exports_module.exports=lambda-function.js @@ -0,0 +1,18 @@ +/** @module test-export-20190913222027 */ + +class _NotExported { +} + +/** + * + */ +class _Foo { +} + +/** + * Default export with 'module.exports =' on a lambda function. + * @returns {_Foo | null} + */ +module.exports = function() { + return null; +} diff --git a/test/fixtures/exports_module.exports=named-class.js b/test/fixtures/exports_module.exports=named-class.js new file mode 100644 index 0000000..29e2a1d --- /dev/null +++ b/test/fixtures/exports_module.exports=named-class.js @@ -0,0 +1,39 @@ +/** @module test-export-20190913222429 */ + +class _NotExported { +} + +/** + * + */ +class _Foo { +} + +/** + * @extends _Foo + */ +class _Bar extends _Foo { +} + +/** + * + */ +class _Baz { +} + +/** + * Default export with 'module.exports =' on a named class. + * @extends _Baz + */ +module.exports = class _Qux extends _Baz { + /** + * @param {_Bar} bar + */ + constructor(bar) { + /** + * @type {_Foo} + * @readonly + */ + this.foo = bar; + } +} diff --git a/test/fixtures/exports_module.exports=named-function.js b/test/fixtures/exports_module.exports=named-function.js new file mode 100644 index 0000000..a89c644 --- /dev/null +++ b/test/fixtures/exports_module.exports=named-function.js @@ -0,0 +1,18 @@ +/** @module test-export-20190913223443 */ + +class _NotExported { +} + +/** + * + */ +class _Foo { +} + +/** + * Default export with 'module.exports =' on a named function. + * @returns {_Foo | null} + */ +module.exports = function _foo() { + return null; +} diff --git a/test/fixtures/exports_module.exports=ref.js b/test/fixtures/exports_module.exports=ref.js new file mode 100644 index 0000000..8281286 --- /dev/null +++ b/test/fixtures/exports_module.exports=ref.js @@ -0,0 +1,15 @@ +/** @module test-export-20190913215617 */ + +class _NotExported { +} + +/** + * + */ +function _foo() { +} + +/** + * Default export with 'module.exports =' on a referenced type. + */ +module.exports = _foo; diff --git a/test/fixtures/exports_module.exports={name=lambda-class}.js b/test/fixtures/exports_module.exports={name=lambda-class}.js new file mode 100644 index 0000000..65606fe --- /dev/null +++ b/test/fixtures/exports_module.exports={name=lambda-class}.js @@ -0,0 +1,49 @@ +/** @module test-export-20190914004600 */ + +class _NotExported { +} + +/** + * + */ +class _Foo { +} + +/** + * @extends _Foo + */ +class _Bar extends _Foo { +} + +/** + * + */ +class _Baz { +} + +/** + * Jsdoc comment for 'documented' generation strategy. + */ +module.exports = { + /** + * Named export with 'module.exports = {name: ...}' on a lambda class. + * @param {_Bar} bar + * @extends _Baz + */ + Qux: + class extends _Baz { + /** + * Does not work! No doclet generated for this constructor. + * @see [jsdoc#1699](https://github.com/jsdoc/jsdoc/issues/1699) + * Please set the constructor documentation in the class declaration documentation. + * @param {_Bar} bar + */ + constructor(bar) { + /** + * @type {_Foo} + * @readonly + */ + this.foo = bar; + } + } +}; diff --git a/test/fixtures/exports_module.exports={name=lambda-function}.js b/test/fixtures/exports_module.exports={name=lambda-function}.js new file mode 100644 index 0000000..410a8e6 --- /dev/null +++ b/test/fixtures/exports_module.exports={name=lambda-function}.js @@ -0,0 +1,24 @@ +/** @module test-export-20190914005207 */ + +class _NotExported { +} + +/** + * + */ +class _Foo { +} + +/** + * Jsdoc comment for 'documented' generation strategy. + */ +module.exports = { + /** + * Named export with 'module.exports = {name: ...}' on a lambda function. + * @returns {_Foo | null} + */ + foo: + function() { + return null; + } +}; diff --git a/test/fixtures/exports_module.exports={name=named-class}.js b/test/fixtures/exports_module.exports={name=named-class}.js new file mode 100644 index 0000000..9bc4842 --- /dev/null +++ b/test/fixtures/exports_module.exports={name=named-class}.js @@ -0,0 +1,49 @@ +/** @module test-export-20190913235349 */ + +class _NotExported { +} + +/** + * + */ +class _Foo { +} + +/** + * @extends _Foo + */ +class _Bar extends _Foo { +} + +/** + * + */ +class _Baz { +} + +/** + * Jsdoc comment for 'documented' generation strategy. + */ +module.exports = { + /** + * Named export with 'module.exports = {name: ...}' on a named class. + * @param {_Bar} bar + * @extends _Baz + */ + Qux: + class _Qux extends _Baz { + /** + * Does not work! No doclet generated for this constructor. + * @see [jsdoc#1699](https://github.com/jsdoc/jsdoc/issues/1699) + * Please set the constructor documentation in the class declaration documentation. + * @param {_Bar} bar + */ + constructor(bar) { + /** + * @type {_Foo} + * @readonly + */ + this.foo = bar; + } + } +}; diff --git a/test/fixtures/exports_module.exports={name=named-function}.js b/test/fixtures/exports_module.exports={name=named-function}.js new file mode 100644 index 0000000..75c3ff2 --- /dev/null +++ b/test/fixtures/exports_module.exports={name=named-function}.js @@ -0,0 +1,24 @@ +/** @module test-export-20190914000510 */ + +class _NotExported { +} + +/** + * + */ +class _Foo { +} + +/** + * Jsdoc comment for 'documented' generation strategy. + */ +module.exports = { + /** + * Named export with 'module.exports = {name: ...}' on a named function. + * @returns {_Foo | null} + */ + foo: + function _foo() { + return null; + } +}; diff --git a/test/fixtures/exports_module.exports={name=ref-other-name}.js b/test/fixtures/exports_module.exports={name=ref-other-name}.js new file mode 100644 index 0000000..e2807b4 --- /dev/null +++ b/test/fixtures/exports_module.exports={name=ref-other-name}.js @@ -0,0 +1,21 @@ +/** @module test-export-20190913220202 */ + +class _NotExported { +} + +// Hack: ignored for 'documented' generation strategy with a (re)named export, generated twice otherwise. +/** + * @ignore + */ +function _foo() { +} + +/** + * Jsdoc comment for 'documented' generation strategy. + */ +module.exports = { + /** + * Named export with 'module.exports = {name: ...}' on a referenced type. + */ + foo: _foo +}; diff --git a/test/fixtures/exports_module.exports={name=ref-same-name}.js b/test/fixtures/exports_module.exports={name=ref-same-name}.js new file mode 100644 index 0000000..c5f9dc2 --- /dev/null +++ b/test/fixtures/exports_module.exports={name=ref-same-name}.js @@ -0,0 +1,20 @@ +/** @module test-export-20190919181839 */ + +class _NotExported { +} + +/** + * + */ +function foo() { +} + +/** + * Jsdoc comment for 'documented' generation strategy. + */ +module.exports = { + /** + * Named export with 'module.exports = {name: ...}' on a referenced type. + */ + foo: foo +}; diff --git a/test/fixtures/exports_wrong exports.name=.js b/test/fixtures/exports_wrong exports.name=.js new file mode 100644 index 0000000..93809dd --- /dev/null +++ b/test/fixtures/exports_wrong exports.name=.js @@ -0,0 +1,29 @@ +/** @module test-export-20190914020335 */ + +class _NotExported { +} + +/** + * + */ +function _foo() { +} + +/** + * Default export with 'module.exports ='. + */ +module.exports = _foo; +/** + * Wrong 'exports.name =' export. + * @type {number} + * @constant + */ +exports.bar = 0; + +exports = module.exports +/** + * Good 'exports.name =' export. + * @type {string} + * @constant + */ +exports.baz = "hello"; diff --git a/test/fixtures/function_all.js b/test/fixtures/function_all.js index e770bc9..bae53cd 100644 --- a/test/fixtures/function_all.js +++ b/test/fixtures/function_all.js @@ -1,3 +1,5 @@ +/** @module functions */ + /** * * @param {number} [a=10] @@ -35,17 +37,24 @@ function test3(myObjs) { class Test12345 { /** * @function - * @memberof Test12345 + * @memberof module:functions~Test12345 * @name f * @return {number[]} */ /** * @function - * @memberof Test12345 + * @memberof module:functions~Test12345 * @variation 1 * @name f * @param {string} key * @return {number} */ } + +module.exports = { + test1: test1, + test2: test2, + test3: test3, + Test12345: Test12345 +} diff --git a/test/fixtures/interface_all.js b/test/fixtures/interface_all.js index a17c999..b328909 100644 --- a/test/fixtures/interface_all.js +++ b/test/fixtures/interface_all.js @@ -1,3 +1,5 @@ +/** @module interfaces */ + /** * Interface for classes that represent a color. * @@ -6,7 +8,7 @@ function Color() {} /** - * @function Color.staticMethod1 + * @function module:interfaces~Color.staticMethod1 */ Color.staticMethod1 = function() {}; @@ -17,7 +19,7 @@ Color.staticMethod1 = function() {}; Color.staticMethod2 = function() {}; /** - * @member {Number} Color.staticMember1 + * @member {Number} module:interfaces~Color.staticMember1 */ Color.staticMember1 = 1; @@ -29,7 +31,7 @@ Color.staticMember2 = true; /** * @type {String} -* @name Color.staticMember3 +* @name module:interfaces~Color.staticMember3 */ Color.staticMember3 = "foobar"; @@ -63,7 +65,7 @@ Color.prototype.rgb = function() { /** * @function * @name [foobar1] - * @memberof Color# + * @memberof module:interfaces~Color# */ Color.prototype.foobar1 = function() { throw new Error('not implemented'); @@ -71,7 +73,7 @@ Color.prototype.foobar1 = function() { /** * @function [foobar2] - * @memberof Color# + * @memberof module:interfaces~Color# */ Color.prototype.foobar2 = function() { throw new Error('not implemented'); @@ -80,19 +82,23 @@ Color.prototype.foobar2 = function() { /** * @type {Boolean} * @name [foobar3] - * @memberof Color# + * @memberof module:interfaces~Color# */ Color.prototype.foobar3 = undefined; /** * @member {String} * @name [foobar4] - * @memberof Color# + * @memberof module:interfaces~Color# */ Color.prototype.foobar4 = undefined; /** * @member {Object} [foobar5] - * @memberof Color# + * @memberof module:interfaces~Color# */ Color.prototype.foobar5 = undefined; + +module.exports = { + Color: Color +} diff --git a/test/fixtures/mixin_all.js b/test/fixtures/mixin_all.js index c7d1b0b..64c6204 100644 --- a/test/fixtures/mixin_all.js +++ b/test/fixtures/mixin_all.js @@ -1,3 +1,5 @@ +/** @module mixins */ + /** * @mixin */ @@ -32,3 +34,7 @@ class MyBaseClass { class MyMixedClass extends MyBaseClass { } + +module.exports = { + MyMixedClass: MyMixedClass +} diff --git a/test/fixtures/module_all.js b/test/fixtures/module_all.js index 5313081..084f0f6 100644 --- a/test/fixtures/module_all.js +++ b/test/fixtures/module_all.js @@ -26,4 +26,7 @@ const arrayToObjectKeys = (array, valueGenerator = null) => { } +/** + * Export arrayToObjectKeys as default. + */ export default arrayToObjectKeys diff --git a/test/fixtures/namespace_all.js b/test/fixtures/namespace_all.js index 40e6a4f..a722765 100644 --- a/test/fixtures/namespace_all.js +++ b/test/fixtures/namespace_all.js @@ -1,3 +1,5 @@ +/** @module namespaces */ + /** * @namespace FoobarNS */ @@ -15,7 +17,7 @@ FoobarNS.Foo = function Foo() { /** * @callback FCallback * @this S - * @memberof FoobarNS.Foo + * @memberof module:namespaces~FoobarNS.Foo * @param {T} first - The first param. * @param {number} second - The second param. * @param {T[]} third - The third param. @@ -44,6 +46,9 @@ FoobarNS.Bar = function Bar() { /** * A method. + * Seems that when FoobarNS is declared in a module, jsdoc does not detect the override of of Foo.f() automatically anymore. + * Let's tell jsdoc explicitely. + * @override */ FoobarNS.Bar.prototype.f = function f() { }; @@ -121,3 +126,7 @@ FoobarNS.helloWorld8 = 1.2345; * @type {Boolean} */ FoobarNS.helloWorld9 = true; + +module.exports = { + FoobarNS: FoobarNS +} diff --git a/test/fixtures/property_all.js b/test/fixtures/property_all.js index a56b23b..231277a 100644 --- a/test/fixtures/property_all.js +++ b/test/fixtures/property_all.js @@ -1,3 +1,4 @@ +/** @module properties */ /** * @class @@ -12,7 +13,7 @@ class PropTest1 { */ class PropTest2 { /** - * @name PropTest2#otherProp + * @name module:properties~PropTest2#otherProp * @type {Number} */ otherProp = 1; @@ -24,13 +25,13 @@ class PropTest2 { */ class PropTest3 { /** - * @name PropTest3#myProp + * @name module:properties~PropTest3#myProp * @type {Boolean} * @description duplicate */ myProp = true; /** - * @name PropTest3#myProp + * @name module:properties~PropTest3#myProp * @type {Boolean} * @description duplicate */ @@ -50,7 +51,7 @@ var PropTest4 = function() { */ var PropTest5 = function() { /** - * @name PropTest5#otherProp + * @name module:properties~PropTest5#otherProp * @type {Number} */ this.otherProp = 1; @@ -62,13 +63,13 @@ var PropTest5 = function() { */ var PropTest6 = function() { /** - * @name PropTest6#myProp + * @name module:properties~PropTest6#myProp * @type {Boolean} * @description duplicate */ this.myProp = true; /** - * @name PropTest6#myProp + * @name module:properties~PropTest6#myProp * @type {Boolean} * @description duplicate */ @@ -85,3 +86,13 @@ var PropTest6 = function() { */ var PropTest7 = function() { } + +module.exports = { + PropTest1: PropTest1, + PropTest2: PropTest2, + PropTest3: PropTest3, + PropTest4: PropTest4, + PropTest5: PropTest5, + PropTest6: PropTest6, + PropTest7: PropTest7 +} diff --git a/test/fixtures/typedef_all.js b/test/fixtures/typedef_all.js index 1aef65b..696d977 100644 --- a/test/fixtures/typedef_all.js +++ b/test/fixtures/typedef_all.js @@ -1,3 +1,5 @@ +/** @module typedefs */ + /** * The complete Triforce, or one or more components of the Triforce. * @typedef {object} Triforce @@ -75,3 +77,11 @@ * @property {function(string, number): Promise} wobble Wobbles the pattern * (complex function) */ + +module.exports = { + Triforce: Triforce, + Anon: Anon, + GitGraphOptions: GitGraphOptions, + NumberLike: NumberLike, + PatternOptions: PatternOptions +} diff --git a/test/fixtures/typeof_all.js b/test/fixtures/typeof_all.js index 2648a72..9f0b828 100644 --- a/test/fixtures/typeof_all.js +++ b/test/fixtures/typeof_all.js @@ -1,3 +1,5 @@ +/** @module typeofs */ + /** * @property {Class} x * @property {Class.< MyClass >} y @@ -23,3 +25,8 @@ class Foobar1 {} * @property {typeof Array} s */ class Foobar2 {} + +module.exports = { + Foobar1: Foobar1, + Foobar2: Foobar2 +} diff --git a/test/lib/index.ts b/test/lib/index.ts index 8a1e5c6..5cc5f4c 100644 --- a/test/lib/index.ts +++ b/test/lib/index.ts @@ -1,8 +1,23 @@ +// tslint:disable-next-line +/// + import * as fs from 'fs'; import * as path from 'path'; import { expect } from 'chai'; +// See: https://github.com/Alexis-ROYER/tsd-default-export/blob/master/README.md +// Use ES6 default import. +import walkBack from 'walk-back'; +import { JsdocApi } from './jsdoc-api'; import { renderSync } from './render'; +// jsdoc-api may actually work with a jsdoc instance installed in its own `node_modules` subdirectory. +// Use the same kind of 'walk-back' call as jsdoc-api does in order to find the jsdoc instance actually used. +const jsdocPath = walkBack( + path.join(__dirname, '../../node_modules/jsdoc-api'), + path.join('node_modules', 'jsdoc') +); +const jsdocInfo = require(path.join(jsdocPath || '../../node_modules/jsdoc', 'package')); + const DEST_DIR = path.resolve(path.join(__dirname, '../_temp')); const DATA_DIR = path.resolve(path.join(__dirname, '../fixtures')); const EXPECT_DIR = path.resolve(path.join(__dirname, '../expected')); @@ -15,24 +30,95 @@ before(() => { } catch (e) { /* Do Nothing */ } }); -export function compileJsdoc(sourcePath: string) { +function explainJsdoc(sourcePath: string): string { + // Create a jsdoc configuration file. + let jsdocConf = JSON.parse(JSON.stringify(require(CONFIG_PATH))); // <= Ensure we modify a clone object. + if (jsdocConf.opts && jsdocConf.opts.template) + // Remove the tsd-jsdoc template. + delete jsdocConf.opts.template; + const confPath = path.join(DEST_DIR, path.basename(sourcePath).replace(".js", `-conf-explain.json`)); + fs.writeFileSync(confPath, JSON.stringify(jsdocConf, null, 2)); + + // Call the `JsdocApi.explainSync()` function. + const rawDestPath = path.join(DEST_DIR, path.basename(sourcePath).replace(".js", `-jsdoc@${jsdocInfo.version}-explain.json`)); + const jsdocRawOutput = JsdocApi.explainSync({ + files: sourcePath, + cache: false, + configure: confPath + }); + fs.writeFileSync( + rawDestPath, + JSON.stringify(jsdocRawOutput, null, 2) + ); + + // Eventually return the path of the raw output file. + return rawDestPath; +} + +function compileJsdoc(sourcePath: string, generationStrategy: 'documented' | 'exported'): string { + // Create a jsdoc configuration file. + let jsdocConf = JSON.parse(JSON.stringify(require(CONFIG_PATH))); // <= Ensure we modify a clone object. + if (! jsdocConf.opts) + jsdocConf.opts = {} + jsdocConf.opts.generationStrategy = generationStrategy + const confPath = path.join(DEST_DIR, path.basename(sourcePath).replace(".js", `-conf-${generationStrategy}.json`)); + fs.writeFileSync(confPath, JSON.stringify(jsdocConf, null, 2)); + + // Launch jsdoc with the tsd-jsdoc template. renderSync({ files: sourcePath, cache: false, destination: DEST_DIR, - configure: CONFIG_PATH + configure: confPath }); + // Rename the `types.d.ts` output file into a `-jsdoc@-.d.ts` file, + // being inspired from the `sourcePath` parameter. + // The tag in the file name helps investigations when something fails between jsdoc@3.5.x vs jsdoc@3.6.x. + // The tag in the file name helps investigations when something fails between 'documented' vs 'exported' generation strategies. + const destPath = path.join(DEST_DIR, path.basename(sourcePath).replace(".js", `-jsdoc@${jsdocInfo.version}-${generationStrategy}.d.ts`)); + fs.renameSync( + path.join(DEST_DIR, `types.d.ts`), + destPath + ); + + // Eventually return the path of the output file. + return destPath; } -export function expectJsDoc(fileName: string) { - const destPath = path.join(DEST_DIR, `types.d.ts`); +function expectJsDoc(fileName: string, generationStrategy: 'documented' | 'exported') { const dataPath = path.join(DATA_DIR, `${fileName}.js`); - const expectPath = path.join(EXPECT_DIR, `${fileName}.d.ts`); - compileJsdoc(dataPath); + // Let's first export the jsdoc raw output in a file tagged with the jsdoc version, + // in order to help investigations when somethings fails between jsdoc@3.5.x vs jsdoc@3.6.x. + explainJsdoc(dataPath); + // Execute tsd-jsdoc. + const destPath = compileJsdoc(dataPath, generationStrategy); const destStr = fs.readFileSync(destPath, 'utf8'); - const expectStr = fs.readFileSync(expectPath, 'utf8'); + // Check result. + const expectPath = path.join(EXPECT_DIR, `${fileName}.d.ts`); + const expectStr = fs.readFileSync(expectPath, 'utf8'); expect(destStr.trim()).to.equal(expectStr.trim()); } + +/** + * Defines two tests for each test case: one for 'documented', the other for 'exported' generation strategy. + * This function is the only exported symbol for this file, so that every test case pass through it and be declined in its 'documented' and 'exported' versions. + * @param testName Name of the test. + * @param basename Fixture file base name. + */ +export function tsdJsdocTestCase(testName: string, basename: string, description?: string) { + test( + testName + ' '.repeat(40-testName.length) + '\'documented\' generation strategy ' + (description ? description : ''), + function() { + expectJsDoc(basename, 'documented'); + } + ); + test( + testName + ' '.repeat(40-testName.length) + '\'exported\' generation strategy ' + (description ? description : ''), + function() { + expectJsDoc(basename, 'exported'); + } + ); +} diff --git a/test/specs/class.ts b/test/specs/class.ts index c5c3da1..6eac358 100644 --- a/test/specs/class.ts +++ b/test/specs/class.ts @@ -1,7 +1,5 @@ -import { expectJsDoc } from '../lib'; +import { tsdJsdocTestCase } from '../lib'; suite('Class Checks', () => { - test('All', () => { - expectJsDoc('class_all'); - }); + tsdJsdocTestCase('All', 'class_all'); }); diff --git a/test/specs/constructor.ts b/test/specs/constructor.ts new file mode 100644 index 0000000..c8135e1 --- /dev/null +++ b/test/specs/constructor.ts @@ -0,0 +1,5 @@ +import { tsdJsdocTestCase } from '../lib'; + +suite('Constructor Checks', () => { + tsdJsdocTestCase('All', 'constructor_all'); +}); diff --git a/test/specs/enum.ts b/test/specs/enum.ts index 41c895b..f3aa6eb 100644 --- a/test/specs/enum.ts +++ b/test/specs/enum.ts @@ -1,7 +1,5 @@ -import { expectJsDoc } from '../lib'; +import { tsdJsdocTestCase } from '../lib'; suite('Enum Checks', () => { - test('All', () => { - expectJsDoc('enum_all'); - }); + tsdJsdocTestCase('All', 'enum_all'); }); diff --git a/test/specs/exports.ts b/test/specs/exports.ts new file mode 100644 index 0000000..5fb87d6 --- /dev/null +++ b/test/specs/exports.ts @@ -0,0 +1,62 @@ +import { tsdJsdocTestCase } from '../lib'; + +suite('Default exports Checks', () => { + class ExportTest { + public readonly id: string; + public readonly desc: string; + public constructor(id: string, desc: string) { + this.id = id; + this.desc = desc; + } + } + const exportTests : Array = [ + // Default export by reference. + {id: "export default ref", desc: "'export default' keywords with reference to an existing type"}, + {id: "module.exports=ref", desc: "'module.exports =' default export pattern with reference to an existing type"}, + // Named exports. + {id: "module.exports.name=ref-same-name", desc: "'module.exports.name =' named export pattern with reference to an existing type with the same same"}, + {id: "module.exports.name=ref-other-name", desc: "'module.exports.name =' named export pattern with reference to an existing type with a different name"}, + {id: "exports.name=ref-same-name", desc: "'exports.name =' named export pattern with reference to an existing type with the same name"}, + {id: "exports.name=ref-other-name", desc: "'exports.name =' named export pattern with reference to an existing type with a different name"}, + // Not supported yet: {id: "export default {name=ref-same-name}", desc: "'export default { name: ... }' named export pattern with references to existing types with same names"}, + // Not supported yet: {id: "export default {name=ref-other-name}", desc: "'export default { name: ... }' named export pattern with references to existing types with different names"}, + {id: "module.exports={name=ref-same-name}", desc: "'module.exports = { name: ... }' named export pattern with references to existing types with same names"}, + {id: "module.exports={name=ref-other-name}", desc: "'module.exports = { name: ... }' named export pattern with references to existing types with different names"}, + // Inline named types. + {id: "export default named-class", desc: "'export default' with named class inline"}, + {id: "export default named-function", desc: "'export default' with named function inline"}, + {id: "module.exports=named-class", desc: "'module.exports =' default export pattern with named class inline"}, + {id: "module.exports=named-function", desc: "'module.exports =' default export pattern with named function inline"}, + {id: "export named-types", desc: "'export' keyword before a named type"}, + {id: "module.exports.name=named-class", desc: "'module.exports.name =' named export pattern with named class inline"}, + {id: "module.exports.name=named-function", desc: "'module.exports.name =' named export pattern with named function inline"}, + {id: "exports.name=named-class", desc: "'exports.name =' named export pattern with named class inline"}, + {id: "exports.name=named-function", desc: "'exports.name =' named export pattern with named function inline"}, + // Not supported yet: {id: "export default {name=named-class}", desc: "'export default { name: ...}' named export pattern with named class inline"}, + // Not supported yet: {id: "export default {name=named-function}", desc: "'export default { name: ...}' named export pattern with named function inline"}, + {id: "module.exports={name=named-class}", desc: "'module.exports = { name: ... }' named export pattern with named class inline"}, + {id: "module.exports={name=named-function}", desc: "'module.exports = { name: ... }' named export pattern with named function inline"}, + // Lambdas. + {id: "export default lambda-class", desc: "'export default' with lambda class"}, + {id: "export default lambda-function", desc: "'export default' with lambda function"}, + {id: "module.exports=lambda-class", desc: "'module.exports =' default export pattern with lambda class"}, + {id: "module.exports=lambda-function", desc: "'module.exports =' default export pattern with lambda function"}, + {id: "module.exports.name=lambda-class", desc: "'module.exports.name =' named export pattern with lambda class"}, + {id: "module.exports.name=lambda-function", desc: "'module.exports.name =' named export pattern with lambda function"}, + {id: "exports.name=lambda-class", desc: "'exports.name =' named export pattern with lambda class"}, + {id: "exports.name=lambda-function", desc: "'exports.name =' named export pattern with lambda function"}, + {id: "module.exports={name=lambda-class}", desc: "'module.exports = { name: ... }' named export pattern with lambda class"}, + {id: "module.exports={name=lambda-function}", desc: "'module.exports = { name: ... }' named export pattern with lambda function"}, + // Mixs. + {id: "exports=module.exports=", desc: "'exports = module.exports =' pattern"}, + {id: "mix-default-named", desc: "Mix of default and named exports"}, + // Border cases. + {id: "cyclic-dependencies", desc: "Check no infinite loop on cyclic dependencies"}, + // To be fixed: {id: "indirect(=module.exports).name=", desc: "Named export through an indirection"}, + // Not supported: {id: "wrong exports.name=", desc: "Wrong use of 'exports.name =' while not set to module.exports"}, + ]; + // Execute the tests. + for (const exportTest of exportTests) { + tsdJsdocTestCase(exportTest.id, `exports_${exportTest.id}`, exportTest.desc); + } +}); diff --git a/test/specs/function.ts b/test/specs/function.ts index 3e0e66d..17c90bd 100644 --- a/test/specs/function.ts +++ b/test/specs/function.ts @@ -1,7 +1,5 @@ -import { expectJsDoc } from '../lib'; +import { tsdJsdocTestCase } from '../lib'; suite('Function Checks', () => { - test('All', () => { - expectJsDoc('function_all'); - }); + tsdJsdocTestCase('All', 'function_all'); }); diff --git a/test/specs/interface.ts b/test/specs/interface.ts index 376542c..d025077 100644 --- a/test/specs/interface.ts +++ b/test/specs/interface.ts @@ -1,7 +1,5 @@ -import { expectJsDoc } from '../lib'; +import { tsdJsdocTestCase } from '../lib'; suite('Interface Checks', () => { - test('All', () => { - expectJsDoc('interface_all'); - }); + tsdJsdocTestCase('All', 'interface_all'); }); diff --git a/test/specs/mixin.ts b/test/specs/mixin.ts index 7cfc9ce..a0fcc01 100644 --- a/test/specs/mixin.ts +++ b/test/specs/mixin.ts @@ -1,7 +1,5 @@ -import { expectJsDoc } from '../lib'; +import { tsdJsdocTestCase } from '../lib'; suite('Mixin Checks', () => { - test('All', () => { - expectJsDoc('mixin_all'); - }); + tsdJsdocTestCase('All', 'mixin_all'); }); diff --git a/test/specs/module.ts b/test/specs/module.ts index 9265bf0..cbad28e 100644 --- a/test/specs/module.ts +++ b/test/specs/module.ts @@ -1,7 +1,5 @@ -import { expectJsDoc } from '../lib'; +import { tsdJsdocTestCase } from '../lib'; suite('Module Checks', () => { - test('All', () => { - expectJsDoc('module_all'); - }); + tsdJsdocTestCase('All', 'module_all'); }); diff --git a/test/specs/namespace.ts b/test/specs/namespace.ts index 20e77e6..a281c10 100644 --- a/test/specs/namespace.ts +++ b/test/specs/namespace.ts @@ -1,7 +1,5 @@ -import { expectJsDoc } from '../lib'; +import { tsdJsdocTestCase } from '../lib'; suite('Namespace Checks', () => { - test('All', () => { - expectJsDoc('namespace_all'); - }); + tsdJsdocTestCase('All', 'namespace_all'); }); diff --git a/test/specs/property.ts b/test/specs/property.ts index e19516f..e58fa9c 100644 --- a/test/specs/property.ts +++ b/test/specs/property.ts @@ -1,7 +1,5 @@ -import { expectJsDoc } from '../lib'; +import { tsdJsdocTestCase } from '../lib'; suite('Property Checks', () => { - test('All', () => { - expectJsDoc('property_all'); - }); + tsdJsdocTestCase('All', 'property_all'); }); diff --git a/test/specs/typedef.ts b/test/specs/typedef.ts index 9ee7a62..69a68bc 100644 --- a/test/specs/typedef.ts +++ b/test/specs/typedef.ts @@ -1,7 +1,5 @@ -import { expectJsDoc } from '../lib'; +import { tsdJsdocTestCase } from '../lib'; suite('Typedef Checks', () => { - test('All', () => { - expectJsDoc('typedef_all'); - }); + tsdJsdocTestCase('All', 'typedef_all'); }); diff --git a/test/specs/typeof.ts b/test/specs/typeof.ts index cd6e15c..9be31d0 100644 --- a/test/specs/typeof.ts +++ b/test/specs/typeof.ts @@ -1,7 +1,5 @@ -import { expectJsDoc } from '../lib'; +import { tsdJsdocTestCase } from '../lib'; suite('Typeof Checks', () => { - test('All', () => { - expectJsDoc('typeof_all'); - }); + tsdJsdocTestCase('All', 'typeof_all'); }); diff --git a/test/typings/walk-back.d.ts b/test/typings/walk-back.d.ts new file mode 100644 index 0000000..f648bbb --- /dev/null +++ b/test/typings/walk-back.d.ts @@ -0,0 +1,6 @@ +declare function walkBack(startAt: string, lookingFor: string): string | null; +declare module 'walk-back' { + // See: https://github.com/Alexis-ROYER/tsd-default-export/blob/master/README.md + // Use ES6 default export. + export default walkBack; +} diff --git a/tsconfig.json b/tsconfig.json index c063c6f..14f64d2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,16 +2,20 @@ "compilerOptions": { "outDir": "./dist", "target": "es6", + "esModuleInterop": true, "module": "commonjs", "moduleResolution": "node", "declaration": false, "noImplicitAny": true, "removeComments": true, "sourceMap": true, - "strict": true + "strict": true, + "typeRoots": [ + "./node_modules/@types", + "./src/typings" + ] }, "include": [ - "src/typings/**/*.d.ts", "src/**/*.ts" ] }