From c25725c8c7ae412df75d09c68a2cb987faff8bdd Mon Sep 17 00:00:00 2001 From: nik Date: Wed, 7 Feb 2018 19:37:57 +0100 Subject: [PATCH] project configuration: improve automatic grouping by module names Don't split qualified name by dots in places where it leads to incorrect identifiers (e.g. after digits) to avoid creating unnecessary groups for modules which names include version numbers (IDEA-180295). --- .../GroupModulesByQualifiedNamesTest.kt | 14 +++++ ...ngByDotsJoiningIncorrectIdentifiersTest.kt | 57 +++++++++++++++++++ .../openapi/module/impl/moduleGroupers.kt | 54 +++++++++++++++--- 3 files changed, 117 insertions(+), 8 deletions(-) create mode 100644 platform/platform-tests/testSrc/com/intellij/module/SplitStringByDotsJoiningIncorrectIdentifiersTest.kt diff --git a/platform/platform-tests/testSrc/com/intellij/module/GroupModulesByQualifiedNamesTest.kt b/platform/platform-tests/testSrc/com/intellij/module/GroupModulesByQualifiedNamesTest.kt index d785e23d7db2..1c493bc7b05a 100644 --- a/platform/platform-tests/testSrc/com/intellij/module/GroupModulesByQualifiedNamesTest.kt +++ b/platform/platform-tests/testSrc/com/intellij/module/GroupModulesByQualifiedNamesTest.kt @@ -87,6 +87,20 @@ class GroupModulesByQualifiedNamesTest : PlatformTestCase() { assertEmpty(parentGroup.childGroups(grouper)) } + fun `test names with incorrect chars after dots`() { + val module1 = createModule("a.foo-1.2") + val module2 = createModule("a.foo-1.3") + + assertEquals("foo-1.2", grouper.getShortenedName(module1)) + assertEquals("foo-1.3", grouper.getShortenedName(module2)) + + val parentGroup = ModuleGroup(listOf("a")) + assertSameElements(parentGroup.modulesInGroup(grouper, false), module1, module2) + assertSameElements(parentGroup.modulesInGroup(grouper, true), module1, module2) + + assertEmpty(parentGroup.childGroups(grouper)) + } + private val grouper: ModuleGrouper get() = getQualifiedNameModuleGrouper(myProject) } diff --git a/platform/platform-tests/testSrc/com/intellij/module/SplitStringByDotsJoiningIncorrectIdentifiersTest.kt b/platform/platform-tests/testSrc/com/intellij/module/SplitStringByDotsJoiningIncorrectIdentifiersTest.kt new file mode 100644 index 000000000000..80cbc5452ab8 --- /dev/null +++ b/platform/platform-tests/testSrc/com/intellij/module/SplitStringByDotsJoiningIncorrectIdentifiersTest.kt @@ -0,0 +1,57 @@ +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.module + +import com.intellij.openapi.module.impl.splitStringByDotsJoiningIncorrectIdentifiers +import org.junit.Assert.assertEquals +import org.junit.Test + +/** + * @author nik + */ +class SplitStringByDotsJoiningIncorrectIdentifiersTest { + @Test + fun simple() { + splitAndCheck("a", "a") + splitAndCheck("a.b", "a", "b") + splitAndCheck("foo", "foo") + splitAndCheck("foo.bar", "foo", "bar") + } + + @Test + fun `incorrect chars`() { + assertDoNotSplit("a.1") + assertDoNotSplit("a-1.2") + assertDoNotSplit("a. b.") + splitAndCheck("foo.bar-1.2.3", "foo", "bar-1.2.3") + splitAndCheck("a..b", "a.", "b") + } + + @Test + fun `corner cases`() { + assertDoNotSplit("") + assertDoNotSplit(".") + assertDoNotSplit(" ") + assertDoNotSplit("..") + assertDoNotSplit("...") + assertDoNotSplit(".foo") + splitAndCheck("..foo", ".", "foo") + assertDoNotSplit("foo.") + assertDoNotSplit(".foo.") + assertDoNotSplit("foo..") + } + + private fun assertDoNotSplit(s: String) { + splitAndCheck(s, s) + } + + private fun splitAndCheck(s: String, vararg expected: String) { + val (list, last) = splitStringByDotsJoiningIncorrectIdentifiers(s) + if (list.isNotEmpty()) { + assertEquals(list.last(), last) + } + else { + assertEquals("", last) + } + assertEquals(expected.toList(), list) + } +} \ No newline at end of file diff --git a/platform/projectModel-impl/src/com/intellij/openapi/module/impl/moduleGroupers.kt b/platform/projectModel-impl/src/com/intellij/openapi/module/impl/moduleGroupers.kt index 188e111a6155..c28d94165ae1 100644 --- a/platform/projectModel-impl/src/com/intellij/openapi/module/impl/moduleGroupers.kt +++ b/platform/projectModel-impl/src/com/intellij/openapi/module/impl/moduleGroupers.kt @@ -3,7 +3,7 @@ package com.intellij.openapi.module.impl import com.intellij.openapi.module.* import com.intellij.openapi.project.Project -import com.intellij.openapi.util.text.StringUtil +import org.jetbrains.annotations.TestOnly import java.util.* /** @@ -26,19 +26,17 @@ private abstract class ModuleGrouperBase(protected val project: Project, protect } private class QualifiedNameGrouper(project: Project, model: ModifiableModuleModel?) : ModuleGrouperBase(project, model) { - override fun getGroupPath(module: Module): List { - return getGroupPathByModuleName(getModuleName(module)) - } + override fun getGroupPath(module: Module) = getGroupPathByModuleName(getModuleName(module)) override fun getGroupPath(description: ModuleDescription) = getGroupPathByModuleName(description.name) - override fun getShortenedNameByFullModuleName(name: String) = StringUtil.getShortName(name) + override fun getShortenedNameByFullModuleName(name: String) = name.substringAfterLastDotNotFollowedByIncorrectChar() - override fun getGroupPathByModuleName(name: String) = name.split('.').dropLast(1) + override fun getGroupPathByModuleName(name: String) = name.splitByDotsJoiningIncorrectIdentifiers().dropLast(1) - override fun getModuleAsGroupPath(module: Module) = getModuleName(module).split('.') + override fun getModuleAsGroupPath(module: Module) = getModuleName(module).splitByDotsJoiningIncorrectIdentifiers() - override fun getModuleAsGroupPath(description: ModuleDescription) = description.name.split('.') + override fun getModuleAsGroupPath(description: ModuleDescription) = description.name.splitByDotsJoiningIncorrectIdentifiers() } private class ExplicitModuleGrouper(project: Project, model: ModifiableModuleModel?): ModuleGrouperBase(project, model) { @@ -61,3 +59,43 @@ private class ExplicitModuleGrouper(project: Project, model: ModifiableModuleMod override fun getModuleAsGroupPath(description: ModuleDescription) = null } + +/** + * Split by dots where it doesn't lead to incorrect identifiers (i.e. "a.b-1.1" will be split to "a" and "b-1.1", not to "a", "b-1" and "1") + */ +private fun String.splitByDotsJoiningIncorrectIdentifiers(): List { + var start = 0 + var next = 1 + val names = ArrayList() + while (next < length) { + val end = indexOf('.', next) + if (end == -1 || end == length - 1) { + break + } + next = end + 1 + if (this[end + 1].isJavaIdentifierStart()) { + names.add(substring(start, end)) + start = end + 1 + } + } + names.add(substring(start)) + return names +} + +/** + * Returns the same value as `splitByDotsNotFollowedByIncorrectChars().last()` but works more efficiently + */ +private fun String.substringAfterLastDotNotFollowedByIncorrectChar(): String { + if (length <= 1) return this + var i = lastIndexOf('.', length - 2) + while (i != -1 && !this[i + 1].isJavaIdentifierStart()) { + i = lastIndexOf('.', i-1) + } + if (i <= 0) return this + return substring(i+1) +} + +@TestOnly +public fun splitStringByDotsJoiningIncorrectIdentifiers(string: String): Pair, String> { + return Pair(string.splitByDotsJoiningIncorrectIdentifiers(), string.substringAfterLastDotNotFollowedByIncorrectChar()) +} \ No newline at end of file