Skip to content

Commit 61cb06c

Browse files
authored
Allow allowJs and declaration to be used together (#32372)
* Allow allowJs and declaration to be used together This intorduces a new symbol-based declaration emitter - currently this is only used for JSON and JavaScript, as the output is likely worse than what the other declaration emitter is capable of. In addition, it is still incomplete - it does not yet support serializaing namespaces. * Add tests for various import/export forms, add notes on export as namespace and fix export * from * Tests & fixes for computed names * Add test with current @enum tag behavior * fix declaration emit for jsdoc @enum tags * Small adjustments to base class serialization to fix bugs in it * Guard against type/type parameter confusion when using typeParameterToName a bit * Integrate feedback from PR * Fix issue with export= declarations visibility calculation and type declaration emit that impacted all forms of declaration emit * Only make one merged getCommonJsExportEquals symbol for a symbol * Support preserving type reference directives in js declarations * Skip declare mdoifiers for namespace members in ambient contexts * FAKE ALIASES AND NAMESPACES EVERYWHERE * Dont do namespace sugar when type members contain keyword names * Fix json source file export modifier under new output * Such clean nested aliasing, very wow * Fix lint * Add visibility errors, reuse type nodes where possible * Suppoer having correctly named import types in bundled js declaration emit & adjust binding to allow namespaces with aliases to merge when the aliases look to be type-only * Better support for module.exports = class expression * Fix discovered crash bug * Allow export assigned class expressions to be reachable symbols from external declarations * Add missing semicolon * Support @enum tag post-merge * preserve comments on signatures and declarations where possible * Basic support for js classy functions * Add example we should do better with * Prototype assignments make things a bit wonky, but the example from the PR seems OK * Make a ton of changes to support the new way js classes are bound * Remove some old comments, fix import and export default names * Fix bug in object define handling and add tests for object define property declaration emit * Fix organization nits from PR comments * Preserve comments from jsdoc declarations on properties and js declaration type aliases * Merge export declarations with identical specifiers * Remove completed TODO comment * Split lint * Remove now-unused function * PR feedback * Add some project references tests, remove some checks from project refs codepaths that are now invalid * Update project references tests again * Merge and update project references tests * Rename case * Update test to include declaration output * Remove yet another project refernces redirect extension check * Update comment * Add additional import ref to test * Add shorthand prop to test * Fix comment text * Extract var to temp * Simplify function and add whitespace * Update project refs test to use incremental edit entry * Stylistic refactors in the symbol serializer * Another round of PR feedback, mostly style, small bugfix with constructors, and test showing bug in export assigned class expression name shadowing * Use x instead of index
1 parent 62a4357 commit 61cb06c

File tree

304 files changed

+17584
-474
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

304 files changed

+17584
-474
lines changed

src/compiler/binder.ts

+69-8
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,26 @@ namespace ts {
1313
referenced: boolean;
1414
}
1515

16-
export function getModuleInstanceState(node: ModuleDeclaration): ModuleInstanceState {
17-
return node.body ? getModuleInstanceStateWorker(node.body) : ModuleInstanceState.Instantiated;
16+
export function getModuleInstanceState(node: ModuleDeclaration, visited?: Map<ModuleInstanceState | undefined>): ModuleInstanceState {
17+
if (node.body && !node.body.parent) {
18+
// getModuleInstanceStateForAliasTarget needs to walk up the parent chain, so parent pointers must be set on this tree already
19+
setParentPointers(node, node.body);
20+
}
21+
return node.body ? getModuleInstanceStateCached(node.body, visited) : ModuleInstanceState.Instantiated;
1822
}
1923

20-
function getModuleInstanceStateWorker(node: Node): ModuleInstanceState {
24+
function getModuleInstanceStateCached(node: Node, visited = createMap<ModuleInstanceState | undefined>()) {
25+
const nodeId = "" + getNodeId(node);
26+
if (visited.has(nodeId)) {
27+
return visited.get(nodeId) || ModuleInstanceState.NonInstantiated;
28+
}
29+
visited.set(nodeId, undefined);
30+
const result = getModuleInstanceStateWorker(node, visited);
31+
visited.set(nodeId, result);
32+
return result;
33+
}
34+
35+
function getModuleInstanceStateWorker(node: Node, visited: Map<ModuleInstanceState | undefined>): ModuleInstanceState {
2136
// A module is uninstantiated if it contains only
2237
switch (node.kind) {
2338
// 1. interface declarations, type alias declarations
@@ -37,11 +52,27 @@ namespace ts {
3752
return ModuleInstanceState.NonInstantiated;
3853
}
3954
break;
40-
// 4. other uninstantiated module declarations.
55+
// 4. Export alias declarations pointing at only uninstantiated modules or things uninstantiated modules contain
56+
case SyntaxKind.ExportDeclaration:
57+
if (!(node as ExportDeclaration).moduleSpecifier && !!(node as ExportDeclaration).exportClause) {
58+
let state = ModuleInstanceState.NonInstantiated;
59+
for (const specifier of (node as ExportDeclaration).exportClause!.elements) {
60+
const specifierState = getModuleInstanceStateForAliasTarget(specifier, visited);
61+
if (specifierState > state) {
62+
state = specifierState;
63+
}
64+
if (state === ModuleInstanceState.Instantiated) {
65+
return state;
66+
}
67+
}
68+
return state;
69+
}
70+
break;
71+
// 5. other uninstantiated module declarations.
4172
case SyntaxKind.ModuleBlock: {
4273
let state = ModuleInstanceState.NonInstantiated;
4374
forEachChild(node, n => {
44-
const childState = getModuleInstanceStateWorker(n);
75+
const childState = getModuleInstanceStateCached(n, visited);
4576
switch (childState) {
4677
case ModuleInstanceState.NonInstantiated:
4778
// child is non-instantiated - continue searching
@@ -61,7 +92,7 @@ namespace ts {
6192
return state;
6293
}
6394
case SyntaxKind.ModuleDeclaration:
64-
return getModuleInstanceState(node as ModuleDeclaration);
95+
return getModuleInstanceState(node as ModuleDeclaration, visited);
6596
case SyntaxKind.Identifier:
6697
// Only jsdoc typedef definition can exist in jsdoc namespace, and it should
6798
// be considered the same as type alias
@@ -72,6 +103,36 @@ namespace ts {
72103
return ModuleInstanceState.Instantiated;
73104
}
74105

106+
function getModuleInstanceStateForAliasTarget(specifier: ExportSpecifier, visited: Map<ModuleInstanceState | undefined>) {
107+
const name = specifier.propertyName || specifier.name;
108+
let p: Node | undefined = specifier.parent;
109+
while (p) {
110+
if (isBlock(p) || isModuleBlock(p) || isSourceFile(p)) {
111+
const statements = p.statements;
112+
let found: ModuleInstanceState | undefined;
113+
for (const statement of statements) {
114+
if (nodeHasName(statement, name)) {
115+
if (!statement.parent) {
116+
setParentPointers(p, statement);
117+
}
118+
const state = getModuleInstanceStateCached(statement, visited);
119+
if (found === undefined || state > found) {
120+
found = state;
121+
}
122+
if (found === ModuleInstanceState.Instantiated) {
123+
return found;
124+
}
125+
}
126+
}
127+
if (found !== undefined) {
128+
return found;
129+
}
130+
}
131+
p = p.parent;
132+
}
133+
return ModuleInstanceState.Instantiated; // Couldn't locate, assume could refer to a value
134+
}
135+
75136
const enum ContainerFlags {
76137
// The current node is not a container, and no container manipulation should happen before
77138
// recursing into it.
@@ -2561,7 +2622,7 @@ namespace ts {
25612622
// Declare a 'member' if the container is an ES5 class or ES6 constructor
25622623
constructorSymbol.members = constructorSymbol.members || createSymbolTable();
25632624
// It's acceptable for multiple 'this' assignments of the same identifier to occur
2564-
declareSymbol(constructorSymbol.members, constructorSymbol, node, SymbolFlags.Property, SymbolFlags.PropertyExcludes & ~SymbolFlags.Property);
2625+
declareSymbol(constructorSymbol.members, constructorSymbol, node, SymbolFlags.Property | SymbolFlags.Assignment, SymbolFlags.PropertyExcludes & ~SymbolFlags.Property);
25652626
addDeclarationToSymbol(constructorSymbol, constructorSymbol.valueDeclaration, SymbolFlags.Class);
25662627
}
25672628
break;
@@ -2575,7 +2636,7 @@ namespace ts {
25752636
// Bind this property to the containing class
25762637
const containingClass = thisContainer.parent;
25772638
const symbolTable = hasModifier(thisContainer, ModifierFlags.Static) ? containingClass.symbol.exports! : containingClass.symbol.members!;
2578-
declareSymbol(symbolTable, containingClass.symbol, node, SymbolFlags.Property, SymbolFlags.None, /*isReplaceableByMethod*/ true);
2639+
declareSymbol(symbolTable, containingClass.symbol, node, SymbolFlags.Property | SymbolFlags.Assignment, SymbolFlags.None, /*isReplaceableByMethod*/ true);
25792640
break;
25802641
case SyntaxKind.SourceFile:
25812642
// this.property = assignment in a source file -- declare symbol in exports for a module, in locals for a script

0 commit comments

Comments
 (0)