Skip to content

Fix alias naming and structure bugs in js declarations #34786

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 4 commits into from
Oct 30, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 53 additions & 23 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4968,7 +4968,7 @@ namespace ts {
const oldcontext = context;
context = {
...oldcontext,
usedSymbolNames: mapMap(symbolTable, (_symbol, name) => [unescapeLeadingUnderscores(name), true]),
usedSymbolNames: createMap(),
remappedSymbolNames: createMap(),
tracker: {
...oldcontext.tracker,
Expand All @@ -4992,6 +4992,10 @@ namespace ts {
context.usedSymbolNames!.set(name, true);
});
}
forEachEntry(symbolTable, (symbol, name) => {
const baseName = unescapeLeadingUnderscores(name);
void getInternalSymbolName(symbol, baseName); // Called to cache values into `usedSymbolNames` and `remappedSymbolNames`
});
let addingDeclare = !bundled;
const exportEquals = symbolTable.get(InternalSymbolName.ExportEquals);
if (exportEquals && symbolTable.size > 1 && exportEquals.flags & SymbolFlags.Alias) {
Expand Down Expand Up @@ -5204,7 +5208,11 @@ namespace ts {
isPrivate = true;
}
const modifierFlags = (!isPrivate ? ModifierFlags.Export : 0) | (isDefault && !needsPostExportDefault ? ModifierFlags.Default : 0);
if (symbol.flags & SymbolFlags.Function) {
const isConstMergedWithNS = symbol.flags & SymbolFlags.Module &&
symbol.flags & (SymbolFlags.BlockScopedVariable | SymbolFlags.FunctionScopedVariable | SymbolFlags.Property) &&
symbol.escapedName !== InternalSymbolName.ExportEquals;
const isConstMergedWithNSPrintableAsSignatureMerge = isConstMergedWithNS && isTypeRepresentableAsFunctionNamespaceMerge(getTypeOfSymbol(symbol), symbol);
if (symbol.flags & SymbolFlags.Function || isConstMergedWithNSPrintableAsSignatureMerge) {
serializeAsFunctionNamespaceMerge(getTypeOfSymbol(symbol), symbol, getInternalSymbolName(symbol, symbolName), modifierFlags);
}
if (symbol.flags & SymbolFlags.TypeAlias) {
Expand All @@ -5215,7 +5223,8 @@ namespace ts {
if (symbol.flags & (SymbolFlags.BlockScopedVariable | SymbolFlags.FunctionScopedVariable | SymbolFlags.Property)
&& symbol.escapedName !== InternalSymbolName.ExportEquals
&& !(symbol.flags & SymbolFlags.Prototype)
&& !(symbol.flags & SymbolFlags.Class)) {
&& !(symbol.flags & SymbolFlags.Class)
&& !isConstMergedWithNSPrintableAsSignatureMerge) {
serializeVariableOrProperty(symbol, symbolName, isPrivate, needsPostExportDefault, propertyAsAlias, modifierFlags);
}
if (symbol.flags & SymbolFlags.Enum) {
Expand All @@ -5232,7 +5241,7 @@ namespace ts {
serializeAsClass(symbol, getInternalSymbolName(symbol, symbolName), modifierFlags);
}
}
if (symbol.flags & (SymbolFlags.ValueModule | SymbolFlags.NamespaceModule)) {
if ((symbol.flags & (SymbolFlags.ValueModule | SymbolFlags.NamespaceModule) && (!isConstMergedWithNS || isTypeOnlyNamespace(symbol))) || isConstMergedWithNSPrintableAsSignatureMerge) {
serializeModule(symbol, symbolName, modifierFlags);
}
if (symbol.flags & SymbolFlags.Interface) {
Expand All @@ -5259,7 +5268,9 @@ namespace ts {
}

function includePrivateSymbol(symbol: Symbol) {
if (some(symbol.declarations, isParameterDeclaration)) return;
Debug.assertDefined(deferredPrivates);
getUnusedName(unescapeLeadingUnderscores(symbol.escapedName), symbol); // Call to cache unique name for symbol
deferredPrivates!.set("" + getSymbolId(symbol), symbol);
}

Expand Down Expand Up @@ -5333,8 +5344,16 @@ namespace ts {
), modifierFlags);
}

function getNamespaceMembersForSerialization(symbol: Symbol) {
return !symbol.exports ? [] : filter(arrayFrom((symbol.exports).values()), p => !((p.flags & SymbolFlags.Prototype) || (p.escapedName === "prototype")));
}

function isTypeOnlyNamespace(symbol: Symbol) {
return every(getNamespaceMembersForSerialization(symbol), m => !(resolveSymbol(m).flags & SymbolFlags.Value));
}

function serializeModule(symbol: Symbol, symbolName: string, modifierFlags: ModifierFlags) {
const members = !symbol.exports ? [] : filter(arrayFrom((symbol.exports).values()), p => !((p.flags & SymbolFlags.Prototype) || (p.escapedName === "prototype")));
const members = getNamespaceMembersForSerialization(symbol);
// Split NS members up by declaration - members whose parent symbol is the ns symbol vs those whose is not (but were added in later via merging)
const locationMap = arrayToMultiMap(members, m => m.parent && m.parent === symbol ? "real" : "merged");
const realMembers = locationMap.get("real") || emptyArray;
Expand All @@ -5344,18 +5363,21 @@ namespace ts {
// so we don't even have placeholders to fill in.
if (length(realMembers)) {
const localName = getInternalSymbolName(symbol, symbolName);
serializeAsNamespaceDeclaration(realMembers, localName, modifierFlags, !!(symbol.flags & SymbolFlags.Function));
serializeAsNamespaceDeclaration(realMembers, localName, modifierFlags, !!(symbol.flags & (SymbolFlags.Function | SymbolFlags.Assignment)));
}
if (length(mergedMembers)) {
const localName = getInternalSymbolName(symbol, symbolName);
forEach(mergedMembers, includePrivateSymbol);
const nsBody = createModuleBlock([createExportDeclaration(
/*decorators*/ undefined,
/*modifiers*/ undefined,
createNamedExports(map(filter(mergedMembers, n => n.escapedName !== InternalSymbolName.ExportEquals), s => {
const name = unescapeLeadingUnderscores(s.escapedName);
const localName = getInternalSymbolName(s, name);
return createExportSpecifier(name === localName ? undefined : localName, name);
const aliasDecl = s.declarations && getDeclarationOfAliasSymbol(s);
const target = aliasDecl && getTargetOfAliasDeclaration(aliasDecl, /*dontRecursivelyResolve*/ true);
includePrivateSymbol(target || s);
const targetName = target ? getInternalSymbolName(target, unescapeLeadingUnderscores(target.escapedName)) : localName;
return createExportSpecifier(name === targetName ? undefined : targetName, name);
}))
)]);
addResult(createModuleDeclaration(
Expand Down Expand Up @@ -5630,6 +5652,7 @@ namespace ts {
serializeMaybeAliasAssignment(symbol);
break;
case SyntaxKind.BinaryExpression:
case SyntaxKind.PropertyAccessExpression:
// Could be best encoded as though an export specifier or as though an export assignment
// If name is default or export=, do an export assignment
// Otherwise do an export specifier
Expand All @@ -5640,10 +5663,6 @@ namespace ts {
serializeExportSpecifier(localName, targetName);
}
break;
case SyntaxKind.PropertyAccessExpression:
// A PAE alias is _always_ going to exist as an append to a top-level export, where our top level
// handling should always be sufficient to encode the export action itself
break;
default:
return Debug.failBadSyntaxKind(node, "Unhandled alias declaration kind in symbol serializer!");
}
Expand Down Expand Up @@ -5672,7 +5691,8 @@ namespace ts {
const aliasDecl = symbol.declarations && getDeclarationOfAliasSymbol(symbol);
// serialize what the alias points to, preserve the declaration's initializer
const target = aliasDecl && getTargetOfAliasDeclaration(aliasDecl, /*dontRecursivelyResolve*/ true);
if (target) {
// If the target resolves and resolves to a thing defined in this file, emit as an alias, otherwise emit as a const
if (target && length(target.declarations) && some(target.declarations, d => getSourceFileOfNode(d) === getSourceFileOfNode(enclosingDeclaration))) {
// In case `target` refers to a namespace member, look at the declaration and serialize the leftmost symbol in it
// eg, `namespace A { export class B {} }; exports = A.B;`
// Technically, this is all that's required in the case where the assignment is an entity name expression
Expand Down Expand Up @@ -5758,6 +5778,7 @@ namespace ts {
return getObjectFlags(typeToSerialize) & (ObjectFlags.Anonymous | ObjectFlags.Mapped) &&
!getIndexInfoOfType(typeToSerialize, IndexKind.String) &&
!getIndexInfoOfType(typeToSerialize, IndexKind.Number) &&
!!(length(getPropertiesOfType(typeToSerialize)) || length(getSignaturesOfType(typeToSerialize, SignatureKind.Call))) &&
!length(getSignaturesOfType(typeToSerialize, SignatureKind.Construct)) && // TODO: could probably serialize as function + ns + class, now that that's OK
!getDeclarationWithTypeAnnotation(hostSymbol) &&
!(typeToSerialize.symbol && some(typeToSerialize.symbol.declarations, d => getSourceFileOfNode(d) !== ctxSrc)) &&
Expand Down Expand Up @@ -6112,11 +6133,8 @@ namespace ts {
return context.remappedSymbolNames!.get("" + getSymbolId(symbol))!;
}
}
if (input === InternalSymbolName.Default) {
input = "_default";
}
else if (input === InternalSymbolName.ExportEquals) {
input = "_exports";
if (symbol) {
input = getNameCandidateWorker(symbol, input);
}
let i = 0;
const original = input;
Expand All @@ -6131,17 +6149,29 @@ namespace ts {
return input;
}

function getInternalSymbolName(symbol: Symbol, localName: string) {
if (context.remappedSymbolNames!.has("" + getSymbolId(symbol))) {
return context.remappedSymbolNames!.get("" + getSymbolId(symbol))!;
}
function getNameCandidateWorker(symbol: Symbol, localName: string) {
if (localName === InternalSymbolName.Default || localName === InternalSymbolName.Class || localName === InternalSymbolName.Function) {
const flags = context.flags;
context.flags |= NodeBuilderFlags.InInitialEntityName;
const nameCandidate = getNameOfSymbolAsWritten(symbol, context);
context.flags = flags;
localName = isIdentifierText(nameCandidate, languageVersion) && !isStringANonContextualKeyword(nameCandidate) ? nameCandidate : getUnusedName(`_default`, symbol);
localName = nameCandidate.length > 0 && isSingleOrDoubleQuote(nameCandidate.charCodeAt(0)) ? stripQuotes(nameCandidate) : nameCandidate;
}
if (localName === InternalSymbolName.Default) {
localName = "_default";
}
else if (localName === InternalSymbolName.ExportEquals) {
localName = "_exports";
}
localName = isIdentifierText(localName, languageVersion) && !isStringANonContextualKeyword(localName) ? localName : "_" + localName.replace(/[^a-zA-Z0-9]/g, "_");
return localName;
}

function getInternalSymbolName(symbol: Symbol, localName: string) {
if (context.remappedSymbolNames!.has("" + getSymbolId(symbol))) {
return context.remappedSymbolNames!.get("" + getSymbolId(symbol))!;
}
localName = getNameCandidateWorker(symbol, localName);
// The result of this is going to be used as the symbol's name - lock it in, so `getUnusedName` will also pick it up
context.remappedSymbolNames!.set("" + getSymbolId(symbol), localName);
return localName;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
//// [index.js]
// TODO: Fixup
class A {
member = new Q();
}
Expand All @@ -15,7 +14,6 @@ module.exports.Another = Q;


//// [index.js]
// TODO: Fixup
var A = /** @class */ (function () {
function A() {
this.member = new Q();
Expand Down Expand Up @@ -43,44 +41,11 @@ declare class Q {
x: A;
}
declare namespace Q {
export { Another };
export { Q_1 as Another };
}
declare class A {
member: Q;
}
declare var Another: typeof Q;
declare class Q {
declare class Q_1 {
x: number;
}


//// [DtsFileErrors]


out/index.d.ts(2,15): error TS2300: Duplicate identifier 'Q'.
out/index.d.ts(5,19): error TS2300: Duplicate identifier 'Q'.
out/index.d.ts(12,15): error TS2300: Duplicate identifier 'Q'.


==== ./out/index.d.ts (3 errors) ====
export = Q;
declare class Q {
~
!!! error TS2300: Duplicate identifier 'Q'.
x: A;
}
declare namespace Q {
~
!!! error TS2300: Duplicate identifier 'Q'.
export { Another };
}
declare class A {
member: Q;
}
declare var Another: typeof Q;
declare class Q {
~
!!! error TS2300: Duplicate identifier 'Q'.
x: number;
}

Original file line number Diff line number Diff line change
@@ -1,37 +1,36 @@
=== tests/cases/conformance/jsdoc/declarations/index.js ===
// TODO: Fixup
class A {
>A : Symbol(A, Decl(index.js, 0, 0))

member = new Q();
>member : Symbol(A.member, Decl(index.js, 1, 9))
>Q : Symbol(Q, Decl(index.js, 3, 1))
>member : Symbol(A.member, Decl(index.js, 0, 9))
>Q : Symbol(Q, Decl(index.js, 2, 1))
}
class Q {
>Q : Symbol(Q, Decl(index.js, 3, 1))
>Q : Symbol(Q, Decl(index.js, 2, 1))

x = 42;
>x : Symbol(Q.x, Decl(index.js, 4, 9))
>x : Symbol(Q.x, Decl(index.js, 3, 9))
}
module.exports = class Q {
>module.exports : Symbol("tests/cases/conformance/jsdoc/declarations/index", Decl(index.js, 0, 0))
>module : Symbol(export=, Decl(index.js, 6, 1))
>exports : Symbol(export=, Decl(index.js, 6, 1))
>Q : Symbol(Q, Decl(index.js, 7, 16))
>module : Symbol(export=, Decl(index.js, 5, 1))
>exports : Symbol(export=, Decl(index.js, 5, 1))
>Q : Symbol(Q, Decl(index.js, 6, 16))

constructor() {
this.x = new A();
>this.x : Symbol(Q.x, Decl(index.js, 8, 19))
>this : Symbol(Q, Decl(index.js, 7, 16))
>x : Symbol(Q.x, Decl(index.js, 8, 19))
>this.x : Symbol(Q.x, Decl(index.js, 7, 19))
>this : Symbol(Q, Decl(index.js, 6, 16))
>x : Symbol(Q.x, Decl(index.js, 7, 19))
>A : Symbol(A, Decl(index.js, 0, 0))
}
}
module.exports.Another = Q;
>module.exports.Another : Symbol(Another)
>module.exports : Symbol(Another, Decl(index.js, 11, 1))
>module : Symbol(module, Decl(index.js, 6, 1))
>module.exports : Symbol(Another, Decl(index.js, 10, 1))
>module : Symbol(module, Decl(index.js, 5, 1))
>exports : Symbol("tests/cases/conformance/jsdoc/declarations/index", Decl(index.js, 0, 0))
>Another : Symbol(Another, Decl(index.js, 11, 1))
>Q : Symbol(Q, Decl(index.js, 3, 1))
>Another : Symbol(Another, Decl(index.js, 10, 1))
>Q : Symbol(Q, Decl(index.js, 2, 1))

Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
=== tests/cases/conformance/jsdoc/declarations/index.js ===
// TODO: Fixup
class A {
>A : A

Expand Down
6 changes: 4 additions & 2 deletions tests/baselines/reference/jsDeclarationsFunctionsCjs.js
Original file line number Diff line number Diff line change
Expand Up @@ -121,8 +121,7 @@ export namespace b {
}
export function c(): void;
export namespace c {
class Cls {
}
export { Cls };
}
export function d(a: number, b: number): string;
export function e<T, U>(a: T, b: U): T & U;
Expand Down Expand Up @@ -159,3 +158,6 @@ export function i(): void;
export function ii(): void;
export function jj(): void;
export function j(): void;
declare class Cls {
}
export {};
Loading