From 3b1210e29b4a1e0c009f82ac6e311c19e56a7780 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Kozak?= Date: Mon, 5 Jan 2026 14:04:17 +0100 Subject: [PATCH 01/17] upgrade to Scala 3.8.0-RC5, update IntelliJ build, and enhance scalac options --- .github/workflows/ci.yml | 10 +++--- .scalafmt.conf | 2 +- build.sbt | 74 +++++++++++++++++++++++++--------------- 3 files changed, 52 insertions(+), 34 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0f0e9c3..6484987 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,7 +22,7 @@ jobs: strategy: matrix: os: [ubuntu-latest] - scala: [2.13.16] + scala: [3.7.4] java: [temurin@17] runs-on: ${{ matrix.os }} steps: @@ -64,7 +64,7 @@ jobs: strategy: matrix: os: [ubuntu-latest] - scala: [2.13.16] + scala: [3.7.4] java: [temurin@17] runs-on: ${{ matrix.os }} steps: @@ -84,12 +84,12 @@ jobs: - name: Setup sbt uses: sbt/setup-sbt@v1 - - name: Download target directories (2.13.16) + - name: Download target directories (3.7.4) uses: actions/download-artifact@v4 with: - name: target-${{ matrix.os }}-2.13.16-${{ matrix.java }} + name: target-${{ matrix.os }}-3.7.4-${{ matrix.java }} - - name: Inflate target directories (2.13.16) + - name: Inflate target directories (3.7.4) run: | tar xf targets.tar rm targets.tar diff --git a/.scalafmt.conf b/.scalafmt.conf index ec8215b..ec2ebd6 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -1,6 +1,6 @@ //description of properties: https://scalameta.org/scalafmt/docs/configuration.html version = "3.10.3" -runner.dialect = scala213source3 +runner.dialect = scala3 maxColumn = 120 continuationIndent { diff --git a/build.sbt b/build.sbt index c2857fa..3f930dc 100644 --- a/build.sbt +++ b/build.sbt @@ -1,8 +1,8 @@ import org.jetbrains.sbtidea.Keys._ -ThisBuild / scalaVersion := "2.13.16" +ThisBuild / scalaVersion := "3.8.0-RC5" ThisBuild / intellijPluginName := "intellij-hocon" -ThisBuild / intellijBuild := "251.17181.31" +ThisBuild / intellijBuild := "252.26830.84" ThisBuild / githubWorkflowJavaVersions := Seq(JavaSpec.temurin("17")) val junitInterfaceVersion = "0.13.3" @@ -10,29 +10,47 @@ val junitVersion = "4.13.2" val commonsTextVersion = "1.13.1" val opentest4jVersion = "1.3.0" -lazy val hocon = project.in(file(".")).enablePlugins(SbtIdeaPlugin).settings( - version := "2025.1.99-SNAPSHOT", - Compile / scalaSource := baseDirectory.value / "src", - Test / scalaSource := baseDirectory.value / "test", - Compile / resourceDirectory := baseDirectory.value / "resources", - Global / javacOptions ++= Seq("-source", "17", "-target", "17"), - Global / scalacOptions ++= Seq( - "-deprecation", - "-feature", - "-unchecked", - "-Xfatal-warnings", - "-Xsource:3" - ), - ideBasePackages := Seq("org.jetbrains.plugins.hocon"), - intellijPlugins := Seq("com.intellij.java", "com.intellij.java-i18n", "com.intellij.modules.json").map(_.toPlugin), - libraryDependencies ++= Seq( - "org.apache.commons" % "commons-text" % commonsTextVersion, - "com.github.sbt" % "junit-interface" % junitInterfaceVersion % Test, - "junit" % "junit" % junitVersion % Test, - "org.opentest4j" % "opentest4j" % opentest4jVersion % Test, - ), - packageLibraryMappings := Seq.empty, // allow scala-library - patchPluginXml := pluginXmlOptions { xml => - xml.version = version.value - } -) +lazy val hocon = project + .in(file(".")) + .enablePlugins(SbtIdeaPlugin) + .settings( + version := "2025.1.99-SNAPSHOT", + Compile / scalaSource := baseDirectory.value / "src", + Test / scalaSource := baseDirectory.value / "test", + Compile / resourceDirectory := baseDirectory.value / "resources", + Global / javacOptions ++= Seq("-source", "17", "-target", "17"), + Global / scalacOptions ++= Seq( + "-deprecation", + "-feature", + "-unchecked", + "-deprecation", + "-explain", + "-old-syntax", + "-unchecked", + "-language:noAutoTupling", + "-Vprofile", + "-Ycheck:all", + "-Ycheck:macros", + "-Ydebug-missing-refs", + "-Yexplain-lowlevel", + "-Yexplicit-nulls", + "-Wsafe-init", + "-Yshow-suppressed-errors", + "-Yshow-var-bounds", + "-Werror", + "-Wunused:all", + "-preview", + ), + ideBasePackages := Seq("org.jetbrains.plugins.hocon"), + intellijPlugins := Seq("com.intellij.java", "com.intellij.java-i18n", "com.intellij.modules.json").map(_.toPlugin), + libraryDependencies ++= Seq( + "org.apache.commons" % "commons-text" % commonsTextVersion, + "com.github.sbt" % "junit-interface" % junitInterfaceVersion % Test, + "junit" % "junit" % junitVersion % Test, + "org.opentest4j" % "opentest4j" % opentest4jVersion % Test, + ), + packageLibraryMappings := Seq.empty, // allow scala-library + patchPluginXml := pluginXmlOptions { xml => + xml.version = version.value + }, + ) From 43d37d2d4c149f33ad7449b01b9f40a378461307 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Kozak?= Date: Mon, 5 Jan 2026 15:00:57 +0100 Subject: [PATCH 02/17] migrate to Scala 3.8.0 syntax by replacing `_` with `?`, introducing union types, and updating nullability annotations --- .idea/codeStyles/codeStyleConfig.xml | 1 + ...conLanguageCodeStyleSettingsProvider.scala | 6 ++--- .../plugins/hocon/editor/HoconCommenter.scala | 18 +++++++------- .../hocon/editor/HoconQuoteHandler.scala | 2 +- .../plugins/hocon/formatting/HoconBlock.scala | 13 ++++++---- .../hocon/formatting/HoconFormatter.scala | 24 +++++++++---------- .../HoconHighlightUsagesHandlerFactory.scala | 10 +++++--- .../plugins/hocon/lexer/HoconLexer.scala | 16 ++++++------- .../plugins/hocon/lexer/HoconTokenSets.scala | 2 +- .../hocon/manipulators/HKeyManipulator.scala | 2 +- .../manipulators/HStringManipulator.scala | 3 ++- .../misc/HoconBreadcrumbsInfoProvider.scala | 3 ++- .../misc/HoconDocumentationProvider.scala | 12 ++++++---- .../navigation/HoconFindUsagesHandler.scala | 10 ++++---- .../HoconGotoSymbolContributor.scala | 8 +++---- .../hocon/navigation/gotoHandlers.scala | 2 +- src/org/jetbrains/plugins/hocon/package.scala | 24 +++++++++++-------- .../HoconErrorHighlightingAnnotator.scala | 2 +- .../plugins/hocon/parser/HoconPsiParser.scala | 2 +- .../plugins/hocon/psi/HoconPsiElement.scala | 24 +++++++++---------- .../hocon/psi/HoconPsiElementFactory.scala | 12 +++++----- .../plugins/hocon/ref/HKeyReference.scala | 4 ++-- .../HoconPropertiesReferenceProvider.scala | 9 ++++--- .../ref/HoconQualifiedNameProvider.scala | 4 ++-- .../hocon/ref/IncludedFileReferenceSet.scala | 4 ++-- .../hocon/semantics/ResolutionCtx.scala | 8 +++---- .../HoconProjectSettingsConfigurable.scala | 10 ++++---- .../plugins/hocon/HoconActionTest.scala | 4 +++- .../plugins/hocon/HoconMultiModuleTest.scala | 14 +++++------ .../plugins/hocon/HoconSingleModuleTest.scala | 8 ++++--- .../plugins/hocon/HoconTestUtils.scala | 2 +- .../manipulators/HoconManipulatorTest.scala | 2 +- .../resolution/HoconResolutionTest.scala | 4 ++-- .../hocon/usages/HoconFindUsagesTest.scala | 2 +- 34 files changed, 145 insertions(+), 126 deletions(-) diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml index 79ee123..6e6eec1 100644 --- a/.idea/codeStyles/codeStyleConfig.xml +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -1,5 +1,6 @@ \ No newline at end of file diff --git a/src/org/jetbrains/plugins/hocon/codestyle/HoconLanguageCodeStyleSettingsProvider.scala b/src/org/jetbrains/plugins/hocon/codestyle/HoconLanguageCodeStyleSettingsProvider.scala index d3fdafe..9f81539 100644 --- a/src/org/jetbrains/plugins/hocon/codestyle/HoconLanguageCodeStyleSettingsProvider.scala +++ b/src/org/jetbrains/plugins/hocon/codestyle/HoconLanguageCodeStyleSettingsProvider.scala @@ -20,8 +20,8 @@ class HoconLanguageCodeStyleSettingsProvider extends LanguageCodeStyleSettingsPr private val ObjectFieldsWithAssignmentWrap = "Object fields with '=' or '+='" override def customizeSettings(consumer: CodeStyleSettingsCustomizable, settingsType: SettingsType): Unit = { - def showCustomOption(name: String, title: String, group: String, options: AnyRef*): Unit = - consumer.showCustomOption(classOf[HoconCustomCodeStyleSettings], name, title, group, options: _*) + def showCustomOption(name: String, title: String, group: String | Null, options: AnyRef*): Unit = + consumer.showCustomOption(classOf[HoconCustomCodeStyleSettings], name, title, group, options*) import CodeStyleSettingsCustomizable.{WRAP_VALUES, WRAP_VALUES_FOR_SINGLETON} import com.intellij.psi.codeStyle.LanguageCodeStyleSettingsProvider.SettingsType.* @@ -37,7 +37,7 @@ class HoconLanguageCodeStyleSettingsProvider extends LanguageCodeStyleSettingsPr SpacingOption.SPACE_WITHIN_METHOD_CALL_PARENTHESES, SpacingOption.SPACE_BEFORE_COMMA, SpacingOption.SPACE_AFTER_COMMA, - ).map(_.name): _* + ).map(_.name)* ) consumer.renameStandardOption(SpacingOption.SPACE_WITHIN_BRACES.name, "Object braces") diff --git a/src/org/jetbrains/plugins/hocon/editor/HoconCommenter.scala b/src/org/jetbrains/plugins/hocon/editor/HoconCommenter.scala index 2eeaeec..667623f 100644 --- a/src/org/jetbrains/plugins/hocon/editor/HoconCommenter.scala +++ b/src/org/jetbrains/plugins/hocon/editor/HoconCommenter.scala @@ -11,23 +11,23 @@ class HoconCommenter extends CodeDocumentationAwareCommenter { def getLineCommentTokenType: IElementType = HoconTokenType.DoubleSlashComment - def getBlockCommentSuffix: String = null + def getBlockCommentSuffix: String | Null = null - def getBlockCommentPrefix: String = null + def getBlockCommentPrefix: String | Null = null - def getCommentedBlockCommentPrefix: String = null + def getCommentedBlockCommentPrefix: String | Null = null - def getCommentedBlockCommentSuffix: String = null + def getCommentedBlockCommentSuffix: String | Null = null - def getDocumentationCommentLinePrefix: String = null + def getDocumentationCommentLinePrefix: String | Null = null - def getBlockCommentTokenType: IElementType = null + def getBlockCommentTokenType: IElementType | Null = null - def getDocumentationCommentTokenType: IElementType = null + def getDocumentationCommentTokenType: IElementType | Null = null def isDocumentationComment(element: PsiComment): Boolean = false - def getDocumentationCommentSuffix: String = null + def getDocumentationCommentSuffix: String | Null = null - def getDocumentationCommentPrefix: String = null + def getDocumentationCommentPrefix: String | Null = null } diff --git a/src/org/jetbrains/plugins/hocon/editor/HoconQuoteHandler.scala b/src/org/jetbrains/plugins/hocon/editor/HoconQuoteHandler.scala index 4279990..5329a36 100644 --- a/src/org/jetbrains/plugins/hocon/editor/HoconQuoteHandler.scala +++ b/src/org/jetbrains/plugins/hocon/editor/HoconQuoteHandler.scala @@ -14,7 +14,7 @@ class HoconQuoteHandler extends SimpleTokenSetQuoteHandler(HoconTokenType.Quoted def getConcatenatableStringTokenTypes: TokenSet = TokenSet.EMPTY - def getStringConcatenationOperatorRepresentation: String = null + def getStringConcatenationOperatorRepresentation: String | Null = null def getStringTokenTypes: TokenSet = myLiteralTokenSet diff --git a/src/org/jetbrains/plugins/hocon/formatting/HoconBlock.scala b/src/org/jetbrains/plugins/hocon/formatting/HoconBlock.scala index 6f3cd19..237f9a8 100644 --- a/src/org/jetbrains/plugins/hocon/formatting/HoconBlock.scala +++ b/src/org/jetbrains/plugins/hocon/formatting/HoconBlock.scala @@ -10,8 +10,13 @@ import com.intellij.psi.formatter.common.AbstractBlock import org.jetbrains.plugins.hocon.lexer.HoconTokenSets import org.jetbrains.plugins.hocon.parser.HoconElementType -class HoconBlock(formatter: HoconFormatter, node: ASTNode, indent: Indent, wrap: Wrap, alignment: Alignment) - extends AbstractBlock(node, wrap, alignment) { +class HoconBlock( + formatter: HoconFormatter, + node: ASTNode, + indent: Indent | Null, + wrap: Wrap | Null, + alignment: Alignment | Null, +) extends AbstractBlock(node, wrap, alignment) { // HoconFormatter needs these to be able to return exactly the same instances of Wrap and Alignment for // children of this block @@ -24,7 +29,7 @@ class HoconBlock(formatter: HoconFormatter, node: ASTNode, indent: Indent, wrap: } private val alignmentCache = new formatter.AlignmentCache - override def getIndent: Indent = indent + override def getIndent: Indent | Null = indent override def getChildAttributes(newChildIndex: Int) = new ChildAttributes(formatter.getChildIndent(node), formatter.getChildAlignment(alignmentCache, node)) @@ -34,7 +39,7 @@ class HoconBlock(formatter: HoconFormatter, node: ASTNode, indent: Indent, wrap: def isLeaf: Boolean = formatter.getChildren(node).isEmpty - def getSpacing(child1: Block, child2: Block): Spacing = + def getSpacing(child1: Block, child2: Block): Spacing | Null = if (child1 == null) formatter.getFirstSpacing(node, child2.asInstanceOf[HoconBlock].getNode) else diff --git a/src/org/jetbrains/plugins/hocon/formatting/HoconFormatter.scala b/src/org/jetbrains/plugins/hocon/formatting/HoconFormatter.scala index ff9afdc..d24c732 100644 --- a/src/org/jetbrains/plugins/hocon/formatting/HoconFormatter.scala +++ b/src/org/jetbrains/plugins/hocon/formatting/HoconFormatter.scala @@ -44,7 +44,7 @@ class HoconFormatter(settings: CodeStyleSettings) { else Spacing.createSpacing(0, 0, 0, true, getMaxBlankLines(parent.getElementType, firstChild.getElementType)) - def getSpacing(parent: ASTNode, leftChild: ASTNode, rightChild: ASTNode): Spacing = { + def getSpacing(parent: ASTNode, leftChild: ASTNode, rightChild: ASTNode): Spacing | Null = { val keepLineBreaks = commonSettings.KEEP_LINE_BREAKS val maxBlankLines = getMaxBlankLines(parent.getElementType, rightChild.getElementType) @@ -174,13 +174,13 @@ class HoconFormatter(settings: CodeStyleSettings) { // for children of the same parent and these two classes are one way to make it possible. class WrapCache(keyValueSeparator: Option[IElementType]) { - val objectEntryWrap = + val objectEntryWrap: Wrap | Null = Wrap.createWrap(customSettings.OBJECTS_WRAP, false) - val arrayValueWrap = + val arrayValueWrap: Wrap | Null = Wrap.createWrap(customSettings.LISTS_WRAP, false) - val fieldInnerWrap = keyValueSeparator match { + val fieldInnerWrap: Wrap | Null = keyValueSeparator match { case Some(Colon) => Wrap.createWrap(customSettings.OBJECT_FIELDS_WITH_COLON_WRAP, true) case Some(Equals | PlusEquals) => @@ -188,7 +188,7 @@ class HoconFormatter(settings: CodeStyleSettings) { case _ => null } - val keyValueSeparatorWrap = keyValueSeparator match { + val keyValueSeparatorWrap: Wrap | Null = keyValueSeparator match { case Some(Colon) if customSettings.OBJECT_FIELDS_COLON_ON_NEXT_LINE => fieldInnerWrap case Some(Equals | PlusEquals) if customSettings.OBJECT_FIELDS_ASSIGNMENT_ON_NEXT_LINE => @@ -196,23 +196,23 @@ class HoconFormatter(settings: CodeStyleSettings) { case _ => null } - val fieldValueWrap = + val fieldValueWrap: Wrap | Null = if (keyValueSeparatorWrap == null) fieldInnerWrap else null - val includeInnerWrap = + val includeInnerWrap: Wrap | Null = Wrap.createWrap(customSettings.INCLUDED_RESOURCE_WRAP, true) } class AlignmentCache { - val objectEntryAlignment = + val objectEntryAlignment: Alignment | Null = if (customSettings.OBJECTS_ALIGN_WHEN_MULTILINE) Alignment.createAlignment else null - val arrayValueAlignment = + val arrayValueAlignment: Alignment | Null = if (customSettings.LISTS_ALIGN_WHEN_MULTILINE) Alignment.createAlignment else null } - def getWrap(wrapCache: WrapCache, parent: ASTNode, child: ASTNode): Wrap = + def getWrap(wrapCache: WrapCache, parent: ASTNode, child: ASTNode): Wrap | Null = (parent.getElementType, child.getElementType) match { case (Object, Include | KeyedField.extractor()) => wrapCache.objectEntryWrap @@ -232,7 +232,7 @@ class HoconFormatter(settings: CodeStyleSettings) { case _ => null } - def getAlignment(alignmentCache: AlignmentCache, parent: ASTNode, child: ASTNode): Alignment = + def getAlignment(alignmentCache: AlignmentCache, parent: ASTNode, child: ASTNode): Alignment | Null = (parent.getElementType, child.getElementType) match { case (Object, Include | KeyedField.extractor() | Comment.extractor()) => alignmentCache.objectEntryAlignment @@ -261,7 +261,7 @@ class HoconFormatter(settings: CodeStyleSettings) { case _ => Indent.getNoneIndent } - def getChildAlignment(alignmentCache: AlignmentCache, parent: ASTNode): Alignment = parent.getElementType match { + def getChildAlignment(alignmentCache: AlignmentCache, parent: ASTNode): Alignment | Null = parent.getElementType match { case Object => alignmentCache.objectEntryAlignment case Array => alignmentCache.arrayValueAlignment case _ => null diff --git a/src/org/jetbrains/plugins/hocon/highlight/HoconHighlightUsagesHandlerFactory.scala b/src/org/jetbrains/plugins/hocon/highlight/HoconHighlightUsagesHandlerFactory.scala index 73cd168..27b5ebd 100644 --- a/src/org/jetbrains/plugins/hocon/highlight/HoconHighlightUsagesHandlerFactory.scala +++ b/src/org/jetbrains/plugins/hocon/highlight/HoconHighlightUsagesHandlerFactory.scala @@ -12,14 +12,18 @@ import com.intellij.util.Consumer import java.{util => ju} class HoconHighlightUsagesHandlerFactory extends HighlightUsagesHandlerFactoryBase { - def createHighlightUsagesHandler(editor: Editor, file: PsiFile, target: PsiElement): HoconHighlightKeyUsagesHandler = + def createHighlightUsagesHandler( + editor: Editor, + file: PsiFile, + target: PsiElement, + ): HoconHighlightKeyUsagesHandler | Null = target.parentOfType[HKey].map(new HoconHighlightKeyUsagesHandler(editor, _)).orNull } class HoconHighlightKeyUsagesHandler(editor: Editor, hkey: HKey) extends HighlightUsagesHandlerBase[HKey](editor, hkey.hoconFile) { - def computeUsages(targets: JList[_ <: HKey]): Unit = for { + def computeUsages(targets: JList[? <: HKey]): Unit = for { target <- targets.iterator.asScala foundKey <- HoconFindUsagesHandler.localUsagesOf(target) usageList = if (foundKey.inField) myWriteUsages else myReadUsages @@ -29,6 +33,6 @@ class HoconHighlightKeyUsagesHandler(editor: Editor, hkey: HKey) def getTargets: JList[HKey] = ju.Collections.singletonList(hkey) - def selectTargets(targets: JList[_ <: HKey], selectionConsumer: Consumer[_ >: JList[_ <: HKey]]): Unit = + def selectTargets(targets: JList[? <: HKey], selectionConsumer: Consumer[? >: JList[? <: HKey]]): Unit = selectionConsumer.consume(targets) } diff --git a/src/org/jetbrains/plugins/hocon/lexer/HoconLexer.scala b/src/org/jetbrains/plugins/hocon/lexer/HoconLexer.scala index 7a13308..d1206a1 100644 --- a/src/org/jetbrains/plugins/hocon/lexer/HoconLexer.scala +++ b/src/org/jetbrains/plugins/hocon/lexer/HoconLexer.scala @@ -4,7 +4,7 @@ package lexer import com.intellij.lexer.LexerBase import com.intellij.psi.tree.IElementType -import scala.annotation.{switch, tailrec} +import scala.annotation.tailrec object HoconLexer { @@ -49,14 +49,14 @@ class HoconLexer extends LexerBase { case _ => state } - private var input: CharSequence = _ - private var endOffset: Int = _ + private var input: CharSequence = compiletime.uninitialized + private var endOffset: Int = compiletime.uninitialized private var stateBefore: State = Initial private var stateAfter: State = Initial - private var tokenStart: Int = _ - private var tokenEnd: Int = _ - private var token: IElementType = _ + private var tokenStart: Int = compiletime.uninitialized + private var tokenEnd: Int = compiletime.uninitialized + private var token: IElementType | Null = compiletime.uninitialized private def setNewToken(newToken: HoconTokenType, length: Int, newState: State): Unit = { tokenEnd = tokenStart + length @@ -82,7 +82,7 @@ class HoconLexer extends LexerBase { tokenStart = tokenEnd if (endOffset > tokenStart) { - (input.charAt(tokenStart): @switch) match { + input.charAt(tokenStart) match { // todo: no longer avilable @switch case '$' => setNewToken(Dollar, 1, onDollar(stateAfter)) case '?' if stateAfter == SubStarted => setNewToken(QMark, 1, Substitution) case '{' => @@ -163,7 +163,7 @@ class HoconLexer extends LexerBase { def getTokenStart: Int = tokenStart - def getTokenType: IElementType = { + def getTokenType: IElementType | Null = { if (token == null) { advance() } diff --git a/src/org/jetbrains/plugins/hocon/lexer/HoconTokenSets.scala b/src/org/jetbrains/plugins/hocon/lexer/HoconTokenSets.scala index 7cdd26d..161c479 100644 --- a/src/org/jetbrains/plugins/hocon/lexer/HoconTokenSets.scala +++ b/src/org/jetbrains/plugins/hocon/lexer/HoconTokenSets.scala @@ -9,7 +9,7 @@ object HoconTokenSets { import org.jetbrains.plugins.hocon.lexer.HoconTokenType.* - final val Empty = TokenSet.EMPTY + final val Empty: TokenSet = TokenSet.EMPTY final val Whitespace = InlineWhitespace | LineBreakingWhitespace final val Comment = HashComment | DoubleSlashComment final val WhitespaceOrComment = Whitespace | Comment diff --git a/src/org/jetbrains/plugins/hocon/manipulators/HKeyManipulator.scala b/src/org/jetbrains/plugins/hocon/manipulators/HKeyManipulator.scala index 5313686..72e6d62 100644 --- a/src/org/jetbrains/plugins/hocon/manipulators/HKeyManipulator.scala +++ b/src/org/jetbrains/plugins/hocon/manipulators/HKeyManipulator.scala @@ -14,7 +14,7 @@ class HKeyManipulator extends AbstractElementManipulator[HKey] { import org.jetbrains.plugins.hocon.lexer.HoconTokenType.* - def handleContentChange(key: HKey, range: TextRange, newContent: String): HKey = { + def handleContentChange(key: HKey, range: TextRange, newContent: String): HKey | Null = { val psiManager = PsiManager.getInstance(key.getProject) val allStringTypes = key.keyParts.map(_.stringType).toSet diff --git a/src/org/jetbrains/plugins/hocon/manipulators/HStringManipulator.scala b/src/org/jetbrains/plugins/hocon/manipulators/HStringManipulator.scala index 9c144c0..7f5777a 100644 --- a/src/org/jetbrains/plugins/hocon/manipulators/HStringManipulator.scala +++ b/src/org/jetbrains/plugins/hocon/manipulators/HStringManipulator.scala @@ -39,7 +39,8 @@ class HStringManipulator extends AbstractElementManipulator[HString] { case KeyPart => HoconPsiElementFactory.createKeyPart(quotedText, str.getManager) case IncludeTarget => HoconPsiElementFactory.createIncludeTarget(quotedText, str.getManager) } - str.getFirstChild.replace(newString.getFirstChild) + if (newString ne null) + str.getFirstChild.replace(newString.getFirstChild) str } diff --git a/src/org/jetbrains/plugins/hocon/misc/HoconBreadcrumbsInfoProvider.scala b/src/org/jetbrains/plugins/hocon/misc/HoconBreadcrumbsInfoProvider.scala index 582fda8..c80dab4 100644 --- a/src/org/jetbrains/plugins/hocon/misc/HoconBreadcrumbsInfoProvider.scala +++ b/src/org/jetbrains/plugins/hocon/misc/HoconBreadcrumbsInfoProvider.scala @@ -1,6 +1,7 @@ package org.jetbrains.plugins.hocon package misc +import com.intellij.lang.Language import com.intellij.psi.PsiElement import com.intellij.ui.breadcrumbs.BreadcrumbsProvider import org.jetbrains.plugins.hocon.lang.HoconLanguage @@ -18,5 +19,5 @@ class HoconBreadcrumbsInfoProvider extends BreadcrumbsProvider { case _ => false } - def getLanguages = Array(HoconLanguage) + def getLanguages: Array[Language] = Array(HoconLanguage) } diff --git a/src/org/jetbrains/plugins/hocon/misc/HoconDocumentationProvider.scala b/src/org/jetbrains/plugins/hocon/misc/HoconDocumentationProvider.scala index 29f9563..8f7f735 100644 --- a/src/org/jetbrains/plugins/hocon/misc/HoconDocumentationProvider.scala +++ b/src/org/jetbrains/plugins/hocon/misc/HoconDocumentationProvider.scala @@ -10,7 +10,7 @@ import org.jetbrains.plugins.hocon.semantics.{ResOpts, ResolvedField} import scala.annotation.tailrec class HoconDocumentationProvider extends DocumentationProviderEx { - private def key(origElem: PsiElement): Option[HKey] = origElem match { + private def key(origElem: PsiElement | Null): Option[HKey] = origElem match { case null => None case key: HKey => Some(key) case _ => origElem.parentOfType[HKey] @@ -25,13 +25,17 @@ class HoconDocumentationProvider extends DocumentationProviderEx { case None => None } - override def getDocumentationElementForLookupItem(psiManager: PsiManager, obj: Any, element: PsiElement): PsiElement = + override def getDocumentationElementForLookupItem( + psiManager: PsiManager, + obj: Any, + element: PsiElement, + ): PsiElement | Null = obj match { case rf: ResolvedField => rf.hkey // see HoconPropertyLookupElement.getObject case _ => null } - override def getQuickNavigateInfo(element: PsiElement, originalElement: PsiElement): String = { + override def getQuickNavigateInfo(element: PsiElement, originalElement: PsiElement): String | Null = { val res = for { hkey <- key(originalElement) orElse key(element) fullPathText <- hkey.fullPathText @@ -39,7 +43,7 @@ class HoconDocumentationProvider extends DocumentationProviderEx { res.orNull } - override def generateDoc(element: PsiElement, originalElement: PsiElement): String = { + override def generateDoc(element: PsiElement, originalElement: PsiElement | Null): String | Null = { import DocumentationMarkup.* // `element` is already resolved here but it loses context of the `originalElement` so resolving again (key(originalElement) orElse key(element)).flatMap(_.resolved).nullOr { resolved => diff --git a/src/org/jetbrains/plugins/hocon/navigation/HoconFindUsagesHandler.scala b/src/org/jetbrains/plugins/hocon/navigation/HoconFindUsagesHandler.scala index 319f611..282ad17 100644 --- a/src/org/jetbrains/plugins/hocon/navigation/HoconFindUsagesHandler.scala +++ b/src/org/jetbrains/plugins/hocon/navigation/HoconFindUsagesHandler.scala @@ -19,8 +19,6 @@ import com.intellij.psi.tree.TokenSet import com.intellij.usageView.UsageInfo import com.intellij.util.Processor -import scala.annotation.nowarn - class HoconWordsScanner extends DefaultWordsScanner( new HoconLexer, @@ -47,8 +45,8 @@ class HoconFindUsagesProvider extends FindUsagesProvider { getNodeText(element, useFullName = true) def getNodeText(element: PsiElement, useFullName: Boolean): String = element match { - case key: HKey => key.fullPathText.orNull - case _ => null + case key: HKey => key.fullPathText.getOrElse("") + case _ => "" } } @@ -65,7 +63,7 @@ class HoconFindUsagesHandlerFactory extends FindUsagesHandlerFactory { class HoconFindUsagesHandler(element: PsiElement) extends FindUsagesHandler(element) { override def processElementUsages( element: PsiElement, - processor: Processor[_ >: UsageInfo], + processor: Processor[? >: UsageInfo], options: FindUsagesOptions, ): Boolean = { val res = element match { @@ -106,7 +104,7 @@ object HoconFindUsagesHandler { } class HoconUseScopeAdjuster extends UseScopeEnlarger with ScopeOptimizer { - def getAdditionalUseScope(element: PsiElement): SearchScope = element match { + def getAdditionalUseScope(element: PsiElement): SearchScope | Null = element match { // HOCON properties represent paths which can be used anywhere in the project case fk: HFieldKey => ProjectScope.getAllScope(fk.getProject) case _ => null diff --git a/src/org/jetbrains/plugins/hocon/navigation/HoconGotoSymbolContributor.scala b/src/org/jetbrains/plugins/hocon/navigation/HoconGotoSymbolContributor.scala index 7c530d8..6117f15 100644 --- a/src/org/jetbrains/plugins/hocon/navigation/HoconGotoSymbolContributor.scala +++ b/src/org/jetbrains/plugins/hocon/navigation/HoconGotoSymbolContributor.scala @@ -22,10 +22,10 @@ class HoconGotoSymbolContributor extends ChooseByNameContributorEx { private def enabled(project: Project): Boolean = HoconProjectSettings.getInstance(project).searchInGotoSymbol - def processNames(processor: Processor[_ >: String], scope: GlobalSearchScope, filter: IdFilter): Unit = + def processNames(processor: Processor[? >: String], scope: GlobalSearchScope, filter: IdFilter): Unit = FileBasedIndex.getInstance.processAllKeys[String](HoconKeyIndex.Id, processor, scope, filter) - def processElementsWithName(name: String, processor: Processor[_ >: NavigationItem], parameters: FindSymbolParameters) + def processElementsWithName(name: String, processor: Processor[? >: NavigationItem], parameters: FindSymbolParameters) : Unit = if (enabled(parameters.getProject)) ReadAction.run { () => val project = parameters.getProject @@ -49,10 +49,10 @@ case class HoconGotoSymbolItem(key: HKey) extends PsiElementNavigationItem with override def canNavigate: Boolean = key.canNavigate override def canNavigateToSource: Boolean = key.canNavigateToSource override def getPresentation: ItemPresentation = this - override def getPresentableText: String = key.fullPathText.orNull + override def getPresentableText: String | Null = key.fullPathText.orNull override def getIcon(unused: Boolean): Icon = PropertyIcon - override def getLocationString: String = + override def getLocationString: String | Null = key.hoconFile.getVirtualFile.opt.map { vf => val pfi = ProjectFileIndex.getInstance(key.getProject) val rootDir = pfi.getContentRootForFile(vf).opt orElse pfi.getClassRootForFile(vf).opt diff --git a/src/org/jetbrains/plugins/hocon/navigation/gotoHandlers.scala b/src/org/jetbrains/plugins/hocon/navigation/gotoHandlers.scala index e8fb92d..704b163 100644 --- a/src/org/jetbrains/plugins/hocon/navigation/gotoHandlers.scala +++ b/src/org/jetbrains/plugins/hocon/navigation/gotoHandlers.scala @@ -44,6 +44,6 @@ class HoconGotoNextAction extends HoconGotoPrevNextAction(reverse = false) final class HoconGotoPrevNextPromoter extends ActionPromoter { // prioritize HoconGoto{Prev,Next}Action over GotoImplementationAction & GotoSuperAction in HOCON files // ideally these two actions should be completely disabled in HOCON files but I don't know how - override def promote(actions: JList[_ <: AnAction], context: DataContext): JList[AnAction] = + override def promote(actions: JList[? <: AnAction], context: DataContext): JList[AnAction] = actions.iterator.asScala.collectOnly[HoconGotoPrevNextAction].toJList } diff --git a/src/org/jetbrains/plugins/hocon/package.scala b/src/org/jetbrains/plugins/hocon/package.scala index 6e1de26..2a9e7e4 100644 --- a/src/org/jetbrains/plugins/hocon/package.scala +++ b/src/org/jetbrains/plugins/hocon/package.scala @@ -11,7 +11,8 @@ import com.intellij.util.text.CharSequenceSubSequence import org.jetbrains.plugins.hocon.lexer.HoconTokenType import java.net.{MalformedURLException, URL} -import java.{lang => jl, util => ju} +import java.{lang as jl, util as ju} +import javax.swing.Icon import scala.annotation.tailrec import scala.collection.AbstractIterator import scala.collection.convert.{AsJavaExtensions, AsScalaExtensions} @@ -23,8 +24,8 @@ package object hocon extends AsJavaExtensions with AsScalaExtensions { type JCollection[T] = java.util.Collection[T] type JMap[K, V] = java.util.Map[K, V] - final val HoconIcon = AllIcons.FileTypes.Config - final val PropertyIcon = IconManager.getInstance.getIcon("/icons/property.svg", getClass.getClassLoader) + final val HoconIcon: Icon = AllIcons.FileTypes.Config + final val PropertyIcon = IconManager.getInstance.getIcon("/icons/property.svg", this.getClass.getClassLoader) def notWhiteSpaceSibling(element: PsiElement)(sibling: PsiElement => PsiElement): PsiElement = { var result = sibling(element) @@ -34,7 +35,7 @@ package object hocon extends AsJavaExtensions with AsScalaExtensions { result } - private[this] def isWhiteSpace(element: PsiElement): Boolean = element match { + private def isWhiteSpace(element: PsiElement | Null): Boolean = element match { case null => false case _: PsiWhiteSpace => true case _ => @@ -119,14 +120,14 @@ package object hocon extends AsJavaExtensions with AsScalaExtensions { } private class DepthFirstIterator(root: PsiElement) extends AbstractIterator[PsiElement] { - private var _next: PsiElement = root + private var _next: PsiElement | Null = root def hasNext: Boolean = _next ne null def next(): PsiElement = if (!hasNext) throw new NoSuchElementException else { - val res = _next + val res = _next.nn _next = res.getFirstChild match { case null => findNextSibling(res) case child => child @@ -134,7 +135,7 @@ package object hocon extends AsJavaExtensions with AsScalaExtensions { res } - @tailrec private def findNextSibling(cur: PsiElement): PsiElement = + @tailrec private def findNextSibling(cur: PsiElement): PsiElement | Null = if (cur eq root) null else cur.getNextSibling match { @@ -148,8 +149,11 @@ package object hocon extends AsJavaExtensions with AsScalaExtensions { ind + str.replace("\n", "\n" + ind) } - implicit class universalOps[T](private val t: T) extends AnyVal { + implicit class universalNullableOps[T](private val t: T | Null) extends AnyVal { def opt: Option[T] = Option(t) + } + + implicit class universalOps[T](private val t: T) extends AnyVal { def setup(code: T => Unit): T = { code(t) @@ -197,7 +201,7 @@ package object hocon extends AsJavaExtensions with AsScalaExtensions { it.flatMap(a => f.applyOrElse(a, (_: A) => Iterator.empty)) def orElse(other: Iterator[A]): Iterator[A] = new AbstractIterator[A] { - private var chosenIt: Iterator[A] = _ + private var chosenIt: Iterator[A] = compiletime.uninitialized def hasNext: Boolean = if (chosenIt != null) chosenIt.hasNext @@ -221,7 +225,7 @@ package object hocon extends AsJavaExtensions with AsScalaExtensions { result = quotedCharPattern.replaceAllIn( result, m => - m.group(0).charAt(1) match { + m.group(0).nn.charAt(1) match { case '\\' => "\\" case '/' => "/" case '"' => "\"" diff --git a/src/org/jetbrains/plugins/hocon/parser/HoconErrorHighlightingAnnotator.scala b/src/org/jetbrains/plugins/hocon/parser/HoconErrorHighlightingAnnotator.scala index eabbbb6..fc6619d 100644 --- a/src/org/jetbrains/plugins/hocon/parser/HoconErrorHighlightingAnnotator.scala +++ b/src/org/jetbrains/plugins/hocon/parser/HoconErrorHighlightingAnnotator.scala @@ -40,7 +40,7 @@ class HoconErrorHighlightingAnnotator extends Annotator { case Concatenation => @tailrec - def validateConcatenation(constrainingToken: IElementType, child: ASTNode): Unit = if (child != null) { + def validateConcatenation(constrainingToken: IElementType | Null, child: ASTNode): Unit = if (child != null) { (constrainingToken, child.getElementType) match { case (_, Substitution | BadCharacter | TokenType.ERROR_ELEMENT | TokenType.WHITE_SPACE) => diff --git a/src/org/jetbrains/plugins/hocon/parser/HoconPsiParser.scala b/src/org/jetbrains/plugins/hocon/parser/HoconPsiParser.scala index 86e714d..e5cc5d6 100644 --- a/src/org/jetbrains/plugins/hocon/parser/HoconPsiParser.scala +++ b/src/org/jetbrains/plugins/hocon/parser/HoconPsiParser.scala @@ -26,7 +26,7 @@ class HoconPsiParser extends PsiParser { class Parser(builder: PsiBuilder) { object DocumentationCommentsBinder extends WhitespacesAndCommentsBinder { - override def getEdgePosition(tokens: JList[_ <: IElementType], atStreamEdge: Boolean, getter: TokenTextGetter) + override def getEdgePosition(tokens: JList[? <: IElementType], atStreamEdge: Boolean, getter: TokenTextGetter) : Int = { @tailrec diff --git a/src/org/jetbrains/plugins/hocon/psi/HoconPsiElement.scala b/src/org/jetbrains/plugins/hocon/psi/HoconPsiElement.scala index 198cbca..5ee55a4 100644 --- a/src/org/jetbrains/plugins/hocon/psi/HoconPsiElement.scala +++ b/src/org/jetbrains/plugins/hocon/psi/HoconPsiElement.scala @@ -1,8 +1,7 @@ package org.jetbrains.plugins.hocon package psi -import java.{lang => jl} - +import java.lang as jl import com.intellij.extapi.psi.ASTWrapperPsiElement import com.intellij.lang.ASTNode import com.intellij.openapi.fileTypes.FileType @@ -14,6 +13,7 @@ import com.intellij.psi.impl.source.resolve.reference.impl.providers.FileReferen import com.intellij.psi.search.GlobalSearchScope import com.intellij.psi.tree.IElementType import com.intellij.util.IncorrectOperationException + import javax.swing.Icon import org.jetbrains.plugins.hocon.HoconConstants.* import org.jetbrains.plugins.hocon.lang.HoconFileType @@ -88,7 +88,7 @@ sealed abstract class HoconPsiElement(ast: ASTNode) extends ASTWrapperPsiElement Iterator .iterate(this)(_.getParent match { case he: HoconPsiElement => he - case _ => null + case _ => null.asInstanceOf[HoconPsiElement] }) .takeWhile(_ != null) @@ -98,16 +98,16 @@ sealed abstract class HoconPsiElement(ast: ASTNode) extends ASTWrapperPsiElement case _ => false } - def getChild[T >: Null: ClassTag]: T = + def getChild[T: ClassTag]: T = findChildByClass(classTag[T].runtimeClass.asInstanceOf[Class[T]]) - def findChild[T >: Null: ClassTag](reverse: Boolean): Option[T] = + def findChild[T: ClassTag](reverse: Boolean): Option[T] = allChildren(reverse).collectFirst { case t: T => t } - def findChild[T >: Null: ClassTag]: Option[T] = + def findChild[T: ClassTag]: Option[T] = findChild[T](reverse = false) - def findLastChild[T >: Null: ClassTag]: Option[T] = + def findLastChild[T: ClassTag]: Option[T] = findChild[T](reverse = true) def allChildren(reverse: Boolean): Iterator[PsiElement] = @@ -189,8 +189,6 @@ sealed trait HObjectEntry extends HoconPsiElement with HEntriesLike { def nextEntry: Option[HObjectEntry] = nextEntry(reverse = false) def prevEntry: Option[HObjectEntry] = nextEntry(reverse = true) - - def firstOccurrence(key: Option[String], opts: ResOpts, resCtx: ResolutionCtx): Option[ResolvedField] } final class HObjectField(ast: ASTNode) extends HoconPsiElement(ast) with HObjectEntry with HKeyedFieldParent { @@ -273,7 +271,7 @@ sealed abstract class HKeyedField(ast: ASTNode) * iterator of all encountered keyed fields (in bottom-up order, i.e. starting with itself) */ def prefixingFields: Iterator[HKeyedField] = - Iterator.iterate(this)(_.prefixingField.orNull).takeWhile(_ != null) + Iterator.iterate(this)(_.prefixingField.orNull.asInstanceOf[HKeyedField]).takeWhile(_ != null) /** Returns all keys on containing path, assuming they are all valid keys. `None` is returned if not all keys on * containing path are valid. The list is ordered top-down, i.e. `this` key is the last element. "Containing path" @@ -477,7 +475,7 @@ sealed abstract class HKey(ast: ASTNode) extends HoconPsiElement(ast) { final class HFieldKey(ast: ASTNode) extends HKey(ast) with PsiQualifiedNamedElement { type Parent = HKeyedField - override def getQualifiedName: String = fullPathText.orNull + override def getQualifiedName: String | Null = fullPathText.orNull // Implementing PsiNamedElement is required for Find Usages to be triggered on ctrl+click (GotoDeclaration action) // see: com.intellij.codeInsight.navigation.actions.GotoDeclarationAction.invoke @@ -739,7 +737,7 @@ final class HSubstitution(ast: ASTNode) extends HoconPsiElement(ast) with HValue val fixupPrefix = resCtx.substitutionFixupPrefix val res = doResolve(subsKind(resCtx, fixupPrefix), resCtx, opts, fixupPrefix.length + depth, backtrace) if (fixupPrefix.nonEmpty) - res orElse doResolve(subsKind(resCtx, Nil), resCtx, opts, depth, backtrace) + res.orElse(doResolve(subsKind(resCtx, Nil), resCtx, opts, depth, backtrace)) else res } @@ -752,7 +750,7 @@ sealed trait HLiteralValue extends HValue with PsiLiteralValue { } final class HNull(ast: ASTNode) extends HoconPsiElement(ast) with HLiteralValue { - def getValue: Object = null + def getValue: Object | Null = null def configValue: ConfigValue = NullValue } diff --git a/src/org/jetbrains/plugins/hocon/psi/HoconPsiElementFactory.scala b/src/org/jetbrains/plugins/hocon/psi/HoconPsiElementFactory.scala index be92a19..2c92c9d 100644 --- a/src/org/jetbrains/plugins/hocon/psi/HoconPsiElementFactory.scala +++ b/src/org/jetbrains/plugins/hocon/psi/HoconPsiElementFactory.scala @@ -17,21 +17,21 @@ object HoconPsiElementFactory { Iterator.iterate(element)(_.getParent).takeWhile(_ != null).collectFirst { case t: T => t } } - def createStringValue(contents: String, manager: PsiManager): HStringValue = + def createStringValue(contents: String, manager: PsiManager): HStringValue | Null = createElement[HStringValue](manager, s"__k = $contents", 6).orNull - def createKeyPart(contents: String, manager: PsiManager): HKeyPart = + def createKeyPart(contents: String, manager: PsiManager): HKeyPart | Null = createElement[HKeyPart](manager, s"$contents = null", 0).orNull - def createIncludeTarget(contents: String, manager: PsiManager): HIncludeTarget = + def createIncludeTarget(contents: String, manager: PsiManager): HIncludeTarget | Null = createElement[HIncludeTarget](manager, s"include $contents", 8).orNull - def createFieldKey(contents: String, manager: PsiManager): HFieldKey = + def createFieldKey(contents: String, manager: PsiManager): HFieldKey | Null = createElement[HFieldKey](manager, s"$contents = null", 0).orNull - def createSubstitutionKey(contents: String, manager: PsiManager): HSubstitutionKey = + def createSubstitutionKey(contents: String, manager: PsiManager): HSubstitutionKey | Null = createElement[HSubstitutionKey](manager, s"__k = $${$contents}}", 8).orNull - def createPath(path: String, manager: PsiManager): HPath = + def createPath(path: String, manager: PsiManager): HPath | Null = createElement[HSubstitution](manager, s"__k = $${$path}", 8).flatMap(_.path).orNull } diff --git a/src/org/jetbrains/plugins/hocon/ref/HKeyReference.scala b/src/org/jetbrains/plugins/hocon/ref/HKeyReference.scala index 5b0bff2..6b50069 100644 --- a/src/org/jetbrains/plugins/hocon/ref/HKeyReference.scala +++ b/src/org/jetbrains/plugins/hocon/ref/HKeyReference.scala @@ -19,7 +19,7 @@ class HKeyReference(key: HKey) extends PsiReference { def isReferenceTo(element: PsiElement): Boolean = element == resolve() - def bindToElement(element: PsiElement): PsiElement = null + def bindToElement(element: PsiElement): PsiElement | Null = null def handleElementRename(newElementName: String): PsiElement = ElementManipulators.handleContentChange(key, newElementName) @@ -28,7 +28,7 @@ class HKeyReference(key: HKey) extends PsiReference { def getRangeInElement: TextRange = ElementManipulators.getValueTextRange(key) - def resolve(): PsiElement = + def resolve(): PsiElement | Null = key.resolved.map(_.hkey).orNull // completion diff --git a/src/org/jetbrains/plugins/hocon/ref/HoconPropertiesReferenceProvider.scala b/src/org/jetbrains/plugins/hocon/ref/HoconPropertiesReferenceProvider.scala index e717415..ebc8a28 100644 --- a/src/org/jetbrains/plugins/hocon/ref/HoconPropertiesReferenceProvider.scala +++ b/src/org/jetbrains/plugins/hocon/ref/HoconPropertiesReferenceProvider.scala @@ -47,7 +47,7 @@ class HoconPropertiesReferenceProvider extends PsiReferenceProvider { val nextRef = subRefs.headOption val revIndex = nextRef.fold(0)(_.reverseIndex + 1) val keyRange = new TextRange(off, endOffset) - val ref = new HoconPropertyReference(strPath, revIndex, key, element, lit, keyRange) + val ref = new HoconPropertyReference(strPath, revIndex, key, element, keyRange) ref :: subRefs } @@ -62,7 +62,6 @@ class HoconPropertyReference( val reverseIndex: Int, key: String, element: PsiElement, - lit: PsiLiteralValue, range: TextRange, ) extends PsiReference { def getCanonicalText: String = key @@ -74,7 +73,7 @@ class HoconPropertyReference( case _ => ToplevelCtx(element, ToplevelCtx.ApplicationResource) } - def resolve(): PsiElement = createContext + def resolve(): PsiElement | Null = createContext .occurrences(fullPath, ResOpts(reverse = true)) .nextOption() .flatMap(_.ancestorField(reverseIndex)) @@ -96,9 +95,9 @@ class HoconPropertyReference( .toArray[AnyRef] } - def handleElementRename(newElementName: String): PsiElement = null + def handleElementRename(newElementName: String): PsiElement | Null = null - def bindToElement(element: PsiElement): PsiElement = null + def bindToElement(element: PsiElement): PsiElement | Null = null // important for ReferencesSearch and therefore Find Usages def isReferenceTo(element: PsiElement): Boolean = element match { diff --git a/src/org/jetbrains/plugins/hocon/ref/HoconQualifiedNameProvider.scala b/src/org/jetbrains/plugins/hocon/ref/HoconQualifiedNameProvider.scala index c1043de..884c26b 100644 --- a/src/org/jetbrains/plugins/hocon/ref/HoconQualifiedNameProvider.scala +++ b/src/org/jetbrains/plugins/hocon/ref/HoconQualifiedNameProvider.scala @@ -12,10 +12,10 @@ import org.jetbrains.plugins.hocon.psi.HKey class HoconQualifiedNameProvider extends QualifiedNameProvider { def adjustElementToCopy(element: PsiElement): PsiElement = element - def getQualifiedName(element: PsiElement): String = element match { + def getQualifiedName(element: PsiElement): String | Null = element match { case key: HKey => key.fullPathText.orNull case _ => null } - def qualifiedNameToElement(fqn: String, project: Project): PsiElement = null + def qualifiedNameToElement(fqn: String, project: Project): PsiElement | Null = null } diff --git a/src/org/jetbrains/plugins/hocon/ref/IncludedFileReferenceSet.scala b/src/org/jetbrains/plugins/hocon/ref/IncludedFileReferenceSet.scala index be6b938..b649602 100644 --- a/src/org/jetbrains/plugins/hocon/ref/IncludedFileReferenceSet.scala +++ b/src/org/jetbrains/plugins/hocon/ref/IncludedFileReferenceSet.scala @@ -54,7 +54,7 @@ object IncludedFileReferenceSet { loe.getOwnerModule.getModuleRuntimeScope(loe.getScope == DependencyScope.TEST) } - def orderEntryScope = allScopes.reduceOption(_ union _) + def orderEntryScope = allScopes.reduceOption(_ `union` _) def moduleScope = pfi.getModuleForFile(parent).opt.map(_.getModuleRuntimeScope(false)) orderEntryScope orElse moduleScope getOrElse GlobalSearchScope.EMPTY_SCOPE @@ -154,7 +154,7 @@ class IncludedFileReference(refSet: FileReferenceSet, range: TextRange, index: I override def innerResolveInContext( text: String, context: PsiFileSystemItem, - result: JCollection[_ >: ResolveResult], + result: JCollection[? >: ResolveResult], caseSensitive: Boolean, ): Unit = if (lacksExtension(text)) { diff --git a/src/org/jetbrains/plugins/hocon/semantics/ResolutionCtx.scala b/src/org/jetbrains/plugins/hocon/semantics/ResolutionCtx.scala index b0e9736..70fb4bd 100644 --- a/src/org/jetbrains/plugins/hocon/semantics/ResolutionCtx.scala +++ b/src/org/jetbrains/plugins/hocon/semantics/ResolutionCtx.scala @@ -33,28 +33,28 @@ case class SubstitutionCtx( ) sealed abstract class ResolutionCtx { - val toplevelCtx: ToplevelCtx = this match { + lazy val toplevelCtx: ToplevelCtx = this match { case tc: ToplevelCtx => tc case rf: ResolvedField => rf.parentCtx.toplevelCtx case ic: IncludeCtx => ic.parentCtx.toplevelCtx case ac: ArrayCtx => ac.parentCtx.toplevelCtx } - val depth: Int = this match { + lazy val depth: Int = this match { case _: ToplevelCtx => 0 case rf: ResolvedField => rf.parentCtx.depth + 1 case ic: IncludeCtx => ic.parentCtx.depth case ac: ArrayCtx => ac.parentCtx.depth } - val inArray: Boolean = this match { + lazy val inArray: Boolean = this match { case _: ToplevelCtx => false case rf: ResolvedField => rf.parentCtx.inArray case ic: IncludeCtx => ic.parentCtx.inArray case _: ArrayCtx => true } - val lastInclude: Option[IncludeCtx] = this match { + lazy val lastInclude: Option[IncludeCtx] = this match { case _: ToplevelCtx => None case rf: ResolvedField => rf.parentCtx.lastInclude case ic: IncludeCtx => Some(ic) diff --git a/src/org/jetbrains/plugins/hocon/settings/HoconProjectSettingsConfigurable.scala b/src/org/jetbrains/plugins/hocon/settings/HoconProjectSettingsConfigurable.scala index 9725929..723f6f2 100644 --- a/src/org/jetbrains/plugins/hocon/settings/HoconProjectSettingsConfigurable.scala +++ b/src/org/jetbrains/plugins/hocon/settings/HoconProjectSettingsConfigurable.scala @@ -6,19 +6,19 @@ import com.intellij.openapi.project.Project import javax.swing.JComponent class HoconProjectSettingsConfigurable(project: Project) extends Configurable { - private var panel = new HoconProjectSettingsPanel(project) + private var panel: HoconProjectSettingsPanel | Null = new HoconProjectSettingsPanel(project) override def getDisplayName = "HOCON" - override def isModified: Boolean = panel.isModified + override def isModified: Boolean = panel.nn.isModified - override def createComponent(): JComponent = panel.getMainComponent + override def createComponent(): JComponent = panel.nn.getMainComponent override def disposeUIResources(): Unit = { panel = null } - override def apply(): Unit = panel.apply() + override def apply(): Unit = panel.nn.apply() - override def reset(): Unit = panel.loadSettings() + override def reset(): Unit = panel.nn.loadSettings() } diff --git a/test/org/jetbrains/plugins/hocon/HoconActionTest.scala b/test/org/jetbrains/plugins/hocon/HoconActionTest.scala index 8df7bdf..a6997f3 100644 --- a/test/org/jetbrains/plugins/hocon/HoconActionTest.scala +++ b/test/org/jetbrains/plugins/hocon/HoconActionTest.scala @@ -8,6 +8,8 @@ import com.intellij.psi.PsiFile import com.intellij.testFramework.TestActionEvent import org.junit.Assert.assertNotNull +import scala.annotation.unused + /** @author * ghik */ @@ -37,7 +39,7 @@ abstract class HoconActionTest protected (protected val actionId: String, subPat } } - protected def executeAction(dataContext: DataContext, editor: Editor): Unit = { + protected def executeAction(dataContext: DataContext, @unused editor: Editor): Unit = { val action = ActionManager.getInstance.getAction(actionId) val actionEvent = TestActionEvent.createTestEvent(action, dataContext) diff --git a/test/org/jetbrains/plugins/hocon/HoconMultiModuleTest.scala b/test/org/jetbrains/plugins/hocon/HoconMultiModuleTest.scala index 53e2a16..2fdbacd 100644 --- a/test/org/jetbrains/plugins/hocon/HoconMultiModuleTest.scala +++ b/test/org/jetbrains/plugins/hocon/HoconMultiModuleTest.scala @@ -15,9 +15,9 @@ abstract class HoconMultiModuleTest extends UsefulTestCase with HoconTestUtils { import HoconMultiModuleTest.* - private var _fixture: CodeInsightTestFixture = _ + private var _fixture: CodeInsightTestFixture | Null = compiletime.uninitialized - protected def fixture: CodeInsightTestFixture = _fixture + protected def fixture: CodeInsightTestFixture = _fixture.nn def moduleDependencies: Seq[(String, String)] = Seq.empty @@ -49,8 +49,8 @@ abstract class HoconMultiModuleTest extends UsefulTestCase with HoconTestUtils { } _fixture = JavaTestFixtureFactory.getFixtureFactory.createCodeInsightFixture(fixtureBuilder.getFixture) - _fixture.setUp() - _fixture.setTestDataPath(rootPath) + fixture.setUp() + fixture.setTestDataPath(rootPath) inWriteAction { LocalFileSystem.getInstance().refresh(false) @@ -71,11 +71,11 @@ abstract class HoconMultiModuleTest extends UsefulTestCase with HoconTestUtils { models.values.foreach(_.commit()) } - IndexingTestUtil.waitUntilIndexesAreReady(_fixture.getProject) + IndexingTestUtil.waitUntilIndexesAreReady(fixture.getProject) } override def tearDown(): Unit = { - _fixture.tearDown() + fixture.tearDown() _fixture = null super.tearDown() } @@ -106,7 +106,7 @@ object HoconMultiModuleTest { model.addModuleOrderEntry(module).setExported(true) } -class HoconJavaModuleFixtureBuilder(fixtureBuilder: TestFixtureBuilder[_ <: IdeaProjectTestFixture]) +class HoconJavaModuleFixtureBuilder(fixtureBuilder: TestFixtureBuilder[? <: IdeaProjectTestFixture]) extends JavaModuleFixtureBuilderImpl[ModuleFixture](fixtureBuilder) { def instantiateFixture(): ModuleFixture = new ModuleFixtureImpl(this) diff --git a/test/org/jetbrains/plugins/hocon/HoconSingleModuleTest.scala b/test/org/jetbrains/plugins/hocon/HoconSingleModuleTest.scala index 21c5885..52cf145 100644 --- a/test/org/jetbrains/plugins/hocon/HoconSingleModuleTest.scala +++ b/test/org/jetbrains/plugins/hocon/HoconSingleModuleTest.scala @@ -1,13 +1,15 @@ package org.jetbrains.plugins.hocon +import com.intellij.openapi.project.Project import com.intellij.openapi.roots.ModuleRootManager +import com.intellij.psi.PsiManager import com.intellij.testFramework.PsiTestUtil.removeContentEntry import com.intellij.testFramework.{IndexingTestUtil, LightPlatformCodeInsightTestCase} abstract class HoconSingleModuleTest extends LightPlatformCodeInsightTestCase with HoconTestUtils { - final def project = getProject - final def module = getModule - final def psiManager = getPsiManager + final def project: Project = getProject + final def module: com.intellij.openapi.module.Module = getModule + final def psiManager: PsiManager = getPsiManager override def setUp(): Unit = { super.setUp() diff --git a/test/org/jetbrains/plugins/hocon/HoconTestUtils.scala b/test/org/jetbrains/plugins/hocon/HoconTestUtils.scala index 65ce854..a4ef377 100644 --- a/test/org/jetbrains/plugins/hocon/HoconTestUtils.scala +++ b/test/org/jetbrains/plugins/hocon/HoconTestUtils.scala @@ -47,7 +47,7 @@ trait HoconTestUtils { } object HoconTestUtils { - final lazy val TestdataPath = { + final lazy val TestdataPath: String = { @tailrec def find(dir: File): File = if (dir == null) throw new RuntimeException("testdata dir not found") diff --git a/test/org/jetbrains/plugins/hocon/manipulators/HoconManipulatorTest.scala b/test/org/jetbrains/plugins/hocon/manipulators/HoconManipulatorTest.scala index 8e1a080..a72c19a 100644 --- a/test/org/jetbrains/plugins/hocon/manipulators/HoconManipulatorTest.scala +++ b/test/org/jetbrains/plugins/hocon/manipulators/HoconManipulatorTest.scala @@ -9,7 +9,7 @@ import org.junit.runners.AllTests /** @author * ghik */ -abstract class HoconManipulatorTest(clazz: Class[_ <: HoconPsiElement], name: String) +abstract class HoconManipulatorTest(clazz: Class[? <: HoconPsiElement], name: String) extends HoconFileSetTestCase("manipulators/" + name) { import HoconFileSetTestCase.* diff --git a/test/org/jetbrains/plugins/hocon/resolution/HoconResolutionTest.scala b/test/org/jetbrains/plugins/hocon/resolution/HoconResolutionTest.scala index 367d7f9..d4764a2 100644 --- a/test/org/jetbrains/plugins/hocon/resolution/HoconResolutionTest.scala +++ b/test/org/jetbrains/plugins/hocon/resolution/HoconResolutionTest.scala @@ -24,8 +24,8 @@ class HoconResolutionTest extends HoconSingleModuleTest { val traversalResult = render( Iterator - .iterate(ctx.occurrences(path, opts).nextOption().orNull) { rf => - rf.nextOccurrence(opts).orNull + .iterate(ctx.occurrences(path, opts).nextOption().orNull.asInstanceOf[ResolvedField]) { rf => + rf.nextOccurrence(opts).orNull.asInstanceOf[ResolvedField] } .takeWhile(_ != null) ) diff --git a/test/org/jetbrains/plugins/hocon/usages/HoconFindUsagesTest.scala b/test/org/jetbrains/plugins/hocon/usages/HoconFindUsagesTest.scala index c1739bb..7b1db06 100644 --- a/test/org/jetbrains/plugins/hocon/usages/HoconFindUsagesTest.scala +++ b/test/org/jetbrains/plugins/hocon/usages/HoconFindUsagesTest.scala @@ -68,7 +68,7 @@ class HoconFindUsagesTest extends HoconMultiModuleTest { |""".stripMargin, ) - private def testFindUsages[E >: Null <: PsiElement: ClassTag]( + private def testFindUsages[E <: PsiElement: ClassTag]( filename: String, line: Int, column: Int, From 90923546fcf7f954e9f22c2218fc3c5007a9756e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Kozak?= Date: Mon, 5 Jan 2026 15:17:43 +0100 Subject: [PATCH 03/17] migrate to Scala 3 syntax by replacing `implicit` with `given` and `extension`, removing `implicitConversions`, and adopting modern patterns --- .../hocon/formatting/HoconFormatter.scala | 45 +++++++++---------- .../HoconSyntaxHighlightingAnnotator.scala | 2 +- .../plugins/hocon/lexer/HoconTokenSets.scala | 18 ++++---- src/org/jetbrains/plugins/hocon/package.scala | 37 +++++++-------- 4 files changed, 47 insertions(+), 55 deletions(-) diff --git a/src/org/jetbrains/plugins/hocon/formatting/HoconFormatter.scala b/src/org/jetbrains/plugins/hocon/formatting/HoconFormatter.scala index d24c732..b7808a5 100644 --- a/src/org/jetbrains/plugins/hocon/formatting/HoconFormatter.scala +++ b/src/org/jetbrains/plugins/hocon/formatting/HoconFormatter.scala @@ -74,22 +74,22 @@ class HoconFormatter(settings: CodeStyleSettings) { case (LBrace, RBrace) => normalSpacing(commonSettings.SPACE_WITHIN_BRACES) - case (LBrace, Include | KeyedField.extractor()) => + case (LBrace, Include | KeyedField()) => if (customSettings.OBJECTS_NEW_LINE_AFTER_LBRACE) dependentLFSpacing(commonSettings.SPACE_WITHIN_BRACES) else normalSpacing(commonSettings.SPACE_WITHIN_BRACES) - case (Include | KeyedField.extractor(), Include | KeyedField.extractor()) => + case (Include | KeyedField(), Include | KeyedField()) => lineBreakEnsuringSpacing - case (Include | KeyedField.extractor(), Comma) => + case (Include | KeyedField(), Comma) => normalSpacing(commonSettings.SPACE_BEFORE_COMMA) - case (Comma, KeyedField.extractor() | Include) => + case (Comma, KeyedField() | Include) => normalSpacing(commonSettings.SPACE_AFTER_COMMA) - case (KeyedField.extractor() | Include | Comma, RBrace) => + case (KeyedField() | Include | Comma, RBrace) => if (customSettings.OBJECTS_RBRACE_ON_NEXT_LINE) dependentLFSpacing(commonSettings.SPACE_WITHIN_BRACES) else @@ -98,22 +98,22 @@ class HoconFormatter(settings: CodeStyleSettings) { case (LBracket, RBracket) => normalSpacing(commonSettings.SPACE_WITHIN_BRACKETS) - case (LBracket, Value.extractor()) => + case (LBracket, Value()) => if (customSettings.LISTS_NEW_LINE_AFTER_LBRACKET) dependentLFSpacing(commonSettings.SPACE_WITHIN_BRACKETS) else normalSpacing(commonSettings.SPACE_WITHIN_BRACKETS) - case (Value.extractor(), Value.extractor()) => + case (Value(), Value()) => lineBreakEnsuringSpacing - case (Value.extractor(), Comma) => + case (Value(), Comma) => normalSpacing(commonSettings.SPACE_BEFORE_COMMA) - case (Comma, Value.extractor()) => + case (Comma, Value()) => normalSpacing(commonSettings.SPACE_AFTER_COMMA) - case (Value.extractor() | Comma, RBracket) => + case (Value() | Comma, RBracket) => if (customSettings.LISTS_RBRACKET_ON_NEXT_LINE) dependentLFSpacing(commonSettings.SPACE_WITHIN_BRACKETS) else @@ -131,10 +131,10 @@ class HoconFormatter(settings: CodeStyleSettings) { case (FieldKey, Equals | PlusEquals) => normalSpacing(customSettings.SPACE_BEFORE_ASSIGNMENT) - case (Colon, Value.extractor()) => + case (Colon, Value()) => normalSpacing(customSettings.SPACE_AFTER_COLON) - case (Equals | PlusEquals, Value.extractor()) => + case (Equals | PlusEquals, Value()) => normalSpacing(customSettings.SPACE_AFTER_ASSIGNMENT) case (Dollar, SubLBrace) | (SubLBrace, QMark) => @@ -214,16 +214,16 @@ class HoconFormatter(settings: CodeStyleSettings) { def getWrap(wrapCache: WrapCache, parent: ASTNode, child: ASTNode): Wrap | Null = (parent.getElementType, child.getElementType) match { - case (Object, Include | KeyedField.extractor()) => + case (Object, Include | KeyedField()) => wrapCache.objectEntryWrap - case (Array, Value.extractor()) => + case (Array, Value()) => wrapCache.arrayValueWrap - case (ValuedField, KeyValueSeparator.extractor()) => + case (ValuedField, KeyValueSeparator()) => wrapCache.keyValueSeparatorWrap - case (ValuedField, Value.extractor()) => + case (ValuedField, Value()) => wrapCache.fieldValueWrap case (Include, _) => @@ -234,10 +234,10 @@ class HoconFormatter(settings: CodeStyleSettings) { def getAlignment(alignmentCache: AlignmentCache, parent: ASTNode, child: ASTNode): Alignment | Null = (parent.getElementType, child.getElementType) match { - case (Object, Include | KeyedField.extractor() | Comment.extractor()) => + case (Object, Include | KeyedField() | Comment()) => alignmentCache.objectEntryAlignment - case (Array, Value.extractor() | Comment.extractor()) => + case (Array, Value() | Comment()) => alignmentCache.arrayValueAlignment case _ => null @@ -245,10 +245,9 @@ class HoconFormatter(settings: CodeStyleSettings) { def getIndent(parent: ASTNode, child: ASTNode): Indent = (parent.getElementType, child.getElementType) match { - case (Object, Include | KeyedField.extractor() | Comma | Comment.extractor()) | - (Array, Value.extractor() | Comma | Comment.extractor()) => + case (Object, Include | KeyedField() | Comma | Comment()) | (Array, Value() | Comma | Comment()) => Indent.getNormalIndent - case (Include, Included) | (ValuedField, KeyValueSeparator.extractor() | Value.extractor()) => + case (Include, Included) | (ValuedField, KeyValueSeparator() | Value()) => Indent.getContinuationIndent case _ => Indent.getNoneIndent @@ -257,7 +256,7 @@ class HoconFormatter(settings: CodeStyleSettings) { def getChildIndent(parent: ASTNode): Indent = parent.getElementType match { case Object | Array => Indent.getNormalIndent - case Include | KeyedField.extractor() => Indent.getContinuationIndent + case Include | KeyedField() => Indent.getContinuationIndent case _ => Indent.getNoneIndent } @@ -268,7 +267,7 @@ class HoconFormatter(settings: CodeStyleSettings) { } def getChildren(node: ASTNode): Iterator[ASTNode] = node.getElementType match { - case ForcedLeafBlock.extractor() => + case ForcedLeafBlock() => Iterator.empty case HoconFileElementType | Object => // immediately expand ObjectEntries element diff --git a/src/org/jetbrains/plugins/hocon/highlight/HoconSyntaxHighlightingAnnotator.scala b/src/org/jetbrains/plugins/hocon/highlight/HoconSyntaxHighlightingAnnotator.scala index ede0dea..aace49d 100644 --- a/src/org/jetbrains/plugins/hocon/highlight/HoconSyntaxHighlightingAnnotator.scala +++ b/src/org/jetbrains/plugins/hocon/highlight/HoconSyntaxHighlightingAnnotator.scala @@ -40,7 +40,7 @@ class HoconSyntaxHighlightingAnnotator extends Annotator { case KeyPart if firstChildType == UnquotedString => val textAttributesKey = element.getParent.getParent.getNode.getElementType match { case Path => HoconHighlighterColors.SubstitutionKey - case KeyedField.extractor() => HoconHighlighterColors.EntryKey + case KeyedField() => HoconHighlighterColors.EntryKey } annot(textAttributesKey) diff --git a/src/org/jetbrains/plugins/hocon/lexer/HoconTokenSets.scala b/src/org/jetbrains/plugins/hocon/lexer/HoconTokenSets.scala index 161c479..3a66078 100644 --- a/src/org/jetbrains/plugins/hocon/lexer/HoconTokenSets.scala +++ b/src/org/jetbrains/plugins/hocon/lexer/HoconTokenSets.scala @@ -3,8 +3,6 @@ package lexer import com.intellij.psi.tree.TokenSet -import scala.language.implicitConversions - object HoconTokenSets { import org.jetbrains.plugins.hocon.lexer.HoconTokenType.* @@ -27,15 +25,17 @@ object HoconTokenSets { final val ValueStart = SimpleValuePart | LBrace | LBracket | Dollar | KeyValueSeparator | BadCharacter final val ObjectEntryStart = PathStart | UnquotedCharsOrParens - case class Matcher(tokenSet: TokenSet, requireNoNewLine: Boolean, matchNewLine: Boolean, matchEof: Boolean) { - def noNewLine: Matcher = copy(requireNoNewLine = true) - def orNewLineOrEof: Matcher = copy(matchNewLine = true, matchEof = true) - def orEof: Matcher = copy(matchEof = true) + into case class Matcher(tokenSet: TokenSet, requireNoNewLine: Boolean, matchNewLine: Boolean, matchEof: Boolean) + + extension(matcher: Matcher){ + def noNewLine: Matcher = matcher.copy(requireNoNewLine = true) + def orNewLineOrEof: Matcher =matcher. copy(matchNewLine = true, matchEof = true) + def orEof: Matcher = matcher.copy(matchEof = true) } - implicit def token2Matcher(token: HoconTokenType): Matcher = + given Conversion[HoconTokenType, Matcher] = token => Matcher(TokenSet.create(token), requireNoNewLine = false, matchNewLine = false, matchEof = false) - implicit def tokenSet2Matcher(tokenSet: TokenSet): Matcher = + given Conversion[TokenSet, Matcher] = tokenSet => Matcher(tokenSet, requireNoNewLine = false, matchNewLine = false, matchEof = false) -} +} \ No newline at end of file diff --git a/src/org/jetbrains/plugins/hocon/package.scala b/src/org/jetbrains/plugins/hocon/package.scala index 2a9e7e4..24c9262 100644 --- a/src/org/jetbrains/plugins/hocon/package.scala +++ b/src/org/jetbrains/plugins/hocon/package.scala @@ -13,10 +13,10 @@ import org.jetbrains.plugins.hocon.lexer.HoconTokenType import java.net.{MalformedURLException, URL} import java.{lang as jl, util as ju} import javax.swing.Icon +import scala.Conversion.into import scala.annotation.tailrec import scala.collection.AbstractIterator import scala.collection.convert.{AsJavaExtensions, AsScalaExtensions} -import scala.language.implicitConversions import scala.reflect.{classTag, ClassTag} package object hocon extends AsJavaExtensions with AsScalaExtensions { @@ -45,27 +45,23 @@ package object hocon extends AsJavaExtensions with AsScalaExtensions { } } - implicit def liftSingleToken(token: IElementType): TokenSet = TokenSet.create(token) + given Conversion[IElementType, TokenSet] = TokenSet.create(_) - implicit class TokenSetOps(tokenSet: TokenSet) { - def |(otherTokenSet: TokenSet): TokenSet = + extension (tokenSet: into[TokenSet]) { + def |(otherTokenSet: into[TokenSet]): TokenSet = TokenSet.orSet(tokenSet, otherTokenSet) - def &(otherTokenSet: TokenSet): TokenSet = + def &(otherTokenSet: into[TokenSet]): TokenSet = TokenSet.andSet(tokenSet, otherTokenSet) - def &^(otherTokenSet: TokenSet): TokenSet = + def &^(otherTokenSet: into[TokenSet]): TokenSet = TokenSet.andNot(tokenSet, otherTokenSet) def unapply(tokenType: IElementType): Boolean = tokenSet.contains(tokenType) - - val extractor: TokenSetOps = this } - implicit def token2TokenSetOps(token: IElementType): TokenSetOps = new TokenSetOps(token) - - implicit class CharSequenceOps(private val cs: CharSequence) extends AnyVal { + extension (cs: CharSequence) { /** Like `subSequence` but makes sure a wrapper is created instead of making a copy */ def subSeqView(start: Int, end: Int = cs.length): CharSequence = @@ -87,7 +83,7 @@ package object hocon extends AsJavaExtensions with AsScalaExtensions { Iterator.range(0, cs.length).map(cs.charAt) } - implicit class NodeOps(private val node: ASTNode) extends AnyVal { + extension (node: ASTNode) { def childrenIterator: Iterator[ASTNode] = Iterator.iterate(node.getFirstChildNode)(_.getTreeNext).takeWhile(_ != null) @@ -98,7 +94,7 @@ package object hocon extends AsJavaExtensions with AsScalaExtensions { node.getFirstChildNode != null && node.getFirstChildNode.getTreeNext == null } - implicit class PsiElementOps(private val elem: PsiElement) extends AnyVal { + extension (elem: PsiElement) { def elementType: IElementType = elem.getNode.getElementType @@ -144,16 +140,16 @@ package object hocon extends AsJavaExtensions with AsScalaExtensions { } } - implicit class StringOps(private val str: String) extends AnyVal { + extension (str: String) { def indent(ind: String): String = ind + str.replace("\n", "\n" + ind) } - implicit class universalNullableOps[T](private val t: T | Null) extends AnyVal { + extension [T](t: T | Null) { def opt: Option[T] = Option(t) } - implicit class universalOps[T](private val t: T) extends AnyVal { + extension [T](t: T) { def setup(code: T => Unit): T = { code(t) @@ -171,7 +167,7 @@ package object hocon extends AsJavaExtensions with AsScalaExtensions { } } - implicit class OptionOps[A](private val option: Option[A]) extends AnyVal { + extension [A](option: Option[A]) { def collectOnly[T: ClassTag]: Option[T] = option.collect { case t: T => t } def nullOr[T >: Null](f: A => T): T = option.fold(null: T)(f) @@ -182,7 +178,7 @@ package object hocon extends AsJavaExtensions with AsScalaExtensions { } } - implicit class collectionOps[A](private val coll: IterableOnce[A]) extends AnyVal { + extension [A](coll: IterableOnce[A]) { def toJList[B >: A]: JList[B] = { val result = new ju.ArrayList[B] coll.iterator.foreach(result.add) @@ -190,10 +186,7 @@ package object hocon extends AsJavaExtensions with AsScalaExtensions { } } - implicit class IteratorOps[A](private val it: Iterator[A]) extends AnyVal { - def nextOption: Option[A] = - if (it.hasNext) Option(it.next()) else None - + extension [A](it: Iterator[A]) { def collectOnly[T: ClassTag]: Iterator[T] = it.collect { case t: T => t } From 6f98d22323c3952ff12184fcf940296491ee490f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Kozak?= Date: Mon, 5 Jan 2026 15:36:45 +0100 Subject: [PATCH 04/17] update `ResolutionCtx` to use Scala 3 union types and nullability annotations, add `@tailrec` annotations where applicable --- .../hocon/semantics/ResolutionCtx.scala | 30 ++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/src/org/jetbrains/plugins/hocon/semantics/ResolutionCtx.scala b/src/org/jetbrains/plugins/hocon/semantics/ResolutionCtx.scala index 70fb4bd..8441c12 100644 --- a/src/org/jetbrains/plugins/hocon/semantics/ResolutionCtx.scala +++ b/src/org/jetbrains/plugins/hocon/semantics/ResolutionCtx.scala @@ -54,10 +54,10 @@ sealed abstract class ResolutionCtx { case _: ArrayCtx => true } - lazy val lastInclude: Option[IncludeCtx] = this match { - case _: ToplevelCtx => None + lazy val lastInclude: IncludeCtx | Null = this match { + case _: ToplevelCtx => null case rf: ResolvedField => rf.parentCtx.lastInclude - case ic: IncludeCtx => Some(ic) + case ic: IncludeCtx => ic case ac: ArrayCtx => ac.parentCtx.lastInclude } @@ -79,11 +79,11 @@ sealed abstract class ResolutionCtx { def localEndOffset: Int = localTextRange._3 // ignores substitutions! - final def sameAs(other: ResolutionCtx): Boolean = (this eq other) || + @tailrec final def sameAs(other: ResolutionCtx): Boolean = (this eq other) || depth == other.depth && localTextRange == other.localTextRange && ((lastInclude, other.lastInclude) match { - case (Some(inc1), Some(inc2)) => inc1.parentCtx.sameAs(inc2.parentCtx) - case (None, None) => true + case (inc1: IncludeCtx, inc2: IncludeCtx) => inc1.parentCtx.sameAs(inc2.parentCtx) + case (null, null) => true case _ => false }) @@ -95,12 +95,12 @@ sealed abstract class ResolutionCtx { case ic: IncludeCtx => loop(ic.parentCtx, suffix) case _: ArrayCtx => Nil } - lastInclude.filterNot(_.inArray).map(loop(_, Nil)).getOrElse(Nil) + lastInclude.opt.filterNot(_.inArray).map(loop(_, Nil)).getOrElse(Nil) } @tailrec final def isAlreadyIn(file: HoconPsiFile): Boolean = lastInclude match { - case Some(ic) => ic.allFiles.contains(file) || ic.parentCtx.isAlreadyIn(file) - case None => false + case ic: IncludeCtx => ic.allFiles.contains(file) || ic.parentCtx.isAlreadyIn(file) + case null => false } private def pathsInResolution( @@ -180,14 +180,16 @@ sealed abstract class ResolutionCtx { } def trace: String = { - def loop(ic: Option[IncludeCtx], suffix: String): String = - ic.fold(suffix) { incCtx => + @tailrec def loop(ic: IncludeCtx | Null, suffix: String): String = ic match { + case null => suffix + case incCtx => incCtx.source match { case IncludeSource.Element(inc) => loop(incCtx.parentCtx.lastInclude, s"${inc.pos}->$suffix") case _ => suffix } - } + } + loop( lastInclude, this match { @@ -317,8 +319,8 @@ case class ResolvedField( lazy val includeChain: List[IncludeCtx] = { @tailrec def loop(rf: ResolutionCtx, suffix: List[IncludeCtx]): List[IncludeCtx] = rf.lastInclude match { - case Some(ic) => loop(ic.parentCtx, ic :: suffix) - case None => suffix + case ic: IncludeCtx => loop(ic.parentCtx, ic :: suffix) + case null => suffix } loop(this, Nil) } From 561e9cf207c85ab1c923f848c3a9e914c2bbc494 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Kozak?= Date: Mon, 5 Jan 2026 15:36:52 +0100 Subject: [PATCH 05/17] refactor: inline extensions in `package.scala` and clean up `.scalafmt.conf` for unused keywords --- .scalafmt.conf | 7 ------- src/org/jetbrains/plugins/hocon/package.scala | 17 ++++++----------- 2 files changed, 6 insertions(+), 18 deletions(-) diff --git a/.scalafmt.conf b/.scalafmt.conf index ec2ebd6..cf91045 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -81,13 +81,6 @@ rewrite.neverInfix.excludeFilters = [ theSameElementsAs //below extends default theSameElementsInOrderAs - like - zip - orElse - getOrElse - matchOpt - map - flatMap ] xmlLiterals.assumeFormatted = true diff --git a/src/org/jetbrains/plugins/hocon/package.scala b/src/org/jetbrains/plugins/hocon/package.scala index 24c9262..08700df 100644 --- a/src/org/jetbrains/plugins/hocon/package.scala +++ b/src/org/jetbrains/plugins/hocon/package.scala @@ -146,33 +146,28 @@ package object hocon extends AsJavaExtensions with AsScalaExtensions { } extension [T](t: T | Null) { - def opt: Option[T] = Option(t) - } - - extension [T](t: T) { + inline def opt: Option[T] = Option(t) - def setup(code: T => Unit): T = { + inline def setup(inline code: T => Unit): T = { code(t) t } - def typedOpt[U: ClassTag]: Option[U] = t match { + inline def typedOpt[U]: Option[U] = t match { case u: U => Some(u) case _ => None } - def debug(msg: T => String): T = { + inline def debug(inline msg: T => String): T = { println(msg(t)) t } } extension [A](option: Option[A]) { - def collectOnly[T: ClassTag]: Option[T] = option.collect { case t: T => t } - - def nullOr[T >: Null](f: A => T): T = option.fold(null: T)(f) + inline def collectOnly[T]: Option[T] = option.collect { case t: T => t } - def flatMapIt[T](f: A => Iterator[T]): Iterator[T] = option match { + inline def flatMapIt[T](inline f: A => Iterator[T]): Iterator[T] = option match { case Some(a) => f(a) case None => Iterator.empty } From 6896d8dda33cff7094e283a8cc76e8427b558640 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Kozak?= Date: Mon, 5 Jan 2026 15:37:00 +0100 Subject: [PATCH 06/17] migrate `HoconBlock` and `HoconFormatter` to use Scala 3 union types and nullability annotations --- .../plugins/hocon/formatting/HoconBlock.scala | 4 ++-- .../plugins/hocon/formatting/HoconFormatter.scala | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/org/jetbrains/plugins/hocon/formatting/HoconBlock.scala b/src/org/jetbrains/plugins/hocon/formatting/HoconBlock.scala index 237f9a8..8de9366 100644 --- a/src/org/jetbrains/plugins/hocon/formatting/HoconBlock.scala +++ b/src/org/jetbrains/plugins/hocon/formatting/HoconBlock.scala @@ -23,8 +23,8 @@ class HoconBlock( private val wrapCache = { val pathValueSeparatorType = if (node.getElementType == HoconElementType.ValuedField) - node.childrenIterator.map(_.getElementType).find(HoconTokenSets.KeyValueSeparator.contains) - else None + node.childrenIterator.map(_.getElementType).find(HoconTokenSets.KeyValueSeparator.contains).orNull + else null new formatter.WrapCache(pathValueSeparatorType) } private val alignmentCache = new formatter.AlignmentCache diff --git a/src/org/jetbrains/plugins/hocon/formatting/HoconFormatter.scala b/src/org/jetbrains/plugins/hocon/formatting/HoconFormatter.scala index b7808a5..fe6adad 100644 --- a/src/org/jetbrains/plugins/hocon/formatting/HoconFormatter.scala +++ b/src/org/jetbrains/plugins/hocon/formatting/HoconFormatter.scala @@ -173,7 +173,7 @@ class HoconFormatter(settings: CodeStyleSettings) { // Formatter must be able to return exactly the same instance of Wrap and Alignment objects // for children of the same parent and these two classes are one way to make it possible. - class WrapCache(keyValueSeparator: Option[IElementType]) { + class WrapCache(keyValueSeparator: IElementType | Null) { val objectEntryWrap: Wrap | Null = Wrap.createWrap(customSettings.OBJECTS_WRAP, false) @@ -181,17 +181,17 @@ class HoconFormatter(settings: CodeStyleSettings) { Wrap.createWrap(customSettings.LISTS_WRAP, false) val fieldInnerWrap: Wrap | Null = keyValueSeparator match { - case Some(Colon) => + case Colon => Wrap.createWrap(customSettings.OBJECT_FIELDS_WITH_COLON_WRAP, true) - case Some(Equals | PlusEquals) => + case Equals | PlusEquals => Wrap.createWrap(customSettings.OBJECT_FIELDS_WITH_ASSIGNMENT_WRAP, true) case _ => null } val keyValueSeparatorWrap: Wrap | Null = keyValueSeparator match { - case Some(Colon) if customSettings.OBJECT_FIELDS_COLON_ON_NEXT_LINE => + case Colon if customSettings.OBJECT_FIELDS_COLON_ON_NEXT_LINE => fieldInnerWrap - case Some(Equals | PlusEquals) if customSettings.OBJECT_FIELDS_ASSIGNMENT_ON_NEXT_LINE => + case Equals | PlusEquals if customSettings.OBJECT_FIELDS_ASSIGNMENT_ON_NEXT_LINE => fieldInnerWrap case _ => null } From abbfd2e745023b1fdc69666e7113eb9714d1e81a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Kozak?= Date: Mon, 5 Jan 2026 15:37:12 +0100 Subject: [PATCH 07/17] refactor: adopt Scala 3 chaining syntax with `.orElse` and `.map`, clean up completion and documentation generation logic --- .../misc/HoconDocumentationProvider.scala | 36 ++++++++++--------- .../hocon/ref/IncludedFileReferenceSet.scala | 2 +- .../completion/HoconCompletionTest.scala | 2 +- 3 files changed, 22 insertions(+), 18 deletions(-) diff --git a/src/org/jetbrains/plugins/hocon/misc/HoconDocumentationProvider.scala b/src/org/jetbrains/plugins/hocon/misc/HoconDocumentationProvider.scala index 8f7f735..f3d19a6 100644 --- a/src/org/jetbrains/plugins/hocon/misc/HoconDocumentationProvider.scala +++ b/src/org/jetbrains/plugins/hocon/misc/HoconDocumentationProvider.scala @@ -37,7 +37,7 @@ class HoconDocumentationProvider extends DocumentationProviderEx { override def getQuickNavigateInfo(element: PsiElement, originalElement: PsiElement): String | Null = { val res = for { - hkey <- key(originalElement) orElse key(element) + hkey <- key(originalElement).orElse(key(element)) fullPathText <- hkey.fullPathText } yield fullPathText + hkey.resolved.fold("")(_.resolveValue.valueHint) res.orNull @@ -46,20 +46,24 @@ class HoconDocumentationProvider extends DocumentationProviderEx { override def generateDoc(element: PsiElement, originalElement: PsiElement | Null): String | Null = { import DocumentationMarkup.* // `element` is already resolved here but it loses context of the `originalElement` so resolving again - (key(originalElement) orElse key(element)).flatMap(_.resolved).nullOr { resolved => - val docField = findDocField(Some(resolved)).getOrElse(resolved.field) - val fullPath = docField.key.flatMap(_.fullPathText).getOrElse("") - val hintString = resolved.resolveValue.valueHint - val hintRepr = if (hintString.nonEmpty) s"$GRAYED_START$hintString$GRAYED_END" else "" - val definition = s"$DEFINITION_START$fullPath$hintRepr$DEFINITION_END" - val docComments = docField.enclosingObjectField.docComments - val content = - if (docComments.isEmpty) "" - else - docComments - .map(c => StringEscapeUtils.escapeHtml4(c.getText.stripPrefix("#"))) - .mkString(CONTENT_START, "
", CONTENT_END) - definition + content - } + key(originalElement) + .orElse(key(element)) + .flatMap(_.resolved) + .map { resolved => + val docField = findDocField(Some(resolved)).getOrElse(resolved.field) + val fullPath = docField.key.flatMap(_.fullPathText).getOrElse("") + val hintString = resolved.resolveValue.valueHint + val hintRepr = if (hintString.nonEmpty) s"$GRAYED_START$hintString$GRAYED_END" else "" + val definition = s"$DEFINITION_START$fullPath$hintRepr$DEFINITION_END" + val docComments = docField.enclosingObjectField.docComments + val content = + if (docComments.isEmpty) "" + else + docComments + .map(c => StringEscapeUtils.escapeHtml4(c.getText.stripPrefix("#"))) + .mkString(CONTENT_START, "
", CONTENT_END) + definition + content + } + .orNull } } diff --git a/src/org/jetbrains/plugins/hocon/ref/IncludedFileReferenceSet.scala b/src/org/jetbrains/plugins/hocon/ref/IncludedFileReferenceSet.scala index b649602..5cf3350 100644 --- a/src/org/jetbrains/plugins/hocon/ref/IncludedFileReferenceSet.scala +++ b/src/org/jetbrains/plugins/hocon/ref/IncludedFileReferenceSet.scala @@ -57,7 +57,7 @@ object IncludedFileReferenceSet { def orderEntryScope = allScopes.reduceOption(_ `union` _) def moduleScope = pfi.getModuleForFile(parent).opt.map(_.getModuleRuntimeScope(false)) - orderEntryScope orElse moduleScope getOrElse GlobalSearchScope.EMPTY_SCOPE + orderEntryScope.orElse(moduleScope).getOrElse(GlobalSearchScope.EMPTY_SCOPE) } } diff --git a/test/org/jetbrains/plugins/hocon/completion/HoconCompletionTest.scala b/test/org/jetbrains/plugins/hocon/completion/HoconCompletionTest.scala index b0c406c..cafe9c7 100644 --- a/test/org/jetbrains/plugins/hocon/completion/HoconCompletionTest.scala +++ b/test/org/jetbrains/plugins/hocon/completion/HoconCompletionTest.scala @@ -36,7 +36,7 @@ class HoconCompletionTest extends BasePlatformTestCase { private def testCompletion(expected: String*): Unit = { myFixture.configureByFiles(s"${getTestName(true)}.conf", "included.conf") - val lookups = myFixture.complete(CompletionType.BASIC) map { case hple: HoconPropertyLookupElement => + val lookups = myFixture.complete(CompletionType.BASIC).map { case hple: HoconPropertyLookupElement => hple.repr } assertEquals(lookups.toSeq, expected) From aa4987fb1d1084f54de6ddded6ef3b22be0fa9ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Kozak?= Date: Mon, 5 Jan 2026 15:38:25 +0100 Subject: [PATCH 08/17] refactor: update inline extensions to use `T | Null` for improved null safety --- src/org/jetbrains/plugins/hocon/package.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/org/jetbrains/plugins/hocon/package.scala b/src/org/jetbrains/plugins/hocon/package.scala index 08700df..17fed41 100644 --- a/src/org/jetbrains/plugins/hocon/package.scala +++ b/src/org/jetbrains/plugins/hocon/package.scala @@ -148,7 +148,7 @@ package object hocon extends AsJavaExtensions with AsScalaExtensions { extension [T](t: T | Null) { inline def opt: Option[T] = Option(t) - inline def setup(inline code: T => Unit): T = { + inline def setup(inline code: T | Null => Unit): T | Null = { code(t) t } @@ -158,7 +158,7 @@ package object hocon extends AsJavaExtensions with AsScalaExtensions { case _ => None } - inline def debug(inline msg: T => String): T = { + inline def debug(inline msg: T | Null => String): T | Null = { println(msg(t)) t } From ecc1ce5d6f55a797b18798b37a2602e08953de2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Kozak?= Date: Mon, 5 Jan 2026 15:43:13 +0100 Subject: [PATCH 09/17] refactor: migrate `State` in `HoconLexer` to Scala 3 opaque type and add inline extensions --- src/org/jetbrains/plugins/hocon/lexer/HoconLexer.scala | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/org/jetbrains/plugins/hocon/lexer/HoconLexer.scala b/src/org/jetbrains/plugins/hocon/lexer/HoconLexer.scala index d1206a1..fa5b35f 100644 --- a/src/org/jetbrains/plugins/hocon/lexer/HoconLexer.scala +++ b/src/org/jetbrains/plugins/hocon/lexer/HoconLexer.scala @@ -8,7 +8,12 @@ import scala.annotation.tailrec object HoconLexer { - case class State(raw: Int) extends AnyVal + opaque type State = Int + object State { + inline def apply(inline raw: Int): State = raw + + extension (state: State) inline def raw: Int = state + } final val Initial = State(0) final val Value = State(1) From 78cd7ca1d8094346a144822848dce05a75b49670 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Kozak?= Date: Mon, 5 Jan 2026 15:43:21 +0100 Subject: [PATCH 10/17] remove unused @nowarn annotation in HoconTestUtils and relevant imports cleanup --- test/org/jetbrains/plugins/hocon/HoconTestUtils.scala | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/org/jetbrains/plugins/hocon/HoconTestUtils.scala b/test/org/jetbrains/plugins/hocon/HoconTestUtils.scala index a4ef377..d22b973 100644 --- a/test/org/jetbrains/plugins/hocon/HoconTestUtils.scala +++ b/test/org/jetbrains/plugins/hocon/HoconTestUtils.scala @@ -8,7 +8,7 @@ import com.intellij.openapi.vfs.{LocalFileSystem, VirtualFile} import com.intellij.psi.{PsiFile, PsiManager} import org.jetbrains.plugins.hocon.psi.HoconPsiFile -import scala.annotation.{nowarn, tailrec} +import scala.annotation.tailrec trait HoconTestUtils { def testdataPath: String = HoconTestUtils.TestdataPath @@ -40,7 +40,6 @@ trait HoconTestUtils { ApplicationManager.getApplication match { case application if application.isWriteAccessAllowed => body case application => - @nowarn("msg=deprecated") val computable: Computable[T] = () => body application.runWriteAction(computable) } From ab506841d07e4c7f21d378ffbba81260569fbd8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Kozak?= Date: Wed, 7 Jan 2026 15:41:44 +0100 Subject: [PATCH 11/17] refactor: migrate remaining sealed classes and case classes to Scala 3 enums and types, adopt chaining syntax, and clean up imports --- resources/META-INF/plugin.xml | 8 +- .../hocon/editor/HoconObjectEntryMover.scala | 75 ++++++++++--------- .../plugins/hocon/lexer/HoconTokenType.scala | 54 +++++++------ .../HoconGotoSymbolContributor.scala | 2 +- .../hocon/navigation/gotoHandlers.scala | 9 ++- .../hocon/parser/HoconElementType.scala | 57 +++++++------- .../plugins/hocon/psi/HoconPsiElement.scala | 4 +- .../HoconIncludeResolutionInspection.scala | 12 +-- .../hocon/ref/IncludedFileReferenceSet.scala | 2 +- .../hocon/semantics/ResolutionCtx.scala | 42 +++++------ 10 files changed, 134 insertions(+), 131 deletions(-) diff --git a/resources/META-INF/plugin.xml b/resources/META-INF/plugin.xml index 243ecae..83d71a7 100644 --- a/resources/META-INF/plugin.xml +++ b/resources/META-INF/plugin.xml @@ -76,11 +76,11 @@ displayName="HOCON"/> @@ -108,11 +108,11 @@ - - diff --git a/src/org/jetbrains/plugins/hocon/editor/HoconObjectEntryMover.scala b/src/org/jetbrains/plugins/hocon/editor/HoconObjectEntryMover.scala index d32b92f..626718f 100644 --- a/src/org/jetbrains/plugins/hocon/editor/HoconObjectEntryMover.scala +++ b/src/org/jetbrains/plugins/hocon/editor/HoconObjectEntryMover.scala @@ -133,10 +133,13 @@ class HoconObjectEntryMover extends LineMover { def canInsertInto(field: HObjectField) = !inSingleLine(field) && { val lineToInsertAfter = if (down) firstNonCommentLine(field) else endLine(field) - 1 - file.elementsAt(document.getLineEndOffset(lineToInsertAfter)).collectFirst { - case entries: HObjectEntries => entries.prefixingField.map(_.enclosingObjectField).contains(field) - case _: HKeyedField => false - } getOrElse false + file + .elementsAt(document.getLineEndOffset(lineToInsertAfter)) + .collectFirst { + case entries: HObjectEntries => entries.prefixingField.map(_.enclosingObjectField).contains(field) + case _: HKeyedField => false + } + .getOrElse(false) } def adjacentEntry(entry: HObjectEntry) = @@ -161,43 +164,47 @@ class HoconObjectEntryMover extends LineMover { def trySpecializedFieldMove(objField: HObjectField) = { val sourceRange = lineRange(objField) - fieldToAscendOutOf(objField).map { case (enclosingField, prefixToAdd) => - val targetRange = - if (down) new LineRange(sourceRange.endLine, endLine(enclosingField) + 1) - else new LineRange(startLine(enclosingField), sourceRange.startLine) - val mod = PrefixModification(objField.getTextOffset, 0, prefixToAdd.mkString("", ".", ".")) - (sourceRange, targetRange, Some(mod)) - - } orElse fieldToDescendInto(objField).map { case (adjacentField, prefixToRemove) => - val targetRange = - if (down) new LineRange(sourceRange.endLine, firstNonCommentLine(adjacentField) + 1) - else new LineRange(endLine(adjacentField), sourceRange.startLine) - val prefixStr = prefixToRemove.mkString("", ".", ".") - val needsGuard = document.getCharsSequence.charAt(objField.getTextOffset + prefixStr.length).isWhitespace - val mod = PrefixModification(objField.getTextOffset, prefixStr.length, if (needsGuard) "\"\"" else "") - (sourceRange, targetRange, Some(mod)) - } + fieldToAscendOutOf(objField) + .map { case (enclosingField, prefixToAdd) => + val targetRange = + if (down) new LineRange(sourceRange.endLine, endLine(enclosingField) + 1) + else new LineRange(startLine(enclosingField), sourceRange.startLine) + val mod = (objField.getTextOffset, 0, prefixToAdd.mkString("", ".", ".")) + (sourceRange, targetRange, Some(mod)) + + } + .orElse(fieldToDescendInto(objField).map { case (adjacentField, prefixToRemove) => + val targetRange = + if (down) new LineRange(sourceRange.endLine, firstNonCommentLine(adjacentField) + 1) + else new LineRange(endLine(adjacentField), sourceRange.startLine) + val prefixStr = prefixToRemove.mkString("", ".", ".") + val needsGuard = document.getCharsSequence.charAt(objField.getTextOffset + prefixStr.length).isWhitespace + val mod = (objField.getTextOffset, prefixStr.length, if (needsGuard) "\"\"" else "") + (sourceRange, targetRange, Some(mod)) + }) } def tryEntryMove(entry: HObjectEntry) = { val sourceRange = lineRange(entry) - adjacentMovableEntry(entry).map { adjacentEntry => - (sourceRange, lineRange(adjacentEntry), None) - } orElse { - val maxLinePos = editor.offsetToLogicalPosition(document.getTextLength) - val maxLine = if (maxLinePos.column == 0) maxLinePos.line else maxLinePos.line + 1 - val nearLine = if (down) sourceRange.endLine else sourceRange.startLine - 1 - - if (nearLine >= 0 && nearLine < maxLine) - Some((sourceRange, singleLineRange(nearLine), None)) - else None - } + adjacentMovableEntry(entry) + .map { adjacentEntry => + (sourceRange, lineRange(adjacentEntry), None) + } + .orElse { + val maxLinePos = editor.offsetToLogicalPosition(document.getTextLength) + val maxLine = if (maxLinePos.column == 0) maxLinePos.line else maxLinePos.line + 1 + val nearLine = if (down) sourceRange.endLine else sourceRange.startLine - 1 + + if (nearLine >= 0 && nearLine < maxLine) + Some((sourceRange, singleLineRange(nearLine), None)) + else None + } } val rangesOpt: Option[(LineRange, LineRange, Option[PrefixModification])] = enclosingAnchoredEntry(element).flatMap { case objField: HObjectField => - trySpecializedFieldMove(objField) orElse tryEntryMove(objField) + trySpecializedFieldMove(objField).orElse(tryEntryMove(objField)) case include: HInclude => tryEntryMove(include) } @@ -211,7 +218,7 @@ class HoconObjectEntryMover extends LineMover { } override def beforeMove(editor: Editor, info: MoveInfo, down: Boolean): Unit = - info.getUserData(PrefixModKey).foreach { case PrefixModification(offset, length, replacement) => + info.getUserData(PrefixModKey).foreach { case (offset, length, replacement) => // we need to move caret manually when adding prefix exactly at caret position val caretModel = editor.getCaretModel val shouldMoveCaret = length == 0 && caretModel.getOffset == offset @@ -224,7 +231,7 @@ class HoconObjectEntryMover extends LineMover { object HoconObjectEntryMover { - case class PrefixModification(offset: Int, length: Int, replacement: String) + type PrefixModification = (offset: Int, length: Int, replacement: String) val PrefixModKey = new Key[Option[PrefixModification]]("PrefixMod") } diff --git a/src/org/jetbrains/plugins/hocon/lexer/HoconTokenType.scala b/src/org/jetbrains/plugins/hocon/lexer/HoconTokenType.scala index 8b5009b..b516826 100644 --- a/src/org/jetbrains/plugins/hocon/lexer/HoconTokenType.scala +++ b/src/org/jetbrains/plugins/hocon/lexer/HoconTokenType.scala @@ -1,34 +1,32 @@ package org.jetbrains.plugins.hocon package lexer -import com.intellij.psi.TokenType -import com.intellij.psi.tree.IElementType -import org.jetbrains.plugins.hocon.lang.HoconLanguage +import lang.HoconLanguage -sealed class HoconTokenType(debugString: String) extends IElementType(debugString, HoconLanguage) +import com.intellij.psi.tree.IElementType -object HoconTokenType extends TokenType { - final val InlineWhitespace = new HoconTokenType("INLINE_WHITESPACE") - final val LineBreakingWhitespace = new HoconTokenType("LINE_BREAKING_WHITESPACE") - final val BadCharacter = new HoconTokenType("BAD_CHARACTER") - final val LBrace = new HoconTokenType("LBRACE") - final val RBrace = new HoconTokenType("RBRACE") - final val LBracket = new HoconTokenType("LBRACKET") - final val RBracket = new HoconTokenType("RBRACKET") - final val LParen = new HoconTokenType("LPAREN") - final val RParen = new HoconTokenType("RPAREN") - final val Colon = new HoconTokenType("COLON") - final val Comma = new HoconTokenType("COMMA") - final val Equals = new HoconTokenType("EQUALS") - final val PlusEquals = new HoconTokenType("PLUS_EQUALS") - final val Period = new HoconTokenType("PERIOD") - final val Dollar = new HoconTokenType("DOLLAR") - final val SubLBrace = new HoconTokenType("SUB_LBRACE") - final val QMark = new HoconTokenType("QMARK") - final val SubRBrace = new HoconTokenType("SUB_RBRACE") - final val HashComment = new HoconTokenType("HASH_COMMENT") - final val DoubleSlashComment = new HoconTokenType("DOUBLE_SLASH_COMMENT") - final val UnquotedChars = new HoconTokenType("UNQUOTED_CHARS") - final val QuotedString = new HoconTokenType("QUOTED_STRING") - final val MultilineString = new HoconTokenType("MULTILINE_STRING") +enum HoconTokenType(debugString: String) extends IElementType(debugString, HoconLanguage) { + case InlineWhitespace extends HoconTokenType("INLINE_WHITESPACE") + case LineBreakingWhitespace extends HoconTokenType("LINE_BREAKING_WHITESPACE") + case BadCharacter extends HoconTokenType("BAD_CHARACTER") + case LBrace extends HoconTokenType("LBRACE") + case RBrace extends HoconTokenType("RBRACE") + case LBracket extends HoconTokenType("LBRACKET") + case RBracket extends HoconTokenType("RBRACKET") + case LParen extends HoconTokenType("LPAREN") + case RParen extends HoconTokenType("RPAREN") + case Colon extends HoconTokenType("COLON") + case Comma extends HoconTokenType("COMMA") + case Equals extends HoconTokenType("EQUALS") + case PlusEquals extends HoconTokenType("PLUS_EQUALS") + case Period extends HoconTokenType("PERIOD") + case Dollar extends HoconTokenType("DOLLAR") + case SubLBrace extends HoconTokenType("SUB_LBRACE") + case QMark extends HoconTokenType("QMARK") + case SubRBrace extends HoconTokenType("SUB_RBRACE") + case HashComment extends HoconTokenType("HASH_COMMENT") + case DoubleSlashComment extends HoconTokenType("DOUBLE_SLASH_COMMENT") + case UnquotedChars extends HoconTokenType("UNQUOTED_CHARS") + case QuotedString extends HoconTokenType("QUOTED_STRING") + case MultilineString extends HoconTokenType("MULTILINE_STRING") } diff --git a/src/org/jetbrains/plugins/hocon/navigation/HoconGotoSymbolContributor.scala b/src/org/jetbrains/plugins/hocon/navigation/HoconGotoSymbolContributor.scala index 6117f15..dfed2d2 100644 --- a/src/org/jetbrains/plugins/hocon/navigation/HoconGotoSymbolContributor.scala +++ b/src/org/jetbrains/plugins/hocon/navigation/HoconGotoSymbolContributor.scala @@ -55,7 +55,7 @@ case class HoconGotoSymbolItem(key: HKey) extends PsiElementNavigationItem with override def getLocationString: String | Null = key.hoconFile.getVirtualFile.opt.map { vf => val pfi = ProjectFileIndex.getInstance(key.getProject) - val rootDir = pfi.getContentRootForFile(vf).opt orElse pfi.getClassRootForFile(vf).opt + val rootDir = pfi.getContentRootForFile(vf).opt.orElse(pfi.getClassRootForFile(vf).opt) val rootPath = rootDir.fold("")(_.getPath) s"(in ${vf.getPath.stripPrefix(rootPath).stripPrefix(File.separator)})" }.orNull diff --git a/src/org/jetbrains/plugins/hocon/navigation/gotoHandlers.scala b/src/org/jetbrains/plugins/hocon/navigation/gotoHandlers.scala index 704b163..767e24c 100644 --- a/src/org/jetbrains/plugins/hocon/navigation/gotoHandlers.scala +++ b/src/org/jetbrains/plugins/hocon/navigation/gotoHandlers.scala @@ -18,7 +18,11 @@ class HoconGotoDeclarationHandler extends GotoDeclarationHandler { sourceElement.parentOfType[HPath].flatMap(_.resolveBest()).map(_.field).toArray[PsiElement] } -abstract class HoconGotoPrevNextAction(reverse: Boolean) extends BaseCodeInsightAction with CodeInsightActionHandler { +enum HoconGotoPrevNextAction(reverse: Boolean) extends BaseCodeInsightAction with CodeInsightActionHandler { + case HoconGotoPrevAction extends HoconGotoPrevNextAction(reverse = true) + + case HoconGotoNextAction extends HoconGotoPrevNextAction(reverse = false) + override def isValidForFile(project: Project, editor: Editor, file: PsiFile): Boolean = file.getLanguage == HoconLanguage @@ -38,9 +42,6 @@ abstract class HoconGotoPrevNextAction(reverse: Boolean) extends BaseCodeInsight } } -class HoconGotoPrevAction extends HoconGotoPrevNextAction(reverse = true) -class HoconGotoNextAction extends HoconGotoPrevNextAction(reverse = false) - final class HoconGotoPrevNextPromoter extends ActionPromoter { // prioritize HoconGoto{Prev,Next}Action over GotoImplementationAction & GotoSuperAction in HOCON files // ideally these two actions should be completely disabled in HOCON files but I don't know how diff --git a/src/org/jetbrains/plugins/hocon/parser/HoconElementType.scala b/src/org/jetbrains/plugins/hocon/parser/HoconElementType.scala index d8117f7..a3ba3ef 100644 --- a/src/org/jetbrains/plugins/hocon/parser/HoconElementType.scala +++ b/src/org/jetbrains/plugins/hocon/parser/HoconElementType.scala @@ -1,14 +1,11 @@ package org.jetbrains.plugins.hocon package parser -import com.intellij.psi.tree.{IElementType, IFileElementType} -import org.jetbrains.plugins.hocon.lang.HoconLanguage - -class HoconElementType(debugName: String) extends IElementType(debugName, HoconLanguage) +import lang.HoconLanguage -object HoconElementType { +import com.intellij.psi.tree.{IElementType, IFileElementType} - val HoconFileElementType = new IFileElementType("HOCON_FILE", HoconLanguage) +enum HoconElementType(debugName: String) extends IElementType(debugName, HoconLanguage) { /** Object, i.e. object entries inside braces. * @@ -19,7 +16,7 @@ object HoconElementType { * } * }}} */ - val Object = new HoconElementType("OBJECT") + case Object extends HoconElementType("OBJECT") /** Contents of HOCON file or object, contains includes and object fields. * @@ -28,7 +25,7 @@ object HoconElementType { * some.path = value * }}} */ - val ObjectEntries = new HoconElementType("OBJECT_ENTRIES") + case ObjectEntries extends HoconElementType("OBJECT_ENTRIES") /** `include` clause * @@ -36,7 +33,7 @@ object HoconElementType { * include file("stuff") * }}} */ - val Include = new HoconElementType("INCLUDE") + case Include extends HoconElementType("INCLUDE") /** Thing that comes after `include` keyword, including possible `required` modifier * @@ -44,7 +41,7 @@ object HoconElementType { * required(file("stuff")) * }}} */ - val Included = new HoconElementType("INCLUDED") + case Included extends HoconElementType("INCLUDED") /** Thing that comes after `include` keyword but without the enclosing `required` modifier, if any. * @@ -52,7 +49,7 @@ object HoconElementType { * file("stuff") * }}} */ - val QualifiedIncluded = new HoconElementType("QUALIFIED_INCLUDED") + case QualifiedIncluded extends HoconElementType("QUALIFIED_INCLUDED") /** Keyed field (i.e. prefixed field or valued field) along with documentation comments. * @@ -64,7 +61,7 @@ object HoconElementType { * * Even if there are no doc comments, keyed field is always enclosed inside object field. */ - val ObjectField = new HoconElementType("OBJECT_FIELD") + case ObjectField extends HoconElementType("OBJECT_FIELD") /** A path-value field in which path contains more than one key: * @@ -75,7 +72,7 @@ object HoconElementType { * Prefixed field divides itself into first key (`prefix` in above example) and rest of the prefixed field which may * be another prefixed field or valued field (`key = value` in above example, which is a valued field). */ - val PrefixedField = new HoconElementType("PREFIXED_FIELD") + case PrefixedField extends HoconElementType("PREFIXED_FIELD") /** A key-value association (NOT path-value): * @@ -83,60 +80,64 @@ object HoconElementType { * key = value * }}} */ - val ValuedField = new HoconElementType("VALUED_FIELD") + case ValuedField extends HoconElementType("VALUED_FIELD") /** Path inside substitution. Divides into prefix path and last key. */ - val Path = new HoconElementType("PATH") + case Path extends HoconElementType("PATH") /** Key inside field (prefixed field or valued field). */ - val FieldKey = new HoconElementType("FIELD_KEY") + case FieldKey extends HoconElementType("FIELD_KEY") /** Key inside substitution path. */ - val SubstitutionKey = new HoconElementType("SUBSTITUTION_KEY") + case SubstitutionKey extends HoconElementType("SUBSTITUTION_KEY") /** HOCON array, i.e. brackets with sequence of values inside. */ - val Array = new HoconElementType("ARRAY") + case Array extends HoconElementType("ARRAY") /** HOCON substitution, i.e. path enclosed in `${}` (with optional `?` sign) */ - val Substitution = new HoconElementType("SUBSTITUTION") + case Substitution extends HoconElementType("SUBSTITUTION") /** Concatenation of two or more HOCON values. */ - val Concatenation = new HoconElementType("CONCATENATION") + case Concatenation extends HoconElementType("CONCATENATION") /** Unquoted string - a concatenation of whitespace, unquoted chars, parens and periods. This element type exists * primarily so that [[String]] element always has exactly one child (unquoted, quoted or multiline string). Unquoted * string occurs as a child of [[String]] or [[Key]]. */ - val UnquotedString = new HoconElementType("UNQUOTED_STRING") + case UnquotedString extends HoconElementType("UNQUOTED_STRING") - /** Encapsulates either an unquoted, quoted or multiline string - in value context. + /** Encapsulates either an unquoted, quoted or multiline string - in case ue context. */ - val StringValue = new HoconElementType("STRING_VALUE") + case StringValue extends HoconElementType("STRING_VALUE") /** Quoted string in `include` clause context. */ - val IncludeTarget = new HoconElementType("INCLUDE_TARGET") + case IncludeTarget extends HoconElementType("INCLUDE_TARGET") /** Encapsulates either an unquoted, quoted or multiline string - in key context. */ - val KeyPart = new HoconElementType("KEY_PART") + case KeyPart extends HoconElementType("KEY_PART") /** Literal numeric value. */ - val Number = new HoconElementType("NUMBER") + case Number extends HoconElementType("NUMBER") /** Literal `null` value. */ - val Null = new HoconElementType("NULL") + case Null extends HoconElementType("NULL") /** Literal boolean value. */ - val Boolean = new HoconElementType("BOOLEAN") + case Boolean extends HoconElementType("BOOLEAN") } + +object HoconElementType { + val HoconFileElementType = new IFileElementType("HOCON_FILE", HoconLanguage) +} diff --git a/src/org/jetbrains/plugins/hocon/psi/HoconPsiElement.scala b/src/org/jetbrains/plugins/hocon/psi/HoconPsiElement.scala index 5ee55a4..f4d5405 100644 --- a/src/org/jetbrains/plugins/hocon/psi/HoconPsiElement.scala +++ b/src/org/jetbrains/plugins/hocon/psi/HoconPsiElement.scala @@ -243,7 +243,7 @@ sealed abstract class HKeyedField(ast: ASTNode) def hasKeyValue(key: String): Boolean = keyString.contains(key) - def sameKeyAs(other: HKeyedField): Boolean = (key zip other.key).exists { case (k1, k2) => + def sameKeyAs(other: HKeyedField): Boolean = key.zip(other.key).exists { case (k1, k2) => k1.stringValue == k2.stringValue } @@ -704,7 +704,7 @@ final class HSubstitution(ast: ASTNode) extends HoconPsiElement(ast) with HValue depth: Int, backtrace: Boolean, ): Iterator[ResolvedField] = { - val subsCtx = Some(SubstitutionCtx(resCtx, this, subsKind)) + val subsCtx = Some((resCtx, this, subsKind)) val newCtx = resCtx.toplevelCtx.copy(subsCtx = subsCtx) def addBacktrace(it: Iterator[ResolvedField]): Iterator[ResolvedField] = diff --git a/src/org/jetbrains/plugins/hocon/ref/HoconIncludeResolutionInspection.scala b/src/org/jetbrains/plugins/hocon/ref/HoconIncludeResolutionInspection.scala index 3284461..ee04ea7 100644 --- a/src/org/jetbrains/plugins/hocon/ref/HoconIncludeResolutionInspection.scala +++ b/src/org/jetbrains/plugins/hocon/ref/HoconIncludeResolutionInspection.scala @@ -1,11 +1,16 @@ package org.jetbrains.plugins.hocon package ref +import psi.HIncludeTarget + import com.intellij.codeInspection.{LocalInspectionTool, ProblemHighlightType, ProblemsHolder} import com.intellij.psi.{PsiElement, PsiElementVisitor} -import org.jetbrains.plugins.hocon.psi.HIncludeTarget -abstract class AbstractHoconIncludeResolutionInspection(forRequired: Boolean) extends LocalInspectionTool { +enum AbstractHoconIncludeResolutionInspection(forRequired: Boolean) extends LocalInspectionTool { + case HoconIncludeResolutionInspection extends AbstractHoconIncludeResolutionInspection(false) + + case HoconRequiredIncludeResolutionInspection extends AbstractHoconIncludeResolutionInspection(true) + override def buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor = new PsiElementVisitor { override def visitElement(element: PsiElement): Unit = element match { @@ -24,6 +29,3 @@ abstract class AbstractHoconIncludeResolutionInspection(forRequired: Boolean) ex } } } - -class HoconIncludeResolutionInspection extends AbstractHoconIncludeResolutionInspection(false) -class HoconRequiredIncludeResolutionInspection extends AbstractHoconIncludeResolutionInspection(true) diff --git a/src/org/jetbrains/plugins/hocon/ref/IncludedFileReferenceSet.scala b/src/org/jetbrains/plugins/hocon/ref/IncludedFileReferenceSet.scala index 5cf3350..563a9a5 100644 --- a/src/org/jetbrains/plugins/hocon/ref/IncludedFileReferenceSet.scala +++ b/src/org/jetbrains/plugins/hocon/ref/IncludedFileReferenceSet.scala @@ -29,7 +29,7 @@ import java.{util => ju} * and .properties files at the end. This reflects the order in which Typesafe Config merges those files. */ object IncludedFileReferenceSet { - case class DefaultContexts( + type DefaultContexts = ( scope: GlobalSearchScope, contexts: ju.Collection[PsiFileSystemItem], ) diff --git a/src/org/jetbrains/plugins/hocon/semantics/ResolutionCtx.scala b/src/org/jetbrains/plugins/hocon/semantics/ResolutionCtx.scala index 8441c12..5666571 100644 --- a/src/org/jetbrains/plugins/hocon/semantics/ResolutionCtx.scala +++ b/src/org/jetbrains/plugins/hocon/semantics/ResolutionCtx.scala @@ -1,36 +1,31 @@ package org.jetbrains.plugins.hocon package semantics +import psi.* +import ref.{IncludedFileReferenceSet, PackageDirsEnumerator} +import semantics.SubstitutionKind.SelfReferential + import com.intellij.openapi.project.Project import com.intellij.openapi.util.TextRange import com.intellij.openapi.vfs.VirtualFile import com.intellij.psi.search.{FilenameIndex, GlobalSearchScope} import com.intellij.psi.{PsiElement, PsiManager} -import org.jetbrains.plugins.hocon.psi.* -import org.jetbrains.plugins.hocon.ref.{IncludedFileReferenceSet, PackageDirsEnumerator} -import org.jetbrains.plugins.hocon.semantics.SubstitutionKind.SelfReferential import scala.annotation.tailrec -case class ResOpts( - reverse: Boolean, - resolveIncludes: Boolean = true, - resolveSubstitutions: Boolean = true, -) - -sealed abstract class SubstitutionKind -object SubstitutionKind { - case class Full(path: List[String]) extends SubstitutionKind - case class SelfReferential(path: List[String], selfReferenced: ResolvedField) extends SubstitutionKind - case object Circular extends SubstitutionKind - case object Invalid extends SubstitutionKind +type ResOpts = (reverse: Boolean, resolveIncludes: Boolean, resolveSubstitutions: Boolean) + +def ResOpts(reverse: Boolean, resolveIncludes: Boolean = true, resolveSubstitutions: Boolean = true) = + (reverse, resolveIncludes, resolveSubstitutions) + +enum SubstitutionKind { + case Full(path: List[String]) + case SelfReferential(path: List[String], selfReferenced: ResolvedField) + case Circular + case Invalid } -case class SubstitutionCtx( - ctx: ResolutionCtx, - subst: HSubstitution, - subsKind: SubstitutionKind, -) +type SubstitutionCtx = (ctx: ResolutionCtx, subst: HSubstitution, subsKind: SubstitutionKind) sealed abstract class ResolutionCtx { lazy val toplevelCtx: ToplevelCtx = this match { @@ -404,10 +399,9 @@ case class ResolvedField( } } -sealed trait IncludeSource -object IncludeSource { - case class Element(inc: HInclude) extends IncludeSource - case class ToplevelFile(toplevelCtx: ToplevelCtx) extends IncludeSource +enum IncludeSource { + case Element(inc: HInclude) + case ToplevelFile(toplevelCtx: ToplevelCtx) } case class IncludeCtx( From 4ca68ac254f25068046ef178ef91928ae5e98ab1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Kozak?= Date: Wed, 7 Jan 2026 15:52:45 +0100 Subject: [PATCH 12/17] refactor: replace `Iterator.takeWhile(_ != null)` with new `Iterator.iterateNonNull` extension method for improved null safety --- .../hocon/editor/HoconObjectEntryMover.scala | 4 ++-- src/org/jetbrains/plugins/hocon/package.scala | 22 ++++++++++++++++--- .../plugins/hocon/psi/HoconPsiElement.scala | 22 ++++++++----------- .../hocon/psi/HoconPsiElementFactory.scala | 2 +- .../resolution/HoconResolutionTest.scala | 8 +++---- 5 files changed, 34 insertions(+), 24 deletions(-) diff --git a/src/org/jetbrains/plugins/hocon/editor/HoconObjectEntryMover.scala b/src/org/jetbrains/plugins/hocon/editor/HoconObjectEntryMover.scala index 626718f..f14c518 100644 --- a/src/org/jetbrains/plugins/hocon/editor/HoconObjectEntryMover.scala +++ b/src/org/jetbrains/plugins/hocon/editor/HoconObjectEntryMover.scala @@ -7,7 +7,7 @@ import com.intellij.openapi.editor.Editor import com.intellij.openapi.util.Key import com.intellij.psi.{PsiElement, PsiFile} import org.jetbrains.plugins.hocon.editor.HoconObjectEntryMover.{PrefixModKey, PrefixModification} -import org.jetbrains.plugins.hocon.psi.* +import org.jetbrains.plugins.hocon.psi.{HObjectField, *} import scala.annotation.tailrec @@ -147,7 +147,7 @@ class HoconObjectEntryMover extends LineMover { def fieldToDescendInto(field: HObjectField): Option[(HObjectField, List[String])] = for { - adjacentField <- adjacentEntry(field).collect { case f: HObjectField => f }.filter(canInsertInto) + adjacentField <- adjacentEntry(field).collectOnly[HObjectField].filter(canInsertInto) prefixToRemove <- { val prefix = adjacentField.keyedField.fieldsInPathForward.map(keyString).toList val removablePrefix = field.keyedField.fieldsInPathForward diff --git a/src/org/jetbrains/plugins/hocon/package.scala b/src/org/jetbrains/plugins/hocon/package.scala index 45b0f25..6910016 100644 --- a/src/org/jetbrains/plugins/hocon/package.scala +++ b/src/org/jetbrains/plugins/hocon/package.scala @@ -17,7 +17,7 @@ import scala.Conversion.into import scala.annotation.tailrec import scala.collection.AbstractIterator import scala.collection.convert.{AsJavaExtensions, AsScalaExtensions} -import scala.reflect.{classTag, ClassTag} +import scala.reflect.{ClassTag, Typeable, classTag} package object hocon extends AsJavaExtensions with AsScalaExtensions { type JList[T] = java.util.List[T] @@ -86,7 +86,7 @@ package object hocon extends AsJavaExtensions with AsScalaExtensions { extension (node: ASTNode) { def childrenIterator: Iterator[ASTNode] = - Iterator.iterate(node.getFirstChildNode)(_.getTreeNext).takeWhile(_ != null) + Iterator.iterateNonNull(node.getFirstChildNode)(_.getTreeNext) def children: Seq[ASTNode] = childrenIterator.toVector: Seq[ASTNode] @@ -183,7 +183,7 @@ package object hocon extends AsJavaExtensions with AsScalaExtensions { } extension [A](it: Iterator[A]) { - def collectOnly[T: ClassTag]: Iterator[T] = + def collectOnly[T: Typeable]: Iterator[T] = it.collect { case t: T => t } def flatCollect[B](f: PartialFunction[A, IterableOnce[B]]): Iterator[B] = @@ -246,4 +246,20 @@ package object hocon extends AsJavaExtensions with AsScalaExtensions { } catch { case _: MalformedURLException | _: IllegalArgumentException => false } + + extension (it: Iterator.type) { + def iterateNonNull[T](start: T | Null)(f: T => T | Null): Iterator[T] = new AbstractIterator[T] { + private var _next = start + + override def hasNext: Boolean = _next != null + + override def next(): T = { + if (!hasNext) Iterator.empty.next() + + val res = _next.nn + _next = f(res) + res + } + } + } } diff --git a/src/org/jetbrains/plugins/hocon/psi/HoconPsiElement.scala b/src/org/jetbrains/plugins/hocon/psi/HoconPsiElement.scala index f4d5405..e342cca 100644 --- a/src/org/jetbrains/plugins/hocon/psi/HoconPsiElement.scala +++ b/src/org/jetbrains/plugins/hocon/psi/HoconPsiElement.scala @@ -85,12 +85,10 @@ sealed abstract class HoconPsiElement(ast: ASTNode) extends ASTWrapperPsiElement getParent.asInstanceOf[Parent] def hoconParents: Iterator[HoconPsiElement] = - Iterator - .iterate(this)(_.getParent match { - case he: HoconPsiElement => he - case _ => null.asInstanceOf[HoconPsiElement] - }) - .takeWhile(_ != null) + Iterator.iterateNonNull(this)(_.getParent match { + case he: HoconPsiElement => he + case _ => null + }) def inArray: Boolean = hoconParents.exists { case _: HArray => true @@ -111,13 +109,13 @@ sealed abstract class HoconPsiElement(ast: ASTNode) extends ASTWrapperPsiElement findChild[T](reverse = true) def allChildren(reverse: Boolean): Iterator[PsiElement] = - Iterator.iterate(if (reverse) getLastChild else getFirstChild)(_.getNextSibling(reverse)).takeWhile(_ != null) + Iterator.iterateNonNull(if (reverse) getLastChild else getFirstChild)(_.getNextSibling(reverse)) def nextSibling[T: ClassTag](reverse: Boolean): Option[T] = moreSiblings(reverse).collectFirst { case t: T => t } def moreSiblings(reverse: Boolean): Iterator[PsiElement] = - Iterator.iterate(this.getNextSibling(reverse))(_.getNextSibling(reverse)).takeWhile(_ != null) + Iterator.iterateNonNull(this.getNextSibling(reverse))(_.getNextSibling(reverse)) def nonWhitespaceChildren: Iterator[PsiElement] = allChildren(reverse = false).filterNot(ch => ch.getNode.getElementType == TokenType.WHITE_SPACE) @@ -137,8 +135,7 @@ sealed abstract class HoconPsiElement(ast: ASTNode) extends ASTWrapperPsiElement final class HObjectEntries(ast: ASTNode) extends HoconPsiElement(ast) with HEntriesLike { type Parent = HObjectEntriesParent - def containingObject: Option[HObject] = - Option(parent).collect { case obj: HObject => obj } + def containingObject: Option[HObject] = parent.typedOpt[HObject] def isToplevel: Boolean = parent match { case _: HoconPsiFile => true @@ -271,7 +268,7 @@ sealed abstract class HKeyedField(ast: ASTNode) * iterator of all encountered keyed fields (in bottom-up order, i.e. starting with itself) */ def prefixingFields: Iterator[HKeyedField] = - Iterator.iterate(this)(_.prefixingField.orNull.asInstanceOf[HKeyedField]).takeWhile(_ != null) + Iterator.iterateNonNull(this)(_.prefixingField.orNull) /** Returns all keys on containing path, assuming they are all valid keys. `None` is returned if not all keys on * containing path are valid. The list is ordered top-down, i.e. `this` key is the last element. "Containing path" @@ -599,8 +596,7 @@ sealed trait HValue extends HoconPsiElement { case _: HoconPsiFile => None } - def concatParent: Option[HConcatenation] = - Option(parent).collect { case hc: HConcatenation => hc } + def concatParent: Option[HConcatenation] = parent.typedOpt[HConcatenation] def moreConcatenated(reverse: Boolean): Iterator[HValue] = concatParent.flatMapIt(_ => moreSiblings(reverse).collectOnly[HValue]) diff --git a/src/org/jetbrains/plugins/hocon/psi/HoconPsiElementFactory.scala b/src/org/jetbrains/plugins/hocon/psi/HoconPsiElementFactory.scala index 2c92c9d..fcea576 100644 --- a/src/org/jetbrains/plugins/hocon/psi/HoconPsiElementFactory.scala +++ b/src/org/jetbrains/plugins/hocon/psi/HoconPsiElementFactory.scala @@ -14,7 +14,7 @@ object HoconPsiElementFactory { .getInstance(manager.getProject) .createFileFromText(Dummy + HoconFileType.DefaultExtension, new HoconFileType, text) .findElementAt(offset) - Iterator.iterate(element)(_.getParent).takeWhile(_ != null).collectFirst { case t: T => t } + Iterator.iterateNonNull(element)(_.getParent).collectFirst { case t: T => t } } def createStringValue(contents: String, manager: PsiManager): HStringValue | Null = diff --git a/test/org/jetbrains/plugins/hocon/resolution/HoconResolutionTest.scala b/test/org/jetbrains/plugins/hocon/resolution/HoconResolutionTest.scala index d4764a2..3daaeb7 100644 --- a/test/org/jetbrains/plugins/hocon/resolution/HoconResolutionTest.scala +++ b/test/org/jetbrains/plugins/hocon/resolution/HoconResolutionTest.scala @@ -23,11 +23,9 @@ class HoconResolutionTest extends HoconSingleModuleTest { Assert.assertEquals(expected, actualResult) val traversalResult = render( - Iterator - .iterate(ctx.occurrences(path, opts).nextOption().orNull.asInstanceOf[ResolvedField]) { rf => - rf.nextOccurrence(opts).orNull.asInstanceOf[ResolvedField] - } - .takeWhile(_ != null) + Iterator.iterateNonNull(ctx.occurrences(path, opts).nextOption().orNull) { rf => + rf.nextOccurrence(opts).orNull + } ) Assert.assertEquals(expected, traversalResult) } From 9d4459691d99403241003d586642e31efdf46f20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Kozak?= Date: Wed, 7 Jan 2026 15:54:24 +0100 Subject: [PATCH 13/17] update: bump Scala version in CI workflow to `3.8.0-RC5` --- .github/workflows/ci.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b082209..2d96723 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,7 +22,7 @@ jobs: strategy: matrix: os: [ubuntu-latest] - scala: [3.7.4] + scala: [3.8.0-RC5] java: [temurin@21] runs-on: ${{ matrix.os }} steps: @@ -64,7 +64,7 @@ jobs: strategy: matrix: os: [ubuntu-latest] - scala: [3.7.4] + scala: [3.8.0-RC5] java: [temurin@21] runs-on: ${{ matrix.os }} steps: @@ -84,12 +84,12 @@ jobs: - name: Setup sbt uses: sbt/setup-sbt@v1 - - name: Download target directories (3.7.4) + - name: Download target directories (3.8.0-RC5) uses: actions/download-artifact@v6 with: - name: target-${{ matrix.os }}-3.7.4-${{ matrix.java }} + name: target-${{ matrix.os }}-3.8.0-RC5-${{ matrix.java }} - - name: Inflate target directories (3.7.4) + - name: Inflate target directories (3.8.0-RC5) run: | tar xf targets.tar rm targets.tar From 3b38c00792833016d62d4384525400ec653ad6c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Kozak?= Date: Wed, 7 Jan 2026 16:04:51 +0100 Subject: [PATCH 14/17] Update src/org/jetbrains/plugins/hocon/lexer/HoconLexer.scala Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/org/jetbrains/plugins/hocon/lexer/HoconLexer.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/org/jetbrains/plugins/hocon/lexer/HoconLexer.scala b/src/org/jetbrains/plugins/hocon/lexer/HoconLexer.scala index fa5b35f..4b6f695 100644 --- a/src/org/jetbrains/plugins/hocon/lexer/HoconLexer.scala +++ b/src/org/jetbrains/plugins/hocon/lexer/HoconLexer.scala @@ -87,7 +87,7 @@ class HoconLexer extends LexerBase { tokenStart = tokenEnd if (endOffset > tokenStart) { - input.charAt(tokenStart) match { // todo: no longer avilable @switch + input.charAt(tokenStart) match { // todo: no longer available @switch case '$' => setNewToken(Dollar, 1, onDollar(stateAfter)) case '?' if stateAfter == SubStarted => setNewToken(QMark, 1, Substitution) case '{' => From 473f1e040480faeaad69024374206e4927c213e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Kozak?= Date: Wed, 7 Jan 2026 16:05:37 +0100 Subject: [PATCH 15/17] Update src/org/jetbrains/plugins/hocon/parser/HoconElementType.scala Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/org/jetbrains/plugins/hocon/parser/HoconElementType.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/org/jetbrains/plugins/hocon/parser/HoconElementType.scala b/src/org/jetbrains/plugins/hocon/parser/HoconElementType.scala index a3ba3ef..55093c1 100644 --- a/src/org/jetbrains/plugins/hocon/parser/HoconElementType.scala +++ b/src/org/jetbrains/plugins/hocon/parser/HoconElementType.scala @@ -112,7 +112,7 @@ enum HoconElementType(debugName: String) extends IElementType(debugName, HoconLa */ case UnquotedString extends HoconElementType("UNQUOTED_STRING") - /** Encapsulates either an unquoted, quoted or multiline string - in case ue context. + /** Encapsulates either an unquoted, quoted or multiline string - in value context. */ case StringValue extends HoconElementType("STRING_VALUE") From acafd063da59a9917ec481fd965058ed45a5da2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Kozak?= Date: Wed, 7 Jan 2026 16:15:11 +0100 Subject: [PATCH 16/17] refactor: replace `ClassTag` with `Typeable` in `findChildren` for improved type safety --- src/org/jetbrains/plugins/hocon/psi/HoconPsiElement.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/org/jetbrains/plugins/hocon/psi/HoconPsiElement.scala b/src/org/jetbrains/plugins/hocon/psi/HoconPsiElement.scala index e342cca..8304f1b 100644 --- a/src/org/jetbrains/plugins/hocon/psi/HoconPsiElement.scala +++ b/src/org/jetbrains/plugins/hocon/psi/HoconPsiElement.scala @@ -24,7 +24,7 @@ import org.jetbrains.plugins.hocon.ref.{HKeyReference, IncludedFileReferenceSet, import org.jetbrains.plugins.hocon.semantics.* import scala.annotation.tailrec -import scala.reflect.{classTag, ClassTag} +import scala.reflect.{ClassTag, Typeable, classTag} sealed trait HoconPsiParent extends PsiElement @@ -125,10 +125,10 @@ sealed abstract class HoconPsiElement(ast: ASTNode) extends ASTWrapperPsiElement (HoconTokenSets.Comment | TokenType.WHITE_SPACE).contains(ch.getNode.getElementType) ) - def findChildren[T <: HoconPsiElement: ClassTag]: Iterator[T] = + def findChildren[T <: HoconPsiElement: Typeable]: Iterator[T] = findChildren[T](reverse = false) - def findChildren[T <: HoconPsiElement: ClassTag](reverse: Boolean): Iterator[T] = + def findChildren[T <: HoconPsiElement: Typeable](reverse: Boolean): Iterator[T] = allChildren(reverse).collectOnly[T] } From ec5f5f0a55376d6548baa749baf648d7dbeba30f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Kozak?= Date: Wed, 7 Jan 2026 16:46:07 +0100 Subject: [PATCH 17/17] refactor: adjust `plugin.xml` inspection class references and migrate `HoconGotoPrevNextAction` to use case class constructors --- resources/META-INF/plugin.xml | 4 ++-- src/org/jetbrains/plugins/hocon/navigation/gotoHandlers.scala | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/resources/META-INF/plugin.xml b/resources/META-INF/plugin.xml index 83d71a7..ea65b21 100644 --- a/resources/META-INF/plugin.xml +++ b/resources/META-INF/plugin.xml @@ -76,11 +76,11 @@ displayName="HOCON"/> diff --git a/src/org/jetbrains/plugins/hocon/navigation/gotoHandlers.scala b/src/org/jetbrains/plugins/hocon/navigation/gotoHandlers.scala index 767e24c..fac9eb6 100644 --- a/src/org/jetbrains/plugins/hocon/navigation/gotoHandlers.scala +++ b/src/org/jetbrains/plugins/hocon/navigation/gotoHandlers.scala @@ -19,9 +19,9 @@ class HoconGotoDeclarationHandler extends GotoDeclarationHandler { } enum HoconGotoPrevNextAction(reverse: Boolean) extends BaseCodeInsightAction with CodeInsightActionHandler { - case HoconGotoPrevAction extends HoconGotoPrevNextAction(reverse = true) + case HoconGotoPrevAction() extends HoconGotoPrevNextAction(reverse = true) - case HoconGotoNextAction extends HoconGotoPrevNextAction(reverse = false) + case HoconGotoNextAction() extends HoconGotoPrevNextAction(reverse = false) override def isValidForFile(project: Project, editor: Editor, file: PsiFile): Boolean = file.getLanguage == HoconLanguage