diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5f865b5a..9179dcd9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,4 @@
+- Added Inline Type Alias refactoring: place the caret on a Kotlin `typealias` declaration or one of its type uses, press **Ctrl+Alt+N** or choose **Refactor → Inline...**, then expand every supported Kotlin occurrence or only the selected use and optionally retain the alias declaration; generic arguments, imports, and parenthesisation use the IntelliJ IDEA K2 engine, while **Undo Last Refactoring** restores all changed Kotlin files.
- Added Change Kotlin Package refactoring: use **Refactor → Change Kotlin Package...** for the active Kotlin file or a folder's recursive Kotlin descendants to update package directives in place; nested files retain their relative package hierarchy, supported Kotlin code references and direct imports update, and **Undo Last Refactoring** restores the original contents.
- Added Move Kotlin File and Directory refactoring: move the active `.kt` file from **Refactor → Move Kotlin File...**, or right-click a folder and choose **Move Kotlin Directory...** to recursively move its Kotlin files while retaining the selected directory and nested structure below a selected source root/package; matching package directives and supported Kotlin code references are updated, and **Undo Last Refactoring** restores the original locations and contents.
- Improved Kotlin Inline Variable: local `var` declarations with one initializer and no subsequent writes can now be inlined from either a declaration or selected usage; assignments and increment/decrement writes are rejected before changing the source with the conflicting expression identified.
diff --git a/CoreImpl/pom.xml b/CoreImpl/pom.xml
index 4fafda65..47ef404b 100644
--- a/CoreImpl/pom.xml
+++ b/CoreImpl/pom.xml
@@ -5,7 +5,7 @@
io.github.nbplugins
netbeans-plugin-kotlin
- 0.11.249-SNAPSHOT
+ 0.11.250-SNAPSHOT
../pom.xml
netbeans-plugin-kotlin-core-impl
diff --git a/KotlinCompilerCliBase/pom.xml b/KotlinCompilerCliBase/pom.xml
index c5739481..01070bbc 100644
--- a/KotlinCompilerCliBase/pom.xml
+++ b/KotlinCompilerCliBase/pom.xml
@@ -5,7 +5,7 @@
io.github.nbplugins
netbeans-plugin-kotlin
- 0.11.249-SNAPSHOT
+ 0.11.250-SNAPSHOT
../pom.xml
netbeans-plugin-kotlin-compiler-cli-base
diff --git a/KotlinFixesImpl/pom.xml b/KotlinFixesImpl/pom.xml
index 8c9b4c9e..66817fa4 100644
--- a/KotlinFixesImpl/pom.xml
+++ b/KotlinFixesImpl/pom.xml
@@ -5,7 +5,7 @@
io.github.nbplugins
netbeans-plugin-kotlin
- 0.11.249-SNAPSHOT
+ 0.11.250-SNAPSHOT
../pom.xml
netbeans-plugin-kotlin-fixes-impl
diff --git a/KotlinFormatter/pom.xml b/KotlinFormatter/pom.xml
index a77898eb..67a8e35c 100644
--- a/KotlinFormatter/pom.xml
+++ b/KotlinFormatter/pom.xml
@@ -5,7 +5,7 @@
io.github.nbplugins
netbeans-plugin-kotlin
- 0.11.249-SNAPSHOT
+ 0.11.250-SNAPSHOT
../pom.xml
netbeans-plugin-kotlin-formatter
diff --git a/KotlinHighlighting/pom.xml b/KotlinHighlighting/pom.xml
index 9b5f2c69..5f19ad8c 100644
--- a/KotlinHighlighting/pom.xml
+++ b/KotlinHighlighting/pom.xml
@@ -5,7 +5,7 @@
io.github.nbplugins
netbeans-plugin-kotlin
- 0.11.249-SNAPSHOT
+ 0.11.250-SNAPSHOT
../pom.xml
netbeans-plugin-kotlin-highlighting
diff --git a/KotlinIcons/pom.xml b/KotlinIcons/pom.xml
index 9ef79aaa..0fa07b8a 100644
--- a/KotlinIcons/pom.xml
+++ b/KotlinIcons/pom.xml
@@ -5,7 +5,7 @@
io.github.nbplugins
netbeans-plugin-kotlin
- 0.11.249-SNAPSHOT
+ 0.11.250-SNAPSHOT
../pom.xml
netbeans-plugin-kotlin-icons
diff --git a/KotlinIntentionUtils/pom.xml b/KotlinIntentionUtils/pom.xml
index 1d555881..b60e0827 100644
--- a/KotlinIntentionUtils/pom.xml
+++ b/KotlinIntentionUtils/pom.xml
@@ -5,7 +5,7 @@
io.github.nbplugins
netbeans-plugin-kotlin
- 0.11.249-SNAPSHOT
+ 0.11.250-SNAPSHOT
../pom.xml
netbeans-plugin-kotlin-intention-utils
diff --git a/KotlinRefactoring/pom.xml b/KotlinRefactoring/pom.xml
index 8326f753..4ca08293 100644
--- a/KotlinRefactoring/pom.xml
+++ b/KotlinRefactoring/pom.xml
@@ -5,7 +5,7 @@
io.github.nbplugins
netbeans-plugin-kotlin
- 0.11.249-SNAPSHOT
+ 0.11.250-SNAPSHOT
../pom.xml
netbeans-plugin-kotlin-refactoring
@@ -279,6 +279,7 @@
org/jetbrains/kotlin/idea/k2/refactoring/inline/KotlinInlinePropertyProcessor.kt
org/jetbrains/kotlin/idea/k2/refactoring/inline/KotlinInlineFunctionProcessor.kt
+ org/jetbrains/kotlin/idea/k2/refactoring/inline/KotlinInlineTypeAliasProcessor.kt
org/jetbrains/kotlin/idea/k2/refactoring/inline/KotlinInlineAnonymousFunctionProcessor.kt
org/jetbrains/kotlin/idea/k2/refactoring/inline/codeInliner/**
org/jetbrains/kotlin/idea/k2/refactoring/util/**
diff --git a/KotlinRefactoring/src/main/kotlin/io/github/nbplugins/kotlin/refactoring/KaInlineTypeAliasComputer.kt b/KotlinRefactoring/src/main/kotlin/io/github/nbplugins/kotlin/refactoring/KaInlineTypeAliasComputer.kt
new file mode 100644
index 00000000..3180cab5
--- /dev/null
+++ b/KotlinRefactoring/src/main/kotlin/io/github/nbplugins/kotlin/refactoring/KaInlineTypeAliasComputer.kt
@@ -0,0 +1,161 @@
+/*******************************************************************************
+ * Copyright 2000-2024 JetBrains s.r.o.
+ * Copyright 2026 nbplugins contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *******************************************************************************/
+package io.github.nbplugins.kotlin.refactoring
+
+import com.intellij.psi.PsiElement
+import com.intellij.psi.util.PsiTreeUtil
+import org.jetbrains.kotlin.analysis.api.analyze
+import org.jetbrains.kotlin.idea.k2.refactoring.inline.codeInliner.TypeAliasUsageReplacementStrategy
+import org.jetbrains.kotlin.idea.references.mainReference
+import org.jetbrains.kotlin.psi.KtFile
+import org.jetbrains.kotlin.psi.KtNameReferenceExpression
+import org.jetbrains.kotlin.psi.KtReferenceExpression
+import org.jetbrains.kotlin.psi.KtTreeVisitorVoid
+import org.jetbrains.kotlin.psi.KtTypeAlias
+
+/**
+ * Prepared IDEA-engine data for a Kotlin **Inline Type Alias** refactoring.
+ *
+ * @param typeAlias declaration selected for expansion
+ * @param strategy actual IDEA K2 replacement strategy responsible for type substitution and shortening
+ * @param usages Kotlin references resolving to [typeAlias], grouped by their session-managed file
+ * @param triggerUsage reference at the invocation caret, when Inline was started from an alias use
+ * @param declarationName simple type-alias name for the UI
+ */
+data class KaInlineTypeAliasResult(
+ val typeAlias: KtTypeAlias,
+ val strategy: TypeAliasUsageReplacementStrategy,
+ val usages: Map>,
+ val triggerUsage: KtReferenceExpression?,
+ val declarationName: String,
+)
+
+/**
+ * User-facing validation failure returned by [KaInlineTypeAliasComputer].
+ *
+ * @param message explanation of why the selected alias cannot be inlined
+ * @param declarationName alias name, when available
+ */
+data class KaInlineTypeAliasError(
+ val message: String,
+ val declarationName: String,
+)
+
+/**
+ * Creates a standalone K2 plan for Kotlin **Inline Type Alias**.
+ *
+ * The computer uses IDEA's [TypeAliasUsageReplacementStrategy] rather than reconstructing an alias
+ * expansion as text. NetBeans owns the dialog, preview, persistence, and undo; the IDEA strategy owns
+ * K2 type substitution, parenthesisation, imports, and reference shortening.
+ *
+ * @param cursorKtFile session-managed Kotlin file containing the invocation caret
+ * @param offset caret offset within [cursorKtFile]
+ * @param projectKtFiles all Kotlin source files currently registered by the standalone session
+ */
+class KaInlineTypeAliasComputer(
+ private val cursorKtFile: KtFile,
+ private val offset: Int,
+ private val projectKtFiles: Collection,
+) {
+
+ /** Result of [compute]. */
+ sealed class Outcome {
+ /** The target alias and IDEA replacement plan are ready. */
+ data class Ready(val result: KaInlineTypeAliasResult) : Outcome()
+
+ /** A type alias was selected but lacks data required by IDEA's replacement engine. */
+ data class Error(val error: KaInlineTypeAliasError) : Outcome()
+
+ /** The caret is neither on a type alias declaration nor a reference to one. */
+ data object NotApplicable : Outcome()
+ }
+
+ /**
+ * Resolves the type alias at the caret and discovers portable Kotlin usages across the active session.
+ *
+ * @return [Outcome.Ready] with the IDEA strategy, [Outcome.Error] for an invalid alias, or
+ * [Outcome.NotApplicable] when no type alias is selected
+ */
+ fun compute(): Outcome {
+ val element = cursorKtFile.findElementAt(offset) ?: return Outcome.NotApplicable
+ val triggerUsage = PsiTreeUtil.getParentOfType(element, KtReferenceExpression::class.java, false)
+ val typeAlias = resolveTypeAliasFromReference(element)
+ ?: PsiTreeUtil.getParentOfType(element, KtTypeAlias::class.java, false)
+ ?: return Outcome.NotApplicable
+ val declarationName = typeAlias.name ?: return Outcome.Error(
+ KaInlineTypeAliasError("Cannot inline type alias: it has no name.", ""),
+ )
+ if (typeAlias.getTypeReference() == null) {
+ return Outcome.Error(
+ KaInlineTypeAliasError(
+ "Cannot inline type alias '$declarationName': it has no expanded type.",
+ declarationName,
+ ),
+ )
+ }
+
+ return Outcome.Ready(
+ KaInlineTypeAliasResult(
+ typeAlias = typeAlias,
+ strategy = TypeAliasUsageReplacementStrategy(typeAlias),
+ usages = findUsages(typeAlias),
+ triggerUsage = triggerUsage?.takeIf { resolvesTo(it, typeAlias) },
+ declarationName = declarationName,
+ ),
+ )
+ }
+
+ /** Resolves a reference at the caret to its declared [KtTypeAlias], when applicable. */
+ private fun resolveTypeAliasFromReference(element: PsiElement): KtTypeAlias? {
+ val reference = PsiTreeUtil.getParentOfType(element, KtNameReferenceExpression::class.java, false)
+ ?: return null
+ return runCatching {
+ analyze(reference) {
+ reference.mainReference?.resolveToSymbol()?.psi as? KtTypeAlias
+ }
+ }.getOrNull()
+ }
+
+ /** Collects K2-resolved Kotlin references to [typeAlias] from every active source file. */
+ private fun findUsages(typeAlias: KtTypeAlias): Map> {
+ val usagesByFile = linkedMapOf>()
+ for (file in projectKtFiles) {
+ val usages = mutableListOf()
+ runCatching {
+ analyze(file) {
+ file.accept(object : KtTreeVisitorVoid() {
+ override fun visitReferenceExpression(expression: KtReferenceExpression) {
+ super.visitReferenceExpression(expression)
+ if (resolvesTo(expression, typeAlias)) usages += expression
+ }
+ })
+ }
+ }
+ if (usages.isNotEmpty()) usagesByFile[file] = usages
+ }
+ return usagesByFile
+ }
+
+ /** Checks K2 symbol identity rather than spelling so unrelated same-name aliases are excluded. */
+ private fun resolvesTo(reference: KtReferenceExpression, typeAlias: KtTypeAlias): Boolean =
+ runCatching {
+ analyze(reference) {
+ reference.mainReference?.resolveToSymbol()?.psi == typeAlias
+ }
+ }.getOrDefault(false)
+}
diff --git a/Nbm/pom.xml b/Nbm/pom.xml
index 73999424..a1261aae 100644
--- a/Nbm/pom.xml
+++ b/Nbm/pom.xml
@@ -5,7 +5,7 @@
io.github.nbplugins
netbeans-plugin-kotlin
- 0.11.249-SNAPSHOT
+ 0.11.250-SNAPSHOT
../pom.xml
netbeans-plugin-kotlin-nbm
diff --git a/Nbm/src/main/java/io/github/nbplugins/kotlin/nbm/refactoring/KotlinInlineTypeAliasUI.java b/Nbm/src/main/java/io/github/nbplugins/kotlin/nbm/refactoring/KotlinInlineTypeAliasUI.java
new file mode 100644
index 00000000..d173ab92
--- /dev/null
+++ b/Nbm/src/main/java/io/github/nbplugins/kotlin/nbm/refactoring/KotlinInlineTypeAliasUI.java
@@ -0,0 +1,153 @@
+/*******************************************************************************
+ * Copyright 2000-2024 JetBrains s.r.o.
+ * Copyright 2026 nbplugins contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *******************************************************************************/
+package io.github.nbplugins.kotlin.nbm.refactoring;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.GridLayout;
+import javax.swing.BorderFactory;
+import javax.swing.ButtonGroup;
+import javax.swing.JCheckBox;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JRadioButton;
+import javax.swing.event.ChangeListener;
+import org.netbeans.modules.refactoring.api.AbstractRefactoring;
+import org.netbeans.modules.refactoring.api.Problem;
+import org.netbeans.modules.refactoring.spi.ui.CustomRefactoringPanel;
+import org.netbeans.modules.refactoring.spi.ui.RefactoringUI;
+import org.openide.util.HelpCtx;
+
+/**
+ * NetBeans UI adapter for Kotlin **Inline Type Alias**.
+ *
+ * Users can expand every portable Kotlin usage or only the invocation usage. Expanding all usages
+ * optionally retains the declaration, mirroring the IDEA type-alias dialog without importing its
+ * IntelliJ UI and command lifecycle.
+ *
+ * @param symbolName display name of the selected type alias
+ * @param refactoring carrier receiving the selected options
+ */
+public final class KotlinInlineTypeAliasUI implements RefactoringUI {
+
+ private final String symbolName;
+ private final KotlinInlineTypeAliasRefactoring refactoring;
+
+ /**
+ * Creates a type-alias inline UI.
+ *
+ * @param symbolName selected alias name, possibly empty before validation
+ * @param refactoring carrier receiving options
+ */
+ public KotlinInlineTypeAliasUI(String symbolName, KotlinInlineTypeAliasRefactoring refactoring) {
+ this.symbolName = symbolName;
+ this.refactoring = refactoring;
+ }
+
+ @Override
+ public String getName() {
+ return "Inline Type Alias";
+ }
+
+ @Override
+ public String getDescription() {
+ return symbolName == null || symbolName.isEmpty()
+ ? getName()
+ : "Inline type alias '" + symbolName + "'";
+ }
+
+ @Override
+ public boolean isQuery() {
+ return false;
+ }
+
+ @Override
+ public CustomRefactoringPanel getPanel(ChangeListener parent) {
+ return new InlineTypeAliasPanel(refactoring, getDescription());
+ }
+
+ @Override
+ public Problem setParameters() {
+ return null;
+ }
+
+ @Override
+ public Problem checkParameters() {
+ return null;
+ }
+
+ @Override
+ public boolean hasParameters() {
+ return true;
+ }
+
+ @Override
+ public AbstractRefactoring getRefactoring() {
+ return refactoring;
+ }
+
+ @Override
+ public HelpCtx getHelpCtx() {
+ return null;
+ }
+
+ /** Panel binding the two supported type-alias inline choices to the carrier. */
+ private static final class InlineTypeAliasPanel implements CustomRefactoringPanel {
+ private final JPanel component = new JPanel(new BorderLayout(0, 8));
+ private final KotlinInlineTypeAliasRefactoring refactoring;
+ private final JRadioButton allOccurrences = new JRadioButton("Inline all occurrences", true);
+ private final JRadioButton thisOccurrence = new JRadioButton("Inline this occurrence only");
+ private final JCheckBox keepDeclaration = new JCheckBox("Keep the type alias declaration");
+
+ InlineTypeAliasPanel(KotlinInlineTypeAliasRefactoring refactoring, String description) {
+ this.refactoring = refactoring;
+ JLabel label = new JLabel(description);
+ label.setBorder(BorderFactory.createEmptyBorder(8, 12, 0, 12));
+ component.add(label, BorderLayout.NORTH);
+
+ JPanel options = new JPanel(new GridLayout(0, 1, 0, 4));
+ options.setBorder(BorderFactory.createEmptyBorder(0, 12, 8, 12));
+ ButtonGroup group = new ButtonGroup();
+ group.add(allOccurrences);
+ group.add(thisOccurrence);
+ options.add(allOccurrences);
+ options.add(thisOccurrence);
+ options.add(keepDeclaration);
+ component.add(options, BorderLayout.CENTER);
+
+ allOccurrences.addActionListener(event -> updateOptions());
+ thisOccurrence.addActionListener(event -> updateOptions());
+ keepDeclaration.addActionListener(event -> updateOptions());
+ updateOptions();
+ }
+
+ /** Transfers enabled controls to the NetBeans refactoring carrier. */
+ private void updateOptions() {
+ boolean one = thisOccurrence.isSelected();
+ keepDeclaration.setEnabled(!one);
+ if (one) {
+ keepDeclaration.setSelected(true);
+ }
+ refactoring.setInlineThisOnly(one);
+ refactoring.setKeepDeclaration(keepDeclaration.isSelected());
+ }
+
+ @Override public void initialize() { updateOptions(); }
+ @Override public Component getComponent() { return component; }
+ }
+}
diff --git a/Nbm/src/main/java/org/jetbrains/kotlin/refactorings/rename/KotlinRefactoringsFactory.java b/Nbm/src/main/java/org/jetbrains/kotlin/refactorings/rename/KotlinRefactoringsFactory.java
index aa1580fb..e9400f3c 100644
--- a/Nbm/src/main/java/org/jetbrains/kotlin/refactorings/rename/KotlinRefactoringsFactory.java
+++ b/Nbm/src/main/java/org/jetbrains/kotlin/refactorings/rename/KotlinRefactoringsFactory.java
@@ -32,6 +32,8 @@
import io.github.nbplugins.kotlin.nbm.refactoring.KotlinInlineFunctionRefactoring;
import io.github.nbplugins.kotlin.nbm.refactoring.KotlinInlineVariablePlugin;
import io.github.nbplugins.kotlin.nbm.refactoring.KotlinInlineVariableRefactoring;
+import io.github.nbplugins.kotlin.nbm.refactoring.KotlinInlineTypeAliasPlugin;
+import io.github.nbplugins.kotlin.nbm.refactoring.KotlinInlineTypeAliasRefactoring;
import io.github.nbplugins.kotlin.nbm.refactoring.KotlinIntroduceConstantPlugin;
import io.github.nbplugins.kotlin.nbm.refactoring.KotlinIntroduceConstantRefactoring;
import io.github.nbplugins.kotlin.nbm.refactoring.KotlinCopyDeclarationPlugin;
@@ -83,6 +85,7 @@
* {@link SafeDeleteRefactoring} — delegates to {@link KotlinSafeDeletePlugin}
* {@link KotlinInlineVariableRefactoring} — delegates to {@link KotlinInlineVariablePlugin}
* {@link KotlinInlineFunctionRefactoring} — delegates to {@link KotlinInlineFunctionPlugin}
+ * {@link KotlinInlineTypeAliasRefactoring} — delegates to {@link KotlinInlineTypeAliasPlugin}
* {@link KotlinIntroduceVariableRefactoring} — delegates to {@link KotlinIntroduceVariablePlugin}
* {@link KotlinExtractFunctionRefactoring} — delegates to {@link KotlinExtractFunctionPlugin}
* {@link KotlinIntroduceConstantRefactoring} — delegates to {@link KotlinIntroduceConstantPlugin}
@@ -132,6 +135,9 @@ public RefactoringPlugin createInstance(AbstractRefactoring refactoring) {
if (refactoring instanceof KotlinInlineFunctionRefactoring) {
return new KotlinInlineFunctionPlugin((KotlinInlineFunctionRefactoring) refactoring);
}
+ if (refactoring instanceof KotlinInlineTypeAliasRefactoring) {
+ return new KotlinInlineTypeAliasPlugin((KotlinInlineTypeAliasRefactoring) refactoring);
+ }
if (refactoring instanceof KotlinIntroduceVariableRefactoring) {
return new KotlinIntroduceVariablePlugin((KotlinIntroduceVariableRefactoring) refactoring);
}
diff --git a/Nbm/src/main/kotlin/io/github/nbplugins/kotlin/nbm/refactoring/KotlinInlineTypeAliasPlugin.kt b/Nbm/src/main/kotlin/io/github/nbplugins/kotlin/nbm/refactoring/KotlinInlineTypeAliasPlugin.kt
new file mode 100644
index 00000000..94888738
--- /dev/null
+++ b/Nbm/src/main/kotlin/io/github/nbplugins/kotlin/nbm/refactoring/KotlinInlineTypeAliasPlugin.kt
@@ -0,0 +1,225 @@
+/*******************************************************************************
+ * Copyright 2000-2024 JetBrains s.r.o.
+ * Copyright 2026 nbplugins contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *******************************************************************************/
+package io.github.nbplugins.kotlin.nbm.refactoring
+
+import io.github.nbplugins.kotlin.nbm.navigation.KotlinFindUsagesResultElement
+import io.github.nbplugins.kotlin.nbm.resolve.KotlinAnalysisAPISession
+import io.github.nbplugins.kotlin.refactoring.KaInlineTypeAliasComputer
+import org.jetbrains.kotlin.psi.KtFile
+import org.jetbrains.kotlin.psi.KtReferenceExpression
+import org.jetbrains.kotlin.psi.psiUtil.startOffset
+import org.jetbrains.kotlin.utils.ProjectUtils
+import org.netbeans.modules.csl.api.OffsetRange
+import org.netbeans.modules.refactoring.api.Problem
+import org.netbeans.modules.refactoring.spi.ProgressProviderAdapter
+import org.netbeans.modules.refactoring.spi.RefactoringElementsBag
+import org.netbeans.modules.refactoring.spi.RefactoringPlugin
+import org.netbeans.modules.refactoring.spi.SimpleRefactoringElementImplementation
+import org.openide.filesystems.FileObject
+import org.openide.filesystems.FileUtil
+import org.openide.loaders.DataObject
+import org.openide.text.CloneableEditorSupport
+import org.openide.text.PositionBounds
+import org.openide.util.Lookup
+import org.openide.util.lookup.Lookups
+import javax.swing.text.Position.Bias
+
+/**
+ * Bridges NetBeans refactoring UI to IDEA's K2 **Inline Type Alias** strategy.
+ *
+ * @param refactoring document, caret, and UI options chosen by the user
+ */
+class KotlinInlineTypeAliasPlugin(
+ private val refactoring: KotlinInlineTypeAliasRefactoring,
+) : ProgressProviderAdapter(), RefactoringPlugin {
+
+ override fun preCheck(): Problem? = null
+ override fun fastCheckParameters(): Problem? = null
+ override fun checkParameters(): Problem? = null
+ override fun cancelRequest() {}
+
+ /**
+ * Builds portable Kotlin preview items and one transactional apply element.
+ *
+ * @param bag NetBeans element bag to populate
+ * @return fatal validation problem, or `null` when a valid plan was prepared
+ */
+ override fun prepare(bag: RefactoringElementsBag): Problem? {
+ val doc = refactoring.doc
+ val sourceFile = ProjectUtils.getFileObjectForDocument(doc) ?: return null
+ val nbProject = ProjectUtils.getKotlinProjectForFileObject(sourceFile) ?: return null
+ val session = KotlinAnalysisAPISession.getSession(nbProject)
+ val ktFile = session.getKtFileForPath(sourceFile.path) ?: return null
+ val projectKtFiles = session.session.modulesWithFiles.values.flatten().filterIsInstance()
+
+ return when (val outcome = KaInlineTypeAliasComputer(ktFile, refactoring.offset, projectKtFiles).compute()) {
+ is KaInlineTypeAliasComputer.Outcome.NotApplicable -> null
+ is KaInlineTypeAliasComputer.Outcome.Error -> Problem(true, outcome.error.message)
+ is KaInlineTypeAliasComputer.Outcome.Ready -> {
+ val selectedUsages = selectedUsages(outcome.result.usages, outcome.result.triggerUsage)
+ if (refactoring.inlineThisOnly && selectedUsages.isEmpty()) {
+ return Problem(true, "Cannot inline this occurrence: the type alias usage is no longer available.")
+ }
+ selectedUsages.forEach { (usageFile, usages) ->
+ val usageFo = fileObjectFor(usageFile) ?: return@forEach
+ usages.forEach { usage ->
+ runCatching {
+ bag.add(
+ refactoring,
+ KotlinFindUsagesResultElement(
+ OffsetRange(usage.textRange.startOffset, usage.textRange.endOffset),
+ usageFo,
+ ),
+ )
+ }
+ }
+ }
+ val declarationFile = fileObjectFor(outcome.result.typeAlias.containingKtFile) ?: sourceFile
+ bag.add(
+ refactoring,
+ KotlinInlineTypeAliasApplyElement(declarationFile, nbProject, sourceFile.path, refactoring),
+ )
+ null
+ }
+ }
+ }
+
+ /** Limits the plan to its trigger reference only when the UI chose that mode. */
+ private fun selectedUsages(
+ allUsages: Map>,
+ triggerUsage: KtReferenceExpression?,
+ ): Map> {
+ if (!refactoring.inlineThisOnly) return allUsages
+ val trigger = triggerUsage ?: return emptyMap()
+ val file = trigger.containingKtFile
+ return mapOf(file to listOf(trigger))
+ }
+
+ /** Converts a session virtual path to a writable NetBeans file. */
+ private fun fileObjectFor(file: KtFile): FileObject? =
+ file.virtualFile?.path?.let { FileUtil.toFileObject(FileUtil.normalizeFile(java.io.File(it))) }
+}
+
+/**
+ * Performs an Inline Type Alias change as a hunk-preserving NetBeans transaction.
+ *
+ * @param declarationFile parent file used by the preview hierarchy
+ * @param nbProject NetBeans project owning the standalone K2 session
+ * @param cursorFilePath source file path at action invocation
+ * @param refactoring carrier with mode options
+ */
+class KotlinInlineTypeAliasApplyElement(
+ private val declarationFile: FileObject,
+ private val nbProject: org.netbeans.api.project.Project,
+ private val cursorFilePath: String,
+ private val refactoring: KotlinInlineTypeAliasRefactoring,
+) : SimpleRefactoringElementImplementation() {
+
+ private var transaction: KotlinRefactoringTransaction? = null
+
+ override fun getText(): String = "Inline type alias"
+ override fun getDisplayText(): String = text
+ override fun getLookup(): Lookup = Lookups.fixed(declarationFile)
+ override fun getParentFile(): FileObject = declarationFile
+
+ override fun getPosition(): PositionBounds? = try {
+ val support = DataObject.find(declarationFile).lookup.lookup(CloneableEditorSupport::class.java) ?: return null
+ PositionBounds(support.createPositionRef(0, Bias.Forward), support.createPositionRef(0, Bias.Backward))
+ } catch (_: Exception) {
+ null
+ }
+
+ /** Recomputes and executes the IDEA strategy, committing all generated document text together. */
+ override fun performChange() {
+ var pending: KotlinRefactoringTransaction? = null
+ try {
+ val session = KotlinAnalysisAPISession.getSession(nbProject)
+ val cursorKtFile = session.getKtFileForPath(cursorFilePath) ?: return
+ val projectKtFiles = session.session.modulesWithFiles.values.flatten().filterIsInstance()
+ val ready = KaInlineTypeAliasComputer(cursorKtFile, refactoring.offset, projectKtFiles).compute()
+ as? KaInlineTypeAliasComputer.Outcome.Ready ?: return
+ val result = ready.result
+ val usages = chooseUsages(result.usages, result.triggerUsage)
+ if (refactoring.inlineThisOnly && usages.isEmpty()) return
+
+ val affectedKtFiles = linkedSetOf()
+ affectedKtFiles += usages.keys
+ if (!refactoring.inlineThisOnly && !refactoring.keepDeclaration) {
+ affectedKtFiles += result.typeAlias.containingKtFile
+ }
+ val affectedFiles = affectedKtFiles.mapNotNull { ktFile ->
+ fileObjectFor(ktFile)?.let { file -> ktFile to file }
+ }.toMap(linkedMapOf())
+ if (affectedFiles.size != affectedKtFiles.size) return
+
+ val currentTransaction = KotlinRefactoringTransaction()
+ pending = currentTransaction
+ affectedFiles.values.forEach(currentTransaction::captureExisting)
+
+ for ((_, references) in usages) {
+ for (usage in references.sortedForInline()) {
+ if (!usage.isValid) continue
+ result.strategy.createReplacer(usage)?.invoke()
+ }
+ }
+ if (!refactoring.inlineThisOnly && !refactoring.keepDeclaration) result.typeAlias.delete()
+
+ affectedKtFiles.forEach { ktFile ->
+ val file = affectedFiles.getValue(ktFile)
+ currentTransaction.stageHunkText(file, ktFile.text, nbProject)
+ }
+ currentTransaction.commit()
+ transaction = currentTransaction
+ pending = null
+ KotlinAnalysisAPISession.invalidate(nbProject)
+ } catch (_: Throwable) {
+ runCatching { pending?.rollback() }
+ KotlinAnalysisAPISession.invalidate(nbProject)
+ }
+ }
+
+ /** Restores all edited files and invalidates cached standalone PSI. */
+ override fun undoChange() {
+ runCatching { transaction?.undo() }
+ KotlinAnalysisAPISession.invalidate(nbProject)
+ }
+
+ /** Applies the UI's all-usages/this-occurrence selection to fresh K2 references. */
+ private fun chooseUsages(
+ allUsages: Map>,
+ triggerUsage: KtReferenceExpression?,
+ ): Map> {
+ if (!refactoring.inlineThisOnly) return allUsages
+ val trigger = triggerUsage ?: return emptyMap()
+ return mapOf(trigger.containingKtFile to listOf(trigger))
+ }
+
+ /** Resolves a K2 file path to its NetBeans file counterpart. */
+ private fun fileObjectFor(file: KtFile): FileObject? =
+ file.virtualFile?.path?.let { FileUtil.toFileObject(FileUtil.normalizeFile(java.io.File(it))) }
+
+ /** Orders nested references exactly as IDEA's `UsageReplacementStrategy` does. */
+ private fun List.sortedForInline(): List =
+ sortedWith { first, second ->
+ if (first.parent.textRange.intersects(second.parent.textRange)) {
+ compareValuesBy(second, first) { it.startOffset }
+ } else {
+ compareValuesBy(first, second) { it.startOffset }
+ }
+ }
+}
diff --git a/Nbm/src/main/kotlin/io/github/nbplugins/kotlin/nbm/refactoring/KotlinInlineTypeAliasRefactoring.kt b/Nbm/src/main/kotlin/io/github/nbplugins/kotlin/nbm/refactoring/KotlinInlineTypeAliasRefactoring.kt
new file mode 100644
index 00000000..656ae681
--- /dev/null
+++ b/Nbm/src/main/kotlin/io/github/nbplugins/kotlin/nbm/refactoring/KotlinInlineTypeAliasRefactoring.kt
@@ -0,0 +1,40 @@
+/*******************************************************************************
+ * Copyright 2000-2024 JetBrains s.r.o.
+ * Copyright 2026 nbplugins contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *******************************************************************************/
+package io.github.nbplugins.kotlin.nbm.refactoring
+
+import org.netbeans.modules.refactoring.api.AbstractRefactoring
+import org.openide.util.lookup.Lookups
+import javax.swing.text.StyledDocument
+
+/**
+ * Carries Kotlin **Inline Type Alias** inputs and user-selected options through NetBeans refactoring UI.
+ *
+ * @param doc active Kotlin editor document
+ * @param offset caret offset in [doc]
+ */
+class KotlinInlineTypeAliasRefactoring(
+ val doc: StyledDocument,
+ val offset: Int,
+) : AbstractRefactoring(Lookups.fixed(doc, Integer.valueOf(offset))) {
+
+ /** True when only the reference at [offset] is expanded. */
+ var inlineThisOnly: Boolean = false
+
+ /** True when the alias declaration remains after all selected references are expanded. */
+ var keepDeclaration: Boolean = false
+}
diff --git a/Nbm/src/main/kotlin/io/github/nbplugins/kotlin/nbm/refactoring/KotlinInlineVariableAction.kt b/Nbm/src/main/kotlin/io/github/nbplugins/kotlin/nbm/refactoring/KotlinInlineVariableAction.kt
index 6b3fdd2e..4f216387 100644
--- a/Nbm/src/main/kotlin/io/github/nbplugins/kotlin/nbm/refactoring/KotlinInlineVariableAction.kt
+++ b/Nbm/src/main/kotlin/io/github/nbplugins/kotlin/nbm/refactoring/KotlinInlineVariableAction.kt
@@ -25,6 +25,7 @@ import org.jetbrains.kotlin.log.KotlinLogger
import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.psi.KtNameReferenceExpression
import org.jetbrains.kotlin.psi.KtProperty
+import org.jetbrains.kotlin.psi.KtTypeAlias
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtNamedDeclaration
import com.intellij.psi.PsiElement
@@ -37,12 +38,13 @@ import javax.swing.text.JTextComponent
import javax.swing.text.StyledDocument
/**
- * Editor action for **Ctrl+Alt+N** — "Inline" for Kotlin `fun` declarations and `val`/`var` properties.
+ * Editor action for **Ctrl+Alt+N** — "Inline" for Kotlin functions, properties, and type aliases.
*
* Registered under action name [ACTION_NAME] in `layer.xml` for `text/x-kotlin`.
* Dispatches to the function or variable inline UI based on what is under the cursor:
* - [KtNamedFunction] → [KotlinInlineFunctionUI] + [KotlinInlineFunctionRefactoring]
* - [KtProperty] → [KotlinInlineVariableUI] + [KotlinInlineVariableRefactoring]
+ * - [KtTypeAlias] → [KotlinInlineTypeAliasUI] + [KotlinInlineTypeAliasRefactoring]
*
* The action does **no validation** on its own — the plugin's `prepare()` validates and surfaces
* a fatal `Problem` if the symbol cannot be inlined. This keeps the action thin and lets the
@@ -76,6 +78,10 @@ class KotlinInlineVariableAction : BaseAction(ACTION_NAME, SAVE_POSITION or ABBR
KotlinInlineVariableUI(targetDeclaration.name.orEmpty(), KotlinInlineVariableRefactoring(doc, targetOffset)),
TopComponent.getRegistry().activated,
)
+ is KtTypeAlias -> UI.openRefactoringUI(
+ KotlinInlineTypeAliasUI(targetDeclaration.name.orEmpty(), KotlinInlineTypeAliasRefactoring(doc, targetOffset)),
+ TopComponent.getRegistry().activated,
+ )
null -> return@runCatching
}
}.onFailure { e ->
@@ -87,13 +93,13 @@ class KotlinInlineVariableAction : BaseAction(ACTION_NAME, SAVE_POSITION or ABBR
* Resolves the directly selected inline declaration.
*
* A reference resolves through K2 to its declaration; a declaration-position caret uses the
- * directly selected property/function token. Unlike the former function-first lookup, this
- * never treats an arbitrary child of an enclosing function as the function itself.
+ * directly selected property/function/type-alias token. Unlike the former function-first lookup,
+ * this never treats an arbitrary child of an enclosing function as the function itself.
*
* @param doc Kotlin editor document containing the selection
* @param selectionStart first selected offset, or the caret offset when empty
* @param selectionEnd offset immediately after the selection, or [selectionStart] when empty
- * @return the target property/function, or `null` when the selection has no inline target
+ * @return the target property/function/type alias, or `null` when the selection has no inline target
*/
private fun resolveTargetAt(
doc: StyledDocument,
@@ -116,7 +122,7 @@ class KotlinInlineVariableAction : BaseAction(ACTION_NAME, SAVE_POSITION or ABBR
resolveTargetAt(ktFile, offset, offset)
/**
- * Resolves the property/function covered by a caret or selection in a session-managed Kotlin file.
+ * Resolves the property/function/type alias covered by a caret or selection in a session-managed Kotlin file.
*
* @param selectionStart first selected offset, or the caret offset for an empty selection
* @param selectionEnd offset immediately after the selection, or [selectionStart] when empty
@@ -135,7 +141,9 @@ class KotlinInlineVariableAction : BaseAction(ACTION_NAME, SAVE_POSITION or ABBR
for (offset in offsets) {
val element = ktFile.findElementAt(offset) ?: continue
resolveReferenceTarget(element)?.let { return it }
- ((element.parent as? KtProperty) ?: (element.parent as? KtNamedFunction))?.let { return it }
+ ((element.parent as? KtProperty)
+ ?: (element.parent as? KtNamedFunction)
+ ?: (element.parent as? KtTypeAlias))?.let { return it }
}
return null
}
@@ -148,7 +156,7 @@ class KotlinInlineVariableAction : BaseAction(ACTION_NAME, SAVE_POSITION or ABBR
analyze(reference) {
reference.mainReference?.resolveToSymbol()?.psi as? KtNamedDeclaration
}
- }.getOrNull()?.takeIf { it is KtProperty || it is KtNamedFunction }
+ }.getOrNull()?.takeIf { it is KtProperty || it is KtNamedFunction || it is KtTypeAlias }
}
companion object {
diff --git a/Nbm/src/test/kotlin/io/github/nbplugins/kotlin/nbm/refactoring/KaInlineTypeAliasTest.kt b/Nbm/src/test/kotlin/io/github/nbplugins/kotlin/nbm/refactoring/KaInlineTypeAliasTest.kt
new file mode 100644
index 00000000..69ea28ef
--- /dev/null
+++ b/Nbm/src/test/kotlin/io/github/nbplugins/kotlin/nbm/refactoring/KaInlineTypeAliasTest.kt
@@ -0,0 +1,146 @@
+/*******************************************************************************
+ * Copyright 2026 nbplugins contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *******************************************************************************/
+package io.github.nbplugins.kotlin.nbm.refactoring
+
+import io.github.nbplugins.kotlin.nbm.resolve.KotlinAnalysisAPISession
+import io.github.nbplugins.kotlin.refactoring.KaInlineTypeAliasComputer
+import org.jetbrains.kotlin.psi.KtFile
+import utils.KotlinTestCase
+import java.nio.file.Files
+import java.nio.file.Path
+
+/** Tests IDEA K2 strategy preparation and application for Kotlin Inline Type Alias. */
+class KaInlineTypeAliasTest : KotlinTestCase("KaInlineTypeAliasTest", "inlineTypeAlias") {
+
+ private companion object {
+ const val CARET = ""
+ }
+
+ /** Finds the Kotlin standard-library JAR required by an isolated standalone K2 session. */
+ private fun findKotlinStdlib(): Path? =
+ System.getProperty("java.class.path")
+ .split(System.getProperty("path.separator"))
+ .map { Path.of(it) }
+ .firstOrNull { it.fileName?.toString()?.startsWith("kotlin-stdlib") == true && it.toFile().exists() }
+
+ /** Builds a real K2 session backed by one fixture and returns its computer/file/temp directory. */
+ private fun prepare(fixture: String): Triple? {
+ val stdlib = findKotlinStdlib() ?: return null
+ val fixtureFolder = dir.getFileObject(fixture) ?: error("Missing fixture: $fixture")
+ val source = fixtureFolder.getFileObject("file.kt")?.asText() ?: error("Missing source for $fixture")
+ val marker = source.indexOf(CARET)
+ check(marker >= 0) { "Missing caret marker in $fixture" }
+ val cleanSource = source.replace(CARET, "")
+ val tempDirectory = Files.createTempDirectory("nbkotlin-inline-type-alias-$fixture")
+ val tempFile = tempDirectory.resolve("file.kt")
+ Files.writeString(tempFile, cleanSource)
+ val session = KotlinAnalysisAPISession.createWithJars(
+ moduleName = "inline-type-alias-$fixture",
+ binaryJars = listOf(stdlib),
+ sourceRoots = listOf(tempDirectory),
+ )
+ val ktFile = session.getKtFileForPath(tempFile.toString()) ?: error("Missing KtFile for $tempFile")
+ val files = session.session.modulesWithFiles.values.flatten().filterIsInstance()
+ return Triple(KaInlineTypeAliasComputer(ktFile, marker, files), ktFile, tempDirectory)
+ }
+
+ /** A declaration caret produces a ready plan with its Kotlin usage. */
+ fun testDeclarationCaret_collectsUsage() {
+ val (computer, _, temporaryDirectory) = prepare("simple") ?: return
+ try {
+ val outcome = computer.compute()
+ assertTrue("Expected ready result, got $outcome", outcome is KaInlineTypeAliasComputer.Outcome.Ready)
+ val result = (outcome as KaInlineTypeAliasComputer.Outcome.Ready).result
+ assertEquals("Name", result.declarationName)
+ assertEquals(1, result.usages.values.sumOf { it.size })
+ assertTrue("Declaration invocation must not retain a usage trigger", result.triggerUsage == null)
+ } finally {
+ temporaryDirectory.toFile().deleteRecursively()
+ }
+ }
+
+ /** A usage caret resolves back to the same alias and records the exact trigger reference. */
+ fun testUsageCaret_resolvesAliasAndRecordsTrigger() {
+ val (computer, _, temporaryDirectory) = prepare("caretOnUsage") ?: return
+ try {
+ val outcome = computer.compute()
+ assertTrue("Expected ready result, got $outcome", outcome is KaInlineTypeAliasComputer.Outcome.Ready)
+ val result = (outcome as KaInlineTypeAliasComputer.Outcome.Ready).result
+ assertEquals("Name", result.declarationName)
+ assertTrue("Usage invocation must retain its trigger", result.triggerUsage != null)
+ assertEquals("Name", result.triggerUsage?.text)
+ } finally {
+ temporaryDirectory.toFile().deleteRecursively()
+ }
+ }
+
+ /** IDEA's real type-alias replacement strategy expands a simple alias and allows declaration deletion. */
+ fun testApply_simpleAlias_expandsUsageAndDeletesDeclaration() {
+ val (computer, ktFile, temporaryDirectory) = prepare("simple") ?: return
+ try {
+ val ready = computer.compute() as? KaInlineTypeAliasComputer.Outcome.Ready
+ assertTrue("Expected ready alias plan, got $ready", ready != null)
+ val result = ready!!.result
+ result.usages.values.flatten().forEach { usage -> result.strategy.createReplacer(usage)?.invoke() }
+ result.typeAlias.delete()
+
+ assertFalse("Alias declaration must be deleted: ${ktFile.text}", ktFile.text.contains("typealias Name"))
+ assertTrue("Usage must expand to String: ${ktFile.text}", ktFile.text.contains("name: String"))
+ } finally {
+ temporaryDirectory.toFile().deleteRecursively()
+ }
+ }
+
+ /** IDEA's strategy substitutes the concrete arguments of each generic alias use independently. */
+ fun testApply_genericAlias_preservesPerUsageSubstitutions() {
+ val (computer, ktFile, temporaryDirectory) = prepare("generic") ?: return
+ try {
+ val ready = computer.compute() as? KaInlineTypeAliasComputer.Outcome.Ready
+ assertNotNull("Expected ready generic alias plan", ready)
+ val result = ready!!.result
+ result.usages.values.flatten().forEach { usage -> result.strategy.createReplacer(usage)?.invoke() }
+ result.typeAlias.delete()
+
+ assertTrue(
+ "String use must stay substituted: ${ktFile.text}",
+ ktFile.text.contains("List") || ktFile.text.contains("List"),
+ )
+ assertTrue(
+ "Int use must stay substituted: ${ktFile.text}",
+ ktFile.text.contains("List") || ktFile.text.contains("List"),
+ )
+ assertFalse("Alias declaration must be deleted: ${ktFile.text}", ktFile.text.contains("typealias Names"))
+ } finally {
+ temporaryDirectory.toFile().deleteRecursively()
+ }
+ }
+
+ /** The upstream processor must link so a future full IDEA processor route remains available. */
+ fun testIdeaInlineTypeAliasProcessor_isLinkable() {
+ try {
+ Class.forName(
+ "org.jetbrains.kotlin.idea.k2.refactoring.inline.KotlinInlineTypeAliasProcessor",
+ true,
+ javaClass.classLoader,
+ )
+ } catch (error: NoClassDefFoundError) {
+ fail("KotlinInlineTypeAliasProcessor cannot link: ${error.message}")
+ } catch (error: ClassNotFoundException) {
+ fail("KotlinInlineTypeAliasProcessor is missing: ${error.message}")
+ }
+ }
+}
diff --git a/Nbm/src/test/kotlin/io/github/nbplugins/kotlin/nbm/refactoring/KotlinInlineVariableActionTest.kt b/Nbm/src/test/kotlin/io/github/nbplugins/kotlin/nbm/refactoring/KotlinInlineVariableActionTest.kt
index 91158879..566cec7e 100644
--- a/Nbm/src/test/kotlin/io/github/nbplugins/kotlin/nbm/refactoring/KotlinInlineVariableActionTest.kt
+++ b/Nbm/src/test/kotlin/io/github/nbplugins/kotlin/nbm/refactoring/KotlinInlineVariableActionTest.kt
@@ -20,6 +20,7 @@ import io.github.nbplugins.kotlin.nbm.resolve.KotlinAnalysisAPISession
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.psi.KtProperty
+import org.jetbrains.kotlin.psi.KtTypeAlias
import org.netbeans.junit.NbTestCase
import java.nio.file.Files
import java.nio.file.Path
@@ -155,6 +156,36 @@ class KotlinInlineVariableActionTest : NbTestCase("KotlinInlineVariableActionTes
}
}
+ /** A type-alias declaration remains a valid unified Inline target. */
+ fun testTypeAliasDeclaration_resolvesTypeAlias() {
+ val source = """
+ typealias Name = String
+
+ fun greet(name: Name) = name
+ """.trimIndent()
+ withKtFile(source) { ktFile ->
+ val target = KotlinInlineVariableAction().resolveTargetAt(ktFile, source.indexOf("Name ="))
+
+ assertTrue("Expected a type alias target, got $target", target is KtTypeAlias)
+ assertEquals("Name", (target as KtTypeAlias).name)
+ }
+ }
+
+ /** A type-alias usage must resolve back to its type-alias declaration. */
+ fun testTypeAliasUsage_resolvesTypeAlias() {
+ val source = """
+ typealias Name = String
+
+ fun greet(name: Name) = name
+ """.trimIndent()
+ withKtFile(source) { ktFile ->
+ val target = KotlinInlineVariableAction().resolveTargetAt(ktFile, source.lastIndexOf("Name"))
+
+ assertTrue("Expected a type alias target, got $target", target is KtTypeAlias)
+ assertEquals("Name", (target as KtTypeAlias).name)
+ }
+ }
+
/** A body expression with no target declaration must not accidentally select `main`. */
fun testUnrelatedBodyExpression_returnsNull() {
val source = """
diff --git a/Nbm/src/test/kotlin/io/github/nbplugins/kotlin/nbm/refactoring/RefactoringCoverageBaselineTest.kt b/Nbm/src/test/kotlin/io/github/nbplugins/kotlin/nbm/refactoring/RefactoringCoverageBaselineTest.kt
index e356e738..75d1b796 100644
--- a/Nbm/src/test/kotlin/io/github/nbplugins/kotlin/nbm/refactoring/RefactoringCoverageBaselineTest.kt
+++ b/Nbm/src/test/kotlin/io/github/nbplugins/kotlin/nbm/refactoring/RefactoringCoverageBaselineTest.kt
@@ -54,10 +54,10 @@ class RefactoringCoverageBaselineTest : NbTestCase("RefactoringCoverageBaselineT
}
}
- val totals = "**Baseline counts:** 0 complete, 20 partial, 4 absent, 24 total."
+ val totals = "**Baseline counts:** 0 complete, 21 partial, 3 absent, 24 total."
assertTrue("Visible baseline totals must match the records", Files.readString(matrix).contains(totals))
- assertEquals(20, records.count { it.status == "partial" })
- assertEquals(4, records.count { it.status == "absent" })
+ assertEquals(21, records.count { it.status == "partial" })
+ assertEquals(3, records.count { it.status == "absent" })
}
/** Finds the checkout root from Surefire's module working directory. */
diff --git a/Nbm/src/test/resources/projForTest/src/inlineTypeAlias/caretOnUsage/file.kt b/Nbm/src/test/resources/projForTest/src/inlineTypeAlias/caretOnUsage/file.kt
new file mode 100644
index 00000000..fd865d86
--- /dev/null
+++ b/Nbm/src/test/resources/projForTest/src/inlineTypeAlias/caretOnUsage/file.kt
@@ -0,0 +1,5 @@
+package inlineTypeAlias.caretOnUsage
+
+typealias Name = String
+
+fun greet(name: Name) = "Hello, $name"
diff --git a/Nbm/src/test/resources/projForTest/src/inlineTypeAlias/generic/file.kt b/Nbm/src/test/resources/projForTest/src/inlineTypeAlias/generic/file.kt
new file mode 100644
index 00000000..3359c4e6
--- /dev/null
+++ b/Nbm/src/test/resources/projForTest/src/inlineTypeAlias/generic/file.kt
@@ -0,0 +1,6 @@
+package inlineTypeAlias.generic
+
+typealias Names = List
+
+val strings: Names = listOf("one")
+val numbers: Names = listOf(1)
diff --git a/Nbm/src/test/resources/projForTest/src/inlineTypeAlias/simple/file.kt b/Nbm/src/test/resources/projForTest/src/inlineTypeAlias/simple/file.kt
new file mode 100644
index 00000000..f4d0befb
--- /dev/null
+++ b/Nbm/src/test/resources/projForTest/src/inlineTypeAlias/simple/file.kt
@@ -0,0 +1,5 @@
+package inlineTypeAlias.simple
+
+typealias Name = String
+
+fun greet(name: Name) = "Hello, $name"
diff --git a/docs/development-plan.md b/docs/development-plan.md
index b6c0e88d..871f6aa4 100644
--- a/docs/development-plan.md
+++ b/docs/development-plan.md
@@ -1003,6 +1003,17 @@ Priority order: E1 → E2 → E3 → E4 → E5 → E6 → E7 → E8 → E9 → E
**E9 is complete.**
+ - [x] **F5.1** — Inline Type Alias (Ctrl+Alt+N)
+ - Reuses IDEA K2's `TypeAliasUsageReplacementStrategy` and compiled
+ `KotlinInlineTypeAliasProcessor`; NetBeans deliberately owns the dialog, preview, transaction,
+ and undo instead of invoking IDEA's processor lifecycle.
+ - The unified Inline action resolves a type-alias declaration or usage; its NetBeans panel supports
+ all occurrences, the invocation occurrence only, and retaining the declaration. K2-generated
+ type substitutions/import changes are committed through `KotlinRefactoringTransaction` so Undo
+ Last Refactoring restores every touched Kotlin document.
+ - Portable standalone scope is Kotlin source files registered in the active session; Java, non-code,
+ and project-index-only references remain unavailable.
+
- **E10** — J2K (Java→Kotlin): reimplement using `j2k/new` from `submodules/IntellijCommunity`
or binary artifact once published; wire up stubbed `Java2KotlinConverter` (stubbed since D2)
and re-enable `J2KTest`
diff --git a/docs/refactoring-coverage.md b/docs/refactoring-coverage.md
index 91fbf33f..be99a949 100644
--- a/docs/refactoring-coverage.md
+++ b/docs/refactoring-coverage.md
@@ -13,7 +13,7 @@ netbeans=; test=; milestone=
-
+
@@ -48,7 +48,7 @@ The corresponding record comments above are deliberately machine-readable. `Refa
| **Absent** | IDEA exposes the Kotlin refactoring family but the NetBeans plugin has no corresponding command. |
| **Not applicable** | IDEA-only integration which is not a Kotlin refactoring target for this plugin. No such rows are currently tracked. |
-**Baseline counts:** 0 complete, 20 partial, 4 absent, 24 total.
+**Baseline counts:** 0 complete, 21 partial, 3 absent, 24 total.
## Current NetBeans refactorings
@@ -59,6 +59,7 @@ The corresponding record comments above are deliberately machine-readable. `Refa
| `change-signature` | Change Signature | `Ctrl+F6` | Partial | K2 signature engine handles calls, overrides, constructors, destructuring and operators; every touched document now commits or rolls back through one hunk-preserving transaction, while broader IDEA scenarios remain. | F1 |
| `inline-variable-property` | Inline property | Inline (`Ctrl+Alt+N`) | Partial | IDEA code inliner supports local `val` and a local `var` with one declaration initializer and no later writes, invoked from a declaration or selected usage; a later assignment or increment/decrement is rejected before mutation with its expression identified. Receiver/member/accessor, comment, Java, and broader index-backed cases remain. | F3 |
| `inline-function` | Inline function | Inline (`Ctrl+Alt+N`) | Partial | K2 engine supports named functions; complex callable-reference, recursion and Java cases need parity tests. | F3 |
+| `inline-type-alias` | Inline Type Alias | Inline (`Ctrl+Alt+N`) | Partial | IDEA K2 type-alias replacement expands Kotlin source aliases from a declaration or usage; the dialog supports all occurrences, this occurrence, and keeping the declaration, while Java/non-code/index-only references remain unavailable. | F5 |
| `extract-function` | Extract Function | `Ctrl+Alt+M` | Partial | Real IDEA generator supports captured parameters, return values, multi-statements and scopes; control-flow, smart-cast, receiver and duplicate matrices remain. | F6 |
| `introduce-variable` | Introduce Variable | `Ctrl+Alt+V` | Partial | Common expression extraction, duplicate replacement and type/`val`/`var` choices work; advanced contexts remain. | F6 |
| `introduce-constant` | Introduce Constant | `Ctrl+Alt+C` | Partial | Compile-time constants and top-level/companion targets work; broader IDEA contexts remain. | F6 |
@@ -79,7 +80,6 @@ The corresponding record comments above are deliberately machine-readable. `Refa
| ID | IDEA family | Why absent | Target |
|---|---|---|---|
-| `inline-type-alias` | Inline Type Alias | The K2 processor is not compiled or adapted to NetBeans. | F5 |
| `inline-anonymous-function` | Inline Anonymous Function/Lambda | The K2 processor is not compiled or adapted to NetBeans. | F5 |
| `move-nested-member-method` | Move nested class/member/method | Requires receiver/visibility/hierarchy conflict support. | F4 |
| `rename-file-package-directory` | Rename file/package/directory | Requires NetBeans filesystem refactoring integration and Kotlin-aware update rules. | F7 |
@@ -108,6 +108,7 @@ The existing NetBeans tests are the initial executable sample set. They intentio
| Change Signature across declaration and separate call site | `KaChangeSignatureTest` | Exact parity baseline |
| Inline Function from a call site with multiple usages | `KaInlineFunctionTest` | Exact parity baseline |
| Inline Variable for a write-free local `var`; later assignments/increments rejected | `KaInlineVariableTest` | Portable K2 single-definition baseline; receiver, Java, and index-only write coverage remain incomplete |
+| Inline Type Alias from a declaration or a selected usage | `KaInlineTypeAliasTest` | IDEA K2 type substitution baseline; Java/non-code/index-only references remain incomplete |
| Move Declaration with external usage retargeting | `KaMoveDeclarationTest` | Exact parity baseline; transactional source/target undo baseline |
| Copy Declaration into new or existing target | `KaCopyDeclarationTest`, `KotlinRefactoringTransactionTest` | Transactional target creation/replacement and undo baseline |
| Pull Members Up direct target collision | `KaPullMembersUpComputerTest` | Documented standalone limitation beyond direct collision |
diff --git a/pom.xml b/pom.xml
index 6cdb1b89..cbc9d211 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,7 +4,7 @@
4.0.0
io.github.nbplugins
netbeans-plugin-kotlin
- 0.11.249-SNAPSHOT
+ 0.11.250-SNAPSHOT
pom
Netbeans Plugin Kotlin