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).
This commit is contained in:
nik
2018-02-07 19:41:52 +01:00
parent a5d7483039
commit c25725c8c7
3 changed files with 117 additions and 8 deletions
@@ -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)
}
@@ -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)
}
}
@@ -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<String> {
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<String> {
var start = 0
var next = 1
val names = ArrayList<String>()
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<List<String>, String> {
return Pair(string.splitByDotsJoiningIncorrectIdentifiers(), string.substringAfterLastDotNotFollowedByIncorrectChar())
}