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

Allow just generation of declarations, also allowJs #15911

Closed
wants to merge 13 commits into from
Closed

Allow just generation of declarations, also allowJs #15911

wants to merge 13 commits into from

Conversation

nojvek
Copy link
Contributor

@nojvek nojvek commented May 17, 2017

Scenario 1: Auto-Generate typings for a pure js project to be bundled with npm module
Scenario 2: Just emit typings for a project that is a hybrid of ts and js

Currently typings for js projects are manually hand maintained. A ton of typings are out of date with the actual code. Typings are kind of documentation and if they are separate from code, they will eventually get out of sync.

with --checkJs, JS lib authors get extra benefit of validating code with jsdoc typings. Being able to auto generate just .d.ts from their existing code would make life easier. This can be done as part of the build step.

Basically if declarations:true and noEmit:true, declarations should still be generated. If allowJs:true, then declarations should also be generated for the js files.

Proposal:

{
    compilerOptions: {
        "outFile": "lib/main.d.ts",
        "declarations": true,
        "allowJs": true,
        "checkJs": true, // optional
        "noEmit": true,
    },
    include: [
        "src/**.js"
    ]
}

This would just generate a single .d.ts file from the project that you can add as part of package.json. No hand maintenance.

{
    "name": "awesome",
    "author": "Awesome Industries",
    "version": "1.0.0",
    "main": "./lib/main.js",
    "types": "./lib/main.d.ts"
}
  • There is an associated issue that is labelled
  • Code is up-to-date with the master branch
  • You've signed the CLA
  • There are new or updated unit tests validating the change
  • You've successfully run gulp runtests locally (Need to fix some tests - WIP)

Fixes #7546 #1866

@@ -904,7 +904,13 @@ namespace ts {
let declarationDiagnostics: Diagnostic[] = [];

if (options.noEmit) {
return { diagnostics: declarationDiagnostics, sourceMaps: undefined, emittedFiles: undefined, emitSkipped: true };
// Allow only emitting of declarations
if (options.declaration) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, this is interesting. The user wrote noEmit, but you're still emitting .d.ts files. Is there a better mechanism to surface this behavior? @mhegazy @bowdenk7

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it makes sense for declaration:true to override noEmit. Since it's something you have to do explicitly. I.e just having noEmit will emit nothing. If you really really want just declarations which is a valid use case then declarations:true and noEmit:true should give you the result.

Copy link
Member

@DanielRosenwasser DanielRosenwasser May 19, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But are there cases where you want to ensure that declaration emit can succeed? For example, just something where you want to ensure that you won't get an error because of non-exported declarations?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I understand what you mean? Care to give an example?

The main usecase for only declarations is you have a pure js node project and you don't want to maintain hand typed .d.ts files. You don't care about emitting since your code is already in js.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@DanielRosenwasser I don't think this overrides --noEmitOnError, just --noEmit. I do think we might want to have a separate compiler option for this however, as always emitting declarations could have undesired consequences.

@mhegazy
Copy link
Contributor

mhegazy commented May 23, 2017

That was the easy part :) the interesting part is what do we do with JS-specific constructors.. e.g.

var x = require("..");   // should emit `import ... = require("..")`

function f() {
}
f.prototype.method = function() {}   // should probably emit a class?

etc..

moreover, today in the declaration emitter we do not walk expressions, but a lot of these JS constructs are expressions (assignments) and not declarations.. so how is that handled..

@nojvek
Copy link
Contributor Author

nojvek commented May 24, 2017

@mhegazy I expect typescript to be esnext -> es old transpiler and typechecker. I would expect it to the same thing as if you renamed the .js file into .ts file and try to run declarations:true.

I think it's fair to say that it shouldn't magically understand prototype calls into classes or require calls into imports. If would expect allowJs + checkJs to treat js files with equal checks and integrity as a ts file but with types in jsdoc comments.

@mhegazy
Copy link
Contributor

mhegazy commented May 25, 2017

@mhegazy I expect typescript to be esnext -> es old transpiler and typechecker. I would expect it to the same thing as if you renamed the .js file into .ts file and try to run declarations:true.

I think it's fair to say that it shouldn't magically understand prototype calls into classes or require calls into imports. If would expect allowJs + checkJs to treat js files with equal checks and integrity as a ts file but with types in jsdoc comments.

TS already supports these patterns.. see https://github.com/Microsoft/TypeScript/wiki/Type-Checking-JavaScript-Files..
I am assuming the feature here is that, now that the compiler figured out my types, write it to a .d.ts file.

@nojvek
Copy link
Contributor Author

nojvek commented May 25, 2017

@mhegazy are you also okay with declarations:true and noEmit:true being the flags to only emit .d.ts declarations?

@mhegazy
Copy link
Contributor

mhegazy commented May 25, 2017

@mhegazy are you also okay with declarations:true and noEmit:true being the flags to only emit .d.ts declarations?

This is the easy part as i said. the hard part is emitting the declaration file. we should figure out that first.

@nojvek
Copy link
Contributor Author

nojvek commented May 25, 2017

I am assuming the feature here is that, now that the compiler figured out my types, write it to a .d.ts file.

This would mean the declaration emitter is smart about expressions right.

e.g.
var x = import('foo') => import * as x from 'foo'

Can this come in as a second PR? Do you consider not making those transformations a breaking change?

@mhegazy
Copy link
Contributor

mhegazy commented May 25, 2017

This would mean the declaration emitter is smart about expressions right.
e.g.
var x = import('foo') => import * as x from 'foo'

yes, and more importantlly:

/**
 * @typedef {Object} SpecialType - creates a new type named 'SpecialType'
 * @property {string} prop1 - a string property of SpecialType
 * @property {number} prop2 - a number property of SpecialType
 */
/** @type {SpecialType} */
var specialTypeObject;

would look like:

type SpecialType: {prop1: string, prop2: number };
declare var specialTypeObject: SpecialType;

@mhegazy
Copy link
Contributor

mhegazy commented May 25, 2017

Can this come in as a second PR? Do you consider not making those transformations a breaking change?

it is not being a breaking change or not. i am just questing the value of writing a .d.ts file that is different from what the compiler understands. to me, a .d.ts should fill in the place of a .js/.ts. so it should have the information you need to build against the file it describes.

@nojvek
Copy link
Contributor Author

nojvek commented May 25, 2017

oh yes, I totally agree with that.

so here's the declaration transforms that I see you mention.

Class

function X()
X.prototype.foo = function(a, b){}

=>

declare class X {
  foo(a, b);
}

Interface

/**
 * @typedef {Object} SpecialType - creates a new type named 'SpecialType'
 * @property {string} prop1 - a string property of SpecialType
 * @property {number} prop2 - a number property of SpecialType
 */
/** @type {SpecialType} */
var specialTypeObject;

=>

type SpecialType: {
  prop1: string, 
  prop2: number 
};
declare var specialTypeObject: SpecialType;

Any others?

I may need someone to help me out, seems pretty involved 😮

@mhegazy
Copy link
Contributor

mhegazy commented May 25, 2017

The complete list of things we do there can be captured by searching the code base for isInJavaScriptFile. this conditions all the special behaviors related to a .js file.

We have a write-up for this in https://github.com/Microsoft/TypeScript/wiki/Type-Checking-JavaScript-Files. For JSDoc supported patterns, you want to look at https://github.com/Microsoft/TypeScript/wiki/JSDoc-support-in-JavaScript for more details.

I do not think you need to think about the whole set now. what is important is thinking of how do we add support for such things.
For JSDoc types, i would expect these to be serialize using functions like emitResolver.writeTypeOfDeclaration. thought that will not write a type alias declaration for @typedef tag, but it will serialize it as an anonymous type, and i would say this is a good start.

For the function -> class transformation, i would assume we have special code in writeFunctionDeclaration that notices that this is a JS file, with some function in it, and then use the symbol to write the class instead of the node.

@@ -1888,7 +1888,7 @@ namespace ts {
/* @internal */
export function writeDeclarationFile(declarationFilePath: string, sourceFileOrBundle: SourceFile | Bundle, host: EmitHost, resolver: EmitResolver, emitterDiagnostics: DiagnosticCollection, emitOnlyDtsFiles: boolean) {
const emitDeclarationResult = emitDeclarations(host, resolver, emitterDiagnostics, declarationFilePath, sourceFileOrBundle, emitOnlyDtsFiles);
const emitSkipped = emitDeclarationResult.reportedDeclarationError || host.isEmitBlocked(declarationFilePath) || host.getCompilerOptions().noEmit;
const emitSkipped = emitDeclarationResult.reportedDeclarationError || host.isEmitBlocked(declarationFilePath);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mhegazy Do we want to emit declaration files for --declaration --noEmit for TypeScript (i.e. without --allowJs)? This would be a breaking change and could have undesired consequences. Do we need to have a new compiler option for this behavior?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mhegazy Do we want to emit declaration files for --declaration --noEmit for TypeScript (i.e. without --allowJs)? This would be a breaking change and could have undesired consequences. Do we need to have a new compiler option for this behavior?

I agree, we should not do that. either we emit declarations for Js files with --declaration --allowjs, and users have to pass a --outDir tmp, then ignore it (this is still potentially a breaking change since we did not emit these files in the past so current --allowjs users would find this surprising), or we add a new flag --emitDeclarationsOnly.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bikeshedding here: perhaps --noEmitJs as a looser --noEmit that still emits declarations but not js output?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that sounds better than my proposal.

@nojvek
Copy link
Contributor Author

nojvek commented May 30, 2017 via email

@nojvek
Copy link
Contributor Author

nojvek commented May 30, 2017 via email

@nojvek
Copy link
Contributor Author

nojvek commented May 30, 2017 via email

@asvetliakov
Copy link

What's status of this? Since it's now possible to use only babel for TS compilation, this PR will be very helpful.

@mhegazy
Copy link
Contributor

mhegazy commented Nov 6, 2017

Closing for now since it has been stale for over 6 months now. As noted earlier in #15911 (comment) the challenge here is figuring out how to emit declarations for JS declarations that come from expressions.

@mhegazy mhegazy closed this Nov 6, 2017
@nojvek
Copy link
Contributor Author

nojvek commented Nov 7, 2017 via email

@nojvek nojvek mentioned this pull request Jan 29, 2018
9 tasks
@microsoft microsoft locked and limited conversation to collaborators Jun 14, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Allow --declaration with --allowJs
7 participants