diff --git a/packages/api/src/localization.ts b/packages/api/src/localization.ts index 5d33ef1..5da5bf5 100644 --- a/packages/api/src/localization.ts +++ b/packages/api/src/localization.ts @@ -46,6 +46,8 @@ export const KindText: Record< interface: "interface", method: "method", jsx_component: "component", + namespace: "namespace", + module: "module", } export function getKindText( diff --git a/packages/api/src/localizedTree.ts b/packages/api/src/localizedTree.ts index 3bedd0c..77a151f 100644 --- a/packages/api/src/localizedTree.ts +++ b/packages/api/src/localizedTree.ts @@ -311,6 +311,11 @@ function getChildren( return getLocalizedTypeParameter(info, typeArgument) } + case "module": + case "namespace": { + return info.exports.map(localize) + } + default: { return undefined } diff --git a/packages/api/src/tree.ts b/packages/api/src/tree.ts index b93f2ce..b2db77e 100644 --- a/packages/api/src/tree.ts +++ b/packages/api/src/tree.ts @@ -54,6 +54,8 @@ import { isReadonlySymbol, isReadonlyArrayType, isReadonlyTupleType, + getSymbolExports, + isNamespace, } from "./util" const maxDepthExceeded: TypeInfo = { kind: "max_depth", id: getEmptyTypeId() } @@ -96,6 +98,8 @@ function _generateTypeTree( type = getSymbolType(tsCtx, symbol!) } + const typeSymbol = type.symbol as ts.Symbol | undefined + let isAnonymousSymbol = !symbol if (!symbol) { @@ -226,6 +230,15 @@ function _generateTypeTree( return typeInfoId function createNode(type: ts.Type): TypeInfoNoId { + for (const s of [symbol, typeSymbol]) { + if (s && s.flags & ts.SymbolFlags.Module) { + return { + kind: isNamespace(tsCtx, s) ? "namespace" : "module", + exports: parseSymbols(getSymbolExports(s)), + } + } + } + const flags = type.getFlags() if (flags & ts.TypeFlags.TypeParameter) { @@ -316,7 +329,6 @@ function _generateTypeTree( value: (type as ts.BigIntLiteralType).value, } } else if (flags & ts.TypeFlags.Object) { - const { symbol: typeSymbol } = type if (typeSymbol && typeSymbol.flags & SymbolFlags.Enum) { return { kind: "enum", @@ -827,6 +839,11 @@ export function getTypeInfoChildren(info: TypeInfo): TypeInfo[] { case "string_mapping": { return [info.type] } + + case "module": + case "namespace": { + return [...info.exports] + } } return [] diff --git a/packages/api/src/types.ts b/packages/api/src/types.ts index dd566e8..8f42271 100644 --- a/packages/api/src/types.ts +++ b/packages/api/src/types.ts @@ -191,6 +191,14 @@ export type TypeInfoNoId = { | { kind: "max_depth" } + | { + kind: "namespace" + exports: TypeInfo[] + } + | { + kind: "module" + exports: TypeInfo[] + } ) export type TypeId = string diff --git a/packages/api/src/util.ts b/packages/api/src/util.ts index 0d7c721..0985660 100644 --- a/packages/api/src/util.ts +++ b/packages/api/src/util.ts @@ -735,7 +735,7 @@ export function getSignatureTypeArguments( ) ?.typeArguments?.map((t) => getTypeFromTypeNode(ctx, t, enclosingDeclaration) - ) + ) // TODO: error here... } export function getDescendantAtPosition( @@ -846,7 +846,7 @@ export function getSymbolOrTypeOfNode( if (symbol) { const symbolType = getSymbolType(ctx, symbol, node) - if (isValidType(symbolType)) { + if (isValidType(symbolType) || symbol.flags & ts.SymbolFlags.Module) { return { symbol, node } } } @@ -922,3 +922,44 @@ export function isReadonlySymbol( // some(symbol.declarations, isReadonlyAssignmentDeclaration) ) } + +export function getSymbolExports(symbol: ts.Symbol): ts.Symbol[] { + const result: ts.Symbol[] = [] + + symbol.exports?.forEach((value) => result.push(value)) + symbol.globalExports?.forEach((value) => result.push(value)) + + return result +} + +export function isNamespace( + { ts }: TypescriptContext, + symbol: ts.Symbol +): boolean { + const declaration = getDeclarationOfKind( + symbol, + ts.SyntaxKind.ModuleDeclaration + ) + const isNamespace = + declaration && + declaration.name && + declaration.name.kind === ts.SyntaxKind.Identifier + + return !!isNamespace +} + +function getDeclarationOfKind( + symbol: ts.Symbol, + kind: T["kind"] +): T | undefined { + const declarations = symbol.declarations + if (declarations) { + for (const declaration of declarations) { + if (declaration.kind === kind) { + return declaration as T + } + } + } + + return undefined +} diff --git a/packages/typescript-explorer-vscode/src/view/typeTreeView.ts b/packages/typescript-explorer-vscode/src/view/typeTreeView.ts index 25187bc..f36ac6c 100644 --- a/packages/typescript-explorer-vscode/src/view/typeTreeView.ts +++ b/packages/typescript-explorer-vscode/src/view/typeTreeView.ts @@ -436,6 +436,14 @@ function getMeta(info: LocalizedTypeInfo, depth: number): TypeTreeItemMeta { return ["symbol-interface"] } + case "namespace": { + return ["symbol-namespace"] + } + + case "module": { + return ["symbol-module"] + } + case "class": { return ["symbol-class"] } diff --git a/tests/baselines/reference/module.localized.tree b/tests/baselines/reference/module.localized.tree new file mode 100644 index 0000000..db8246f --- /dev/null +++ b/tests/baselines/reference/module.localized.tree @@ -0,0 +1,283 @@ +=== module.ts === + +import { c } from "./moduleExport" +> { c } +> { c } +> c +> c +> c --- { + "kindText": "namespace", + "kind": "namespace", + "symbol": { + "name": "c", + "locations": [ + { + "fileName": "cases/module.ts", + "range": { + "start": { + "line": 0, + "character": 9 + }, + "end": { + "line": 0, + "character": 10 + } + } + } + ] + }, + "locations": [ + { + "fileName": "cases/module.ts", + "range": { + "start": { + "line": 0, + "character": 9 + }, + "end": { + "line": 0, + "character": 10 + } + } + } + ], + "children": [ + { + "kindText": "4", + "kind": "number_literal", + "symbol": { + "name": "val", + "locations": [ + { + "fileName": "cases/moduleExport.ts", + "range": { + "start": { + "line": 4, + "character": 17 + }, + "end": { + "line": 4, + "character": 20 + } + } + } + ] + }, + "locations": [ + { + "fileName": "cases/moduleExport.ts", + "range": { + "start": { + "line": 4, + "character": 17 + }, + "end": { + "line": 4, + "character": 20 + } + } + } + ], + "children": [], + "_id": "1" + } + ], + "_id": "0" +} +> "./moduleExport" --- { + "kindText": "module", + "kind": "module", + "symbol": { + "name": "\"/Users/maxstoumen/Projects/ts-type-explorer/tests/cases/moduleExport\"", + "locations": [ + { + "fileName": "cases/moduleExport.ts", + "range": { + "start": { + "line": 0, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + } + } + ] + }, + "locations": [ + { + "fileName": "cases/moduleExport.ts", + "range": { + "start": { + "line": 0, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + } + } + ], + "children": [ + { + "kindText": "5", + "kind": "number_literal", + "symbol": { + "name": "a", + "locations": [ + { + "fileName": "cases/moduleExport.ts", + "range": { + "start": { + "line": 0, + "character": 13 + }, + "end": { + "line": 0, + "character": 14 + } + } + } + ] + }, + "locations": [ + { + "fileName": "cases/moduleExport.ts", + "range": { + "start": { + "line": 0, + "character": 13 + }, + "end": { + "line": 0, + "character": 14 + } + } + } + ], + "children": [], + "_id": "1" + }, + { + "kindText": "\"string\"", + "kind": "string_literal", + "symbol": { + "name": "b", + "locations": [ + { + "fileName": "cases/moduleExport.ts", + "range": { + "start": { + "line": 1, + "character": 13 + }, + "end": { + "line": 1, + "character": 14 + } + } + } + ] + }, + "locations": [ + { + "fileName": "cases/moduleExport.ts", + "range": { + "start": { + "line": 1, + "character": 13 + }, + "end": { + "line": 1, + "character": 14 + } + } + } + ], + "children": [], + "_id": "2" + }, + { + "kindText": "namespace", + "kind": "namespace", + "symbol": { + "name": "c", + "locations": [ + { + "fileName": "cases/moduleExport.ts", + "range": { + "start": { + "line": 3, + "character": 17 + }, + "end": { + "line": 3, + "character": 18 + } + } + } + ] + }, + "locations": [ + { + "fileName": "cases/moduleExport.ts", + "range": { + "start": { + "line": 3, + "character": 17 + }, + "end": { + "line": 3, + "character": 18 + } + } + } + ], + "children": [ + { + "kindText": "4", + "kind": "number_literal", + "symbol": { + "name": "val", + "locations": [ + { + "fileName": "cases/moduleExport.ts", + "range": { + "start": { + "line": 4, + "character": 17 + }, + "end": { + "line": 4, + "character": 20 + } + } + } + ] + }, + "locations": [ + { + "fileName": "cases/moduleExport.ts", + "range": { + "start": { + "line": 4, + "character": 17 + }, + "end": { + "line": 4, + "character": 20 + } + } + } + ], + "children": [], + "_id": "4" + } + ], + "_id": "3" + } + ], + "_id": "0" +} \ No newline at end of file diff --git a/tests/baselines/reference/module.tree b/tests/baselines/reference/module.tree new file mode 100644 index 0000000..f084f49 --- /dev/null +++ b/tests/baselines/reference/module.tree @@ -0,0 +1,192 @@ +=== module.ts === + +import { c } from "./moduleExport" +> { c } +> { c } +> c +> c +> c --- { + "kind": "namespace", + "exports": [ + { + "kind": "number_literal", + "value": 4, + "symbolMeta": { + "name": "val", + "flags": 2, + "declarations": [ + { + "location": { + "fileName": "cases/moduleExport.ts", + "range": { + "start": { + "line": 4, + "character": 17 + }, + "end": { + "line": 4, + "character": 20 + } + } + } + } + ] + }, + "id": "1" + } + ], + "symbolMeta": { + "name": "c", + "flags": 2097152, + "declarations": [ + { + "location": { + "fileName": "cases/module.ts", + "range": { + "start": { + "line": 0, + "character": 9 + }, + "end": { + "line": 0, + "character": 10 + } + } + } + } + ] + }, + "id": "0" +} +> "./moduleExport" --- { + "kind": "module", + "exports": [ + { + "kind": "number_literal", + "value": 5, + "symbolMeta": { + "name": "a", + "flags": 2, + "declarations": [ + { + "location": { + "fileName": "cases/moduleExport.ts", + "range": { + "start": { + "line": 0, + "character": 13 + }, + "end": { + "line": 0, + "character": 14 + } + } + } + } + ] + }, + "id": "1" + }, + { + "kind": "string_literal", + "value": "string", + "symbolMeta": { + "name": "b", + "flags": 2, + "declarations": [ + { + "location": { + "fileName": "cases/moduleExport.ts", + "range": { + "start": { + "line": 1, + "character": 13 + }, + "end": { + "line": 1, + "character": 14 + } + } + } + } + ] + }, + "id": "2" + }, + { + "kind": "namespace", + "exports": [ + { + "kind": "number_literal", + "value": 4, + "symbolMeta": { + "name": "val", + "flags": 2, + "declarations": [ + { + "location": { + "fileName": "cases/moduleExport.ts", + "range": { + "start": { + "line": 4, + "character": 17 + }, + "end": { + "line": 4, + "character": 20 + } + } + } + } + ] + }, + "id": "4" + } + ], + "symbolMeta": { + "name": "c", + "flags": 512, + "declarations": [ + { + "location": { + "fileName": "cases/moduleExport.ts", + "range": { + "start": { + "line": 3, + "character": 17 + }, + "end": { + "line": 3, + "character": 18 + } + } + } + } + ] + }, + "id": "3" + } + ], + "symbolMeta": { + "name": "\"/Users/maxstoumen/Projects/ts-type-explorer/tests/cases/moduleExport\"", + "flags": 512, + "declarations": [ + { + "location": { + "fileName": "cases/moduleExport.ts", + "range": { + "start": { + "line": 0, + "character": 0 + }, + "end": { + "line": 5, + "character": 1 + } + } + } + } + ] + }, + "id": "0" +} \ No newline at end of file diff --git a/tests/baselines/reference/moduleExport.localized.tree b/tests/baselines/reference/moduleExport.localized.tree new file mode 100644 index 0000000..cd3167d --- /dev/null +++ b/tests/baselines/reference/moduleExport.localized.tree @@ -0,0 +1,219 @@ +=== moduleExport.ts === + +export const a = 5 +> const a = 5 +> a = 5 +> a = 5 +> a --- { + "kindText": "5", + "kind": "number_literal", + "symbol": { + "name": "a", + "locations": [ + { + "fileName": "cases/moduleExport.ts", + "range": { + "start": { + "line": 0, + "character": 13 + }, + "end": { + "line": 0, + "character": 14 + } + } + } + ] + }, + "locations": [ + { + "fileName": "cases/moduleExport.ts", + "range": { + "start": { + "line": 0, + "character": 13 + }, + "end": { + "line": 0, + "character": 14 + } + } + } + ], + "children": [], + "_id": "0" +} + +export const b = "string" +> const b = "string" +> b = "string" +> b = "string" +> b --- { + "kindText": "\"string\"", + "kind": "string_literal", + "symbol": { + "name": "b", + "locations": [ + { + "fileName": "cases/moduleExport.ts", + "range": { + "start": { + "line": 1, + "character": 13 + }, + "end": { + "line": 1, + "character": 14 + } + } + } + ] + }, + "locations": [ + { + "fileName": "cases/moduleExport.ts", + "range": { + "start": { + "line": 1, + "character": 13 + }, + "end": { + "line": 1, + "character": 14 + } + } + } + ], + "children": [], + "_id": "0" +} + +export namespace c { + export const val = 4 +} +> c --- { + "kindText": "namespace", + "kind": "namespace", + "symbol": { + "name": "c", + "locations": [ + { + "fileName": "cases/moduleExport.ts", + "range": { + "start": { + "line": 3, + "character": 17 + }, + "end": { + "line": 3, + "character": 18 + } + } + } + ] + }, + "locations": [ + { + "fileName": "cases/moduleExport.ts", + "range": { + "start": { + "line": 3, + "character": 17 + }, + "end": { + "line": 3, + "character": 18 + } + } + } + ], + "children": [ + { + "kindText": "4", + "kind": "number_literal", + "symbol": { + "name": "val", + "locations": [ + { + "fileName": "cases/moduleExport.ts", + "range": { + "start": { + "line": 4, + "character": 17 + }, + "end": { + "line": 4, + "character": 20 + } + } + } + ] + }, + "locations": [ + { + "fileName": "cases/moduleExport.ts", + "range": { + "start": { + "line": 4, + "character": 17 + }, + "end": { + "line": 4, + "character": 20 + } + } + } + ], + "children": [], + "_id": "1" + } + ], + "_id": "0" +} +> { + export const val = 4 +} +> export const val = 4 +> export const val = 4 +> const val = 4 +> val = 4 +> val = 4 +> val --- { + "kindText": "4", + "kind": "number_literal", + "symbol": { + "name": "val", + "locations": [ + { + "fileName": "cases/moduleExport.ts", + "range": { + "start": { + "line": 4, + "character": 17 + }, + "end": { + "line": 4, + "character": 20 + } + } + } + ] + }, + "locations": [ + { + "fileName": "cases/moduleExport.ts", + "range": { + "start": { + "line": 4, + "character": 17 + }, + "end": { + "line": 4, + "character": 20 + } + } + } + ], + "children": [], + "_id": "0" +} \ No newline at end of file diff --git a/tests/baselines/reference/moduleExport.tree b/tests/baselines/reference/moduleExport.tree new file mode 100644 index 0000000..cb17524 --- /dev/null +++ b/tests/baselines/reference/moduleExport.tree @@ -0,0 +1,154 @@ +=== moduleExport.ts === + +export const a = 5 +> const a = 5 +> a = 5 +> a = 5 +> a --- { + "kind": "number_literal", + "value": 5, + "symbolMeta": { + "name": "a", + "flags": 2, + "declarations": [ + { + "location": { + "fileName": "cases/moduleExport.ts", + "range": { + "start": { + "line": 0, + "character": 13 + }, + "end": { + "line": 0, + "character": 14 + } + } + } + } + ] + }, + "id": "0" +} + +export const b = "string" +> const b = "string" +> b = "string" +> b = "string" +> b --- { + "kind": "string_literal", + "value": "string", + "symbolMeta": { + "name": "b", + "flags": 2, + "declarations": [ + { + "location": { + "fileName": "cases/moduleExport.ts", + "range": { + "start": { + "line": 1, + "character": 13 + }, + "end": { + "line": 1, + "character": 14 + } + } + } + } + ] + }, + "id": "0" +} + +export namespace c { + export const val = 4 +} +> c --- { + "kind": "namespace", + "exports": [ + { + "kind": "number_literal", + "value": 4, + "symbolMeta": { + "name": "val", + "flags": 2, + "declarations": [ + { + "location": { + "fileName": "cases/moduleExport.ts", + "range": { + "start": { + "line": 4, + "character": 17 + }, + "end": { + "line": 4, + "character": 20 + } + } + } + } + ] + }, + "id": "1" + } + ], + "symbolMeta": { + "name": "c", + "flags": 512, + "declarations": [ + { + "location": { + "fileName": "cases/moduleExport.ts", + "range": { + "start": { + "line": 3, + "character": 17 + }, + "end": { + "line": 3, + "character": 18 + } + } + } + } + ] + }, + "id": "0" +} +> { + export const val = 4 +} +> export const val = 4 +> export const val = 4 +> const val = 4 +> val = 4 +> val = 4 +> val --- { + "kind": "number_literal", + "value": 4, + "symbolMeta": { + "name": "val", + "flags": 2, + "declarations": [ + { + "location": { + "fileName": "cases/moduleExport.ts", + "range": { + "start": { + "line": 4, + "character": 17 + }, + "end": { + "line": 4, + "character": 20 + } + } + } + } + ] + }, + "id": "0" +} \ No newline at end of file diff --git a/tests/baselines/reference/namespace.localized.tree b/tests/baselines/reference/namespace.localized.tree new file mode 100644 index 0000000..bd459b1 --- /dev/null +++ b/tests/baselines/reference/namespace.localized.tree @@ -0,0 +1,130 @@ +=== namespace.ts === + +namespace Namespace { + export type b = number +} +> Namespace --- { + "kindText": "namespace", + "kind": "namespace", + "symbol": { + "name": "Namespace", + "locations": [ + { + "fileName": "cases/namespace.ts", + "range": { + "start": { + "line": 0, + "character": 10 + }, + "end": { + "line": 0, + "character": 19 + } + } + } + ] + }, + "locations": [ + { + "fileName": "cases/namespace.ts", + "range": { + "start": { + "line": 0, + "character": 10 + }, + "end": { + "line": 0, + "character": 19 + } + } + } + ], + "children": [ + { + "kindText": "number", + "kind": "primitive", + "primitiveKind": "number", + "symbol": { + "name": "b", + "locations": [ + { + "fileName": "cases/namespace.ts", + "range": { + "start": { + "line": 1, + "character": 14 + }, + "end": { + "line": 1, + "character": 15 + } + } + } + ] + }, + "locations": [ + { + "fileName": "cases/namespace.ts", + "range": { + "start": { + "line": 1, + "character": 14 + }, + "end": { + "line": 1, + "character": 15 + } + } + } + ], + "children": [], + "_id": "1" + } + ], + "_id": "0" +} +> { + export type b = number +} +> export type b = number +> export type b = number +> b --- { + "kindText": "number", + "kind": "primitive", + "primitiveKind": "number", + "symbol": { + "name": "b", + "locations": [ + { + "fileName": "cases/namespace.ts", + "range": { + "start": { + "line": 1, + "character": 14 + }, + "end": { + "line": 1, + "character": 15 + } + } + } + ] + }, + "locations": [ + { + "fileName": "cases/namespace.ts", + "range": { + "start": { + "line": 1, + "character": 14 + }, + "end": { + "line": 1, + "character": 15 + } + } + } + ], + "children": [], + "_id": "0" +} \ No newline at end of file diff --git a/tests/baselines/reference/namespace.tree b/tests/baselines/reference/namespace.tree new file mode 100644 index 0000000..96e8946 --- /dev/null +++ b/tests/baselines/reference/namespace.tree @@ -0,0 +1,89 @@ +=== namespace.ts === + +namespace Namespace { + export type b = number +} +> Namespace --- { + "kind": "namespace", + "exports": [ + { + "kind": "primitive", + "primitive": "number", + "symbolMeta": { + "name": "b", + "flags": 524288, + "declarations": [ + { + "location": { + "fileName": "cases/namespace.ts", + "range": { + "start": { + "line": 1, + "character": 14 + }, + "end": { + "line": 1, + "character": 15 + } + } + } + } + ] + }, + "id": "1" + } + ], + "symbolMeta": { + "name": "Namespace", + "flags": 1024, + "declarations": [ + { + "location": { + "fileName": "cases/namespace.ts", + "range": { + "start": { + "line": 0, + "character": 10 + }, + "end": { + "line": 0, + "character": 19 + } + } + } + } + ] + }, + "id": "0" +} +> { + export type b = number +} +> export type b = number +> export type b = number +> b --- { + "kind": "primitive", + "primitive": "number", + "symbolMeta": { + "name": "b", + "flags": 524288, + "declarations": [ + { + "location": { + "fileName": "cases/namespace.ts", + "range": { + "start": { + "line": 1, + "character": 14 + }, + "end": { + "line": 1, + "character": 15 + } + } + } + } + ] + }, + "id": "0" +} \ No newline at end of file diff --git a/tests/cases/module.ts b/tests/cases/module.ts new file mode 100644 index 0000000..5752e9f --- /dev/null +++ b/tests/cases/module.ts @@ -0,0 +1 @@ +import { c } from "./moduleExport" \ No newline at end of file diff --git a/tests/cases/moduleExport.ts b/tests/cases/moduleExport.ts new file mode 100644 index 0000000..7de0f51 --- /dev/null +++ b/tests/cases/moduleExport.ts @@ -0,0 +1,6 @@ +export const a = 5 +export const b = "string" + +export namespace c { + export const val = 4 +} \ No newline at end of file diff --git a/tests/cases/namespace.ts b/tests/cases/namespace.ts new file mode 100644 index 0000000..3cc8548 --- /dev/null +++ b/tests/cases/namespace.ts @@ -0,0 +1,3 @@ +namespace Namespace { + export type b = number +} \ No newline at end of file