Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion jacodb-ets/src/main/kotlin/org/jacodb/ets/dto/Convert.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
69 changes: 59 additions & 10 deletions jacodb-ets/src/main/kotlin/org/jacodb/ets/model/Export.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -33,7 +34,7 @@ data class EtsExportInfo(
) : Base {

// Note: Export statements do not have decorators in JS/TS.
override val decorators: List<EtsDecorator> = emptyList()
override val decorators: List<EtsDecorator> get() = emptyList()

/**
* Export clause name without any aliasing.
Expand Down Expand Up @@ -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.
Expand All @@ -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'"
Expand All @@ -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;
}
83 changes: 38 additions & 45 deletions jacodb-ets/src/main/kotlin/org/jacodb/ets/model/Import.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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<EtsDecorator> = emptyList()
override val decorators: List<EtsDecorator> 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
Expand All @@ -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.
Expand All @@ -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'"
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
Expand Down
Loading