Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Have tsd-jsdoc generate default exports #98

Merged
merged 43 commits into from
May 2, 2020
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
23f7c8c
Very first 'export default' generation.
alxroyer Aug 30, 2019
d398400
Removal of the IExportDefaultDoclet hack.
alxroyer Sep 2, 2019
a9ad12f
Code moved from _createTreeNodes() to _buildTree() for 'export defaul…
alxroyer Sep 2, 2019
96efc52
'module.exports =' pattern management.
alxroyer Sep 2, 2019
186f4c0
module3 test addition.
alxroyer Sep 3, 2019
31f17dc
Removal of K&R style braces.
alxroyer Sep 4, 2019
5d8387e
Addition of debug traces.
alxroyer Sep 6, 2019
ea81fb1
opts.generationStrategy option addition.
alxroyer Sep 6, 2019
3b76685
correctly write doclets at es6 class constructors
HackbrettXXX Apr 23, 2019
1cefe92
add a test case for constructors (fails currently)
HackbrettXXX Apr 24, 2019
82e01e4
Constructor generation with documentation.
alxroyer Sep 6, 2019
ea101c9
Emitter._buildTree() strengthening with full jsdoc doclets.
alxroyer Sep 9, 2019
7602d90
First 'exported' generation strategy implementation. Still needs to b…
alxroyer Sep 11, 2019
d50d96f
Test additions for testing the 'exported' generation strategy.
alxroyer Sep 14, 2019
10aa4d7
Merge tag 'v2.4.0' into export-default
alxroyer Sep 14, 2019
be91778
(Re)named exports.
alxroyer Sep 18, 2019
390222c
Warning addition while waiting for [tsd-jsdoc#104](https://github.com…
alxroyer Sep 18, 2019
69e1671
'export <named type>' pattern support.
alxroyer Sep 19, 2019
91a44e1
Named exports with reference to a type of the same name.
alxroyer Sep 20, 2019
99d8827
'export default <named type>' pattern support (works like a lambda cl…
alxroyer Sep 21, 2019
40e18f1
'export default <lambda class>' pattern support.
alxroyer Sep 21, 2019
7151feb
'export default <lambda function>' and 'export default <named functio…
alxroyer Sep 21, 2019
8f7607b
'module.exports=<lambda type>' and 'module.exports=<named type>' patt…
alxroyer Sep 21, 2019
d1c6fc9
'module.exports.name=<lambda type>' and 'module.exports.name=<named t…
alxroyer Sep 21, 2019
47b0147
'exports.name=<lambda type>' and 'exports.name=<named type>' patterns…
alxroyer Sep 21, 2019
da14615
'module.exports={name=<lambda type>} and 'module.exports={name=<named…
alxroyer Sep 22, 2019
1d726fd
cyclic dependencies with 'exported' generation strategy.
alxroyer Sep 23, 2019
8b205c6
Test class_all.js working with 'exported' generation strategy.
alxroyer Sep 25, 2019
15f02af
Test constructors.js & enum_all.js working with 'exported' generation…
alxroyer Sep 25, 2019
c0a06d7
Tests function_all.js and interface_all.js working with 'exported' ge…
alxroyer Sep 26, 2019
741ce56
Test namespace_all.js working with 'exported' generation strategy.
alxroyer Sep 26, 2019
5898db8
Tests property_all.js and typedef_all.js working with 'exported' gene…
alxroyer Sep 27, 2019
d9fbb28
'documented' & 'exported' test cases.
alxroyer Sep 27, 2019
e86b4ef
[email protected] installation.
alxroyer Sep 29, 2019
43d80a5
[email protected] fixes.
alxroyer Sep 30, 2019
eada5f0
Merge branch 'master' into export-default
alxroyer Sep 30, 2019
bf2eaab
`walk-back.d.ts` typescript declaration.
alxroyer Sep 30, 2019
1ac9c2c
`walk-back.d.ts`: application of "tsd-default-export" recommendations.
alxroyer Apr 18, 2020
fb0a981
Merge tag 'v2.5.0' into export-default
alxroyer Apr 19, 2020
a024bcd
Corrections to @englercj’s remarks.
alxroyer Apr 25, 2020
80519d5
Merge branch 'master' into export-default
alxroyer Apr 25, 2020
20d58b6
Fix merge tag 'v2.5.0' into export-default
alxroyer Apr 25, 2020
5b826a7
Corrections to @englercj’s remarks.
alxroyer May 1, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 64 additions & 11 deletions src/Emitter.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
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 {
createClass,
Expand All @@ -11,6 +11,7 @@ import {
createClassMember,
createInterfaceMember,
createNamespaceMember,
createExportDefault,
createModule,
createNamespace,
createTypedef,
Expand Down Expand Up @@ -39,6 +40,14 @@ function isEnum(doclet: TDoclet)
return (doclet.kind === 'member' || doclet.kind === 'constant') && doclet.isEnum;
}

function isExportDefault(doclet: TDoclet)
{
return (
(doclet.kind === 'member') && doclet.longname.startsWith('module:')
&& doclet.meta && (doclet.meta.code.name === "module.exports")
);
}

function shouldMoveOutOfClass(doclet: TDoclet)
{
return isClassLike(doclet)
Expand All @@ -61,6 +70,8 @@ export class Emitter

parse(docs?: TAnyDoclet[])
{
debug(`Emitter.parse()`);

this.results = [];
this._treeRoots = [];
this._treeNodes = {};
Expand All @@ -75,6 +86,8 @@ export class Emitter

emit()
{
debug(`Emitter.emit()`);

const resultFile = ts.createSourceFile(
'types.d.ts',
'',
Expand All @@ -100,22 +113,32 @@ export class Emitter

private _createTreeNodes(docs: TAnyDoclet[])
{
debug(`Emitter._createTreeNodes()`);
alxroyer marked this conversation as resolved.
Show resolved Hide resolved

for (let i = 0; i < docs.length; ++i)
{
const doclet = docs[i];

if (doclet.kind === 'package' || this._ignoreDoclet(doclet))
{
debug(`Emitter._createTreeNodes(): skipping ${docletDebugInfo(doclet)} (package or ignored)`, doclet);
continue;
}

if (!this._treeNodes[doclet.longname])
{
debug(`Emitter._createTreeNodes(): adding ${docletDebugInfo(doclet)} to this._treeNodes`);
this._treeNodes[doclet.longname] = { doclet, children: [] };
} else {
alxroyer marked this conversation as resolved.
Show resolved Hide resolved
debug(`Emitter._createTreeNodes(): skipping ${docletDebugInfo(doclet)} (default)`, doclet);
}
}
}

private _buildTree(docs: TAnyDoclet[])
{
debug(`Emitter._buildTree()`);

for (let i = 0; i < docs.length; ++i)
{
const doclet = docs[i];
Expand Down Expand Up @@ -158,7 +181,18 @@ export class Emitter
}
}

if (doclet.memberof)
if (isExportDefault(doclet))
{
const parentModule = this._treeNodes[doclet.longname];
if (parentModule) {
debug(`Emitter._buildTree(): adding 'export default' ${docletDebugInfo(doclet)} to module ${docletDebugInfo(parentModule.doclet)}`);
parentModule.children.push({ doclet: doclet, children: [] });
} else {
warn(`Failed to find parent module of 'export default' doclet ${doclet.longname}`, doclet);
continue;
}
}
else if (doclet.memberof)
{
const parent = this._treeNodes[doclet.memberof];

Expand Down Expand Up @@ -211,6 +245,8 @@ export class Emitter

private _parseTree()
{
debug(`Emitter._parseTree()`);

for (let i = 0; i < this._treeRoots.length; ++i)
{
const node = this._parseTreeNode(this._treeRoots[i]);
Expand All @@ -222,6 +258,8 @@ export class Emitter

private _parseTreeNode(node: IDocletTreeNode, parent?: IDocletTreeNode): ts.Node | null
{
debug(`Emitter._parseTreeNode(${docletDebugInfo(node.doclet)}, parent=${parent ? docletDebugInfo(parent.doclet) : parent})`);

const children: ts.Node[] = [];

if (children)
Expand All @@ -242,14 +280,18 @@ export class Emitter

case 'constant':
case 'member':
if (node.doclet.isEnum)
return createEnum(node.doclet);
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);
if (isExportDefault(node.doclet)) {
return createExportDefault(node.doclet);
} else {
if (node.doclet.isEnum)
return createEnum(node.doclet);
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);
}

case 'callback':
case 'function':
Expand Down Expand Up @@ -296,6 +338,7 @@ export class Emitter
if (doclet.kind === 'package'
|| doclet.ignore
|| (!this.options.private && doclet.access === 'private')) {
debug(`Emitter._ignoreDoclet(doclet=${docletDebugInfo(doclet)}) => true (package, ignored or private disabled)`);
return true
}

Expand All @@ -304,21 +347,31 @@ export class Emitter
}

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
{
debug(`Emitter._getInterfaceKey('${longname}')`);

return longname ? longname + '$$interface$helper' : '';
}

private _getNamespaceKey(longname?: string): string
{
debug(`Emitter._getNamespaceKey('${longname}')`);

return longname ? longname + '$$namespace$helper' : '';
}

private _getOrCreateClassNamespace(obj: IDocletTreeNode): IDocletTreeNode
{
debug(`Emitter._getOrCreateClassNamespace(${docletDebugInfo(obj.doclet)})`);

if (obj.doclet.kind === 'namespace')
return obj;

Expand Down
55 changes: 53 additions & 2 deletions src/create_helpers.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import * as fs from 'fs';
import * as path from 'path';
import * as ts from 'typescript';
import { warn } from './logger';
import { warn, debug } from './logger';
import {
createFunctionParams,
createFunctionReturnType,
Expand Down Expand Up @@ -57,7 +59,8 @@ function validateModuleChildren(children?: ts.Node[])
&& !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);
Expand Down Expand Up @@ -355,6 +358,54 @@ export function createNamespaceMember(doclet: IMemberDoclet): ts.VariableStateme
));
}

export function createExportDefault(doclet: IMemberDoclet): ts.ExportAssignment | null
{
if (doclet.meta)
{
let exportDefaultValue = doclet.meta.code.value;
if (! exportDefaultValue)
{
// When the 'export default' 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);
debug(`create_helpers.ts:createExportDefault(): Reading '${sourcePath}'`);
const fd = fs.openSync(sourcePath, "r");
if (fd < 0)
{
warn(`Could not read from '${sourcePath}'`);
return null;
}
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 null;
}
exportDefaultValue = buffer.toString().trim();
debug(`create_helpers.ts:createExportDefault(): exportDefaultValue = '${exportDefaultValue}'`);
if (exportDefaultValue.endsWith(";"))
{
exportDefaultValue = exportDefaultValue.slice(0, -1).trimRight();
}
if (exportDefaultValue.match(/^export +default +/))
{
exportDefaultValue = exportDefaultValue.replace(/^export +default +/, "");
}
}
debug(`create_helpers.ts:createExportDefault(): exportDefaultValue = '${exportDefaultValue}'`);
if (exportDefaultValue)
{
const expression : ts.Expression = ts.createIdentifier(exportDefaultValue);
return handleComment(doclet, ts.createExportDefault(expression));
}
}
warn(`Cannot create an 'export default' instruction. Information missing.`, doclet);
return null;
}

export function createModule(doclet: INamespaceDoclet, nested: boolean, children?: ts.Node[]): ts.ModuleDeclaration
{
validateModuleChildren(children);
Expand Down
29 changes: 29 additions & 0 deletions src/logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,32 @@ export function warn(msg: string, data?: any)
console.warn(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) {
console.log(`${header} ${msg}`);
if (arguments.length > 1)
{
console.log(data);
}
}
}

export function docletDebugInfo(doclet: TAnyDoclet) : string {
if ((doclet.kind !== 'package') && doclet.meta && doclet.meta.range) {
return `{longname='${doclet.longname}', kind='${doclet.kind}, range=[${doclet.meta.range[0]}-${doclet.meta.range[1]}]'}`;
} else {
return `{longname='${doclet.longname}', kind='${doclet.kind}'}`;
}
}
3 changes: 2 additions & 1 deletion src/publish.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 } from './logger';

/**
* @param {TAFFY} data - The TaffyDB containing the data that jsdoc parsed.
Expand All @@ -17,6 +17,7 @@ export function publish(data: TDocletDb, opts: ITemplateConfig)
const docs = data().get();

setVerbose(!!opts.verbose);
setDebug(!!opts.verbose);

// create an emitter to parse the docs
const emitter = new Emitter(opts);
Expand Down
8 changes: 8 additions & 0 deletions src/typings/jsdoc.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ declare interface IDocletTag {
value: string;
}

/**
* @warning All attributes listed below are not necessarily generated by jsdoc ('scope' at least), and should be declared optional.
* To be checked.
*/
alxroyer marked this conversation as resolved.
Show resolved Hide resolved
declare interface IDocletBase {
meta?: IDocletMeta;
name: string;
Expand Down Expand Up @@ -121,6 +125,10 @@ declare interface IFunctionDoclet extends IDocletBase {
virtual?: string[];
}

/**
* @warning All attributes listed below are not necessarily generated by jsdoc ('iEnum' and 'type' at least), and should be declared optional.
* To be checked.
*/
alxroyer marked this conversation as resolved.
Show resolved Hide resolved
declare interface IMemberDoclet extends IDocletBase {
kind: 'member' | 'constant';
readonly: boolean;
Expand Down
4 changes: 4 additions & 0 deletions test/expected/module.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,8 @@ declare module "array-to-object-keys" {
function arrayToObjectKeys(array: string[], valueGenerator?: valueGenerator | any): {
[key: string]: any;
};
/**
* Export arrayToObjectKeys as default.
*/
export default arrayToObjectKeys;
}
34 changes: 34 additions & 0 deletions test/expected/module2.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/** @module array-to-object-keys
*/
declare module "array-to-object-keys" {
/**
* @typedef valueGenerator
* @type {function}
* @param {string} value The original array entry
* @param {number} index The index of the array entry (starts at 0)
* @returns {*}
*/
type valueGenerator = (value: string, index: number) => any;
/**
* Converts an array to an object with static keys and customizable values
* @example
* arrayToObjectKeys(["a", "b"])
* // {a: null, b: null}
* @example
* arrayToObjectKeys(["a", "b"], "value")
* // {a: "value", b: "value"}
* @example
* arrayToObjectKeys(["a", "b"], (key, index) => `value for ${key} #${index + 1}`)
* // {a: "value for a #1", b: "value for b #2"}
* @param {string[]} array Keys for the generated object
* @param {valueGenerator|*} [valueGenerator=null] Optional function that sets the object values based on key and index
* @returns {Object<string, *>} A generated object based on the array input
*/
function arrayToObjectKeys(array: string[], valueGenerator?: valueGenerator | any): {
[key: string]: any;
};
/**
* Export arrayToObjectKeys as default.
*/
export default arrayToObjectKeys;
}
3 changes: 3 additions & 0 deletions test/fixtures/module.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,7 @@
const arrayToObjectKeys = (array, valueGenerator = null) => {
}

/**
* Export arrayToObjectKeys as default.
*/
export default arrayToObjectKeys
Loading