Skip to content

Commit

Permalink
fix: Export declarations within namespaces weren't detected
Browse files Browse the repository at this point in the history
Closes #1366
  • Loading branch information
Gerrit0 committed Sep 21, 2020
1 parent 6fb388e commit 983c0e6
Show file tree
Hide file tree
Showing 4 changed files with 185 additions and 31 deletions.
106 changes: 80 additions & 26 deletions src/lib/converter/nodes/export.ts
Original file line number Diff line number Diff line change
@@ -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<ts.ExportAssignment> {
@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);
Expand All @@ -32,8 +40,13 @@ export class ExportConverter extends ConverterNodeComponent<ts.ExportAssignment>
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) {
Expand All @@ -55,12 +68,22 @@ export class ExportConverter extends ConverterNodeComponent<ts.ExportAssignment>
}

@Component({ name: 'node:export-declaration' })
export class ExportDeclarationConverter extends ConverterNodeComponent<ts.ExportDeclaration> {
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;
}

Expand All @@ -69,33 +92,64 @@ export class ExportDeclarationConverter extends ConverterNodeComponent<ts.Export
throw new Error('Expected to be within a container');
}

if (node.exportClause && node.exportClause.kind === ts.SyntaxKind.NamedExports) { // export { a, a as b }
node.exportClause.elements.forEach(specifier => {
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)
);
}
}

Expand Down
8 changes: 8 additions & 0 deletions src/test/converter/declaration/namespaces.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export interface Foo {
prop: number;
}

export namespace GH1366 {
// This is only allowed in an ambient context.
export { Foo };
}
94 changes: 93 additions & 1 deletion src/test/converter/declaration/specs.d.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": [
Expand All @@ -126,7 +217,8 @@
"kind": 1,
"children": [
1,
5
5,
8
]
}
]
Expand Down
8 changes: 4 additions & 4 deletions src/test/renderer/specs/modules/_functions_.html
Original file line number Diff line number Diff line change
Expand Up @@ -223,8 +223,8 @@ <h5>paramG: <span class="tsd-signature-type">any</span></h5>
<h5>paramA: <a href="../interfaces/_classes_.nameinterface.html" class="tsd-signature-type">NameInterface</a></h5>
<div class="tsd-comment tsd-typography">
<p>This is a <strong>parameter</strong> pointing to an interface.</p>
<pre><code>var value:BaseClass = <span class="hljs-keyword">new</span> <span class="hljs-constructor">BaseClass('<span class="hljs-params">test</span>')</span>;
<span class="hljs-keyword">function</span><span class="hljs-constructor">WithArguments('<span class="hljs-params">arg</span>', 0, <span class="hljs-params">value</span>)</span>;</code></pre>
<pre><code>var value:BaseClass = <span class="hljs-keyword">new</span> <span class="hljs-constructor">BaseClass(&#x27;<span class="hljs-params">test</span>&#x27;)</span>;
<span class="hljs-keyword">function</span><span class="hljs-constructor">WithArguments(&#x27;<span class="hljs-params">arg</span>&#x27;, 0, <span class="hljs-params">value</span>)</span>;</code></pre>
</div>
</li>
</ul>
Expand Down Expand Up @@ -507,8 +507,8 @@ <h5>paramA: <a href="../interfaces/_classes_.nameinterface.html" class="tsd-sign
<div class="tsd-comment tsd-typography">
<div class="lead">
<p>This is a <strong>parameter</strong> pointing to an interface.</p>
<pre><code>var value:BaseClass = <span class="hljs-keyword">new</span> <span class="hljs-constructor">BaseClass('<span class="hljs-params">test</span>')</span>;
<span class="hljs-keyword">function</span><span class="hljs-constructor">WithArguments('<span class="hljs-params">arg</span>', 0, <span class="hljs-params">value</span>)</span>;</code></pre>
<pre><code>var value:BaseClass = <span class="hljs-keyword">new</span> <span class="hljs-constructor">BaseClass(&#x27;<span class="hljs-params">test</span>&#x27;)</span>;
<span class="hljs-keyword">function</span><span class="hljs-constructor">WithArguments(&#x27;<span class="hljs-params">arg</span>&#x27;, 0, <span class="hljs-params">value</span>)</span>;</code></pre>
</div>
</div>
</li>
Expand Down

0 comments on commit 983c0e6

Please sign in to comment.