From 983c0e60fb7b58e89a0c62a0a65eb2ee2b6e04c6 Mon Sep 17 00:00:00 2001 From: Gerrit Birkeland Date: Sun, 20 Sep 2020 19:51:12 -0600 Subject: [PATCH] fix: Export declarations within namespaces weren't detected Closes #1366 --- src/lib/converter/nodes/export.ts | 106 +++++++++++++----- .../converter/declaration/namespaces.d.ts | 8 ++ src/test/converter/declaration/specs.d.json | 94 +++++++++++++++- .../renderer/specs/modules/_functions_.html | 8 +- 4 files changed, 185 insertions(+), 31 deletions(-) create mode 100644 src/test/converter/declaration/namespaces.d.ts diff --git a/src/lib/converter/nodes/export.ts b/src/lib/converter/nodes/export.ts index 3d6388a9a..2399fe499 100644 --- a/src/lib/converter/nodes/export.ts +++ b/src/lib/converter/nodes/export.ts @@ -1,25 +1,33 @@ import * as ts from 'typescript'; -import { Reflection, ReflectionFlag, DeclarationReflection, ContainerReflection } from '../../models/index'; +import { + Reflection, + ReflectionFlag, + DeclarationReflection, + ContainerReflection +} from '../../models/index'; import { Context } from '../context'; import { Component, ConverterNodeComponent } from '../components'; import { createReferenceReflection } from '../factories/reference'; import { SourceFileMode } from '../../utils'; -@Component({name: 'node:export'}) -export class ExportConverter extends ConverterNodeComponent { +@Component({ name: 'node:export' }) +export class ExportConverter extends ConverterNodeComponent< + ts.ExportAssignment +> { /** * List of supported TypeScript syntax kinds. */ - supports: ts.SyntaxKind[] = [ - ts.SyntaxKind.ExportAssignment - ]; + supports: ts.SyntaxKind[] = [ts.SyntaxKind.ExportAssignment]; convert(context: Context, node: ts.ExportAssignment): Reflection { let symbol: ts.Symbol | undefined; // default export - if (node.symbol && (node.symbol.flags & ts.SymbolFlags.Alias) === ts.SymbolFlags.Alias) { + if ( + node.symbol && + (node.symbol.flags & ts.SymbolFlags.Alias) === ts.SymbolFlags.Alias + ) { symbol = context.checker.getAliasedSymbol(node.symbol); } else { let type = context.getTypeAtLocation(node.expression); @@ -32,8 +40,13 @@ export class ExportConverter extends ConverterNodeComponent return; } - const reflection = project.getReflectionFromFQN(context.checker.getFullyQualifiedName(declaration.symbol)); - if (node.isExportEquals && reflection instanceof DeclarationReflection) { + const reflection = project.getReflectionFromFQN( + context.checker.getFullyQualifiedName(declaration.symbol) + ); + if ( + node.isExportEquals && + reflection instanceof DeclarationReflection + ) { reflection.setFlag(ReflectionFlag.ExportAssignment, true); } if (reflection) { @@ -55,12 +68,22 @@ export class ExportConverter extends ConverterNodeComponent } @Component({ name: 'node:export-declaration' }) -export class ExportDeclarationConverter extends ConverterNodeComponent { +export class ExportDeclarationConverter extends ConverterNodeComponent< + ts.ExportDeclaration +> { supports = [ts.SyntaxKind.ExportDeclaration]; - convert(context: Context, node: ts.ExportDeclaration): Reflection | undefined { + convert( + context: Context, + node: ts.ExportDeclaration + ): Reflection | undefined { + const withinNamespace = node.parent.kind === ts.SyntaxKind.ModuleBlock; + // It doesn't make sense to convert export declarations if we are pretending everything is global. - if (this.application.options.getValue('mode') === SourceFileMode.File) { + if ( + this.application.options.getValue('mode') === SourceFileMode.File && + !withinNamespace + ) { return; } @@ -69,33 +92,64 @@ export class ExportDeclarationConverter extends ConverterNodeComponent { + if ( + node.exportClause && + node.exportClause.kind === ts.SyntaxKind.NamedExports + ) { + // export { a, a as b } + node.exportClause.elements.forEach((specifier) => { const source = context.expectSymbolAtLocation(specifier.name); - const target = context.resolveAliasedSymbol(context.expectSymbolAtLocation(specifier.propertyName ?? specifier.name)); + const target = context.resolveAliasedSymbol( + context.expectSymbolAtLocation( + specifier.propertyName ?? specifier.name + ) + ); // If the original declaration is in this file, export {} was used with something // defined in this file and we don't need to create a reference unless the name is different. - if (!node.moduleSpecifier && !specifier.propertyName) { + if ( + !node.moduleSpecifier && + !specifier.propertyName && + !withinNamespace + ) { return; } createReferenceReflection(context, source, target); }); - } else if (node.exportClause && node.exportClause.kind === ts.SyntaxKind.NamespaceExport) { // export * as ns - const source = context.expectSymbolAtLocation(node.exportClause.name); + } else if ( + node.exportClause && + node.exportClause.kind === ts.SyntaxKind.NamespaceExport + ) { + // export * as ns from ... + const source = context.expectSymbolAtLocation( + node.exportClause.name + ); if (!node.moduleSpecifier) { - throw new Error('Namespace export is missing a module specifier.'); + throw new Error( + 'Namespace export is missing a module specifier.' + ); } - const target = context.resolveAliasedSymbol(context.expectSymbolAtLocation(node.moduleSpecifier)); + const target = context.resolveAliasedSymbol( + context.expectSymbolAtLocation(node.moduleSpecifier) + ); createReferenceReflection(context, source, target); - - } else if (node.moduleSpecifier) { // export * from ... - const sourceFileSymbol = context.expectSymbolAtLocation(node.moduleSpecifier); - for (const symbol of context.checker.getExportsOfModule(sourceFileSymbol)) { - if (symbol.name === 'default') { // Default exports are not re-exported with export * + } else if (node.moduleSpecifier) { + // export * from ... + const sourceFileSymbol = context.expectSymbolAtLocation( + node.moduleSpecifier + ); + for (const symbol of context.checker.getExportsOfModule( + sourceFileSymbol + )) { + if (symbol.name === 'default') { + // Default exports are not re-exported with export * continue; } - createReferenceReflection(context, symbol, context.resolveAliasedSymbol(symbol)); + createReferenceReflection( + context, + symbol, + context.resolveAliasedSymbol(symbol) + ); } } diff --git a/src/test/converter/declaration/namespaces.d.ts b/src/test/converter/declaration/namespaces.d.ts new file mode 100644 index 000000000..cd8810a54 --- /dev/null +++ b/src/test/converter/declaration/namespaces.d.ts @@ -0,0 +1,8 @@ +export interface Foo { + prop: number; +} + +export namespace GH1366 { + // This is only allowed in an ambient context. + export { Foo }; +} diff --git a/src/test/converter/declaration/specs.d.json b/src/test/converter/declaration/specs.d.json index a6b0fa228..52e038255 100644 --- a/src/test/converter/declaration/specs.d.json +++ b/src/test/converter/declaration/specs.d.json @@ -118,6 +118,97 @@ ] } ] + }, + { + "id": 8, + "name": "\"namespaces.d\"", + "kind": 1, + "kindString": "Module", + "flags": { + "isExported": true + }, + "originalName": "%BASE%/declaration/namespaces.d.ts", + "children": [ + { + "id": 11, + "name": "GH1366", + "kind": 2, + "kindString": "Namespace", + "flags": { + "isExported": true + }, + "children": [ + { + "id": 12, + "name": "Foo", + "kind": 16777216, + "kindString": "Reference", + "flags": { + "isExported": true + }, + "target": 9 + } + ], + "groups": [ + { + "title": "References", + "kind": 16777216, + "children": [ + 12 + ] + } + ] + }, + { + "id": 9, + "name": "Foo", + "kind": 256, + "kindString": "Interface", + "flags": { + "isExported": true + }, + "children": [ + { + "id": 10, + "name": "prop", + "kind": 1024, + "kindString": "Property", + "flags": { + "isExported": true + }, + "type": { + "type": "intrinsic", + "name": "number" + } + } + ], + "groups": [ + { + "title": "Properties", + "kind": 1024, + "children": [ + 10 + ] + } + ] + } + ], + "groups": [ + { + "title": "Namespaces", + "kind": 2, + "children": [ + 11 + ] + }, + { + "title": "Interfaces", + "kind": 256, + "children": [ + 9 + ] + } + ] } ], "groups": [ @@ -126,7 +217,8 @@ "kind": 1, "children": [ 1, - 5 + 5, + 8 ] } ] diff --git a/src/test/renderer/specs/modules/_functions_.html b/src/test/renderer/specs/modules/_functions_.html index 6b8df2777..31f78c2f9 100644 --- a/src/test/renderer/specs/modules/_functions_.html +++ b/src/test/renderer/specs/modules/_functions_.html @@ -223,8 +223,8 @@
paramG: any
paramA: NameInterface

This is a parameter pointing to an interface.

-
var value:BaseClass = new BaseClass('test');
-functionWithArguments('arg', 0, value);
+
var value:BaseClass = new BaseClass('test');
+functionWithArguments('arg', 0, value);
@@ -507,8 +507,8 @@
paramA:

This is a parameter pointing to an interface.

-
var value:BaseClass = new BaseClass('test');
-functionWithArguments('arg', 0, value);
+
var value:BaseClass = new BaseClass('test');
+functionWithArguments('arg', 0, value);