microsoft / TypeScript

TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

Home Page:https://www.typescriptlang.org

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Allow `--declaration` with `--allowJs`

blakeembrey opened this issue · comments

From #7535, it would be useful for JavaScript package authors to have the ability to generate declarations from their project. This would help with getting started on a type definition (it would be populated with known interfaces/functions/types that are exported) and would also allow the creation of continuous integration scripts that check the JavaScript types with the hand-coded definition for inconsistencies.

PRs are welcomed. the fix should be removing the error, making sure the call is wired correctly, adding tests and making sure the declaration emitter handles the JS types correctly,

anyone currently working on this? If not i may take a stab at it.

anyone currently working on this? If not i may take a stab at it.

got for it.

This would be definitely helpful 👍

👍

Take a look at dts-gen and let us know what you think. At the moment it doesn't do a good job of detecting changes between generations. But let us know if this helps!

Thanks @bowdenk7!

Would a simple approach like #7535 (comment) (internally) be enough to check if the definition has changed? It would be great to be able to run dts-gen in CI and error when there has been un-documented API changes to prompt an update to the .d.ts.

Another use-case for this is that I have some plain JS tests that I use as both a form of documentation and as a way of validating that my code is callable from native ES5 in a way that is reasonable. All of my distributed source code is sourced from TypeScript and I would like to generate definition files for NPM publishing. However, because I have that one JS test sitting in my source tree I need allowJs=true so the test will correctly land in the output directory (not committed to source control) and be run along with all of my other tests. This, of course, means I can't have TSC output definition files for the rest of my project.

@bengreenier Wondering if you have made much progress on this? Let me know if there is anyway I can help speed things up. Would love to see this out in the wild.

Based on the discussion in #15747, we would like to take this change.

@nojvek i've also made no more progress 😁 - got pulled into a different project and had to re-prioritize.

I'm on it. I pulled in your changes to a private branch and working on emitDts only scenario for JS files.

@mhegazy @bengreenier The PR #15911 is still WIP, I am working on tests. wdyt?

@mhegazy I do need your help with some tests though.

It seems the mismatched types here should give an error but they seem not to. This makes a duplicate definition in .d.ts

       | //// [tests/cases/compiler/jsFileCompilationDuplicateVariableErrorReported.ts] ////
       | 
       | //// [b.js]
       | var x = "hello";
       | 
       | //// [a.ts]
       | var x = 10; // Error reported so no declaration file generated?
       | 
       | //// [out.js]
       | var x = "hello";
       | var x = 10; // Error reported so no declaration file generated?
       | 
       | 
       | //// [out.d.ts]
       | declare var x: string;
     ->| declare var x: string;
       | 

It seems the mismatched types here should give an error but they seem not to. This makes a duplicate definition in .d.ts

the input has an error, no guarantees about the output.

  1. But shouldn't typescript complain about this?
  2. Are you okay if I modify the baseline to have double declarations then?

But shouldn't typescript complain about this?

there should be an error in the .ts file, and another one in hte .js file with --checkJs.

Taking a second stab at this: #21455 but still plenty of work to do

following

One can also subscribe by clicking the subscribe button in the right hand column, this will subscribe without alerting anyone. Comments alert everyone.

screen shot 2018-03-05 at 9 41 05 am

any updates on that? Current behavior is quite weird : I am using it with webpack 4.20, and webpack config has 2 configurations - development and production. So It actually DOES generate declarations bundle.d.ts and bundle.js itself but only for first configuration, then it throws error "TS5053" and does not even start second (production) configuration. So to point the elephant in the room - can it be fixed simply by changing TS5053 to a warning from error?

This makes it really problematic to reference projects with a tsconfig.json which had --allowJs and now must use --declaration for it to work.

We also just ran into this issue. Any status on how it's progressing? :-)

I’ll take another stab at the PR to deliver a MVP .js -> .d.ts types. We’ve converted a ton of things to .js with jsdoc types and this would be quite useful to the rest of the community.

I think you can already do that with dts-gen, but it would be great to have the functionality in TypeScript itself.

@nickmccurdy It looks like dts-gen hasn't been updated in over a year and still uses TS 2.x

dts-gen is preferable for a certain set of people (and its functionality is now a quickfix in the compiler, which is why the standalone project isn't maintained so much) - it inspects the types it can find at runtime, and so can be pretty accurate with no annotations present. What this feature is for, I imagine, is people using mostly jsdoc to strongly type their JS, who want their documentation and types preserved and transformed into a public API.

While generating types from javascript would be useful, is there a way to simply skip the javascript files when emitting types? Surely emitting some types is better than nothing at all.

As a workaround, I used tsc to generate dist folder with allowJs: true in tsconfig.json and then used tsc -d --emitDeclarationOnly --allowJs false to generate typings in dist folder.

This would be very very nice to have as now that we have completely typed out our webpack configuration and types for our public api, exposing this on a single d.ts would mark a lot of progress for thrid party integrations and plugin authors. :-) <3 @DanielRosenwasser @weswigham :-)

@talhajunaidd Thanks, It's work for me. Also I wrote a blog for those who want help:https://shuoit.net/tech-notes/Allow--declaration-with--allowJs-1546511333.html

For what it is worth, I recently discovered over at
#29056 (comment)

That if a consumer sets maxNodeModuleJsDepth to say 5 in their tsconfig, then typescript is likely able to pickup the jsdoc types of the packages they consume automatically, without the need for any d.ts file. Which is likely a suitable workaround for many here.

I've updated https://github.com/bevry/projectz to automatically generate an installation guidance in readmes of maxNodeModuleJsDepth to 5 if the project it is running against has jsdoc in its dependencies.

The result of this generation can be seem on the caterpillar package for example:
https://github.com/bevry/caterpillar

@DanielRosenwasser Was help wanted removed because this is now being considered as a feature worked on by the TypeScript team?

It would be a great step forward if we could export the jsdoc typings from webpack into a distributable definition file and I guess the same is true for many other open source projects.

Am I understanding it correct that you simply cannot use declaration, and hence cannot package reusable libraries if you use allowJs to consume JsDoc-based .js files?
My use case is that I want to create an Angular component library which uses OpenLayers, which recently moved to a TypeScript-compatible JsDoc in 6.0.0-beta.1.
But as I read this issue, this is simply not possible, as allowJs precludes the use of the needed declaration for a distributable Angular library?

Just a kind ping. Is this something that is tracked in the Roadmap or still awaiting someone from the community to make a pass at this?

commented

@balupton

you said

That if a consumer sets maxNodeModuleJsDepth to say 5 in their tsconfig, then typescript is likely able to pickup the jsdoc types of the packages they consume automatically, without the need for any d.ts file.

No need to use maxNodeModuleJsDepth. For mongoose project, simply use include and paths options like described at https://github.com/openlayers/openlayers#intellisense-support-and-type-checking-for-vs-code or openlayers/openlayers#9178 (comment) (for .tsconfig). Tested and work fine ! As it, tsc will no parse no useful modules, because with maxNodeModuleJsDepth you parse all the node_modules, could become very huge and not very good ! See #17183

So are we just dead in the water on this? Three years? I'm trying to add a library to a existing Angular 7 project with two apps and the newly generated library with no modification throws error TS5053: Option 'allowJs' cannot be specified with option 'declaration'.

I am also facing this issue when trying to transpile .ts files inside of node_modules while also generating an index.d.ts for a webpack bundle.

My use case is that I want to publish a node lib which is vanilla JS with JSDoc. I'm using typescript for typechecking and I was hoping that it could generate the .d.ts typings as well but nope.

Agree w/ @phaux . Our company has over a dozen internal JS packages that have excellent JSDoc coverage. If we could generate typings for those, it would make it much easier to get executive buy-in to make the leap to TS.

Actually it would be nice if typescript was smart enough to look into current project jsdoc'd dependencies and just use these. Currently it screams something about missing definitions for dependency even if it has jsdocs. It could look at source/module/main package.json field and just look at it as a regular file and get typings from there.

It's 30th of August and still no fix? What a shame.

For what it's worth, in most cases I think it's possible to work around this, and at least in the cases I've come across, it's probably preferable to work around it. Not so much as a workaround as just doing things in a clean way.

  • If you're using typescript to provide typechecking for your code, and you want to emit a .d.ts - change the file extensions from 'js' to 'ts'. Turn off allowJs.
  • If you're generating code that's valid JS but invalid TS (this was my case), you might be able to just get away with using a require instead of importing. Turn off allowJs. (Even better would be to generate valid TS, but this was a bit tricky in my case, so went the hacky route instead - require worked just fine, and by wrapping in a TS file which applied some types, I was able to quarantine the hackiness to a single file.)
  • If you have JS files that are only used for certain build / validation processes, use a separate tsconfig.allowJs.json that extends your tsconfig.json with allowJs=true, and have allowJs=false in your main tsconfig.json.
  • If you've got a shared library you want to use, publish it as an npm module with types (or publish the types separately). You don't need allowJs=true to import an npm module.

In many of these cases it'd be great if it was possible to just use the JS directly, of course, but if you're feeling blocked by this, rest assured there are ways forward :-) If you've got a case that's not covered by this @mention me and I'll do my best to help.

The primary use case I had in mind is when you are using typescript only for typing through vanilla javascript and JSDoc. Typescript turns into a great typing annotation engine, and it knows all the types .. and instead of building a declaration file yourself, it should be able to to generate one.

Hmm yes, had a play with that just now. It's really a "so close and yet so far" situation isn't it?

An answer still might be to bite the bullet and shift to .ts. You might consider using something like ts-morph to make this less painful. A bit of a micky-mouse example:

// test.js
export class MyClass {}

/**
 *
 * @param {string} a
 * @param {MyClass} b
 * @returns {number}
 */
export function blah(a, b) {
  return 1;
}

And then you can make the type annotations explicit and change the extension to ts using ts-morph

// transform.ts

import { Project } from "ts-morph";

const project = new Project({
  tsConfigFilePath: "./tsconfig.json",
  addFilesFromTsConfig: false
});

// will want to have a better way to add all the files you want to transform...
const file = project.addExistingSourceFile("./test.js");

// I guess you'd need to change more than just functions, but for a proof of concept...
const functions = file.getFunctions();

functions.forEach(fn => {
  const parameters = fn.getParameters();
  parameters.forEach(param => {
    // get typescripts best guess of what type the param is
    const type = param.getType();
    // and make it explicit
    param.setType(type.getText());
  });
  // likewise for return type
  const returnType = fn.getReturnType();
  fn.setReturnType(returnType.getText());
});

// move the file
file.move("./test.ts");

// save the project - will write out all the files that have changed.
project.save();

This issue is important because nobody should have to bite a bullet to get value out of tsc without making any substantive changes.

iow, the point is so you don’t have to convince people to author anything in typescript before they can try out the value of a type system.

There's also technical value in fixing this, namely enabling using --incremental (and --composite) for projects with --allowJs, imo.

@weswigham @ljharb - not trying to argue this isn't valuable, I'd have loved it when I needed it. Just trying to identify some other approaches.

@industrialCoder would it be possible to change the rest of the files across to .ts and apply any yourself?

@studds when I say a large legacy library, I mean a very large legacy library... The time it would take to convert all of the .js files over to .ts and add the syntax required to make them valid TS (even typing everything as any) would be obscene to say the least. Is saying type = importSourceType == js ? any : determineTSType(source) really that huge of a lift? Not familiar with the TS compiler code, but seems like this would be good bandaid solution that would help a lot of people out and should be much easier to implement than the solutions a lot of other people here are purposing....

@amitbeck I solved it for now by generating types separately and using --allowJs=false on the type generation step.

I have an issue opened on TypeScript problems with understanding imported JSDoc types from imported node modules. It's currently flagged as backlog. Feel free to comment there to push this along: #33136. Getting this fixed would allow JavaScript authors to provide JSDoc types for their code that end users could take advantage of for type linting and Intellisense in VSCode with allowjs set to true.

At Unsplash, this is blocking us from using project references aka composite (because composite requires declaration to be turned on).

Glorious day! Thanks @weswigham! Looks like this is slated for 3.7? Man that is going to be a great release.

Does the conversion mechanism recognize node packaging? It seems it generates one module per file, although Node.js exports one module per dependency.

A module is a file is a module; an npm package has 0, 1, or N files/modules.

You are right. Still, it does not help with producing declarations compatible with the node module lookup mechanism. You may want to refer to this question on StackOverflow for an example of what is precisely happening: https://stackoverflow.com/questions/59564524/generating-typescript-declarations-for-re-exported-js-functions-in-a-node-js-mod

commented

is this resolved?