diff --git a/jacodb-ets/src/main/kotlin/org/jacodb/ets/dto/Convert.kt b/jacodb-ets/src/main/kotlin/org/jacodb/ets/dto/Convert.kt index 628aba2eb..75fb62326 100644 --- a/jacodb-ets/src/main/kotlin/org/jacodb/ets/dto/Convert.kt +++ b/jacodb-ets/src/main/kotlin/org/jacodb/ets/dto/Convert.kt @@ -800,7 +800,7 @@ private fun Int.toEtsClassCategory(): EtsClassCategory { private fun Int.toEtsExportType(): EtsExportType { return when (this) { - 0 -> EtsExportType.NAME_SPACE + 0 -> EtsExportType.NAMESPACE 1 -> EtsExportType.CLASS 2 -> EtsExportType.METHOD 3 -> EtsExportType.LOCAL diff --git a/jacodb-ets/src/main/kotlin/org/jacodb/ets/model/Export.kt b/jacodb-ets/src/main/kotlin/org/jacodb/ets/model/Export.kt index 5bde585dc..7158beeb0 100644 --- a/jacodb-ets/src/main/kotlin/org/jacodb/ets/model/Export.kt +++ b/jacodb-ets/src/main/kotlin/org/jacodb/ets/model/Export.kt @@ -23,6 +23,7 @@ package org.jacodb.ets.model * @property type The [type][EtsExportType] of export. * @property from The module or path being exported from (null for direct exports). * @property nameBeforeAs The original name before 'as' aliasing (null if no aliasing). + * @property modifiers Export modifiers. */ data class EtsExportInfo( val name: String, @@ -33,7 +34,7 @@ data class EtsExportInfo( ) : Base { // Note: Export statements do not have decorators in JS/TS. - override val decorators: List = emptyList() + override val decorators: List get() = emptyList() /** * Export clause name without any aliasing. @@ -65,15 +66,26 @@ data class EtsExportInfo( } /** - * Whether this export is a star re-export (re-exporting everything from another module). + * Whether this export is a re-export. + * + * ```ts + * export { value } from './module'; + * export * from './module'; + * ``` + */ + val isReExport: Boolean + get() = from != null + + /** + * Whether this export is a star re-export. * * ```ts * export * from './module'; * export * as Utils from './utils'; * ``` */ - val isStarExport: Boolean - get() = from != null && originalName == "*" + val isStarReExport: Boolean + get() = isReExport && originalName == "*" /** * Whether this export is aliased. @@ -85,17 +97,14 @@ data class EtsExportInfo( * ``` */ val isAliased: Boolean - get() = nameBeforeAs != null && nameBeforeAs != name - - override val isDefault: Boolean - get() = isDefaultExport + get() = name != originalName override fun toString(): String { return when { // Re-exports from != null -> { val alias = if (isAliased) " as $name" else "" - if (isStarExport) { + if (isStarReExport) { "export *$alias from '$from'" } else { "export { $originalName$alias } from '$from'" @@ -120,10 +129,50 @@ data class EtsExportInfo( * Type of export in TypeScript/JavaScript. */ enum class EtsExportType { - NAME_SPACE, + /** + * Namespace export: + * ```ts + * export namespace MyNamespace { ... } + * ``` + */ + NAMESPACE, + + /** + * Class export: + * ```ts + * export class MyClass { ... } + * ``` + */ CLASS, + + /** + * Function export: + * ```ts + * export function myFunction() { ... } + * ``` + */ METHOD, + + /** + * Local variable/constant export: + * ```ts + * export const myVariable = 42; + * export let myLet = 'hello'; + * export var myVar = true; + * ``` + */ LOCAL, + + /** + * Type export: + * ```ts + * export type MyType = string | number; + * ``` + */ TYPE, + + /** + * Unknown export type, fallback for unrecognized export patterns. + */ UNKNOWN; } diff --git a/jacodb-ets/src/main/kotlin/org/jacodb/ets/model/Import.kt b/jacodb-ets/src/main/kotlin/org/jacodb/ets/model/Import.kt index 2f1811589..18c434bb7 100644 --- a/jacodb-ets/src/main/kotlin/org/jacodb/ets/model/Import.kt +++ b/jacodb-ets/src/main/kotlin/org/jacodb/ets/model/Import.kt @@ -33,29 +33,41 @@ data class EtsImportInfo( override val modifiers: EtsModifiers = EtsModifiers.EMPTY, ) : Base { + init { + if (type == EtsImportType.SIDE_EFFECT) { + require(name.isEmpty()) { "Side-effect imports should have empty name" } + require(nameBeforeAs == null) { "Side-effect imports should not have nameBeforeAs" } + } else { + require(name.isNotEmpty()) { "Only side-effect imports can have empty name" } + } + } + // Note: Import statements do not have decorators in JS/TS. - override val decorators: List = emptyList() + override val decorators: List get() = emptyList() /** * Import clause name without any aliasing. */ - val originalName: String = nameBeforeAs ?: name + val originalName: String + get() = nameBeforeAs ?: name /** * Whether this is a default import. * * ```ts * import React from 'react'; + * import { default as React } from 'react'; * ``` */ val isDefaultImport: Boolean - get() = type == EtsImportType.DEFAULT || originalName == "default" + get() = type == EtsImportType.DEFAULT /** * Whether this is a named import. * * ```ts * import { useState } from 'react'; + * import { Component as ReactComponent } from 'react'; * ``` */ val isNamedImport: Boolean @@ -69,7 +81,7 @@ data class EtsImportInfo( * ``` */ val isNamespaceImport: Boolean - get() = type == EtsImportType.NAMESPACE || nameBeforeAs == "*" + get() = type == EtsImportType.NAMESPACE /** * Whether this is a side-effect import. @@ -81,47 +93,28 @@ data class EtsImportInfo( val isSideEffectImport: Boolean get() = type == EtsImportType.SIDE_EFFECT - /** - * Whether this import uses aliasing. - * - * ```ts - * import { Component as ReactComponent }; - * ``` - */ - val isAliased: Boolean - get() = nameBeforeAs != null && nameBeforeAs != "*" && nameBeforeAs != name - - override val isDefault: Boolean - get() = isDefaultImport || super.isDefault - - override fun toString(): String = buildString { - append("import ") - - when { - isSideEffectImport -> { - // Side effect import: import './styles.css' - append("'$from'") - } - - isNamespaceImport -> { - // Namespace import: import * as Utils from './utils' - append("* as $name from '$from'") - } - - isAliased -> { - // Aliased import: import { Component as ReactComponent } from 'react' - append("{ $originalName as $name } from '$from'") - } - - isNamedImport -> { - // Named import: import { useState } from 'react' - append("{ $name } from '$from'") - } - - isDefaultImport -> { - // Default import: import React from 'react' - append("$name from '$from'") - } + override fun toString(): String = when(type) { + EtsImportType.DEFAULT -> { + // Default import: import React from 'react' + "import $name from '$from'" + } + + EtsImportType.NAMED -> { + // Named import: + // import { useState } from 'react' + // import { Component as ReactComponent } from 'react' + val alias = if (name != originalName) " as $name" else "" + "import { $originalName$alias } from '$from'" + } + + EtsImportType.NAMESPACE -> { + // Namespace import: import * as Utils from './utils' + "import * as $name from '$from'" + } + + EtsImportType.SIDE_EFFECT -> { + // Side effect import: import './styles.css' + "import '$from'" } } } diff --git a/jacodb-ets/src/test/kotlin/org/jacodb/ets/test/EtsImportTest.kt b/jacodb-ets/src/test/kotlin/org/jacodb/ets/test/EtsImportTest.kt index 61fe75492..66315eaa1 100644 --- a/jacodb-ets/src/test/kotlin/org/jacodb/ets/test/EtsImportTest.kt +++ b/jacodb-ets/src/test/kotlin/org/jacodb/ets/test/EtsImportTest.kt @@ -73,7 +73,7 @@ class EtsImportTest { it.name == "React" && it.from == "react" } assertNotNull(reactImport, "Should find React default import") - assertTrue(reactImport.isDefault, "React import should be marked as default") + assertTrue(reactImport.isDefaultImport, "React import should be marked as default") assertNull(reactImport.nameBeforeAs, "React import is not aliased") assertEquals("React", reactImport.name) logger.info { "✓ Default import test passed: $reactImport" }