From 8b39cf2547b536c70c7fbce48e13bb939d5b0d7c Mon Sep 17 00:00:00 2001 From: Arthur Fiorette Date: Wed, 22 May 2024 19:48:32 -0300 Subject: [PATCH] temp --- src/SchemaGenerator.ts | 74 ++++++++++++++++++++--- test/valid-data-type.test.ts | 1 + test/valid-data/export-braces/literal.ts | 5 ++ test/valid-data/export-braces/main.ts | 8 +++ test/valid-data/export-braces/object.ts | 11 ++++ test/valid-data/export-braces/schema.json | 43 +++++++++++++ 6 files changed, 134 insertions(+), 8 deletions(-) create mode 100644 test/valid-data/export-braces/literal.ts create mode 100644 test/valid-data/export-braces/main.ts create mode 100644 test/valid-data/export-braces/object.ts create mode 100644 test/valid-data/export-braces/schema.json diff --git a/src/SchemaGenerator.ts b/src/SchemaGenerator.ts index d8be8606c..f0589c50c 100644 --- a/src/SchemaGenerator.ts +++ b/src/SchemaGenerator.ts @@ -138,7 +138,12 @@ export class SchemaGenerator { this.inspectNode(sourceFile, typeChecker, types); } } - protected inspectNode(node: ts.Node, typeChecker: ts.TypeChecker, allTypes: Map): void { + protected inspectNode( + node: ts.Node, + typeChecker: ts.TypeChecker, + allTypes: Map, + forceExpose = false, + ): void { switch (node.kind) { case ts.SyntaxKind.VariableDeclaration: { const variableDeclarationNode = node as ts.VariableDeclaration; @@ -155,6 +160,7 @@ export class SchemaGenerator { case ts.SyntaxKind.EnumDeclaration: case ts.SyntaxKind.TypeAliasDeclaration: if ( + forceExpose || this.config?.expose === "all" || (this.isExportType(node) && !this.isGenericType(node as ts.TypeAliasDeclaration)) ) { @@ -171,18 +177,55 @@ export class SchemaGenerator { case ts.SyntaxKind.ExportSpecifier: { const exportSpecifierNode = node as ts.ExportSpecifier; const symbol = typeChecker.getExportSpecifierLocalTargetSymbol(exportSpecifierNode); - if (symbol?.declarations?.length === 1) { - const declaration = symbol.declarations[0]; + + // should never hit this (maybe type error in user's code) + if (!symbol || !symbol.declarations) { + return; + } + + for (const declaration of symbol.declarations) { if (declaration.kind === ts.SyntaxKind.ImportSpecifier) { // Handling the `Foo` in `import { Foo } from "./lib"; export { Foo };` const importSpecifierNode = declaration as ts.ImportSpecifier; - const type = typeChecker.getTypeAtLocation(importSpecifierNode); - if (type.symbol?.declarations?.length === 1) { - this.inspectNode(type.symbol.declarations[0], typeChecker, allTypes); + + const symbol = + typeChecker.getTypeAtLocation(importSpecifierNode).symbol || + typeChecker.getSymbolAtLocation(importSpecifierNode) || + importSpecifierNode.symbol; + + // should never hit this (maybe type error in user's code) + if (!symbol?.declarations) { + return; + } + + for (const declaration of symbol.declarations) { + // all statements here were inside a export { Foo } statement, + // so they must be exported + + // recursion doesn't work here + if (ts.isImportSpecifier(declaration)) { + // directly import/exported nodes. + const symbol = + typeChecker.getTypeAtLocation(declaration).symbol || + typeChecker.getSymbolAtLocation(declaration) || + declaration.symbol; + + // should never hit this (maybe type error in user's code) + if (!symbol?.declarations) { + console.log(symbol); + return; + } + + for (const subdecl of symbol.declarations) { + console.log(subdecl.kind, subdecl.getText()); + } + } else { + this.inspectNode(declaration, typeChecker, allTypes, true); + } } } else { // Handling the `Bar` in `export { Bar } from './lib';` - this.inspectNode(declaration, typeChecker, allTypes); + this.inspectNode(declaration, typeChecker, allTypes, true); } } return; @@ -192,8 +235,23 @@ export class SchemaGenerator { return; } - // export { variable } clauses if (!node.moduleSpecifier) { + if (!node.exportClause) { + throw new Error( + `ExportDeclaration has no moduleSpecifier or exportClause: ${node.pos === -1 ? "" : node.getText()}`, + ); + } + + if (ts.isNamespaceExport(node.exportClause)) { + throw new Error( + `Namespace exports are not supported: ${node.pos === -1 ? "" : node.getText()}`, + ); + } + + for (const element of node.exportClause.elements) { + this.inspectNode(element, typeChecker, allTypes); + } + return; } diff --git a/test/valid-data-type.test.ts b/test/valid-data-type.test.ts index 39e626b47..ed1f10b62 100644 --- a/test/valid-data-type.test.ts +++ b/test/valid-data-type.test.ts @@ -147,4 +147,5 @@ describe("valid-data-type", () => { it("promise-extensions", assertValidSchema("promise-extensions", "*")); it("export-star", assertValidSchema("export-star", "*", undefined, { mainTsOnly: true })); + it.only("export-braces", assertValidSchema("export-braces", "*", undefined, { mainTsOnly: true })); }); diff --git a/test/valid-data/export-braces/literal.ts b/test/valid-data/export-braces/literal.ts new file mode 100644 index 000000000..e7a9fe6eb --- /dev/null +++ b/test/valid-data/export-braces/literal.ts @@ -0,0 +1,5 @@ +export type A = 1; + +export type B = "string"; + +type C = "internal"; diff --git a/test/valid-data/export-braces/main.ts b/test/valid-data/export-braces/main.ts new file mode 100644 index 000000000..115bda93b --- /dev/null +++ b/test/valid-data/export-braces/main.ts @@ -0,0 +1,8 @@ +import { A, B } from "./literal"; +import { D, E } from "./object"; + +type External = 1; + +type Internal = 2; + +export { A, B, D, E, type External }; diff --git a/test/valid-data/export-braces/object.ts b/test/valid-data/export-braces/object.ts new file mode 100644 index 000000000..e9ebcacea --- /dev/null +++ b/test/valid-data/export-braces/object.ts @@ -0,0 +1,11 @@ +export interface D { + a: 1; +} + +export class E { + b: 2; +} + +interface F { + internal: true; +} diff --git a/test/valid-data/export-braces/schema.json b/test/valid-data/export-braces/schema.json new file mode 100644 index 000000000..0c34121c2 --- /dev/null +++ b/test/valid-data/export-braces/schema.json @@ -0,0 +1,43 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "A": { + "const": 1, + "type": "number" + }, + "B": { + "const": "string", + "type": "string" + }, + "D": { + "additionalProperties": false, + "properties": { + "a": { + "const": 1, + "type": "number" + } + }, + "required": [ + "a" + ], + "type": "object" + }, + "E": { + "additionalProperties": false, + "properties": { + "b": { + "const": 2, + "type": "number" + } + }, + "required": [ + "b" + ], + "type": "object" + }, + "External": { + "const": 1, + "type": "number" + } + } +}