From 2457ea78067a18e6a90ad8a540889e946f737e78 Mon Sep 17 00:00:00 2001 From: Koudai Aono Date: Mon, 16 Dec 2019 01:53:42 +0900 Subject: [PATCH] add config panel (#92) * add config panel * support init_typed --- resources/META-INF/plugin.xml | 9 ++- .../pydantic/PydanticConfigPanel.form | 74 +++++++++++++++++++ .../pydantic/PydanticConfigPanel.java | 35 +++++++++ .../pydantic/PydanticConfigService.kt | 29 ++++++++ .../koxudaxi/pydantic/PydanticConfigurable.kt | 39 ++++++++++ .../koxudaxi/pydantic/PydanticInspection.kt | 18 ++--- .../koxudaxi/pydantic/PydanticTypeProvider.kt | 32 ++++---- .../pydantic/PydanticInspectionTest.kt | 9 +-- .../com/koxudaxi/pydantic/PydanticTestCase.kt | 1 - 9 files changed, 215 insertions(+), 31 deletions(-) create mode 100644 src/com/koxudaxi/pydantic/PydanticConfigPanel.form create mode 100644 src/com/koxudaxi/pydantic/PydanticConfigPanel.java create mode 100644 src/com/koxudaxi/pydantic/PydanticConfigService.kt create mode 100644 src/com/koxudaxi/pydantic/PydanticConfigurable.kt diff --git a/resources/META-INF/plugin.xml b/resources/META-INF/plugin.xml index efa0690a..0fa82edc 100644 --- a/resources/META-INF/plugin.xml +++ b/resources/META-INF/plugin.xml @@ -5,9 +5,10 @@ Koudai Aono @koxudaxi version 0.0.29 -

Features, BugFixes

+

Features

version 0.0.28

Features, BugFixes

@@ -152,10 +153,16 @@ implementationClass="com.koxudaxi.pydantic.PydanticCompletionContributor"/> + + + + + diff --git a/src/com/koxudaxi/pydantic/PydanticConfigPanel.form b/src/com/koxudaxi/pydantic/PydanticConfigPanel.form new file mode 100644 index 00000000..b3a76a72 --- /dev/null +++ b/src/com/koxudaxi/pydantic/PydanticConfigPanel.form @@ -0,0 +1,74 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/src/com/koxudaxi/pydantic/PydanticConfigPanel.java b/src/com/koxudaxi/pydantic/PydanticConfigPanel.java new file mode 100644 index 00000000..b5afd514 --- /dev/null +++ b/src/com/koxudaxi/pydantic/PydanticConfigPanel.java @@ -0,0 +1,35 @@ +package com.koxudaxi.pydantic; + +import com.intellij.openapi.project.Project; + +import javax.swing.*; + +public class PydanticConfigPanel { + + PydanticConfigPanel(Project project) { + PydanticConfigService pydanticConfigService = PydanticConfigService.Companion.getInstance(project); + + this.initTypedCheckBox.setSelected(pydanticConfigService.getInitTyped()); + this.warnUntypedFieldsCheckBox.setSelected(pydanticConfigService.getWarnUntypedFields()); + + } + + private JPanel configPanel; + private JCheckBox initTypedCheckBox; + private JTextPane ifEnabledIncludeTheTextPane; + private JCheckBox warnUntypedFieldsCheckBox; + private JTextPane ifEnabledRaiseATextPane; + + public Boolean getInitTyped() { + return initTypedCheckBox.isSelected(); + } + + public Boolean getWarnUntypedFields() { + return warnUntypedFieldsCheckBox.isSelected(); + } + + public JPanel getConfigPanel() { + return configPanel; + } + +} diff --git a/src/com/koxudaxi/pydantic/PydanticConfigService.kt b/src/com/koxudaxi/pydantic/PydanticConfigService.kt new file mode 100644 index 00000000..9bb2eb66 --- /dev/null +++ b/src/com/koxudaxi/pydantic/PydanticConfigService.kt @@ -0,0 +1,29 @@ +package com.koxudaxi.pydantic + +import com.intellij.openapi.components.PersistentStateComponent +import com.intellij.openapi.components.ServiceManager +import com.intellij.openapi.components.State +import com.intellij.openapi.components.Storage +import com.intellij.openapi.project.Project +import com.intellij.util.xmlb.XmlSerializerUtil + +@State(name = "PydanticConfigService", storages = [Storage("pydantic.xml")]) +class PydanticConfigService : PersistentStateComponent { + var initTyped = true + var warnUntypedFields = false + + override fun getState(): PydanticConfigService { + return this + } + + override fun loadState(config: PydanticConfigService) { + XmlSerializerUtil.copyBean(config, this) + } + + companion object { + fun getInstance(project: Project?): PydanticConfigService { + return ServiceManager.getService(project!!, PydanticConfigService::class.java) + } + } + +} \ No newline at end of file diff --git a/src/com/koxudaxi/pydantic/PydanticConfigurable.kt b/src/com/koxudaxi/pydantic/PydanticConfigurable.kt new file mode 100644 index 00000000..c5124430 --- /dev/null +++ b/src/com/koxudaxi/pydantic/PydanticConfigurable.kt @@ -0,0 +1,39 @@ +package com.koxudaxi.pydantic + +import com.intellij.openapi.options.Configurable +import com.intellij.openapi.project.Project +import javax.swing.JComponent + + +class PydanticConfigurable internal constructor(project: Project) : Configurable { + private val pydanticConfigService: PydanticConfigService = PydanticConfigService.getInstance(project) + private val configPanel: PydanticConfigPanel = PydanticConfigPanel(project) + override fun getDisplayName(): String { + return "Pydantic" + } + + override fun getHelpTopic(): String? { + return null + } + + override fun createComponent(): JComponent? { + reset() + return configPanel.configPanel + } + + override fun reset() {} + + override fun isModified(): Boolean { + if (configPanel.initTyped == null || configPanel.warnUntypedFields == null) return false + return (pydanticConfigService.initTyped != configPanel.initTyped) || + (pydanticConfigService.warnUntypedFields != configPanel.warnUntypedFields) + } + + override fun apply() { + pydanticConfigService.initTyped = configPanel.initTyped + pydanticConfigService.warnUntypedFields = configPanel.warnUntypedFields + } + + override fun disposeUIResources() { + } +} \ No newline at end of file diff --git a/src/com/koxudaxi/pydantic/PydanticInspection.kt b/src/com/koxudaxi/pydantic/PydanticInspection.kt index ab461e55..2c19e319 100644 --- a/src/com/koxudaxi/pydantic/PydanticInspection.kt +++ b/src/com/koxudaxi/pydantic/PydanticInspection.kt @@ -19,10 +19,7 @@ import com.jetbrains.python.psi.types.PyClassType import com.jetbrains.python.psi.types.PyClassTypeImpl import javax.swing.JComponent -var defaultWarnUntypedFields = false - class PydanticInspection : PyInspection() { - var warnUntypedFields = defaultWarnUntypedFields override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean, @@ -30,6 +27,8 @@ class PydanticInspection : PyInspection() { inner class Visitor(holder: ProblemsHolder, session: LocalInspectionToolSession) : PyInspectionVisitor(holder, session) { + val pydanticConfigService = PydanticConfigService.getInstance(holder.project) + override fun visitPyFunction(node: PyFunction?) { super.visitPyFunction(node) @@ -64,7 +63,7 @@ class PydanticInspection : PyInspection() { super.visitPyAssignmentStatement(node) if (node == null) return - if (this@PydanticInspection.warnUntypedFields) { + if (pydanticConfigService.warnUntypedFields) { inspectWarnUntypedFields(node) } inspectReadOnlyProperty(node) @@ -121,16 +120,15 @@ class PydanticInspection : PyInspection() { val pyClass = getPyClassByAttribute(node) ?: return if (!isPydanticModel(pyClass, myTypeEvalContext)) return if (node.annotation != null) return - registerProblem(node, "Untyped fields disallowed", ProblemHighlightType.WARNING) } } - override fun createOptionsPanel(): JComponent? { - val panel = MultipleCheckboxOptionsPanel(this) - panel.addCheckbox( "Warning untyped fields", "warnUntypedFields") - return panel - } +// override fun createOptionsPanel(): JComponent? { +// val panel = MultipleCheckboxOptionsPanel(this) +// panel.addCheckbox( "Warning untyped fields", "warnUntypedFields") +// return panel +// } } \ No newline at end of file diff --git a/src/com/koxudaxi/pydantic/PydanticTypeProvider.kt b/src/com/koxudaxi/pydantic/PydanticTypeProvider.kt index aea5ac3d..e01a6270 100644 --- a/src/com/koxudaxi/pydantic/PydanticTypeProvider.kt +++ b/src/com/koxudaxi/pydantic/PydanticTypeProvider.kt @@ -8,11 +8,11 @@ import com.jetbrains.python.psi.impl.PyCallExpressionImpl import com.jetbrains.python.psi.impl.PyCallExpressionNavigator import com.jetbrains.python.psi.impl.PySubscriptionExpressionImpl import com.jetbrains.python.psi.types.* +import com.koxudaxi.pydantic.PydanticConfigService.Companion.getInstance import one.util.streamex.StreamEx class PydanticTypeProvider : PyTypeProviderBase() { - override fun getReferenceExpressionType(referenceExpression: PyReferenceExpression, context: TypeEvalContext): PyType? { return getPydanticTypeForCallee(referenceExpression, context) } @@ -77,7 +77,7 @@ class PydanticTypeProvider : PyTypeProviderBase() { .asSequence() .map { when { - it is PyClass -> getPydanticTypeForClass(it, context) + it is PyClass -> getPydanticTypeForClass(it, context, true) it is PyParameter && it.isSelf -> { PsiTreeUtil.getParentOfType(it, PyFunction::class.java) ?.takeIf { it.modifier == PyFunction.Modifier.CLASSMETHOD } @@ -86,14 +86,14 @@ class PydanticTypeProvider : PyTypeProviderBase() { it is PyNamedParameter -> it.getArgumentType(context)?.let { pyType -> getPyClassTypeByPyTypes(pyType).filter { pyClassType -> pyClassType.isDefinition - }.map { filteredPyClassType -> getPydanticTypeForClass(filteredPyClassType.pyClass, context) }.firstOrNull() + }.map { filteredPyClassType -> getPydanticTypeForClass(filteredPyClassType.pyClass, context, true) }.firstOrNull() } it is PyTargetExpression -> (it as? PyTypedElement)?.let { pyTypedElement -> context.getType(pyTypedElement) ?.let { pyType -> getPyClassTypeByPyTypes(pyType) } ?.filter { pyClassType -> pyClassType.isDefinition } ?.map { filteredPyClassType -> - getPydanticTypeForClass(filteredPyClassType.pyClass, context) + getPydanticTypeForClass(filteredPyClassType.pyClass, context, true) }?.firstOrNull() } else -> null @@ -102,11 +102,12 @@ class PydanticTypeProvider : PyTypeProviderBase() { .firstOrNull { it != null } } - private fun getPydanticTypeForClass(pyClass: PyClass, context: TypeEvalContext): PyCallableType? { + private fun getPydanticTypeForClass(pyClass: PyClass, context: TypeEvalContext, init: Boolean = false): PyCallableType? { if (!isPydanticModel(pyClass, context)) return null val clsType = (context.getType(pyClass) as? PyClassLikeType) ?: return null val ellipsis = PyElementGenerator.getInstance(pyClass.project).createEllipsis() + val typed = !init || getInstance(pyClass.project).initTyped val collected = linkedMapOf() val pydanticVersion = getPydanticVersion(pyClass.project, context) val config = getConfig(pyClass, context, true) @@ -117,7 +118,7 @@ class PydanticTypeProvider : PyTypeProviderBase() { if (!isPydanticModel(current, context)) continue getClassVariables(current, context) - .mapNotNull { fieldToParameter(it, ellipsis, context, current, pydanticVersion, config) } + .mapNotNull { fieldToParameter(it, ellipsis, context, current, pydanticVersion, config, typed) } .filter { parameter -> parameter.name?.let { !collected.containsKey(it) } ?: false } .forEach { parameter -> collected[parameter.name!!] = parameter } } @@ -133,7 +134,8 @@ class PydanticTypeProvider : PyTypeProviderBase() { context: TypeEvalContext, pyClass: PyClass, pydanticVersion: KotlinVersion?, - config: HashMap): PyCallableParameter? { + config: HashMap, + typed: Boolean = true): PyCallableParameter? { if (field.name == null || ! isValidFieldName(field.name!!)) return null if (!hasAnnotationValue(field) && !field.hasAssignedValue()) return null // skip fields that are invalid syntax @@ -143,12 +145,16 @@ class PydanticTypeProvider : PyTypeProviderBase() { else -> defaultValueFromField } - val typeForParameter = if (!hasAnnotationValue(field) && defaultValueFromField is PyTypedElement) { - // get type from default value - context.getType(defaultValueFromField) - } else { - // get type from annotation - getTypeForParameter(field, context) + val typeForParameter = when { + !typed -> null + !hasAnnotationValue(field) && defaultValueFromField is PyTypedElement -> { + // get type from default value + context.getType(defaultValueFromField) + } + else -> { + // get type from annotation + getTypeForParameter(field, context) + } } return PyCallableParameterImpl.nonPsi( diff --git a/testSrc/com/koxudaxi/pydantic/PydanticInspectionTest.kt b/testSrc/com/koxudaxi/pydantic/PydanticInspectionTest.kt index f8862ed0..333ee687 100644 --- a/testSrc/com/koxudaxi/pydantic/PydanticInspectionTest.kt +++ b/testSrc/com/koxudaxi/pydantic/PydanticInspectionTest.kt @@ -55,11 +55,8 @@ open class PydanticInspectionTest : PydanticInspectionBase() { } fun testWarnUntypedFields() { - try { - defaultWarnUntypedFields = true - doTest() - } finally { - defaultWarnUntypedFields = false - } + val pydanticConfigService = PydanticConfigService.getInstance(myFixture!!.project) + pydanticConfigService.warnUntypedFields = true + doTest() } } diff --git a/testSrc/com/koxudaxi/pydantic/PydanticTestCase.kt b/testSrc/com/koxudaxi/pydantic/PydanticTestCase.kt index 52647b83..2f4cd19b 100644 --- a/testSrc/com/koxudaxi/pydantic/PydanticTestCase.kt +++ b/testSrc/com/koxudaxi/pydantic/PydanticTestCase.kt @@ -64,7 +64,6 @@ abstract class PydanticTestCase(version: String = "v1") : UsefulTestCase() { packageDir = myFixture!!.findFileInTempDir("package") addSourceRoot(myFixture!!.module, packageDir!!) - PythonDialectsTokenSetProvider.reset() setLanguageLevel(LanguageLevel.PYTHON37) }