Skip to content

Always export typedefs #23723

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

Merged
merged 7 commits into from
Apr 30, 2018
Merged

Always export typedefs #23723

merged 7 commits into from
Apr 30, 2018

Conversation

sandersn
Copy link
Member

This actually just required deleting a check in declareModuleMembers and checking for external AND commonjs modules in a couple of places.

However, while experimenting with this feature, I discovered that even previously-exported typedefs would only be exported if they came after a commonjs export node. So I added a commonjs check to the pass in the parser. It will not catch nested module.exports, but it will catch top-level assignments.

The new test tests both changes.

Fixes #23692

This actually just required deleting a check in declareModuleMembers
and checking for external AND commonjs modules in a couple of places.

However, while experimenting with this feature, I discovered that even
previously-exported typedefs would only be exported if they came after a
commonjs export node. So I added a commonjs check to the pass in the
parser. It will not catch nested module.exports, but it will catch
top-level assignments.

The new test tests both changes.
@sandersn sandersn requested review from weswigham and mhegazy April 26, 2018 21:04
}

function isCommonJsModuleIndicator(statement: Statement) {
if (isExpressionStatement(statement) && isBinaryExpression(statement.expression)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

this is concerning.. first it is work that were not doing before, and second, it is potentially incomplete. in the binder we bind all require calls, not just the top-level ones, and not just binary expressions. so to do this right we need to walk the whole file until we find an indicator.. but we are doing this in the binder already..

is there a different way we can collect these typedefs then bind their export symbols at the end?

Copy link
Member Author

Choose a reason for hiding this comment

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

That sounds like a good approach -- less lossy although less obvious. I'll see if I can get something working.

@sandersn
Copy link
Member Author

OK, typedefs are now bound at the end of source file binding, and I got rid of the check for commonjs constructions in the parser.

Copy link
Member

@weswigham weswigham left a comment

Choose a reason for hiding this comment

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

Do we have any tests of typedefs merging with the various export assignments when those assignments also have value-space members if the same name? Or errors when they conflict, eg,

/** @typedef {{a: string}} Foo */

exports.Foo = class Foo {}

@sandersn
Copy link
Member Author

@weswigham here are some more tests:

/** @typedef {number} Foo */
class Foo {} // error
/** @typedef {number} Foo */
exports.Foo = class { } // error (and with module.exports too)
/** @typedef {number} Foo */
module.exports = { Foo: class { } } // error

All of the above should pass when Foo is 2 instead of class { }.

@sandersn
Copy link
Member Author

sandersn commented Apr 27, 2018

@weswigham I got something that works ... kind of ... take a look and see. Let me know if you can think of anything better.

I still need to add baselines and update existing ones. I'm not sure I like this solution, but I wanted to share it so others could look at it.

@weswigham
Copy link
Member

@sandersn here's a few more that I think may(?) throw a wrench into your current solution

/** @typedef {number} Foo */
const Ns = {};
Ns.Foo = class {}
module.exports = Ns;

also

/** @typedef {number} Foo */
class Bar {}
module.exports = { Foo: Bar };

😄

@sandersn
Copy link
Member Author

I bet if I special cased object literal initializers in bindModuleExportAssignment so that all the properties in the object literal get added to the exports, that would do the trick, and prevent conflicts that I don’t think we catch today, like duplication between module.exports.p = 2 and module.exports = { p: 2 }

@weswigham
Copy link
Member

weswigham commented Apr 27, 2018

like duplication between module.exports.p = 2 and module.exports = { p: 2 }

That's less duplication and more reassignment (I think) - kinda like

export let f = 2;
f = 3;

in TS.

@sandersn
Copy link
Member Author

Yep, after thinking about it I realized that it would break the special-case type unioning code in the checker, too, which we want to keep.

@@ -2304,7 +2324,10 @@ namespace ts {
return s;
});
if (symbol) {
declareSymbol(symbol.exports, symbol, lhs, SymbolFlags.Property | SymbolFlags.ExportValue, SymbolFlags.None);
const flags = isClassExpression(node.right) ?
Copy link
Contributor

Choose a reason for hiding this comment

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

not sure i understand why this is related ot the original change?

Copy link
Member Author

Choose a reason for hiding this comment

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

Now that typedefs are exported, there's a possibility that their names collide, such that there is a difference between

let x: import("./a.js").Foo
// and
import a = require('./a.js')
var foo = new Foo()

In other words, the type reference Foo might refer to the typedef, whereas the value referenced in new Foo might refer to the class. There should be an error to prevent this.

Copy link
Contributor

Choose a reason for hiding this comment

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

i see. thanks.

@sandersn sandersn merged commit 7cda045 into master Apr 30, 2018
@sandersn sandersn deleted the js/always-export-typedefs branch April 30, 2018 21:55
@microsoft microsoft locked and limited conversation to collaborators Jul 31, 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.

3 participants