Skip to content

Commit fc55d7a

Browse files
authored
Added --rename-conflicting-types flag, refactored FacadeGenerator to separate handling of type names and value names
Added --rename-conflicting-types flag to allow the user to control whether types with names that conflict with variable names get renamed or get merged with the variable. Made variables with the same name as types get merged into the types by default Separated handling of type names and value names in FacadeConverter
1 parent b809c53 commit fc55d7a

File tree

10 files changed

+428
-222
lines changed

10 files changed

+428
-222
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ Dart interop facade file is written to stdout.
2121
`--destination=<destination-dir>`: output generated code to destination-dir<br/>
2222
`--base-path=<input d.ts file directory>`: specify the directory that contains the input d.ts files<br/>
2323
`--generate-html`: generate facades for dart:html types rather than importing them<br/>
24+
`--rename-conflicting-types`: rename types to avoid conflicts in cases where a variable and a type have the exact same name, but it is not clear if they are related or not.
2425
`--explicit-static`: disables default assumption that properties declared on the anonymous types of top level variable declarations are static
2526
`--trust-js-types`: Emits @anonymous tags on classes that have neither constructors nor static members. This prevents the Dart Dev Compiler from checking whether or not objects are truly instances of those classes. This flag should be used if the input JS/TS library has structural types, or is otherwise claiming that types match in cases where the correct JS prototype is not there for DDC to check against.
2627

index.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,15 @@ const main = require('./build/lib/main.js');
44

55
var args = require('minimist')(process.argv.slice(2), {
66
base: 'string',
7-
boolean: ['semantic-diagnostics', 'generate-html', 'explicit-static', 'trust-js-types'],
7+
boolean: [
8+
'semantic-diagnostics', 'generate-html', 'rename-conflicting-types', 'explicit-static',
9+
'trust-js-types'
10+
],
811
alias: {
912
'base-path': 'basePath',
1013
'semantic-diagnostics': 'semanticDiagnostics',
1114
'generate-html': 'generateHTML',
15+
'rename-conflicting-types': 'renameConflictingTypes',
1216
'explicit-static': 'explicitStatic',
1317
'trust-js-types': 'trustJSTypes'
1418
}

lib/base.ts

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,6 @@ export class ImportSummary {
7171

7272
export type Constructor = ts.ConstructorDeclaration|ts.ConstructSignatureDeclaration;
7373
export type ClassLike = ts.ClassLikeDeclaration|ts.InterfaceDeclaration;
74-
export type NamedDeclaration = ClassLike|ts.PropertyDeclaration|ts.VariableDeclaration|
75-
ts.MethodDeclaration|ts.ModuleDeclaration|ts.FunctionDeclaration;
7674

7775
/**
7876
* Interface extending the true InterfaceDeclaration interface to add optional state we store on
@@ -105,13 +103,6 @@ export function isFunctionTypedefLikeInterface(ifDecl: ts.InterfaceDeclaration):
105103
ts.isCallSignatureDeclaration(ifDecl.members[0]);
106104
}
107105

108-
export function getDeclaration(type: ts.Type): ts.Declaration {
109-
let symbol = type.getSymbol();
110-
if (!symbol) return null;
111-
if (symbol.valueDeclaration) return symbol.valueDeclaration;
112-
return symbol.declarations && symbol.declarations.length > 0 ? symbol.declarations[0] : null;
113-
}
114-
115106
export function isExtendsClause(heritageClause: ts.HeritageClause) {
116107
return heritageClause.token === ts.SyntaxKind.ExtendsKeyword &&
117108
!ts.isInterfaceDeclaration(heritageClause.parent);

lib/declaration.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -158,12 +158,12 @@ export default class DeclarationTranspiler extends base.TranspilerBase {
158158
this.visit(name);
159159
return;
160160
}
161-
// Have to rewrite names in this case as we could have conflicts
162-
// due to needing to support multiple JS modules in a single Dart module
161+
// Have to rewrite names in this case as we could have conflicts due to needing to support
162+
// multiple JS modules in a single Dart module.
163163
if (!ts.isIdentifier(name)) {
164164
throw 'Internal error: unexpected function name kind:' + name.kind;
165165
}
166-
let entry = this.fc.lookupCustomDartTypeName(name);
166+
let entry = this.fc.lookupDartValueName(name);
167167
if (entry) {
168168
this.emit(entry.name);
169169
return;
@@ -813,7 +813,7 @@ export default class DeclarationTranspiler extends base.TranspilerBase {
813813
if (name.kind !== ts.SyntaxKind.Identifier) {
814814
this.reportError(name, 'Unexpected name kind:' + name.kind);
815815
}
816-
this.fc.visitTypeName(name);
816+
this.visitName(name);
817817
}
818818

819819
if (fn.typeParameters && fn.typeParameters.length > 0) {

lib/facade_converter.ts

Lines changed: 105 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ function hasVarArgs(parameters: ts.ParameterDeclaration[]): boolean {
8585
* }
8686
* Path: m1.m2.foo
8787
*/
88-
function fullJsPath(node: base.NamedDeclaration): string {
88+
function fullJsPath(node: ts.NamedDeclaration): string {
8989
const parts: Array<string> = [base.ident(node.name)];
9090
let p: ts.Node = node.parent;
9191
while (p != null) {
@@ -131,7 +131,7 @@ export class NameRewriter {
131131

132132
constructor(private fc: FacadeConverter) {}
133133

134-
private computeName(node: base.NamedDeclaration): DartNameRecord {
134+
private computeName(node: ts.NamedDeclaration): DartNameRecord {
135135
const fullPath = fullJsPath(node);
136136
if (this.dartTypes.has(fullPath)) {
137137
return this.dartTypes.get(fullPath);
@@ -178,7 +178,7 @@ export class NameRewriter {
178178
}
179179
}
180180

181-
lookupName(node: base.NamedDeclaration, context: ts.Node) {
181+
lookupName(node: ts.NamedDeclaration, context: ts.Node) {
182182
let name = this.computeName(node).name;
183183
return this.fc.resolveImportForSourceFile(node.getSourceFile(), context.getSourceFile(), name);
184184
}
@@ -610,52 +610,83 @@ export class FacadeConverter extends base.TranspilerBase {
610610
}
611611
}
612612

613+
replaceNode(original: ts.Node, replacement: ts.Node) {
614+
if (ts.isVariableDeclaration(original) && ts.isClassDeclaration(replacement)) {
615+
// Handle the speical case in mergeVariablesIntoClasses where we upgrade variable declarations
616+
// to classes.
617+
const symbol = this.tc.getSymbolAtLocation(original.name);
618+
symbol.declarations = symbol.getDeclarations().map((declaration: ts.Declaration) => {
619+
// TODO(derekx): Changing the declarations of a symbol like this is a hack. It would be
620+
// cleaner and safer to generate a new Program and TypeChecker after performing
621+
// gatherClasses and mergeVariablesIntoClasses.
622+
if (declaration === original) {
623+
return replacement;
624+
}
625+
return declaration;
626+
});
627+
}
628+
629+
super.replaceNode(original, replacement);
630+
}
631+
613632
getSymbolAtLocation(identifier: ts.EntityName) {
614633
let symbol = this.tc.getSymbolAtLocation(identifier);
615634
while (symbol && symbol.flags & ts.SymbolFlags.Alias) symbol = this.tc.getAliasedSymbol(symbol);
616635
return symbol;
617636
}
618637

619-
getSymbolDeclaration(symbol: ts.Symbol, n?: ts.Node): ts.Declaration {
620-
if (!symbol || this.tc.isUnknownSymbol(symbol)) return null;
621-
let decl = symbol.valueDeclaration;
622-
if (!decl) {
623-
// In the case of a pure declaration with no assignment, there is no value declared.
624-
// Just grab the first declaration, hoping it is declared once.
625-
if (!symbol.declarations || symbol.declarations.length === 0) {
626-
this.reportError(n, 'no declarations for symbol ' + symbol.name);
627-
return;
628-
}
629-
decl = symbol.declarations[0];
638+
getValueDeclarationOfSymbol(symbol: ts.Symbol, n?: ts.Node): ts.Declaration|undefined {
639+
if (!symbol || this.tc.isUnknownSymbol(symbol)) {
640+
return undefined;
641+
}
642+
if (!symbol.valueDeclaration) {
643+
this.reportError(n, `no value declaration for symbol ${symbol.name}`);
644+
return undefined;
630645
}
631-
return decl;
646+
return symbol.valueDeclaration;
647+
}
648+
649+
getTypeDeclarationOfSymbol(symbol: ts.Symbol, n?: ts.Node): ts.Declaration|undefined {
650+
if (!symbol || this.tc.isUnknownSymbol(symbol)) {
651+
return undefined;
652+
}
653+
const typeDeclaration = symbol.declarations.find((declaration: ts.Declaration) => {
654+
return ts.isInterfaceDeclaration(declaration) || ts.isClassDeclaration(declaration) ||
655+
ts.isTypeAliasDeclaration(declaration) || ts.isTypeParameterDeclaration(declaration);
656+
});
657+
if (!typeDeclaration) {
658+
this.reportError(n, `no type declarations for symbol ${symbol.name}`);
659+
return undefined;
660+
}
661+
return typeDeclaration;
632662
}
633663

634664
generateDartName(identifier: ts.EntityName, options: TypeDisplayOptions): string {
635-
let ret = this.lookupCustomDartTypeName(identifier, options);
636-
if (ret) return base.formatType(ret.name, ret.comment, options);
665+
const ret = this.lookupCustomDartTypeName(identifier, options);
666+
if (ret) {
667+
return base.formatType(ret.name, ret.comment, options);
668+
}
637669
// TODO(jacobr): handle library import prefixes more robustly. This generally works but is
638670
// fragile.
639671
return this.maybeAddTypeArguments(base.ident(identifier), options);
640672
}
641673

642674
/**
643-
* Returns null if declaration cannot be found or is not valid in Dart.
675+
* Resolves TypeReferences to find the declaration of the referenced type that matches the
676+
* predicate.
677+
*
678+
* For example, if the type passed is a reference to X and the predicate passed is
679+
* ts.isInterfaceDeclaration, then this function will will return the declaration of interface X,
680+
* or undefined if there is no such declaration.
644681
*/
645-
getDeclaration(identifier: ts.EntityName): ts.Declaration {
646-
let symbol: ts.Symbol;
647-
648-
symbol = this.getSymbolAtLocation(identifier);
649-
650-
let declaration = this.getSymbolDeclaration(symbol, identifier);
651-
if (symbol && symbol.flags & ts.SymbolFlags.TypeParameter) {
652-
let kind = declaration.parent.kind;
653-
// Only kinds of TypeParameters supported by Dart.
654-
if (kind !== ts.SyntaxKind.ClassDeclaration && kind !== ts.SyntaxKind.InterfaceDeclaration) {
655-
return null;
656-
}
682+
getDeclarationOfReferencedType(
683+
type: ts.TypeReferenceNode,
684+
predicate: (declaration: ts.Declaration) => boolean): ts.Declaration {
685+
const referenceSymbol = this.tc.getTypeAtLocation(type.typeName).getSymbol();
686+
if (!referenceSymbol) {
687+
return undefined;
657688
}
658-
return declaration;
689+
return referenceSymbol.getDeclarations().find(predicate);
659690
}
660691

661692
maybeAddTypeArguments(name: string, options: TypeDisplayOptions): string {
@@ -667,8 +698,7 @@ export class FacadeConverter extends base.TranspilerBase {
667698
}
668699

669700
/**
670-
* Returns a custom Dart type name or null if the type isn't a custom Dart
671-
* type.
701+
* Returns a custom Dart type name or null if the type isn't a custom Dart type.
672702
*/
673703
lookupCustomDartTypeName(identifier: ts.EntityName, options?: TypeDisplayOptions):
674704
{name?: string, comment?: string, keep?: boolean} {
@@ -678,23 +708,22 @@ export class FacadeConverter extends base.TranspilerBase {
678708
insideTypeArgument: this.insideTypeArgument
679709
};
680710
}
681-
let ident = base.ident(identifier);
711+
const ident = base.ident(identifier);
682712
if (ident === 'Promise' && this.emitPromisesAsFutures) {
683713
return {name: this.maybeAddTypeArguments('Future', options)};
684714
}
685-
let symbol: ts.Symbol = this.getSymbolAtLocation(identifier);
686-
let declaration = this.getSymbolDeclaration(symbol, identifier);
715+
const symbol: ts.Symbol = this.getSymbolAtLocation(identifier);
687716
if (symbol && symbol.flags & ts.SymbolFlags.TypeParameter) {
688-
let kind = declaration.parent.kind;
717+
const parent = this.getTypeDeclarationOfSymbol(symbol).parent;
689718
if (options.resolvedTypeArguments && options.resolvedTypeArguments.has(ident)) {
690719
return {
691720
name: this.generateDartTypeName(
692721
options.resolvedTypeArguments.get(ident), removeResolvedTypeArguments(options))
693722
};
694723
}
695724
// Only kinds of TypeParameters supported by Dart.
696-
if (kind !== ts.SyntaxKind.ClassDeclaration && kind !== ts.SyntaxKind.InterfaceDeclaration &&
697-
kind !== ts.SyntaxKind.TypeAliasDeclaration) {
725+
if (!ts.isClassDeclaration(parent) && !ts.isInterfaceDeclaration(parent) &&
726+
!ts.isTypeAliasDeclaration(parent)) {
698727
return {name: 'dynamic', comment: ident};
699728
}
700729
}
@@ -704,7 +733,7 @@ export class FacadeConverter extends base.TranspilerBase {
704733
return null;
705734
}
706735

707-
let fileAndName = this.getFileAndName(identifier, symbol);
736+
const fileAndName = this.getFileAndName(identifier, symbol);
708737

709738
if (fileAndName) {
710739
let fileSubs = TS_TO_DART_TYPENAMES.get(fileAndName.fileName);
@@ -727,6 +756,8 @@ export class FacadeConverter extends base.TranspilerBase {
727756
}
728757
}
729758
}
759+
760+
const declaration = this.getTypeDeclarationOfSymbol(symbol, identifier);
730761
if (declaration) {
731762
if (symbol.flags & ts.SymbolFlags.Enum) {
732763
// We can't treat JavaScript enums as Dart enums in this case.
@@ -738,8 +769,7 @@ export class FacadeConverter extends base.TranspilerBase {
738769
if (supportedDeclaration) {
739770
return {
740771
name: this.maybeAddTypeArguments(
741-
this.nameRewriter.lookupName(<base.NamedDeclaration>declaration, identifier),
742-
options),
772+
this.nameRewriter.lookupName(declaration, identifier), options),
743773
keep: true
744774
};
745775
}
@@ -751,13 +781,10 @@ export class FacadeConverter extends base.TranspilerBase {
751781
};
752782
}
753783

754-
let kind = declaration.kind;
755-
if (kind === ts.SyntaxKind.ClassDeclaration || kind === ts.SyntaxKind.InterfaceDeclaration ||
756-
kind === ts.SyntaxKind.VariableDeclaration ||
757-
kind === ts.SyntaxKind.PropertyDeclaration ||
758-
kind === ts.SyntaxKind.FunctionDeclaration) {
759-
let name = this.nameRewriter.lookupName(<base.NamedDeclaration>declaration, identifier);
760-
if (kind === ts.SyntaxKind.InterfaceDeclaration &&
784+
if (ts.isClassDeclaration(declaration) || ts.isInterfaceDeclaration(declaration) ||
785+
ts.isTypeAliasDeclaration(declaration)) {
786+
const name = this.nameRewriter.lookupName(declaration, identifier);
787+
if (ts.isInterfaceDeclaration(declaration) &&
761788
base.isFunctionTypedefLikeInterface(<ts.InterfaceDeclaration>declaration) &&
762789
base.getAncestor(identifier, ts.SyntaxKind.HeritageClause)) {
763790
// TODO(jacobr): we need to specify a specific call method for this
@@ -770,6 +797,32 @@ export class FacadeConverter extends base.TranspilerBase {
770797
return null;
771798
}
772799

800+
/**
801+
* Looks up an identifier that is used as the name of a value (variable or function). Uses the
802+
* name rewriter to fix naming conflicts.
803+
*
804+
* Returns the original name if it doesn't cause any conflicts, otherwise returns a renamed
805+
* identifier.
806+
*/
807+
lookupDartValueName(identifier: ts.Identifier, options?: TypeDisplayOptions):
808+
{name?: string, comment?: string, keep?: boolean} {
809+
if (!options) {
810+
options = {
811+
insideComment: this.insideCodeComment,
812+
insideTypeArgument: this.insideTypeArgument
813+
};
814+
}
815+
const symbol: ts.Symbol = this.getSymbolAtLocation(identifier);
816+
const declaration = this.getValueDeclarationOfSymbol(symbol, identifier);
817+
if (declaration) {
818+
if (ts.isVariableDeclaration(declaration) || ts.isPropertyDeclaration(declaration) ||
819+
ts.isFunctionDeclaration(declaration)) {
820+
const name = this.nameRewriter.lookupName(declaration, identifier);
821+
return {name: this.maybeAddTypeArguments(name, options), keep: true};
822+
}
823+
}
824+
}
825+
773826
// TODO(jacobr): performance of this method could easily be optimized.
774827
/**
775828
* This method works around the lack of Dart support for union types
@@ -837,7 +890,7 @@ export class FacadeConverter extends base.TranspilerBase {
837890
// TODO(jacobr): property need to prefix the name better.
838891
referenceType.typeName = this.createEntityName(symbol);
839892
referenceType.typeName.parent = referenceType;
840-
let decl = this.getSymbolDeclaration(symbol);
893+
const decl = this.getTypeDeclarationOfSymbol(symbol);
841894
base.copyLocation(decl, referenceType);
842895
return referenceType;
843896
}
@@ -855,7 +908,7 @@ export class FacadeConverter extends base.TranspilerBase {
855908
// that is a typedef like interface causes the typescript compiler to stack
856909
// overflow. Not sure if this is a bug in the typescript compiler or I am
857910
// missing something obvious.
858-
let declaration = base.getDeclaration(type) as ts.InterfaceDeclaration;
911+
const declaration = this.getTypeDeclarationOfSymbol(type.symbol) as ts.InterfaceDeclaration;
859912
if (base.isFunctionTypedefLikeInterface(declaration)) {
860913
return [];
861914
}
@@ -923,7 +976,7 @@ export class FacadeConverter extends base.TranspilerBase {
923976
private getFileAndName(n: ts.Node, originalSymbol: ts.Symbol): {fileName: string, qname: string} {
924977
let symbol = originalSymbol;
925978
while (symbol.flags & ts.SymbolFlags.Alias) symbol = this.tc.getAliasedSymbol(symbol);
926-
let decl = this.getSymbolDeclaration(symbol, n);
979+
const decl = this.getTypeDeclarationOfSymbol(symbol, n);
927980

928981
const fileName = decl.getSourceFile().fileName;
929982
const canonicalFileName = this.getRelativeFileName(fileName)

lib/main.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,11 @@ export interface TranspilerOptions {
4545
* Generate browser API facades instead of importing them from dart:html.
4646
*/
4747
generateHTML?: boolean;
48+
/**
49+
* Rename types to avoid conflicts in cases where a variable and a type have the exact same name,
50+
* but it is not clear if they are related or not.
51+
*/
52+
renameConflictingTypes?: boolean;
4853
/**
4954
* Do not assume that all properties declared on the anonymous types of top level variable
5055
* declarations are static.
@@ -288,7 +293,9 @@ export class Transpiler {
288293
}
289294

290295
this.lastCommentIdx = -1;
291-
merge.normalizeSourceFile(sourceFile, this.fc, fileSet, this.options.explicitStatic);
296+
merge.normalizeSourceFile(
297+
sourceFile, this.fc, fileSet, this.options.renameConflictingTypes,
298+
this.options.explicitStatic);
292299
this.pushContext(OutputContext.Default);
293300
this.visit(sourceFile);
294301
this.popContext();

0 commit comments

Comments
 (0)