Skip to content

Commit

Permalink
feat: Mostly working library mode
Browse files Browse the repository at this point in the history
  • Loading branch information
Gerrit0 committed Oct 23, 2020
1 parent ada7d04 commit bcbd401
Show file tree
Hide file tree
Showing 16 changed files with 133 additions and 224 deletions.
10 changes: 10 additions & 0 deletions .vscode/launch.json
Expand Up @@ -4,6 +4,16 @@
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Attach",
"port": 9229,
"request": "attach",
"skipFiles": [
"<node_internals>/**"
],
"type": "pwa-node",
"sourceMaps": true
},
{
"type": "node",
"request": "launch",
Expand Down
2 changes: 1 addition & 1 deletion src/lib/converter/context.ts
Expand Up @@ -245,7 +245,7 @@ export class Context {
* @param node The TypeScript node containing the source file declaration.
* @param callback The callback that should be executed.
*/
withSourceFile(node: ts.SourceFile, callback: Function) {
withSourceFile(node: ts.SourceFile, callback: () => void) {
const isExternal = this.isExternalFile(node.fileName);
if (this.isOutsideDocumentation(node.fileName, isExternal)) {
return;
Expand Down
3 changes: 2 additions & 1 deletion src/lib/converter/converter.ts
Expand Up @@ -225,6 +225,7 @@ export class Converter extends ChildableComponent<
}

const project = this.resolve(context);
// This should only do anything if a plugin does something bad.
project.removeDanglingReferences();

this.trigger(Converter.EVENT_END, context);
Expand Down Expand Up @@ -254,7 +255,7 @@ export class Converter extends ChildableComponent<
result = this.nodeConverters[node.kind].convert(context, node);
} else {
this.application.logger.warn(
`Missing converter for node having kind ${
`Missing converter for node with kind ${
ts.SyntaxKind[node.kind]
}`
);
Expand Down
4 changes: 2 additions & 2 deletions src/lib/converter/factories/declaration.ts
Expand Up @@ -49,10 +49,10 @@ function shouldBeIgnoredAsNotDocumented(
node: ts.Declaration,
kind: ReflectionKind
): boolean {
// never ignore modules, global, and enum members
// never ignore modules, the project, or enum members
if (
kind === ReflectionKind.Module ||
kind === ReflectionKind.Global ||
kind === ReflectionKind.Project ||
kind === ReflectionKind.EnumMember
) {
return false;
Expand Down
23 changes: 9 additions & 14 deletions src/lib/converter/factories/reference.ts
Expand Up @@ -5,11 +5,9 @@ import {
ReferenceReflection,
ContainerReflection,
DeclarationReflection,
ReflectionKind,
} from "../../models";
import { Context } from "../context";
import { Converter } from "../converter";
import { createDeclaration } from "./declaration";

/**
* Create a new reference type pointing to the given symbol.
Expand Down Expand Up @@ -59,30 +57,27 @@ export function createReferenceOrDeclarationReflection(
return;
}

let reflection: DeclarationReflection | undefined;
let reflection: DeclarationReflection | undefined = undefined;
if (context.project.getReflectionFromSymbol(target)) {
reflection = new ReferenceReflection(
source.name,
target,
context.scope
);

// target === source happens when doing export * from ...
// and the original symbol is not renamed and exported from the imported module.
context.registerReflection(
reflection,
target === source ? undefined : source
);
context.registerReflection(reflection, source);
context.scope.children ??= [];
context.scope.children.push(reflection);
context.trigger(Converter.EVENT_CREATE_DECLARATION, reflection);
} else {
reflection = createDeclaration(
} else if (target.getDeclarations()?.[0]) {
const refl = context.converter.convertNode(
context,
target.valueDeclaration,
ReflectionKind.Variable,
source.name
target.declarations[0]
);
if (refl instanceof DeclarationReflection) {
refl.name = source.name;
reflection = refl;
}
}

return reflection;
Expand Down
89 changes: 71 additions & 18 deletions src/lib/converter/nodes/block.ts
Expand Up @@ -4,6 +4,9 @@ import { Reflection, ReflectionKind } from "../../models/index";
import { createDeclaration } from "../factories/index";
import { Context } from "../context";
import { Component, ConverterNodeComponent } from "../components";
import { Converter } from "..";
import { getCommonDirectory } from "../../utils/fs";
import { relative, resolve } from "path";

@Component({ name: "node:block" })
export class BlockConverter extends ConverterNodeComponent<
Expand All @@ -17,6 +20,20 @@ export class BlockConverter extends ConverterNodeComponent<
ts.SyntaxKind.SourceFile,
];

// Created in initialize
private entryPoints!: string[];
private baseDir!: string;

initialize() {
super.initialize();
this.owner.on(Converter.EVENT_BEGIN, () => {
this.entryPoints = this.application.options
.getValue("entryPoints")
.map((path) => this.normalizeFileName(resolve(path)));
this.baseDir = getCommonDirectory(this.entryPoints);
});
}

/**
* Analyze the given class declaration node and create a suitable reflection.
*
Expand All @@ -27,9 +44,9 @@ export class BlockConverter extends ConverterNodeComponent<
convert(
context: Context,
node: ts.SourceFile | ts.ModuleBlock
): Reflection {
): Reflection | undefined {
if (node.kind === ts.SyntaxKind.SourceFile) {
this.convertSourceFile(context, node);
return this.convertSourceFile(context, node);
} else {
for (const exp of this.getExports(context, node)) {
for (const decl of exp.getDeclarations() ?? []) {
Expand All @@ -55,27 +72,42 @@ export class BlockConverter extends ConverterNodeComponent<
let result: Reflection | undefined = context.scope;

context.withSourceFile(node, () => {
if (context.inFirstPass) {
result = createDeclaration(
context,
node,
ReflectionKind.Module,
node.fileName
);
context.withScope(result, () => {
this.convertExports(context, node);
});
} else {
if (this.isEntryPoint(node.fileName)) {
const symbol =
context.checker.getSymbolAtLocation(node) ?? node.symbol;

if (symbol) {
if (context.inFirstPass) {
if (this.entryPoints.length === 1) {
result = context.project;
context.project.registerReflection(result, symbol);
} else {
result = createDeclaration(
context,
node,
ReflectionKind.Module,
this.getModuleName(node.fileName)
);
}
context.withScope(result, () => {
this.convertExports(context, node);
});
} else if (symbol) {
result = context.project.getReflectionFromSymbol(symbol);

context.withScope(result, () => {
this.convertReExports(context, node);
});
}
} else {
result = createDeclaration(
context,
node,
ReflectionKind.Module,
this.getModuleName(node.fileName)
);
context.withScope(result, () => {
this.convertExports(context, node);
});
}
});

Expand All @@ -88,8 +120,11 @@ export class BlockConverter extends ConverterNodeComponent<
) {
// We really need to rebuild the converters to work on a symbol basis rather than a node
// basis... this relies on us getting declaration merging right, which is dangerous at best
for (const exp of this.getExports(context, node).filter(
(exp) => context.resolveAliasedSymbol(exp) === exp
for (const exp of this.getExports(context, node).filter((exp) =>
context
.resolveAliasedSymbol(exp)
.getDeclarations()
?.every((d) => d.getSourceFile() === node.getSourceFile())
)) {
for (const decl of exp.getDeclarations() ?? []) {
this.owner.convertNode(context, decl);
Expand All @@ -101,8 +136,11 @@ export class BlockConverter extends ConverterNodeComponent<
context: Context,
node: ts.SourceFile | ts.ModuleBlock
) {
for (const exp of this.getExports(context, node).filter(
(exp) => context.resolveAliasedSymbol(exp) !== exp
for (const exp of this.getExports(context, node).filter((exp) =>
context
.resolveAliasedSymbol(exp)
.getDeclarations()
?.some((d) => d.getSourceFile() !== node.getSourceFile())
)) {
for (const decl of exp.getDeclarations() ?? []) {
this.owner.convertNode(context, decl);
Expand Down Expand Up @@ -134,4 +172,19 @@ export class BlockConverter extends ConverterNodeComponent<
?.some((d) => d.getSourceFile() === sourceFile)
);
}

private getModuleName(fileName: string) {
return this.normalizeFileName(relative(this.baseDir, fileName)).replace(
/\.[tj]sx?$/,
""
);
}

private isEntryPoint(fileName: string) {
return this.entryPoints.includes(fileName);
}

private normalizeFileName(fileName: string) {
return fileName.replace(/\\/g, "/");
}
}
83 changes: 0 additions & 83 deletions src/lib/converter/nodes/export.ts
Expand Up @@ -4,7 +4,6 @@ import {
Reflection,
ReflectionFlag,
DeclarationReflection,
ContainerReflection,
} from "../../models/index";
import { Context } from "../context";
import { Component, ConverterNodeComponent } from "../components";
Expand Down Expand Up @@ -55,88 +54,6 @@ export class ExportConverter extends ConverterNodeComponent<
}
}

@Component({ name: "node:export-declaration" })
export class ExportDeclarationConverter extends ConverterNodeComponent<
ts.ExportDeclaration
> {
supports = [ts.SyntaxKind.ExportDeclaration];

convert(
context: Context,
node: ts.ExportDeclaration
): Reflection | undefined {
const withinNamespace = node.parent.kind === ts.SyntaxKind.ModuleBlock;

const scope = context.scope;
if (!(scope instanceof ContainerReflection)) {
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) => {
const source = context.expectSymbolAtLocation(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 &&
!withinNamespace
) {
return;
}

createReferenceOrDeclarationReflection(context, source, target);
});
} 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."
);
}
const target = context.resolveAliasedSymbol(
context.expectSymbolAtLocation(node.moduleSpecifier)
);
createReferenceOrDeclarationReflection(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 *
continue;
}
createReferenceOrDeclarationReflection(
context,
symbol,
context.resolveAliasedSymbol(symbol)
);
}
}

return context.scope;
}
}

@Component({ name: "node:export-specifier" })
export class ExportSpecifierConverter extends ConverterNodeComponent<
ts.ExportSpecifier
Expand Down

0 comments on commit bcbd401

Please sign in to comment.