tsd-jsdoc icon indicating copy to clipboard operation
tsd-jsdoc copied to clipboard

Output files are not recognized without exports

Open Jaid opened this issue 5 years ago • 8 comments

Sorry if I'm understanding something wrong here. I'm still learning TypeScript (or rather exporting TypeScript definitions from a JavaScript project).

The generated d.ts files don't have any export statements. Your readme states that the JSDoc @exports annotation is not needed as everything is exported, but I'm not sure how the generated definitions are supposed to work without export statements.

VSCode

VSCode uses the DefinitelyTyped repository in order to automatically fetch TypeScript definitions for auto completion and documentation tooltips.

image

Every single of them has export statements and they don't seem to work without them.

fast-deep-equal

fast-deep-equal is distributed with its own definition file and VSCode recognizes that one and displays basic typing info.

image

resource-loader

Your module resource-loader also gets distributed with its own definition file, but that one doesn't have export statement as it comes from tsd-jsdoc.

Trying with module.default.

image

Trying with new module.default().

image

Trying with new module.Loader().

image

Question

Do you think tsd-jsdoc could need the option to create a TypeScript definition file that contains export statements? If not, I guess I will have to write a post processor that takes tsd-jsdoc-generated files as input and outputs TypeScript definitions with export statements.

Jaid avatar Feb 07 '19 06:02 Jaid

This lib doesn't create a module by default, it creates ambient declarations. export is only valid within declare module "name" {} blocks.

Currently it is pretty difficult to annotate your jsdoc with the right things to generate the module declaration to work where it can be used with es6 imports like you are doing. But it can be done, and it is up to the jsdoc author to do it.

I'm currently trying to figure out what the best strategy is for letting users declare their jsdoc ambient like is normal, but then pass options to this lib to generate a module definition for them.

englercj avatar Feb 07 '19 17:02 englercj

Also, @exports doesn't map to es6 exports at all. It maps to changes in the naming jsdoc uses for commonjs style exports. JSDoc es6 support is fairly poor in terms of expressing semantics.

englercj avatar Feb 07 '19 17:02 englercj

Thanks for your good explanation!

jsdoc does not look like being in a development state that leads to major updates in the near future. (A recent issue comment from the author looks good though)

Workaround

My current solution is a hacky regex-powered function. Seems to work well, but doesn't feel good of course.

const moduleBlockRegex = /(declare module [^\n]+)(.+?)(\n})/gs
const moduleFieldRegex = /^([\t ]*)(type|function|interface|var|const|class) +(\w+)/gm

const transformField = (type, name) => {
  if (name === "default") {
    return `export default ${type}`
  } else {
    return `export ${type} ${name}`
  }
}

export default text => text.replace(moduleBlockRegex, (blockMatch, blockPrefix, blockContent, blockSuffix) => {
  const newContent = blockContent.replace(moduleFieldRegex, (fieldMatch, fieldPrefix, fieldType, fieldName) => fieldPrefix + transformField(fieldType, fieldName))
  return blockPrefix + newContent + blockSuffix
})

I fed it with this:

/** @module array-to-object-keys
 */
declare module "array-to-object-keys" {
    /**
     * @typedef valueGenerator
     * @type {function}
     * @param {string} value Original array entry
     * @param {number} index Index of the array entry (starts at 0)
     * @returns {*} Anything that will be the object entry value
     */
    type valueGenerator = (value: string, index: number) => any;
    /**
     * Converts an array to an object with static keys and customizable values
     * @example
     * import arrayToObjectKeys from "array-to-object-keys"
     * arrayToObjectKeys(["a", "b"])
     * // {a: null, b: null}
     * @example
     * import arrayToObjectKeys from "array-to-object-keys"
     * arrayToObjectKeys(["a", "b"], "value")
     * // {a: "value", b: "value"}
     * @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 default(array: string[], valueGenerator?: valueGenerator | any): {
        [key: string]: any;
    };
}
declare module module2 {
    /**
     * Converts an array to an object with static keys and customizable values
     */
    function default(array: string[], valueGenerator?: valueGenerator | any): {
        [key: string]: any;
    };
}

And it returned that:

@@ -8,7 +8,7 @@
      * @param {number} index Index of the array entry (starts at 0)
      * @returns {*} Anything that will be the object entry value
      */
-    type valueGenerator = (value: string, index: number) => any;
+    export type valueGenerator = (value: string, index: number) => any;
     /**
      * Converts an array to an object with static keys and customizable values
      * @example
@@ -23,7 +23,7 @@
      * @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 default(array: string[], valueGenerator?: valueGenerator | any): {
+    export default function(array: string[], valueGenerator?: valueGenerator | any): {
         [key: string]: any;
     };
 }
@@ -31,7 +31,7 @@
     /**
      * Converts an array to an object with static keys and customizable values
      */
-    function default(array: string[], valueGenerator?: valueGenerator | any): {
+    export default function(array: string[], valueGenerator?: valueGenerator | any): {
         [key: string]: any;
     };
 }

It blindly throws in some export statements, independent of what is actually exported in the code. I'm temporarily fine with that.

Solution

Could a custom JSDoc tag be a real solution? Something like @tsExport? Or wouldn't that be possible without some changes to JSDoc in the first place?

Also, are you personally fine with adding support for ES6 modules in your project? My personal intended use case with tsd-jsdoc is its integration into my build process, to add TypeScript definitions to my npm-published modules for other people's static analysis features in their IDEs, like auto completion and type warnings. If you're happy with tsd-jsdoc and would prefer not to include such major features, I'd be fine with that as I don't want to force my personal goals on your project.

Jaid avatar Feb 09 '19 07:02 Jaid

Could a custom JSDoc tag be a real solution? Something like @tsExport?

I'm currently leaning towards marking anything within a "declare module" as export, and letting you use @ignore, etc to skip things.

Also, are you personally fine with adding support for ES6 modules in your project?

Depends on what you mean, I definitely want to support ES6 but there is only so much that can be done on this side of jsdoc. I'm open to changes to work around jsdoc limitations though.

englercj avatar Feb 09 '19 20:02 englercj

@englercj

Currently it is pretty difficult to annotate your jsdoc with the right things to generate the module declaration to work where it can be used with es6 imports like you are doing. But it can be done, and it is up to the jsdoc author to do it.

Do you have any example of this?

oBusk avatar Feb 26 '19 09:02 oBusk

@oBusk Modules for jsdoc are files (as in es2015 modules, or commonjs modules). Modules in Typescript are more like a combination of both es2015 modules and npm packages. JSDoc doesn't have anything to describe that except for @memberof module:MyModule which tells me it is in a module (or package) but not that it should be exported or not.

I might be able to piece together enough information to do this if I examine the names it generates (which might contain exports.). What doesn't have any method of being expressed is "wrap all this up in a declare module "mypackage" because this is all in an npm package.

The only real thing you can do at the moment is have every single one of your classes use @memberof module:MyModule in which case we will put all the things in the module declaration. However, like mentioned in this ticket won't be marked as export in some cases. Jsdoc just doesn't have good support for it, and I'm also probably not doing a good job of trying to guess. Maybe someone else has a good idea and can put in a PR.

englercj avatar Feb 27 '19 02:02 englercj

Thanks for this library! Helps a ton with my project

I ran into this same issue. I'm doing something similar to @Jaid and changing the final output to export the correct declarations.

  • script https://github.com/DavidWells/analytics/blob/master/packages/analytics-core/package.json#L31
  • and adding exports at bottom of .d.ts file https://github.com/DavidWells/analytics/blob/master/packages/analytics-core/scripts/types.js#L13

What if things marked @public in the JSdoc were automatically set to export instead of declare?

DavidWells avatar Sep 23 '19 01:09 DavidWells

@englercj,

When using the TypeScript compiler to generate definitions, the following:

// ... classes etc.

module.exports = XlsxPopulate;

Results in:

export = XlsxPopulate;

// ... declare classes etc.

Is it not possible for this library to do the same?

(I'm unable to use the TS compiler because of the issues mentioned here.)

Edit: I guess that might require a separate output file to be generated for each input file?

glen-84 avatar May 29 '20 14:05 glen-84