mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-01-05 01:50:56 +07:00
[pycharm] Address feedback
GitOrigin-RevId: ff5e1efdefa9ce24f76a0d628937a586a1363b78
This commit is contained in:
committed by
intellij-monorepo-bot
parent
8d5a145e6d
commit
18eb19aff3
31
.idea/libraries/tuweni_toml.xml
generated
Normal file
31
.idea/libraries/tuweni_toml.xml
generated
Normal file
@@ -0,0 +1,31 @@
|
||||
<component name="libraryTable">
|
||||
<library name="tuweni-toml" type="repository">
|
||||
<properties maven-id="org.apache.tuweni:tuweni-toml:2.0.0">
|
||||
<verification>
|
||||
<artifact url="file://$MAVEN_REPOSITORY$/org/apache/tuweni/tuweni-toml/2.0.0/tuweni-toml-2.0.0.jar">
|
||||
<sha256sum>b7331e02e955b6b962a8fa89eb8d7db0960d0b232880bfc60e8602fb3fed36ad</sha256sum>
|
||||
</artifact>
|
||||
<artifact url="file://$MAVEN_REPOSITORY$/org/antlr/antlr4-runtime/4.7.1/antlr4-runtime-4.7.1.jar">
|
||||
<sha256sum>43516d19beae35909e04d06af6c0c58c17bc94e0070c85e8dc9929ca640dc91d</sha256sum>
|
||||
</artifact>
|
||||
</verification>
|
||||
<exclude>
|
||||
<dependency maven-id="org.jetbrains.kotlin:kotlin-stdlib-common" />
|
||||
<dependency maven-id="org.jetbrains.kotlin:kotlin-stdlib-jdk7" />
|
||||
<dependency maven-id="org.jetbrains.kotlin:kotlin-stdlib-jdk8" />
|
||||
<dependency maven-id="org.jetbrains.kotlin:kotlin-stdlib" />
|
||||
<dependency maven-id="com.google.code.findbugs:jsr305" />
|
||||
<dependency maven-id="org.jetbrains:annotations" />
|
||||
</exclude>
|
||||
</properties>
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/apache/tuweni/tuweni-toml/2.0.0/tuweni-toml-2.0.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4-runtime/4.7.1/antlr4-runtime-4.7.1.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/apache/tuweni/tuweni-toml/2.0.0/tuweni-toml-2.0.0-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4-runtime/4.7.1/antlr4-runtime-4.7.1-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</component>
|
||||
1
.idea/modules.xml
generated
1
.idea/modules.xml
generated
@@ -1003,6 +1003,7 @@
|
||||
<module fileurl="file://$PROJECT_DIR$/python/python-psi-api/intellij.python.psi.iml" filepath="$PROJECT_DIR$/python/python-psi-api/intellij.python.psi.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/python/python-psi-impl/intellij.python.psi.impl.iml" filepath="$PROJECT_DIR$/python/python-psi-impl/intellij.python.psi.impl.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/python/intellij.python.pydev.iml" filepath="$PROJECT_DIR$/python/intellij.python.pydev.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/python/python-pyproject/intellij.python.pyproject.iml" filepath="$PROJECT_DIR$/python/python-pyproject/intellij.python.pyproject.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/python/python-sdk/intellij.python.sdk.iml" filepath="$PROJECT_DIR$/python/python-sdk/intellij.python.sdk.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/python/python-syntax/intellij.python.syntax.iml" filepath="$PROJECT_DIR$/python/python-syntax/intellij.python.syntax.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/python/python-syntax-core/intellij.python.syntax.core.iml" filepath="$PROJECT_DIR$/python/python-syntax-core/intellij.python.syntax.core.iml" />
|
||||
|
||||
@@ -246,5 +246,6 @@
|
||||
<orderEntry type="module" module-name="intellij.compose.ide.plugin.shared" scope="RUNTIME" />
|
||||
<orderEntry type="module" module-name="intellij.python.community.execService" scope="TEST" />
|
||||
<orderEntry type="module" module-name="intellij.vcs.git.commit.modal" scope="RUNTIME" />
|
||||
<orderEntry type="module" module-name="intellij.python.pyproject" scope="TEST" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -38,6 +38,7 @@ object PythonCommunityPluginModules {
|
||||
"intellij.python.sdk",
|
||||
"intellij.python.terminal",
|
||||
"intellij.python.ml.features",
|
||||
"intellij.python.pyproject",
|
||||
)
|
||||
|
||||
/**
|
||||
|
||||
@@ -82,37 +82,7 @@
|
||||
<orderEntry type="library" name="jna" level="project" />
|
||||
<orderEntry type="module" module-name="intellij.toml" />
|
||||
<orderEntry type="module" module-name="intellij.toml.core" />
|
||||
<orderEntry type="module-library">
|
||||
<library name="tuweni-toml" type="repository">
|
||||
<properties maven-id="org.apache.tuweni:tuweni-toml:2.0.0">
|
||||
<verification>
|
||||
<artifact url="file://$MAVEN_REPOSITORY$/org/apache/tuweni/tuweni-toml/2.0.0/tuweni-toml-2.0.0.jar">
|
||||
<sha256sum>b7331e02e955b6b962a8fa89eb8d7db0960d0b232880bfc60e8602fb3fed36ad</sha256sum>
|
||||
</artifact>
|
||||
<artifact url="file://$MAVEN_REPOSITORY$/org/antlr/antlr4-runtime/4.7.1/antlr4-runtime-4.7.1.jar">
|
||||
<sha256sum>43516d19beae35909e04d06af6c0c58c17bc94e0070c85e8dc9929ca640dc91d</sha256sum>
|
||||
</artifact>
|
||||
</verification>
|
||||
<exclude>
|
||||
<dependency maven-id="org.jetbrains.kotlin:kotlin-stdlib-common" />
|
||||
<dependency maven-id="org.jetbrains.kotlin:kotlin-stdlib-jdk7" />
|
||||
<dependency maven-id="org.jetbrains.kotlin:kotlin-stdlib-jdk8" />
|
||||
<dependency maven-id="org.jetbrains.kotlin:kotlin-stdlib" />
|
||||
<dependency maven-id="com.google.code.findbugs:jsr305" />
|
||||
<dependency maven-id="org.jetbrains:annotations" />
|
||||
</exclude>
|
||||
</properties>
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/apache/tuweni/tuweni-toml/2.0.0/tuweni-toml-2.0.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4-runtime/4.7.1/antlr4-runtime-4.7.1.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/apache/tuweni/tuweni-toml/2.0.0/tuweni-toml-2.0.0-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4-runtime/4.7.1/antlr4-runtime-4.7.1-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</orderEntry>
|
||||
<orderEntry type="library" name="tuweni-toml" level="project" />
|
||||
<orderEntry type="library" name="jsr305" level="project" />
|
||||
<orderEntry type="library" name="jetbrains-annotations" level="project" />
|
||||
<orderEntry type="library" name="kotlin-stdlib" level="project" />
|
||||
|
||||
@@ -20,6 +20,11 @@
|
||||
files:
|
||||
- name: $MAVEN_REPOSITORY$/org/apache/thrift/libthrift/0/libthrift-0.jar
|
||||
reason: withProjectLibrary
|
||||
- name: tuweni-toml
|
||||
files:
|
||||
- name: $MAVEN_REPOSITORY$/org/apache/tuweni/tuweni-toml/2/tuweni-toml-2.jar
|
||||
- name: $MAVEN_REPOSITORY$/org/antlr/antlr4-runtime/4/antlr4-runtime-4.jar
|
||||
reason: <- intellij.python.community.impl
|
||||
modules:
|
||||
- name: intellij.python.community
|
||||
- name: intellij.python.community.core.impl
|
||||
@@ -27,9 +32,6 @@
|
||||
libraries:
|
||||
ml-completion-prev-exprs-models:
|
||||
- name: $MAVEN_REPOSITORY$/completion/ml/python/features/ml-completion-prev-exprs-models/1/ml-completion-prev-exprs-models-1.jar
|
||||
tuweni-toml:
|
||||
- name: $MAVEN_REPOSITORY$/org/apache/tuweni/tuweni-toml/2/tuweni-toml-2.jar
|
||||
- name: $MAVEN_REPOSITORY$/org/antlr/antlr4-runtime/4/antlr4-runtime-4.jar
|
||||
completion-ranking-python-with-full-line:
|
||||
- name: $MAVEN_REPOSITORY$/org/jetbrains/intellij/deps/completion/completion-ranking-python-with-full-line/0/completion-ranking-python-with-full-line-0.jar
|
||||
- name: intellij.python.community.impl.poetry
|
||||
@@ -43,6 +45,7 @@
|
||||
- name: intellij.python.psi.impl
|
||||
- name: intellij.python.pydev
|
||||
- name: intellij.python.sdk
|
||||
- name: intellij.python.pyproject
|
||||
- name: intellij.python.community.plugin
|
||||
contentModules:
|
||||
- name: intellij.python.community.plugin.minor
|
||||
|
||||
@@ -3,33 +3,20 @@
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/resources" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/testResources" type="java-test-resource" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="kotlin-stdlib" level="project" />
|
||||
<orderEntry type="module" module-name="intellij.python.community" />
|
||||
<orderEntry type="module-library">
|
||||
<library>
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/apache/tuweni/tuweni-toml/2.0.0/tuweni-toml-2.0.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4-runtime/4.7.1/antlr4-runtime-4.7.1.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/apache/tuweni/tuweni-toml/2.0.0/tuweni-toml-2.0.0-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4-runtime/4.7.1/antlr4-runtime-4.7.1-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</orderEntry>
|
||||
<orderEntry type="module" module-name="intellij.python.psi.impl" />
|
||||
<orderEntry type="library" name="kotlinc.kotlin-compiler-tests" level="project" />
|
||||
<orderEntry type="module" module-name="intellij.platform.core" />
|
||||
<orderEntry type="module" module-name="intellij.python.sdk" />
|
||||
<orderEntry type="module" module-name="intellij.platform.util" />
|
||||
<orderEntry type="module" module-name="intellij.toml.core" />
|
||||
<orderEntry type="library" name="tuweni-toml" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="JUnit5" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="JUnit5Params" level="project" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -1,3 +1,4 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.python.pyproject
|
||||
|
||||
import com.intellij.psi.PsiElement
|
||||
@@ -7,16 +8,45 @@ import com.jetbrains.python.Result.Companion.success
|
||||
import com.jetbrains.python.mapResult
|
||||
import org.apache.tuweni.toml.TomlArray
|
||||
import org.apache.tuweni.toml.TomlTable
|
||||
import org.jetbrains.annotations.ApiStatus.Internal
|
||||
import org.toml.lang.psi.TomlKeyValue as PsiTomlKeyValue
|
||||
import org.toml.lang.psi.TomlTable as PsiTomlTable
|
||||
import org.toml.lang.psi.TomlLiteral as PsiTomlLiteral
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
/**
|
||||
* The error union used by [TomlTable.safeGet], [TomlTable.safeGetRequired] and [TomlTable.safeGetArr].
|
||||
*/
|
||||
@Internal
|
||||
sealed class TomlTableSafeGetError {
|
||||
/**
|
||||
* Signifies an incorrect type at [path]. Expected value is signified by [expected], actual value is signified by [actual].
|
||||
*/
|
||||
data class UnexpectedType(val path: String, val expected: KClass<*>, val actual: KClass<*>) : TomlTableSafeGetError()
|
||||
|
||||
/**
|
||||
* Signifies a missing value at [path].
|
||||
*/
|
||||
data class RequiredValueMissing(val path: String) : TomlTableSafeGetError()
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to extract a value of type [T] from an instance of [TomlTable] by key.
|
||||
* On an invalid type, returns an instance of [Result.Failure] with an error of [TomlTableSafeGetError.UnexpectedType].
|
||||
* On a missing value, returns an instance of [Result.Success] with a value of null.
|
||||
* On a present value with the valid type, returns an instance of [Result.Success] with that value.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```kotlin
|
||||
* val foo = tomlTable.safeGet<String>("foo")
|
||||
* when (foo) {
|
||||
* is Result.Success -> println("foo is ${foo.result}")
|
||||
* is Result.Failure -> println("foo is of the incorrect type")
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
@Internal
|
||||
inline fun <reified T> TomlTable.safeGet(key: String): Result<T?, TomlTableSafeGetError.UnexpectedType> {
|
||||
val value = get("\"$key\"")
|
||||
|
||||
@@ -37,6 +67,25 @@ inline fun <reified T> TomlTable.safeGet(key: String): Result<T?, TomlTableSafeG
|
||||
return success(value)
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to extract a value of type [T] from an instance of [TomlTable] by key, asserting its presence.
|
||||
* On an invalid type, returns an instance of [Result.Failure] with an error of [TomlTableSafeGetError.UnexpectedType].
|
||||
* On a missing value, returns an instance of [Result.Failure] with an error of [TomlTableSafeGetError.RequiredValueMissing].
|
||||
* On a present value with the valid type, returns an instance of [Result.Success] with that value.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```kotlin
|
||||
* tomlTable.safeGetRequired<String>("foo").getOr { error ->
|
||||
* when (error) {
|
||||
* is TomlTableSafeGetError.UnexpectedType -> print("incorrect type")
|
||||
* is TomlTableSafeGetError.RequiredValueMissing -> print("value is missing")
|
||||
* }
|
||||
* return
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
@Internal
|
||||
inline fun <reified T> TomlTable.safeGetRequired(key: String): Result<T, TomlTableSafeGetError> =
|
||||
safeGet<T>(key).mapResult {
|
||||
if (it == null) {
|
||||
@@ -47,6 +96,27 @@ inline fun <reified T> TomlTable.safeGetRequired(key: String): Result<T, TomlTab
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to extract an array of values of type [T] from an instance of [TomlTable] by key.
|
||||
* On an invalid type, returns an instance of [Result.Failure] with an error of [TomlTableSafeGetError.UnexpectedType].
|
||||
* On a missing value, returns an instance of [Result.Success] with a value of null.
|
||||
* On a present value with the valid type, returns an instance of [Result.Success] with a [List] of type [T].
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```kotlin
|
||||
* val foo = tomlTable.safeGetArr<String>("foo")
|
||||
* when (foo) {
|
||||
* is Result.Success -> foo.result?.forEachIndexed { index, value ->
|
||||
* print("foo[$index] is $value")
|
||||
* } ?: {
|
||||
* print("foo is null")
|
||||
* }
|
||||
* is Result.Failure -> println("foo is of the incorrect type")
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
@Internal
|
||||
inline fun <reified T> TomlTable.safeGetArr(key: String): Result<List<T>?, TomlTableSafeGetError.UnexpectedType> {
|
||||
val array = safeGet<TomlArray>(key).getOr { return it }
|
||||
|
||||
@@ -66,6 +136,19 @@ inline fun <reified T> TomlTable.safeGetArr(key: String): Result<List<T>?, TomlT
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to unwrap a [Result].
|
||||
* On success, returns the unwrapped value and calls the [onNull] callback (if provided) when the value is null.
|
||||
* On failure, wraps the error by calling the [wrapper] callback, adds the result to the provided [issues] list, then return null.
|
||||
* This function is useful for cases requiring to collect a list of parsing issues.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```kotlin
|
||||
* val issues = mutableListOf<>
|
||||
* ```
|
||||
*/
|
||||
@Internal
|
||||
fun <T, E, I> Result<T, E>.getOrIssue(issues: MutableList<I>, wrapper: (E) -> I, onNull: (() -> Unit)? = null): T? =
|
||||
when (this) {
|
||||
is Result.Success -> {
|
||||
@@ -80,16 +163,49 @@ fun <T, E, I> Result<T, E>.getOrIssue(issues: MutableList<I>, wrapper: (E) -> I,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to find a TOML header in [PsiElement] by the name of [name].
|
||||
* Returns null if the file wasn't TOML or the header wasn't found.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```kotlin
|
||||
* val dependencies = psiFile.findTomlHeader("project").findTomlValueByKey("dependencies")
|
||||
* ```
|
||||
*/
|
||||
@Internal
|
||||
fun PsiElement.findTomlHeader(name: String): PsiElement? =
|
||||
children.find { element ->
|
||||
(element as? PsiTomlTable)?.header?.key?.text == name
|
||||
}
|
||||
|
||||
fun PsiElement.findTomlValueByKey(name: String): PsiElement? =
|
||||
/**
|
||||
* Attempts to find a value of a [PsiTomlKeyValue] by the key of [key] in an instance of [PsiElement].
|
||||
* Returns null if the file wasn't TOML or the key wasn't found.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```kotlin
|
||||
* val dependencies = psiFile.findTomlHeader("project").findTomlValueByKey("dependencies")
|
||||
* ```
|
||||
*/
|
||||
@Internal
|
||||
fun PsiElement.findTomlValueByKey(key: String): PsiElement? =
|
||||
(children.find { element ->
|
||||
(element as? PsiTomlKeyValue)?.key?.text == name
|
||||
(element as? PsiTomlKeyValue)?.key?.text == key
|
||||
} as? PsiTomlKeyValue)?.value
|
||||
|
||||
/**
|
||||
* Attempts to find all [PsiTomlLiteral]s found within the children of [PsiElement] that have the text containing [text].
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```kotlin
|
||||
* val dependencies = psiFile.findTomlHeader("project").findTomlValueByKey("dependencies")
|
||||
* val requests = dependencies.findTomlLiteralsContaining("requests")
|
||||
* ```
|
||||
*/
|
||||
@Internal
|
||||
fun PsiElement.findTomlLiteralsContaining(text: String): List<PsiElement> =
|
||||
children.filter { element ->
|
||||
(element as? PsiTomlLiteral)?.text?.contains(text) ?: false
|
||||
|
||||
@@ -1,6 +1,15 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.python.pyproject
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus.Internal
|
||||
|
||||
/**
|
||||
* Represents a parsed `pyproject.toml` file.
|
||||
* Any inconsistencies with the spec and the parsed values are represented by the [PyProjectToml.issues] list after parsing.
|
||||
*
|
||||
* @see [pyproject.toml specification](https://packaging.python.org/en/latest/specifications/pyproject-toml/)
|
||||
*/
|
||||
@Internal
|
||||
data class PyProjectTable(
|
||||
val name: String? = null,
|
||||
val version: String? = null,
|
||||
@@ -20,17 +29,40 @@ data class PyProjectTable(
|
||||
val urls: Map<String, String>? = null,
|
||||
)
|
||||
|
||||
/**
|
||||
* Represents the dependencies of the project.
|
||||
*/
|
||||
@Internal
|
||||
data class PyProjectDependencies(
|
||||
/**
|
||||
* Dependencies provided in `project.dependencies`.
|
||||
*/
|
||||
val project: List<String> = listOf(),
|
||||
|
||||
/**
|
||||
* Dependencies provided in `dependency-groups.dev`.
|
||||
*/
|
||||
val dev: List<String> = listOf(),
|
||||
|
||||
/**
|
||||
* Dependencies provided in `project.optional-dependencies`.
|
||||
*/
|
||||
val optional: Map<String, List<String>> = mapOf(),
|
||||
)
|
||||
|
||||
/**
|
||||
* Represents a file object.
|
||||
*/
|
||||
@Internal
|
||||
data class PyProjectFile(
|
||||
val name: String,
|
||||
val contentType: String? = null,
|
||||
)
|
||||
|
||||
/**
|
||||
* Represents a contact. Both [name] and [email] can't be absent at the same time.
|
||||
*/
|
||||
@Internal
|
||||
data class PyProjectContact(val name: String?, val email: String?) {
|
||||
init {
|
||||
if (name == null && email == null) {
|
||||
|
||||
@@ -16,22 +16,73 @@ import com.jetbrains.python.sdk.basePath
|
||||
import com.jetbrains.python.sdk.findAmongRoots
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.jetbrains.annotations.ApiStatus.Internal
|
||||
import java.nio.file.Path
|
||||
|
||||
/**
|
||||
* Stores the file name of `pyproject.toml`.
|
||||
*/
|
||||
@Internal
|
||||
const val PY_PROJECT_TOML: String = "pyproject.toml"
|
||||
|
||||
/**
|
||||
* Represents an issue that could occur in [PyProjectToml.parse].
|
||||
*/
|
||||
@Internal
|
||||
sealed class PyProjectIssue {
|
||||
/**
|
||||
* Signifies that the name is missing from the `project` section.
|
||||
*/
|
||||
data object MissingName : PyProjectIssue()
|
||||
|
||||
/**
|
||||
* Signifies that the version is missing from the `project` section, while also being absent from the `dynamic` array.
|
||||
*/
|
||||
data object MissingVersion : PyProjectIssue()
|
||||
|
||||
/**
|
||||
* Wraps [TomlTableSafeGetError].
|
||||
*/
|
||||
data class SafeGetError(val error: TomlTableSafeGetError) : PyProjectIssue()
|
||||
|
||||
/**
|
||||
* Signifies that a contact misses both `name` and `email` fields.
|
||||
*/
|
||||
data class InvalidContact(val path: String) : PyProjectIssue()
|
||||
}
|
||||
|
||||
/**
|
||||
* A general handler for `pyproject.toml` files.
|
||||
*/
|
||||
@Internal
|
||||
data class PyProjectToml(
|
||||
/**
|
||||
* Represents the parsed `pyproject.toml` file.
|
||||
* This field can be null when the `project` section is missing.
|
||||
*/
|
||||
val project: PyProjectTable?,
|
||||
|
||||
/**
|
||||
* A list of issues that occurred during the execution of [PyProjectToml.parse].
|
||||
*/
|
||||
val issues: List<PyProjectIssue>,
|
||||
|
||||
/**
|
||||
* An instance of [TomlTable] provided by the TOML parser.
|
||||
*/
|
||||
val toml: TomlTable,
|
||||
) {
|
||||
/**
|
||||
* Gets a specific tool from an object implementing [PyProjectToolFactory].
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```kotlin
|
||||
* val pyProject = PyProjectToml.parse(psiFile.virtualFile.inputStream).orThrow()
|
||||
* val uvTool = pyProject.getTool(UvPyProject)
|
||||
* val hatch = pyProject.getTool(HatchPyProject)
|
||||
* ```
|
||||
*/
|
||||
fun <T : PyProjectToolFactory<U>, U> getTool(tool: T): U {
|
||||
return tool.createTool(
|
||||
mapOf(
|
||||
@@ -43,6 +94,19 @@ data class PyProjectToml(
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Attempts to parse [inputStream] and construct an instance of [PyProjectToml].
|
||||
* On success, returns an instance of [Result.Success] with an instance of [PyProjectToml].
|
||||
* On failure, returns an instance of [Result.Failure] with a list of [TomlParseError]s.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```kotlin
|
||||
* val pyProject = PyProjectToml.parse(psiFile.virtualFile.inputStream).orThrow()
|
||||
* val uvTool = pyProject.getTool(UvPyProject)
|
||||
* val hatch = pyProject.getTool(HatchPyProject)
|
||||
* ```
|
||||
*/
|
||||
fun parse(inputStream: InputStream): Result<PyProjectToml, List<TomlParseError>> {
|
||||
val issues = mutableListOf<PyProjectIssue>()
|
||||
val toml = Toml.parse(inputStream)
|
||||
@@ -163,19 +227,28 @@ data class PyProjectToml(
|
||||
)
|
||||
}
|
||||
|
||||
fun findFileBlocking(module: Module): VirtualFile? =
|
||||
findAmongRoots(module, PY_PROJECT_TOML)
|
||||
|
||||
/**
|
||||
* Attempts to find the `pyproject.toml` file in the provided module.
|
||||
* Returns null if not found.
|
||||
*/
|
||||
suspend fun findFile(module: Module): VirtualFile? =
|
||||
withContext(Dispatchers.IO) {
|
||||
findAmongRoots(module, PY_PROJECT_TOML)
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to find the module's working directory.
|
||||
* Returns a pair of [VirtualFile] to [Path], either of which may be null if not found.
|
||||
*/
|
||||
suspend fun findModuleWorkingDirectory(module: Module): Pair<VirtualFile?, Path?> {
|
||||
val file = findFile(module)
|
||||
return Pair(file, file?.toNioPathOrNull()?.parent ?: module.basePath?.let { Path.of(it) })
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to find the project's working directory.
|
||||
* Returns null if not found.
|
||||
*/
|
||||
fun findProjectWorkingDirectory(project: Project): Path? =
|
||||
project.basePath?.let { Path.of(it) }
|
||||
|
||||
|
||||
@@ -2,8 +2,36 @@
|
||||
package com.intellij.python.pyproject
|
||||
|
||||
import org.apache.tuweni.toml.TomlTable
|
||||
import org.jetbrains.annotations.ApiStatus.Internal
|
||||
|
||||
/**
|
||||
* A factory for `pyproject.toml` tool parsers.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```kotlin
|
||||
* data class UvPyProject(val dependencies: List<String>) {
|
||||
* companion object : PyProjectToolFactory<UvPyProject> {
|
||||
* override val tables: List<String> = listOf("tool.uv")
|
||||
*
|
||||
* override fun createTool(tables: Map<String, TomlTable?>): UvPyProject {
|
||||
* val dependencies = tables["tool.uv"]?.safeGetArr<String>("dev-dependencies")?.successOrNull ?: listOf()
|
||||
* return UvPyProject(uvDevDependencies)
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
@Internal
|
||||
interface PyProjectToolFactory<T> {
|
||||
/**
|
||||
* A list of strings that represent [TomlTable]s to be provided by [PyProjectToml.getTool] to [createTool]'s map.
|
||||
* If a table is absent from the file, the corresponding value in the map will be null.
|
||||
*/
|
||||
val tables: List<String>
|
||||
|
||||
/**
|
||||
* Constructs a concrete tool [T].
|
||||
*/
|
||||
fun createTool(tables: Map<String, TomlTable?>): T
|
||||
}
|
||||
@@ -0,0 +1,722 @@
|
||||
package com.intellij.python.pyproject
|
||||
|
||||
import com.intellij.python.pyproject.PyProjectIssue.InvalidContact
|
||||
import com.intellij.python.pyproject.PyProjectIssue.MissingName
|
||||
import com.intellij.python.pyproject.PyProjectIssue.MissingVersion
|
||||
import com.intellij.python.pyproject.PyProjectIssue.SafeGetError
|
||||
import com.intellij.python.pyproject.TomlTableSafeGetError.RequiredValueMissing
|
||||
import com.intellij.python.pyproject.TomlTableSafeGetError.UnexpectedType
|
||||
import com.jetbrains.python.Result.Failure
|
||||
import com.jetbrains.python.getOrThrow
|
||||
import com.jetbrains.python.isFailure
|
||||
import org.apache.tuweni.toml.TomlArray
|
||||
import org.apache.tuweni.toml.TomlTable
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.params.ParameterizedTest
|
||||
import org.junit.jupiter.params.provider.Arguments
|
||||
import org.junit.jupiter.params.provider.MethodSource
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
class PyProjectTomlTest {
|
||||
@Test
|
||||
fun parseProvidesErrorsOnFailure() {
|
||||
// GIVEN
|
||||
val configContents = "[proj"
|
||||
|
||||
// WHEN
|
||||
val result = PyProjectToml.parse(configContents.byteInputStream())
|
||||
|
||||
// THEN
|
||||
assert(result.isFailure)
|
||||
assert((result as Failure).error.isNotEmpty())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun toolsCanBeCreated() {
|
||||
// GIVEN
|
||||
val configContents = """
|
||||
[project]
|
||||
name="Some project"
|
||||
version="1.2.3"
|
||||
|
||||
[shared_category]
|
||||
foo="test foo"
|
||||
|
||||
[tool.test]
|
||||
bar="test bar"
|
||||
baz="test baz"
|
||||
""".trimIndent()
|
||||
val pyproject = PyProjectToml.parse(configContents.byteInputStream()).orThrow()
|
||||
|
||||
// WHEN
|
||||
val testTool = pyproject.getTool(TestPyProject)
|
||||
|
||||
// THEN
|
||||
assertEquals(2, testTool.tables.size)
|
||||
assert(testTool.tables["tool.test"] is TomlTable)
|
||||
assert(testTool.tables["shared_category"] is TomlTable)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun toolsCanBeCreatedWithoutProject() {
|
||||
// GIVEN
|
||||
val configContents = """
|
||||
[shared_category]
|
||||
foo="test foo"
|
||||
|
||||
[tool.test]
|
||||
bar="test bar"
|
||||
baz="test baz"
|
||||
""".trimIndent()
|
||||
|
||||
// WHEN
|
||||
val pyproject = PyProjectToml.parse(configContents.byteInputStream()).orThrow()
|
||||
val testTool = pyproject.getTool(TestPyProject)
|
||||
|
||||
// THEN
|
||||
assertEquals(pyproject.project, null)
|
||||
assertEquals(pyproject.issues.size, 0)
|
||||
assertEquals(2, testTool.tables.size)
|
||||
assert(testTool.tables["tool.test"] is TomlTable)
|
||||
assert(testTool.tables["shared_category"] is TomlTable)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun toolsCanBeCreatedWithProjectThatHasIssues() {
|
||||
// GIVEN
|
||||
val configContents = """
|
||||
[project]
|
||||
|
||||
[shared_category]
|
||||
foo="test foo"
|
||||
|
||||
[tool.test]
|
||||
bar="test bar"
|
||||
baz="test baz"
|
||||
""".trimIndent()
|
||||
|
||||
// WHEN
|
||||
val pyproject = PyProjectToml.parse(configContents.byteInputStream()).orThrow()
|
||||
val testTool = pyproject.getTool(TestPyProject)
|
||||
|
||||
// THEN
|
||||
assertEquals(pyproject.issues, listOf(MissingName, MissingVersion))
|
||||
assertEquals(pyproject.project, PyProjectTable())
|
||||
assertEquals(2, testTool.tables.size)
|
||||
assert(testTool.tables["tool.test"] is TomlTable)
|
||||
assert(testTool.tables["shared_category"] is TomlTable)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun absentToolSectionsResultInNull() {
|
||||
// GIVEN
|
||||
val configContents = """
|
||||
[project]
|
||||
name="Some project"
|
||||
version="1.2.3"
|
||||
""".trimIndent()
|
||||
val pyproject = PyProjectToml.parse(configContents.byteInputStream()).orThrow()
|
||||
|
||||
// WHEN
|
||||
val testTool = pyproject.getTool(TestPyProject)
|
||||
|
||||
// THEN
|
||||
assertEquals(testTool.tables["tool.test"], null)
|
||||
assertEquals(testTool.tables["shared_category"], null)
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{0}")
|
||||
@MethodSource("parseTestCases")
|
||||
fun parseTests(name: String, pyprojectToml: String, expectedProjectTable: PyProjectTable?, expectedIssues: List<PyProjectIssue>) {
|
||||
val result = PyProjectToml.parse(pyprojectToml.byteInputStream())
|
||||
val unwrapped = result.getOrThrow()
|
||||
|
||||
assertEquals(expectedProjectTable, unwrapped.project)
|
||||
assertEquals(expectedIssues, unwrapped.issues)
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun parseTestCases(): List<Arguments> = listOf(
|
||||
ParseTestCase(
|
||||
"empty config results with no table and empty issues",
|
||||
"",
|
||||
null,
|
||||
listOf()
|
||||
),
|
||||
|
||||
ParseTestCase(
|
||||
"empty project section results with table and name + version issues",
|
||||
"[project]",
|
||||
PyProjectTable(),
|
||||
listOf(MissingName, MissingVersion)
|
||||
),
|
||||
|
||||
ParseTestCase(
|
||||
"empty name results in an issue",
|
||||
"""
|
||||
[project]
|
||||
version = "123"
|
||||
""".trimIndent(),
|
||||
PyProjectTable(version = "123"),
|
||||
listOf(MissingName)
|
||||
),
|
||||
|
||||
ParseTestCase(
|
||||
"empty name results in an issue, even when mentioned in dynamic",
|
||||
"""
|
||||
[project]
|
||||
version = "123"
|
||||
dynamic = ["name"]
|
||||
""".trimIndent(),
|
||||
PyProjectTable(version = "123", dynamic = listOf("name")),
|
||||
listOf(MissingName)
|
||||
),
|
||||
|
||||
ParseTestCase(
|
||||
"empty version results in an issue",
|
||||
"""
|
||||
[project]
|
||||
name = "some_name"
|
||||
""".trimIndent(),
|
||||
PyProjectTable(name = "some_name"),
|
||||
listOf(MissingVersion)
|
||||
),
|
||||
|
||||
ParseTestCase(
|
||||
"empty version doesn't result in an issue when it's present in dynamic",
|
||||
"""
|
||||
[project]
|
||||
name = "some_name"
|
||||
dynamic = ["version"]
|
||||
""".trimIndent(),
|
||||
PyProjectTable(name = "some_name", dynamic = listOf("version")),
|
||||
listOf()
|
||||
),
|
||||
|
||||
ParseTestCase(
|
||||
"name of wrong type results in an issue",
|
||||
"""
|
||||
[project]
|
||||
name = 12
|
||||
version = "123"
|
||||
""".trimIndent(),
|
||||
PyProjectTable(version = "123"),
|
||||
listOf(SafeGetError(UnexpectedType("name", String::class, Long::class)))
|
||||
),
|
||||
|
||||
ParseTestCase(
|
||||
"version of wrong type results in an issue",
|
||||
"""
|
||||
[project]
|
||||
name = "name"
|
||||
version = 123
|
||||
""".trimIndent(),
|
||||
PyProjectTable(name = "name"),
|
||||
listOf(SafeGetError(UnexpectedType("version", String::class, Long::class)))
|
||||
),
|
||||
|
||||
ParseTestCase(
|
||||
"name and version resolve correctly when correctly specified",
|
||||
"""
|
||||
[project]
|
||||
name = "name"
|
||||
version = "123"
|
||||
""".trimIndent(),
|
||||
PyProjectTable(name = "name", version = "123"),
|
||||
listOf()
|
||||
),
|
||||
|
||||
*listOf<Pair<String, KClass<*>>>(
|
||||
"requires-python" to String::class,
|
||||
"authors" to TomlArray::class,
|
||||
"maintainers" to TomlArray::class,
|
||||
"description" to String::class,
|
||||
"readme" to TomlTable::class,
|
||||
"license" to String::class,
|
||||
"license-files" to TomlArray::class,
|
||||
"keywords" to TomlArray::class,
|
||||
"classifiers" to TomlArray::class,
|
||||
"dependencies" to TomlArray::class,
|
||||
"optional-dependencies" to TomlTable::class,
|
||||
"scripts" to TomlTable::class,
|
||||
"gui-scripts" to TomlTable::class,
|
||||
"urls" to TomlTable::class,
|
||||
).map {
|
||||
ParseTestCase(
|
||||
"${it.first} of wrong type results in an issue",
|
||||
"""
|
||||
[project]
|
||||
name = "name"
|
||||
version = "123"
|
||||
${it.first} = 123
|
||||
""".trimIndent(),
|
||||
PyProjectTable(name = "name", version = "123"),
|
||||
listOf(SafeGetError(UnexpectedType(it.first, it.second, Long::class)))
|
||||
)
|
||||
}.toTypedArray(),
|
||||
|
||||
*listOf("authors", "maintainers").flatMap {
|
||||
listOf(
|
||||
ParseTestCase(
|
||||
"contacts of wrong type in $it result in an issue",
|
||||
"""
|
||||
[project]
|
||||
name = "name"
|
||||
version = "123"
|
||||
$it = [
|
||||
123,
|
||||
]
|
||||
""".trimIndent(),
|
||||
PyProjectTable(name = "name", version = "123"),
|
||||
listOf(
|
||||
SafeGetError(UnexpectedType("$it[0]", TomlTable::class, Long::class)),
|
||||
)
|
||||
),
|
||||
|
||||
ParseTestCase(
|
||||
"contacts without name and email in $it result in an issue",
|
||||
"""
|
||||
[project]
|
||||
name = "name"
|
||||
version = "123"
|
||||
$it = [
|
||||
{foo = 123, bar = "qwf"}
|
||||
]
|
||||
""".trimIndent(),
|
||||
PyProjectTable(
|
||||
name = "name",
|
||||
version = "123",
|
||||
authors = if (it == "authors") listOf() else null,
|
||||
maintainers = if (it == "maintainers") listOf() else null,
|
||||
),
|
||||
listOf(
|
||||
InvalidContact("$it[0]"),
|
||||
)
|
||||
),
|
||||
|
||||
ParseTestCase(
|
||||
"contacts with only a name in $it resolve",
|
||||
"""
|
||||
[project]
|
||||
name = "name"
|
||||
version = "123"
|
||||
$it = [
|
||||
{name = "name1"},
|
||||
{name = "name2"}
|
||||
]
|
||||
""".trimIndent(),
|
||||
run {
|
||||
val contacts = listOf(PyProjectContact(name = "name1", email = null), PyProjectContact(name = "name2", email = null))
|
||||
PyProjectTable(
|
||||
name = "name",
|
||||
version = "123",
|
||||
authors = if (it == "authors") contacts else null,
|
||||
maintainers = if (it == "maintainers") contacts else null,
|
||||
)
|
||||
},
|
||||
listOf(),
|
||||
),
|
||||
|
||||
ParseTestCase(
|
||||
"contacts with only an email in $it resolve",
|
||||
"""
|
||||
[project]
|
||||
name = "name"
|
||||
version = "123"
|
||||
$it = [
|
||||
{email = "email1"},
|
||||
{email = "email2"}
|
||||
]
|
||||
""".trimIndent(),
|
||||
run {
|
||||
val contacts = listOf(PyProjectContact(name = null, email = "email1"), PyProjectContact(name = null, email = "email2"))
|
||||
PyProjectTable(
|
||||
name = "name",
|
||||
version = "123",
|
||||
authors = if (it == "authors") contacts else null,
|
||||
maintainers = if (it == "maintainers") contacts else null,
|
||||
)
|
||||
},
|
||||
listOf(),
|
||||
),
|
||||
|
||||
ParseTestCase(
|
||||
"contacts with both name and email in $it resolve",
|
||||
"""
|
||||
[project]
|
||||
name = "name"
|
||||
version = "123"
|
||||
$it = [
|
||||
{name = "name1", email = "email1"},
|
||||
{name = "name2", email = "email2"}
|
||||
]
|
||||
""".trimIndent(),
|
||||
run {
|
||||
val contacts = listOf(PyProjectContact(name = "name1", email = "email1"), PyProjectContact(name = "name2", email = "email2"))
|
||||
PyProjectTable(
|
||||
name = "name",
|
||||
version = "123",
|
||||
authors = if (it == "authors") contacts else null,
|
||||
maintainers = if (it == "maintainers") contacts else null,
|
||||
)
|
||||
},
|
||||
listOf(),
|
||||
)
|
||||
)
|
||||
}.toTypedArray(),
|
||||
|
||||
*listOf("license-files", "keywords", "classifiers", "dependencies").map {
|
||||
ParseTestCase(
|
||||
"elements in $it that are of the wrong type resolve in an issue",
|
||||
"""
|
||||
[project]
|
||||
name = "name"
|
||||
version = "123"
|
||||
$it = [123]
|
||||
""".trimIndent(),
|
||||
PyProjectTable(name = "name", version = "123"),
|
||||
listOf(SafeGetError(UnexpectedType("$it[0]", String::class, Long::class)))
|
||||
)
|
||||
}.toTypedArray(),
|
||||
|
||||
ParseTestCase(
|
||||
"correctly defined dependencies resolve",
|
||||
"""
|
||||
[project]
|
||||
name = "name"
|
||||
version = "123"
|
||||
dependencies = ["a", "b"]
|
||||
""".trimIndent(),
|
||||
PyProjectTable(
|
||||
name = "name",
|
||||
version = "123",
|
||||
dependencies = PyProjectDependencies(
|
||||
project = listOf("a", "b")
|
||||
)
|
||||
),
|
||||
listOf()
|
||||
),
|
||||
|
||||
ParseTestCase(
|
||||
"dev with wrong type in dependency-groups results in an issue",
|
||||
"""
|
||||
[project]
|
||||
name = "name"
|
||||
version = "123"
|
||||
|
||||
[dependency-groups]
|
||||
dev = 123
|
||||
""".trimIndent(),
|
||||
PyProjectTable(
|
||||
name = "name",
|
||||
version = "123",
|
||||
),
|
||||
listOf(SafeGetError(UnexpectedType("dev", TomlArray::class, Long::class)))
|
||||
),
|
||||
|
||||
ParseTestCase(
|
||||
"correctly defined dev dependencies resolve",
|
||||
"""
|
||||
[project]
|
||||
name = "name"
|
||||
version = "123"
|
||||
|
||||
[dependency-groups]
|
||||
dev = ["a", "b"]
|
||||
""".trimIndent(),
|
||||
PyProjectTable(
|
||||
name = "name",
|
||||
version = "123",
|
||||
dependencies = PyProjectDependencies(
|
||||
dev = listOf("a", "b")
|
||||
)
|
||||
),
|
||||
listOf()
|
||||
),
|
||||
|
||||
ParseTestCase(
|
||||
"optional dependency entries with wrong type result in an issue",
|
||||
"""
|
||||
[project]
|
||||
name = "name"
|
||||
version = "123"
|
||||
|
||||
[project.optional-dependencies]
|
||||
a = 123
|
||||
b = [123]
|
||||
""".trimIndent(),
|
||||
PyProjectTable(
|
||||
name = "name",
|
||||
version = "123",
|
||||
),
|
||||
listOf(
|
||||
SafeGetError(UnexpectedType("a", TomlArray::class, Long::class)),
|
||||
SafeGetError(UnexpectedType("b[0]", String::class, Long::class)),
|
||||
)
|
||||
),
|
||||
|
||||
ParseTestCase(
|
||||
"correctly defined optional dependencies resolve",
|
||||
"""
|
||||
[project]
|
||||
name = "name"
|
||||
version = "123"
|
||||
|
||||
[project.optional-dependencies]
|
||||
foo = ["a", "b"]
|
||||
bar = ["c", "d"]
|
||||
""".trimIndent(),
|
||||
PyProjectTable(
|
||||
name = "name",
|
||||
version = "123",
|
||||
dependencies = PyProjectDependencies(
|
||||
optional = mapOf(
|
||||
"foo" to listOf("a", "b"),
|
||||
"bar" to listOf("c", "d")
|
||||
)
|
||||
)
|
||||
),
|
||||
listOf()
|
||||
),
|
||||
|
||||
*listOf("scripts", "gui-scripts", "urls").flatMap {
|
||||
listOf(
|
||||
ParseTestCase(
|
||||
"$it entries with wrong type result in an issue",
|
||||
"""
|
||||
[project]
|
||||
name = "name"
|
||||
version = "123"
|
||||
|
||||
[project.$it]
|
||||
a = 123
|
||||
b = 123
|
||||
""".trimIndent(),
|
||||
PyProjectTable(
|
||||
name = "name",
|
||||
version = "123",
|
||||
scripts = if (it == "scripts") mapOf() else null,
|
||||
guiScripts = if (it == "gui-scripts") mapOf() else null,
|
||||
urls = if (it == "urls") mapOf() else null,
|
||||
),
|
||||
listOf(
|
||||
SafeGetError(UnexpectedType("a", String::class, Long::class)),
|
||||
SafeGetError(UnexpectedType("b", String::class, Long::class)),
|
||||
)
|
||||
),
|
||||
|
||||
ParseTestCase(
|
||||
"correctly defined entries in $it resolve",
|
||||
"""
|
||||
[project]
|
||||
name = "name"
|
||||
version = "123"
|
||||
|
||||
[project.$it]
|
||||
a = "item1"
|
||||
b = "item2"
|
||||
""".trimIndent(),
|
||||
run {
|
||||
val items = mapOf("a" to "item1", "b" to "item2")
|
||||
PyProjectTable(
|
||||
name = "name",
|
||||
version = "123",
|
||||
scripts = if (it == "scripts") items else null,
|
||||
guiScripts = if (it == "gui-scripts") items else null,
|
||||
urls = if (it == "urls") items else null,
|
||||
)
|
||||
},
|
||||
listOf()
|
||||
),
|
||||
)
|
||||
}.toTypedArray(),
|
||||
|
||||
ParseTestCase(
|
||||
"readme can be defined as a string",
|
||||
"""
|
||||
[project]
|
||||
name = "name"
|
||||
version = "123"
|
||||
readme = "README.md"
|
||||
""".trimIndent(),
|
||||
PyProjectTable(
|
||||
name = "name",
|
||||
version = "123",
|
||||
readme = PyProjectFile("README.md"),
|
||||
),
|
||||
listOf(),
|
||||
),
|
||||
|
||||
ParseTestCase(
|
||||
"readme can be defined as an object",
|
||||
"""
|
||||
[project]
|
||||
name = "name"
|
||||
version = "123"
|
||||
readme = {name = "README.md", content-type = "text/markdown"}
|
||||
""".trimIndent(),
|
||||
PyProjectTable(
|
||||
name = "name",
|
||||
version = "123",
|
||||
readme = PyProjectFile("README.md", "text/markdown"),
|
||||
),
|
||||
listOf(),
|
||||
),
|
||||
|
||||
ParseTestCase(
|
||||
"readme object with missing name results in an issue",
|
||||
"""
|
||||
[project]
|
||||
name = "name"
|
||||
version = "123"
|
||||
readme = {content-type = "text/markdown"}
|
||||
""".trimIndent(),
|
||||
PyProjectTable(
|
||||
name = "name",
|
||||
version = "123",
|
||||
),
|
||||
listOf(SafeGetError(RequiredValueMissing("name"))),
|
||||
),
|
||||
|
||||
ParseTestCase(
|
||||
"readme object with missing content-type results in an issue",
|
||||
"""
|
||||
[project]
|
||||
name = "name"
|
||||
version = "123"
|
||||
readme = {name = "README.md"}
|
||||
""".trimIndent(),
|
||||
PyProjectTable(
|
||||
name = "name",
|
||||
version = "123",
|
||||
),
|
||||
listOf(SafeGetError(RequiredValueMissing("content-type"))),
|
||||
),
|
||||
|
||||
ParseTestCase(
|
||||
"correctly parses full example",
|
||||
"""
|
||||
[project]
|
||||
name = "spam-eggs"
|
||||
version = "2020.0.0"
|
||||
dependencies = [
|
||||
"httpx",
|
||||
"gidgethub[httpx]>4.0.0",
|
||||
"django>2.1; os_name != 'nt'",
|
||||
"django>2.0; os_name == 'nt'",
|
||||
]
|
||||
requires-python = ">=3.8"
|
||||
authors = [
|
||||
{name = "Pradyun Gedam", email = "pradyun@example.com"},
|
||||
{name = "Tzu-Ping Chung", email = "tzu-ping@example.com"},
|
||||
{name = "Another person"},
|
||||
{email = "different.person@example.com"},
|
||||
]
|
||||
maintainers = [
|
||||
{name = "Brett Cannon", email = "brett@example.com"}
|
||||
]
|
||||
description = "Lovely Spam! Wonderful Spam!"
|
||||
readme = "README.rst"
|
||||
license = "MIT"
|
||||
license-files = ["LICEN[CS]E.*"]
|
||||
keywords = ["egg", "bacon", "sausage", "tomatoes", "Lobster Thermidor"]
|
||||
classifiers = [
|
||||
"Development Status :: 4 - Beta",
|
||||
"Programming Language :: Python"
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
gui = ["PyQt5"]
|
||||
cli = [
|
||||
"rich",
|
||||
"click",
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
Homepage = "https://example.com"
|
||||
Documentation = "https://readthedocs.org"
|
||||
Repository = "https://github.com/me/spam.git"
|
||||
"Bug Tracker" = "https://github.com/me/spam/issues"
|
||||
Changelog = "https://github.com/me/spam/blob/master/CHANGELOG.md"
|
||||
|
||||
[project.scripts]
|
||||
spam-cli = "spam:main_cli"
|
||||
|
||||
[project.gui-scripts]
|
||||
spam-gui = "spam:main_gui"
|
||||
|
||||
[dependency-groups]
|
||||
dev = ["foo", "bar"]
|
||||
""".trimIndent(),
|
||||
PyProjectTable(
|
||||
name = "spam-eggs",
|
||||
version = "2020.0.0",
|
||||
requiresPython = ">=3.8",
|
||||
authors = listOf(
|
||||
PyProjectContact(name = "Pradyun Gedam", email = "pradyun@example.com"),
|
||||
PyProjectContact(name = "Tzu-Ping Chung", email = "tzu-ping@example.com"),
|
||||
PyProjectContact(name = "Another person", email = null),
|
||||
PyProjectContact(name = null, email = "different.person@example.com"),
|
||||
),
|
||||
maintainers = listOf(
|
||||
PyProjectContact(name = "Brett Cannon", email = "brett@example.com"),
|
||||
),
|
||||
description = "Lovely Spam! Wonderful Spam!",
|
||||
readme = PyProjectFile("README.rst"),
|
||||
license = "MIT",
|
||||
licenseFiles = listOf("LICEN[CS]E.*"),
|
||||
keywords = listOf("egg", "bacon", "sausage", "tomatoes", "Lobster Thermidor"),
|
||||
classifiers = listOf(
|
||||
"Development Status :: 4 - Beta",
|
||||
"Programming Language :: Python",
|
||||
),
|
||||
dependencies = PyProjectDependencies(
|
||||
project = listOf(
|
||||
"httpx",
|
||||
"gidgethub[httpx]>4.0.0",
|
||||
"django>2.1; os_name != 'nt'",
|
||||
"django>2.0; os_name == 'nt'",
|
||||
),
|
||||
dev = listOf("foo", "bar"),
|
||||
optional = mapOf(
|
||||
"gui" to listOf("PyQt5"),
|
||||
"cli" to listOf("rich", "click"),
|
||||
),
|
||||
),
|
||||
urls = mapOf(
|
||||
"Homepage" to "https://example.com",
|
||||
"Documentation" to "https://readthedocs.org",
|
||||
"Repository" to "https://github.com/me/spam.git",
|
||||
"Bug Tracker" to "https://github.com/me/spam/issues",
|
||||
"Changelog" to "https://github.com/me/spam/blob/master/CHANGELOG.md",
|
||||
),
|
||||
scripts = mapOf(
|
||||
"spam-cli" to "spam:main_cli",
|
||||
),
|
||||
guiScripts = mapOf(
|
||||
"spam-gui" to "spam:main_gui",
|
||||
),
|
||||
),
|
||||
listOf(),
|
||||
),
|
||||
).map {
|
||||
Arguments.of(it.name, it.pyprojectToml, it.expectedProjectTable, it.expectedIssues)
|
||||
}
|
||||
|
||||
data class ParseTestCase(
|
||||
val name: String,
|
||||
val pyprojectToml: String,
|
||||
val expectedProjectTable: PyProjectTable?,
|
||||
val expectedIssues: List<PyProjectIssue>,
|
||||
)
|
||||
|
||||
data class TestPyProject(val tables: Map<String, TomlTable?>) {
|
||||
companion object : PyProjectToolFactory<TestPyProject> {
|
||||
override val tables: List<String> = listOf("tool.test", "shared_category")
|
||||
override fun createTool(tables: Map<String, TomlTable?>): TestPyProject = TestPyProject(tables)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import com.intellij.codeInspection.ProblemHighlightType
|
||||
import com.intellij.codeInspection.ProblemsHolder
|
||||
import com.intellij.openapi.module.Module
|
||||
import com.intellij.openapi.module.ModuleUtilCore
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiElementVisitor
|
||||
import com.intellij.psi.PsiFile
|
||||
@@ -20,6 +21,7 @@ import com.jetbrains.python.PyBundle
|
||||
import com.jetbrains.python.packaging.PyRequirementParser
|
||||
import com.jetbrains.python.packaging.management.PythonPackageManager
|
||||
import com.jetbrains.python.sdk.PythonSdkUtil
|
||||
import com.jetbrains.python.sdk.findAmongRoots
|
||||
|
||||
internal class UvPackageVersionsInspection : LocalInspectionTool() {
|
||||
override fun buildVisitor(
|
||||
@@ -39,12 +41,15 @@ internal class UvPackageVersionsInspection : LocalInspectionTool() {
|
||||
return ModuleUtilCore.findModuleForPsiElement(element)
|
||||
}
|
||||
|
||||
@RequiresBackgroundThread
|
||||
private fun Module.pyProjectTomlBlocking(): VirtualFile? = findAmongRoots(this, PY_PROJECT_TOML)
|
||||
|
||||
@RequiresBackgroundThread
|
||||
override fun visitFile(file: PsiFile) {
|
||||
val module = guessModule(file) ?: return
|
||||
val sdk = PythonSdkUtil.findPythonSdk(module) ?: return
|
||||
|
||||
if (!sdk.isUv || file.name != PY_PROJECT_TOML || file.virtualFile != PyProjectToml.findFileBlocking(module)) {
|
||||
if (!sdk.isUv || file.name != PY_PROJECT_TOML || file.virtualFile != module.pyProjectTomlBlocking()) {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user