mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
PY-20003 Fixed: Breadcrumbs for Python files
Introduce PyBreadcrumbsInfoProvider which supports: classes and functions (incl. async) definitions, lambdas, try-except-else-finally, if-elif-else, for-else (incl. async), while-else, with and key-value statements
This commit is contained in:
committed by
Semyon Proshev
parent
f54070028a
commit
cde66f2f64
@@ -24,6 +24,6 @@
|
||||
<orderEntry type="module" module-name="xdebugger-impl" />
|
||||
<orderEntry type="module" module-name="xml-psi-impl" />
|
||||
<orderEntry type="library" name="gson" level="project" />
|
||||
<orderEntry type="module" module-name="xml-openapi" />
|
||||
</component>
|
||||
</module>
|
||||
|
||||
</module>
|
||||
@@ -602,6 +602,8 @@
|
||||
<!-- postfix templates!-->
|
||||
<codeInsight.template.postfixTemplateProvider language="Python"
|
||||
implementationClass="com.jetbrains.python.codeInsight.postfix.PyPostfixTemplateProvider"/>
|
||||
|
||||
<breadcrumbsInfoProvider implementation="com.jetbrains.python.breadcrumbs.PyBreadcrumbsInfoProvider"/>
|
||||
</extensions>
|
||||
|
||||
<extensionPoints>
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
/*
|
||||
* Copyright 2000-2016 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.jetbrains.python.breadcrumbs
|
||||
|
||||
import com.intellij.openapi.util.text.StringUtil
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.xml.breadcrumbs.BreadcrumbsInfoProvider
|
||||
import com.jetbrains.python.PythonLanguage
|
||||
import com.jetbrains.python.psi.*
|
||||
|
||||
class PyBreadcrumbsInfoProvider : BreadcrumbsInfoProvider() {
|
||||
|
||||
companion object {
|
||||
private val LANGUAGES = arrayOf(PythonLanguage.getInstance())
|
||||
private val HELPERS = listOf<Helper<*>>(
|
||||
LambdaHelper,
|
||||
SimpleHelper<PyTryPart>(PyTryPart::class.java, "try"),
|
||||
ExceptHelper,
|
||||
SimpleHelper<PyFinallyPart>(PyFinallyPart::class.java, "finally"),
|
||||
SimpleHelper<PyElsePart>(PyElsePart::class.java, "else"),
|
||||
IfHelper,
|
||||
ForHelper,
|
||||
WhileHelper,
|
||||
WithHelper,
|
||||
ClassHelper,
|
||||
FunctionHelper,
|
||||
KeyValueHelper
|
||||
)
|
||||
}
|
||||
|
||||
override fun getLanguages() = LANGUAGES
|
||||
override fun acceptElement(e: PsiElement) = getHelper(e) != null
|
||||
override fun getElementInfo(e: PsiElement) = getHelper(e)!!.elementInfo(e as PyElement)
|
||||
override fun getElementTooltip(e: PsiElement) = getHelper(e)!!.elementTooltip(e as PyElement)
|
||||
|
||||
private fun getHelper(e: PsiElement): Helper<in PyElement>? {
|
||||
if (e !is PyElement) return null
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return HELPERS.firstOrNull { it.type.isInstance(e) && (it as Helper<in PyElement>).accepts(e) } as Helper<in PyElement>?
|
||||
}
|
||||
|
||||
private abstract class Helper<T: PyElement>(val type: Class<T>) {
|
||||
abstract fun accepts(e: T): Boolean
|
||||
abstract fun elementInfo(e: T): String
|
||||
abstract fun elementTooltip(e: T): String
|
||||
}
|
||||
|
||||
private abstract class AbstractHelper<T : PyElement>(type: Class<T>) : Helper<T>(type) {
|
||||
override fun accepts(e: T): Boolean = true
|
||||
override fun elementInfo(e: T): String = getTruncatedPresentation(e, 16)
|
||||
override fun elementTooltip(e: T): String = getTruncatedPresentation(e, 96)
|
||||
|
||||
abstract fun getPresentation(e: T): String
|
||||
|
||||
private fun getTruncatedPresentation(e: T, maxLength: Int) = StringUtil.shortenTextWithEllipsis(getPresentation(e), maxLength, 0, true)
|
||||
}
|
||||
|
||||
private class SimpleHelper<T : PyElement>(type: Class<T>, val representation: String) : AbstractHelper<T>(type) {
|
||||
override fun getPresentation(e: T) = representation
|
||||
}
|
||||
|
||||
private object LambdaHelper : AbstractHelper<PyLambdaExpression>(PyLambdaExpression::class.java) {
|
||||
override fun getPresentation(e: PyLambdaExpression) = "lambda ${e.parameterList.getPresentableText(false)}"
|
||||
}
|
||||
|
||||
private object ExceptHelper : AbstractHelper<PyExceptPart>(PyExceptPart::class.java) {
|
||||
override fun getPresentation(e: PyExceptPart): String {
|
||||
val exceptClass = e.exceptClass ?: return "except"
|
||||
val target = e.target ?: return "except ${exceptClass.text}"
|
||||
|
||||
return "except ${exceptClass.text} as ${target.text}"
|
||||
}
|
||||
}
|
||||
|
||||
private object IfHelper : AbstractHelper<PyIfPart>(PyIfPart::class.java) {
|
||||
override fun getPresentation(e: PyIfPart): String {
|
||||
val prefix = if (e.isElif) "elif" else "if"
|
||||
val condition = e.condition ?: return prefix
|
||||
|
||||
return "$prefix ${condition.text}"
|
||||
}
|
||||
}
|
||||
|
||||
private object ForHelper : AbstractHelper<PyForPart>(PyForPart::class.java) {
|
||||
override fun getPresentation(e: PyForPart): String {
|
||||
val parent = e.parent
|
||||
val prefix = if (parent is PyForStatement && parent.isAsync) "async for" else "for"
|
||||
|
||||
val target = e.target ?: return prefix
|
||||
val source = e.source ?: return prefix
|
||||
|
||||
return "$prefix ${target.text} in ${source.text}"
|
||||
}
|
||||
}
|
||||
|
||||
private object WhileHelper : AbstractHelper<PyWhilePart>(PyWhilePart::class.java) {
|
||||
override fun getPresentation(e: PyWhilePart): String {
|
||||
val condition = e.condition ?: return "while"
|
||||
|
||||
return "while ${condition.text}"
|
||||
}
|
||||
}
|
||||
|
||||
private object WithHelper : AbstractHelper<PyWithStatement>(PyWithStatement::class.java) {
|
||||
override fun getPresentation(e: PyWithStatement): String {
|
||||
val getItemPresentation = fun(item: PyWithItem): String? {
|
||||
val expression = item.expression ?: return null
|
||||
val target = item.target ?: return expression.text
|
||||
return "${expression.text} as ${target.text}"
|
||||
}
|
||||
|
||||
val prefix = if (e.isAsync) "async with " else "with "
|
||||
|
||||
return e.withItems
|
||||
.orEmpty()
|
||||
.asSequence()
|
||||
.map(getItemPresentation)
|
||||
.filterNotNull()
|
||||
.joinToString(prefix = prefix)
|
||||
}
|
||||
}
|
||||
|
||||
private object ClassHelper : AbstractHelper<PyClass>(PyClass::class.java) {
|
||||
override fun getPresentation(e: PyClass) = e.name ?: "class"
|
||||
}
|
||||
|
||||
private object FunctionHelper : AbstractHelper<PyFunction>(PyFunction::class.java) {
|
||||
override fun getPresentation(e: PyFunction): String {
|
||||
val prefix = if (e.isAsync) "async " else ""
|
||||
val name = e.name ?: return "function"
|
||||
|
||||
return "$prefix$name()"
|
||||
}
|
||||
}
|
||||
|
||||
private object KeyValueHelper : AbstractHelper<PyKeyValueExpression>(PyKeyValueExpression::class.java) {
|
||||
override fun getPresentation(e: PyKeyValueExpression): String = e.key.text ?: "key"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
async def foo():
|
||||
async for i in range(10):
|
||||
print(<caret>i)
|
||||
@@ -0,0 +1,8 @@
|
||||
Crumb:
|
||||
async foo()
|
||||
Tooltip:
|
||||
async foo()
|
||||
Crumb:
|
||||
async for i in …
|
||||
Tooltip:
|
||||
async for i in range(10)
|
||||
@@ -0,0 +1,3 @@
|
||||
async def foo():
|
||||
async with open("file.txt"), open("file2.txt"):
|
||||
print("a<caret>bc")
|
||||
@@ -0,0 +1,3 @@
|
||||
async def foo():
|
||||
async with open("file.txt") as f, open("file2.txt") as f2:
|
||||
print("a<caret>bc")
|
||||
@@ -0,0 +1,8 @@
|
||||
Crumb:
|
||||
async foo()
|
||||
Tooltip:
|
||||
async foo()
|
||||
Crumb:
|
||||
async with open…
|
||||
Tooltip:
|
||||
async with open("file.txt") as f, open("file2.txt") as f2
|
||||
@@ -0,0 +1,8 @@
|
||||
Crumb:
|
||||
async foo()
|
||||
Tooltip:
|
||||
async foo()
|
||||
Crumb:
|
||||
async with open…
|
||||
Tooltip:
|
||||
async with open("file.txt"), open("file2.txt")
|
||||
@@ -0,0 +1,3 @@
|
||||
async def foo():
|
||||
async with open("file.txt"):
|
||||
print("a<caret>bc")
|
||||
@@ -0,0 +1,3 @@
|
||||
async def foo():
|
||||
async with open("file.txt") as f:
|
||||
print("a<caret>bc")
|
||||
@@ -0,0 +1,8 @@
|
||||
Crumb:
|
||||
async foo()
|
||||
Tooltip:
|
||||
async foo()
|
||||
Crumb:
|
||||
async with open…
|
||||
Tooltip:
|
||||
async with open("file.txt") as f
|
||||
@@ -0,0 +1,8 @@
|
||||
Crumb:
|
||||
async foo()
|
||||
Tooltip:
|
||||
async foo()
|
||||
Crumb:
|
||||
async with open…
|
||||
Tooltip:
|
||||
async with open("file.txt")
|
||||
@@ -0,0 +1,2 @@
|
||||
class A:
|
||||
pa<caret>ss
|
||||
@@ -0,0 +1,4 @@
|
||||
Crumb:
|
||||
A
|
||||
Tooltip:
|
||||
A
|
||||
@@ -0,0 +1 @@
|
||||
a = {"abc": "d<caret>ef"}
|
||||
@@ -0,0 +1,4 @@
|
||||
Crumb:
|
||||
"abc"
|
||||
Tooltip:
|
||||
"abc"
|
||||
@@ -0,0 +1,4 @@
|
||||
if True:
|
||||
print "abc"
|
||||
elif False:
|
||||
print "d<caret>ef"
|
||||
@@ -0,0 +1,4 @@
|
||||
Crumb:
|
||||
elif False
|
||||
Tooltip:
|
||||
elif False
|
||||
@@ -0,0 +1,4 @@
|
||||
try:
|
||||
print "abc"
|
||||
except KeyError:
|
||||
print "d<caret>ef"
|
||||
@@ -0,0 +1,4 @@
|
||||
try:
|
||||
print "abc"
|
||||
except:
|
||||
print "d<caret>ef"
|
||||
@@ -0,0 +1,4 @@
|
||||
Crumb:
|
||||
except
|
||||
Tooltip:
|
||||
except
|
||||
@@ -0,0 +1,4 @@
|
||||
try:
|
||||
print "abc"
|
||||
except KeyError as e:
|
||||
print "d<caret>ef"
|
||||
@@ -0,0 +1,4 @@
|
||||
Crumb:
|
||||
except KeyError…
|
||||
Tooltip:
|
||||
except KeyError as e
|
||||
@@ -0,0 +1,4 @@
|
||||
Crumb:
|
||||
except KeyError
|
||||
Tooltip:
|
||||
except KeyError
|
||||
@@ -0,0 +1,4 @@
|
||||
try:
|
||||
print "abc"
|
||||
finally:
|
||||
print "d<caret>ef"
|
||||
@@ -0,0 +1,4 @@
|
||||
Crumb:
|
||||
finally
|
||||
Tooltip:
|
||||
finally
|
||||
@@ -0,0 +1,2 @@
|
||||
for i in range(10):
|
||||
print <caret>i
|
||||
@@ -0,0 +1,4 @@
|
||||
for i in range(10):
|
||||
print i
|
||||
else:
|
||||
print "a<caret>bc"
|
||||
@@ -0,0 +1,4 @@
|
||||
Crumb:
|
||||
else
|
||||
Tooltip:
|
||||
else
|
||||
@@ -0,0 +1,4 @@
|
||||
Crumb:
|
||||
for i in range(…
|
||||
Tooltip:
|
||||
for i in range(10)
|
||||
@@ -0,0 +1,2 @@
|
||||
def foo(self):
|
||||
pa<caret>ss
|
||||
@@ -0,0 +1,4 @@
|
||||
Crumb:
|
||||
foo()
|
||||
Tooltip:
|
||||
foo()
|
||||
@@ -0,0 +1,2 @@
|
||||
if True:
|
||||
print "a<caret>bc"
|
||||
@@ -0,0 +1,4 @@
|
||||
if True:
|
||||
print "abc"
|
||||
else:
|
||||
print "d<caret>ef"
|
||||
@@ -0,0 +1,4 @@
|
||||
Crumb:
|
||||
else
|
||||
Tooltip:
|
||||
else
|
||||
@@ -0,0 +1,4 @@
|
||||
Crumb:
|
||||
if True
|
||||
Tooltip:
|
||||
if True
|
||||
@@ -0,0 +1 @@
|
||||
map(lambda x, y: <caret>x * y, [])
|
||||
@@ -0,0 +1,4 @@
|
||||
Crumb:
|
||||
lambda (x, y)
|
||||
Tooltip:
|
||||
lambda (x, y)
|
||||
@@ -0,0 +1 @@
|
||||
map(lambda a, aa, aaa, aaaa, aaaaa, aaaaaa, aaaaaaa, aaaaaaaa, aaaaaaaaa, aaaaaaaaaa, aaaaaaaaaaa, aaaaaaaaaaaa: <caret>a * a, [])
|
||||
@@ -0,0 +1,4 @@
|
||||
Crumb:
|
||||
lambda (a, aa, …
|
||||
Tooltip:
|
||||
lambda (a, aa, aaa, aaaa, aaaaa, aaaaaa, aaaaaaa, aaaaaaaa, aaaaaaaaa, aaaaaaaaaa, aaaaaaaaaaa,…
|
||||
@@ -0,0 +1,3 @@
|
||||
class A:
|
||||
def foo(self):
|
||||
pa<caret>ss
|
||||
@@ -0,0 +1,8 @@
|
||||
Crumb:
|
||||
A
|
||||
Tooltip:
|
||||
A
|
||||
Crumb:
|
||||
foo()
|
||||
Tooltip:
|
||||
foo()
|
||||
@@ -0,0 +1,2 @@
|
||||
with open("file.txt"), open("file2.txt"):
|
||||
print("a<caret>bc")
|
||||
@@ -0,0 +1,2 @@
|
||||
with open("file.txt") as f, open("file2.txt") as f2:
|
||||
print("a<caret>bc")
|
||||
@@ -0,0 +1,4 @@
|
||||
Crumb:
|
||||
with open("file…
|
||||
Tooltip:
|
||||
with open("file.txt") as f, open("file2.txt") as f2
|
||||
@@ -0,0 +1,4 @@
|
||||
Crumb:
|
||||
with open("file…
|
||||
Tooltip:
|
||||
with open("file.txt"), open("file2.txt")
|
||||
@@ -0,0 +1,4 @@
|
||||
try:
|
||||
print "a<caret>bc"
|
||||
finally:
|
||||
print "def"
|
||||
@@ -0,0 +1,6 @@
|
||||
try:
|
||||
print "abc"
|
||||
except:
|
||||
print "def"
|
||||
else:
|
||||
print "g<caret>hi"
|
||||
@@ -0,0 +1,4 @@
|
||||
Crumb:
|
||||
else
|
||||
Tooltip:
|
||||
else
|
||||
@@ -0,0 +1,4 @@
|
||||
Crumb:
|
||||
try
|
||||
Tooltip:
|
||||
try
|
||||
@@ -0,0 +1,3 @@
|
||||
i = 10
|
||||
while i < 30:
|
||||
print <caret>i
|
||||
@@ -0,0 +1,5 @@
|
||||
i = 10
|
||||
while i < 30:
|
||||
print i
|
||||
else:
|
||||
print "a<caret>bc"
|
||||
@@ -0,0 +1,4 @@
|
||||
Crumb:
|
||||
else
|
||||
Tooltip:
|
||||
else
|
||||
@@ -0,0 +1,4 @@
|
||||
Crumb:
|
||||
while i < 30
|
||||
Tooltip:
|
||||
while i < 30
|
||||
@@ -0,0 +1,2 @@
|
||||
with open("file.txt"):
|
||||
print("a<caret>bc")
|
||||
@@ -0,0 +1,2 @@
|
||||
with open("file.txt") as f:
|
||||
print("a<caret>bc")
|
||||
@@ -0,0 +1,4 @@
|
||||
Crumb:
|
||||
with open("file…
|
||||
Tooltip:
|
||||
with open("file.txt") as f
|
||||
@@ -0,0 +1,4 @@
|
||||
Crumb:
|
||||
with open("file…
|
||||
Tooltip:
|
||||
with open("file.txt")
|
||||
@@ -0,0 +1,179 @@
|
||||
/*
|
||||
* Copyright 2000-2016 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.jetbrains.python.breadcrumbs;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.xml.breadcrumbs.BreadcrumbsInfoProvider;
|
||||
import com.jetbrains.python.fixtures.PyTestCase;
|
||||
import com.jetbrains.python.psi.LanguageLevel;
|
||||
import one.util.streamex.StreamEx;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class PyBreadcrumbsTest extends PyTestCase {
|
||||
|
||||
@Override
|
||||
protected String getTestDataPath() {
|
||||
return super.getTestDataPath() + "/breadcrumbs";
|
||||
}
|
||||
|
||||
public void testDictKey() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testLambda() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testLargeLambda() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testTry() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testExcept() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testExceptAll() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testExceptAs() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testFinally() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testTryElse() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testIf() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testElif() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testIfElse() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testFor() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testAsyncFor() {
|
||||
runWithLanguageLevel(LanguageLevel.PYTHON35, this::doTest);
|
||||
}
|
||||
|
||||
public void testForElse() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testWhile() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testWhileElse() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testWith() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testWithAs() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testSeveralWith() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testSeveralWithAs() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testAsyncWith() {
|
||||
runWithLanguageLevel(LanguageLevel.PYTHON35, this::doTest);
|
||||
}
|
||||
|
||||
public void testAsyncWithAs() {
|
||||
runWithLanguageLevel(LanguageLevel.PYTHON35, this::doTest);
|
||||
}
|
||||
|
||||
public void testAsyncSeveralWith() {
|
||||
runWithLanguageLevel(LanguageLevel.PYTHON35, this::doTest);
|
||||
}
|
||||
|
||||
public void testAsyncSeveralWithAs() {
|
||||
runWithLanguageLevel(LanguageLevel.PYTHON35, this::doTest);
|
||||
}
|
||||
|
||||
public void testClass() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testMethod() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testFunction() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
private void doTest() {
|
||||
final String testName = getTestName(true);
|
||||
|
||||
myFixture.configureByFile(testName + ".py");
|
||||
|
||||
final PyBreadcrumbsInfoProvider provider = new PyBreadcrumbsInfoProvider();
|
||||
final String breadcrumbsAndTooltips = getBreadcrumbsAndTooltips(provider, elementsFromTopToCaret(provider));
|
||||
|
||||
assertSameLinesWithFile(getTestDataPath() + "/" + testName + "_crumbs.txt", breadcrumbsAndTooltips);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private List<PsiElement> elementsFromTopToCaret(@NotNull BreadcrumbsInfoProvider provider) {
|
||||
final List<PsiElement> fromElementToNullExcluded = StreamEx
|
||||
.iterate(myFixture.getFile().findElementAt(myFixture.getCaretOffset()), provider::getParent)
|
||||
.takeWhile(Objects::nonNull)
|
||||
.filter(provider::acceptElement)
|
||||
.toList();
|
||||
|
||||
return Lists.reverse(fromElementToNullExcluded);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static String getBreadcrumbsAndTooltips(@NotNull BreadcrumbsInfoProvider provider, @NotNull List<PsiElement> elements) {
|
||||
return elements
|
||||
.stream()
|
||||
.flatMap(element -> Stream.of("Crumb:", provider.getElementInfo(element), "Tooltip:", provider.getElementTooltip(element)))
|
||||
.collect(Collectors.joining("\n"));
|
||||
}
|
||||
}
|
||||
@@ -22,5 +22,6 @@
|
||||
<orderEntry type="library" scope="TEST" name="mockito" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Mocks" level="project" />
|
||||
<orderEntry type="library" exported="" scope="TEST" name="hamcrest" level="project" />
|
||||
<orderEntry type="module" module-name="xml-openapi" />
|
||||
</component>
|
||||
</module>
|
||||
Reference in New Issue
Block a user