Skip to content

Commit 4e80794

Browse files
committed
specially handle "import {foo}" for jsdoc
Summary: Translate import {foo} from 'bar'; as import {foo as tsickle_foo} from 'bar'; const foo = tsickle_foo; This is because JSDoc comments will use plain "foo", but the ES6->ES5 translation done by the TS emit will namespace all references to imports. More work on #112. Reviewers: rkirov, mprobst Reviewed By: mprobst Subscribers: mprobst, typescript-eng Differential Revision: https://reviews.angular.io/D128
1 parent 631bde3 commit 4e80794

File tree

9 files changed

+123
-1
lines changed

9 files changed

+123
-1
lines changed

src/tsickle.ts

+61
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,9 @@ class Annotator extends Rewriter {
191191
}
192192

193193
switch (node.kind) {
194+
case ts.SyntaxKind.ImportDeclaration:
195+
this.emitImportDeclaration(node as ts.ImportDeclaration);
196+
return true;
194197
case ts.SyntaxKind.ExportDeclaration:
195198
let exportDecl = <ts.ExportDeclaration>node;
196199
if (!exportDecl.exportClause && exportDecl.moduleSpecifier) {
@@ -360,6 +363,64 @@ class Annotator extends Rewriter {
360363
return Object.keys(reexports);
361364
}
362365

366+
/**
367+
* Handles emit of an "import ..." statement.
368+
* We need to do a bit of rewriting so that imported types show up under the
369+
* correct name in JSDoc.
370+
*/
371+
private emitImportDeclaration(decl: ts.ImportDeclaration) {
372+
if (this.options.untyped) return;
373+
374+
const importClause = decl.importClause;
375+
// Skip "import './foo';" statements.
376+
if (!importClause) {
377+
this.writeNode(decl);
378+
return;
379+
}
380+
381+
if (importClause.name) {
382+
// import foo from ...;
383+
this.writeNode(decl);
384+
this.errorUnimplementedKind(decl, 'TODO: default import');
385+
} else if (importClause.namedBindings.kind === ts.SyntaxKind.NamespaceImport) {
386+
// import * as foo from ...;
387+
// This is the format we already work fine with, so do nothing.
388+
this.writeNode(decl);
389+
} else if (importClause.namedBindings.kind === ts.SyntaxKind.NamedImports) {
390+
// import {a as b} from ...;
391+
// Rewrite it to:
392+
// import {a as tsickle_b} from ...; const b = tsickle_b;
393+
// This is because in the generated ES5, tsickle_b will instead get a generated
394+
// name like module_import.tsickle_b. But in our JSDoc we still refer to it
395+
// as just "b". Importing under a different name and then making a local alias
396+
// evades this. (TS uses the namespace-qualified name so that updates to the
397+
// module are reflected in the aliases. We only do this to types, which can't
398+
// change, so the namespace qualification is not needed.)
399+
const namedImports = importClause.namedBindings as ts.NamedImports;
400+
401+
this.writeRange(decl.getFullStart(), decl.getStart());
402+
// Emit the "import" part, with rewritten binding names.
403+
this.emit('import {');
404+
for (const imp of namedImports.elements) {
405+
// imp.propertyName is the name of the property in the module we're importing from,
406+
// while imp.name is the local name.
407+
this.emit(
408+
`${getIdentifierText(imp.propertyName || imp.name)} as tsickle_${getIdentifierText(imp.name)},`);
409+
}
410+
this.emit('} from');
411+
this.writeNode(decl.moduleSpecifier);
412+
this.emit(';\n');
413+
414+
// Emit aliases that bring the renamed name back into the local name.
415+
for (const imp of namedImports.elements) {
416+
this.emit(
417+
`const ${getIdentifierText(imp.name)} = tsickle_${getIdentifierText(imp.name)};\n`);
418+
}
419+
} else {
420+
this.errorUnimplementedKind(decl, 'unexpected kind of import');
421+
}
422+
}
423+
363424
private emitFunctionType(fnDecl: ts.SignatureDeclaration, extraTags: JSDocTag[] = []) {
364425
let typeChecker = this.program.getTypeChecker();
365426
let sig = typeChecker.getSignatureFromDeclaration(fnDecl);

test_files/export/export.js

+3
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,6 @@ exports.exportLocal = 3;
1414
// it to the exports list. export2 should only show up once in the
1515
// above two "export *" lines, though.
1616
let /** @type {number} */ export2 = 3;
17+
// This is just an import, so export5 should still be included.
18+
var export_helper_3 = export_helper_1;
19+
const export5 = export_helper_3.export5;

test_files/export/export.tsickle.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,6 @@ export var /** @type {number} */ exportLocal = 3;
1515
let /** @type {number} */ export2 = 3;
1616

1717
// This is just an import, so export5 should still be included.
18-
import {export5} from './export_helper';
18+
import {export5 as tsickle_export5,} from './export_helper';
19+
const export5 = tsickle_export5;
20+

test_files/jsdoc_types/jsdoc_types.js

+10
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,16 @@ goog.module('tsickle_test.jsdoc_types.jsdoc_types');/**
44
*/
55

66
var module1 = goog.require('tsickle_test.jsdoc_types.module1');
7+
var module2_1 = goog.require('tsickle_test.jsdoc_types.module2');
8+
const ClassOne = module2_1.ClassOne;
9+
var module2_2 = module2_1;
10+
const RenamedClassOne = module2_2.ClassOne;
11+
var module2_3 = module2_1;
12+
const RenamedClassTwo = module2_3.ClassTwo;
713
// Check that imported types get the proper names in JSDoc.
814
let /** @type {module1.Class} */ x1 = new module1.Class();
915
let /** @type {module1.Interface} */ x2 = null;
16+
// Should be refer to the names in module2.
17+
let /** @type {ClassOne} */ x3 = new ClassOne();
18+
let /** @type {ClassOne} */ x4 = new RenamedClassOne();
19+
let /** @type {RenamedClassTwo} */ x5 = new RenamedClassTwo();

test_files/jsdoc_types/jsdoc_types.ts

+8
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,15 @@
44
*/
55

66
import * as module1 from './module1';
7+
import {ClassOne} from './module2';
8+
import {ClassOne as RenamedClassOne} from './module2';
9+
import {ClassTwo as RenamedClassTwo} from './module2';
710

811
// Check that imported types get the proper names in JSDoc.
912
let x1 = new module1.Class();
1013
let x2: module1.Interface = null;
14+
15+
// Should be refer to the names in module2.
16+
let x3 = new ClassOne();
17+
let x4 = new RenamedClassOne();
18+
let x5 = new RenamedClassTwo();

test_files/jsdoc_types/jsdoc_types.tsickle.ts

+14
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,21 @@
44
*/
55

66
import * as module1 from './module1';
7+
import {ClassOne as tsickle_ClassOne,} from './module2';
8+
const ClassOne = tsickle_ClassOne;
9+
10+
import {ClassOne as tsickle_RenamedClassOne,} from './module2';
11+
const RenamedClassOne = tsickle_RenamedClassOne;
12+
13+
import {ClassTwo as tsickle_RenamedClassTwo,} from './module2';
14+
const RenamedClassTwo = tsickle_RenamedClassTwo;
15+
716

817
// Check that imported types get the proper names in JSDoc.
918
let /** @type {module1.Class} */ x1 = new module1.Class();
1019
let /** @type {module1.Interface} */ x2: module1.Interface = null;
20+
21+
// Should be refer to the names in module2.
22+
let /** @type {ClassOne} */ x3 = new ClassOne();
23+
let /** @type {ClassOne} */ x4 = new RenamedClassOne();
24+
let /** @type {RenamedClassTwo} */ x5 = new RenamedClassTwo();

test_files/jsdoc_types/module2.js

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
goog.module('tsickle_test.jsdoc_types.module2');
2+
class ClassOne {
3+
}
4+
exports.ClassOne = ClassOne;
5+
class ClassTwo {
6+
}
7+
exports.ClassTwo = ClassTwo;
8+
/** @record */
9+
function Interface() { }
10+
exports.Interface = Interface;
11+
/** @type {number} */
12+
Interface.prototype.x;

test_files/jsdoc_types/module2.ts

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export class ClassOne {}
2+
export class ClassTwo {}
3+
export interface Interface { x: number }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export class ClassOne {}
2+
export class ClassTwo {}
3+
/** @record */
4+
function Interface() {}
5+
/** @type {number} */
6+
Interface.prototype.x;
7+
export {Interface};
8+
9+
export interface Interface { x: number }

0 commit comments

Comments
 (0)